TI Cortex-M4 I2C DMA与FIFO配置实战:提升嵌入式通信效率

📅 2026/7/18 12:25:20
TI Cortex-M4 I2C DMA与FIFO配置实战:提升嵌入式通信效率
1. 项目概述搞嵌入式开发I2C总线绝对是绕不开的一个老朋友。从读取一个温湿度传感器到配置一块复杂的音频编解码芯片这根看似简单的两根线SCL时钟线和SDA数据线背后承载着无数低速外设与主控芯片之间的对话。但如果你还停留在“起始条件-发送地址-读写数据-停止条件”的轮询操作层面面对需要高速、连续读取大量数据的传感器或者需要同时处理多个I2C外设的复杂系统时CPU被频繁中断、总线效率低下的问题就会立刻凸显出来。这时硬件级的优化手段——直接内存访问DMA和先进先出FIFO缓冲区——就成了提升系统性能、解放CPU的关键。很多现代微控制器比如TI的Tiva/Stellaris系列ARM Cortex-M内核产品其I2C模块已经原生集成了对DMA和FIFO的硬件支持。然而官方技术手册往往篇幅浩繁、细节琐碎真正把寄存器配置、DMA触发、FIFO管理这一套流程打通并稳定应用于实际项目的经验分享却不多见。今天我就结合自己多次在TI Cortex-M4平台上调试I2C DMAFIFO传输的实际经历从最基础的I2C时序讲起深入到TI I2C模块的寄存器位定义最后手把手带你配置一个完整的、基于突发Burst模式的主机接收DMA传输实例。我会重点解释每个配置步骤背后的“为什么”并分享几个从调试中总结出来的、手册上不会写的“避坑指南”。无论你是刚接触I2C的新手还是想优化现有通信协议的老鸟相信这篇内容都能给你带来一些直接的帮助。2. I2C核心原理与TI模块特性回顾在深入寄存器配置之前我们必须对I2C协议和TI I2C模块的增强特性有一个统一的认识。这能帮助我们在后续配置时理解每一个操作的意义。2.1 I2C通信基础框架I2C是一种同步、半双工、多主从的串行总线。它的优雅之处在于极简的物理连接一根串行数据线SDA负责传输数据一根串行时钟线SCL提供同步时钟。所有设备都通过开漏输出连接到这两根线上依靠上拉电阻确保高电平从而实现“线与”逻辑这是实现多主仲裁的基础。一次标准的I2C通信序列包含以下几个关键阶段起始条件SSCL为高电平时SDA出现一个下降沿。这由主机产生标志着一次传输的开始。从机地址帧紧接起始条件后主机发送7位或10位从机地址以及最后1位读写方向位0-写1-读。应答位ACK/NACK每个地址或数据字节8位传输完毕后接收方对于地址帧是从机对于数据帧是当前接收方需要在第9个时钟脉冲期间拉低SDAACK以示确认。若保持高电平则为非应答NACK。数据帧在地址得到应答后开始传输数据字节每个字节后同样跟一个应答位。数据方向由最初的读写位决定但在“重复起始条件”后可以改变。停止条件PSCL为高电平时SDA出现一个上升沿。由主机产生标志传输结束。注意理解“应答”的主体至关重要。地址帧后的ACK由被寻址的从机发出写数据时的ACK由接收数据的从机发出读数据时的ACK则由主机发出主机通常在接收最后一个字节前发送NACK随后发出停止条件。2.2 TI I2C模块的增强功能FIFO与DMA标准的微控制器I2C接口通常只提供数据寄存器如I2CMDR每传输一个字节都需要CPU介入读写该寄存器并处理状态效率低下且中断频繁。TI的I2C模块在此基础上做了重要增强其核心在于两个硬件FIFO通常深度为8字节和与之配套的µDMA微直接内存访问控制器接口发送FIFOTX FIFO用于缓存待发送的数据。CPU或DMA可以一次性写入多个字节到FIFOI2C模块的硬件状态机会在总线就绪时自动从FIFO中取出数据并按I2C协议发出。接收FIFORX FIFO用于缓存接收到的数据。I2C硬件将总线上的数据字节存入RX FIFOCPU或DMA可以从中批量读取。µDMA集成这是性能飞跃的关键。DMA控制器可以配置为与这两个FIFO联动。例如当TX FIFO空到一定阈值时自动触发DMA从内存搬运数据来填充当RX FIFO有数据达到一定阈值时自动触发DMA将数据搬走存入内存。整个过程完全无需CPU干预。这种架构带来了“突发传输Burst”模式。在该模式下你只需通过I2CMBLEN寄存器设定本次突发传输的总字节数然后启动传输。I2C模块会与DMA协作自动处理整个数据块的搬入搬出仅在传输开始、结束或出错时通知CPU极大减轻了CPU负担提升了总线利用率。2.3 关键寄存器概览为了后续的配置我们需要先熟悉几个核心寄存器它们是我们与I2C硬件对话的“语言”。I2CMSA (Master Slave Address)设置目标从机地址和传输方向读/写。I2CMCS (Master Control/Status)这是最核心的控制与状态寄存器。我们通过写入特定的位组合如START, RUN, STOP, BURST来发起和控制传输流程并通过读取它来获取当前状态BUSY, ERROR等。I2CMDR (Master Data)在非FIFO/DMA模式下读写数据都通过它。在Burst模式下数据流改由I2CFIFODATA寄存器或DMA通道处理此寄存器被忽略。I2CMTPR (Master Timer Period)用于配置SCL时钟频率。其TPR字段的值根据系统时钟和期望的SCL频率计算得出。I2CMBLEN (Master Burst Length)在Burst模式下定义本次DMA传输的总字节数。这是启动突发传输前必须正确设置的参数。I2CFIFOCTL (FIFO Control)控制FIFO的使能、TX/RX触发阈值等。I2CFIFOSTATUS (FIFO Status)查看TX/RX FIFO的当前数据量、空满状态。I2CMIMR/I2CMRIS/I2CMICR这一组寄存器用于管理中断。IMR是中断掩码使能RIS是原始中断状态ICR是中断清除寄存器。理解这些寄存器的功能是进行任何高级配置的基础。接下来我们将进入实战环节。3. 从零配置实现I2C主模式DMA接收假设一个典型场景我们的TI TM4C1294微控制器系统时钟80MHz作为主机需要从一个I2C温度传感器假设地址0x48连续读取256个字节的采样数据。我们将使用Burst模式结合DMA来完成目标是配置完成后CPU只需启动传输然后在DMA完成中断中处理数据。3.1 硬件与软件环境准备首先进行基础的硬件和软件初始化这部分是任何I2C操作的前提。1. 引脚复用与配置TI的MCU引脚通常复用多个功能。我们需要将对应的SCL和SDA引脚配置为I2C功能并设置为开漏模式。// 假设使用I2C0SCL: PA6, SDA: PA7 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // 使能GPIOA时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0); // 使能I2C0时钟 // 配置引脚为I2C功能开漏输出 GPIOPinTypeI2C(GPIO_PORTA_BASE, GPIO_PIN_6); // SCL GPIOPinTypeI2CSCL(GPIO_PORTA_BASE, GPIO_PIN_6); // 明确指定SCL内部会配置开漏 GPIOPinTypeI2C(GPIO_PORTA_BASE, GPIO_PIN_7); // SDA GPIOPinTypeI2CSDA(GPIO_PORTA_BASE, GPIO_PIN_7); // 明确指定SDA内部会配置开漏GPIOPinTypeI2CSCL和GPIOPinTypeI2CSDA这两个API是关键它们不仅设置了引脚复用更重要的是配置了开漏输出模式这是I2C总线“线与”特性所必需的。2. I2C模块主控制器初始化初始化主控制器将其置于已知的位后状态。// 初始化I2C主控制器设置为主机模式 I2CMasterInitExpClk(I2C0_BASE, 80000000, false); // 80MHz系统时钟禁用高速模式这个函数内部会操作I2CMCR等寄存器将模块置于标准的主机模式。false参数表示不使用高速模式400kbps以上。3. 配置SCL时钟频率通过I2CMTPR寄存器设置总线速度。目标为100kHz标准模式。计算公式来自数据手册TPR (SysClk / (2 * (SCL_LP SCL_HP) * SCL_CLK)) - 1其中SysClk为系统时钟80MHzSCL_LP低电平周期固定为6SCL_HP高电平周期固定为4SCL_CLK为目标频率100kHz。 计算TPR (80,000,000 / (2 * (64) * 100,000)) - 1 (80,000,000 / 2,000,000) - 1 40 - 1 39I2CMasterInitExpClk(I2C0_BASE, 80000000, false); // 此函数内部已根据参数计算并设置TPR // 如果需要手动设置或更改可以调用 I2CMasterClockSet(I2C0_BASE, 100000); // 设置为100kHz实操心得务必根据实际系统时钟计算TPR值。设置过快可能导致通信不稳定尤其是在长走线或高负载总线上。初期调试建议先用100kHz标准模式稳定后再尝试400kHz快速模式。3.2 FIFO与DMA的详细配置流程基础初始化完成后我们进入核心的FIFO和DMA配置环节。1. 使能并配置FIFO首先需要使能I2C模块的FIFO功能并设置触发阈值。阈值决定了何时向DMA控制器发出请求。// 使能TX和RX FIFO HWREG(I2C0_BASE I2C_FIFOCTL) | (I2C_FIFOCTL_TXFIFOEN | I2C_FIFOCTL_RXFIFOEN); // 配置FIFO触发阈值。例如设置TX FIFO空触发阈值为22时触发DMA请求 // 设置RX FIFO满触发阈值为66时触发DMA请求 // TXTRIG 和 RXTRIG 的位域请参考具体型号的数据手册 #define TX_TRIG_LEVEL 2 #define RX_TRIG_LEVEL 6 uint32_t fifoctl HWREG(I2C0_BASE I2C_FIFOCTL); fifoctl ~(I2C_FIFOCTL_TXTRIG_M | I2C_FIFOCTL_RXTRIG_M); // 清除原有阈值 fifoctl | (TX_TRIG_LEVEL I2C_FIFOCTL_TXTRIG_S); fifoctl | (RX_TRIG_LEVEL I2C_FIFOCTL_RXTRIG_S); HWREG(I2C0_BASE I2C_FIFOCTL) fifoctl;注意事项触发阈值的设置需要权衡。阈值设得太小如TX1RX1会导致DMA请求过于频繁增加总线开销设得太大如TX7RX7则可能因为FIFO满/空而导致I2C总线等待降低吞吐量。对于连续突发传输通常将TX阈值设得较低尽快补充数据RX阈值设得较高攒够一批再搬运以平衡效率和延迟。2. 配置µDMA通道TI的µDMA控制器需要单独配置。我们需要为I2C的TX和RX各分配一个DMA通道并设置传输控制数据结构Control Table Entry。#include “inc/hw_udma.h” #include “driverlib/udma.h” // 使能µDMA控制器 SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); uDMAEnable(); // 设置DMA控制表的基础地址 uDMAControlBaseSet(udmaControlTable); // 分配通道假设使用通道8给I2C0 RX通道9给I2C0 TX具体通道号查数据手册 uint32_t ui32RxChannel UDMA_CH8_I2C0RX; uint32_t ui32TxChannel UDMA_CH9_I2C0TX; // 配置RX DMA通道从I2C FIFO数据寄存器读到内存数组 uDMAChannelControlSet(ui32RxChannel | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4); uDMAChannelTransferSet(ui32RxChannel | UDMA_PRI_SELECT, UDMA_MODE_BASIC, // 基本模式 (void*)(I2C0_BASE I2C_FIFODATA), // 源地址FIFO数据寄存器 pui8RxDataBuffer, // 目的地址内存缓冲区 BUFFER_SIZE); // 传输数据项数量字节数 // 配置TX DMA通道本例为接收TX通道可暂不配置或配置为无效传输 // uDMAChannelControlSet(...); // uDMAChannelTransferSet(...); // 使能DMA通道 uDMAChannelEnable(ui32RxChannel);关键点在于uDMAChannelControlSet中的参数UDMA_SRC_INC_NONE源地址I2CFIFODATA不递增因为我们是不断读取同一个硬件寄存器。UDMA_DST_INC_8目的地址内存缓冲区按8位字节递增。UDMA_ARB_4仲裁大小为4。这意味着每传输4个字节DMA会释放一次总线控制权防止长时间占用总线。这个值需要与FIFO触发阈值配合考虑。3. 配置I2C主控制器的DMA和Burst模式这是将I2C模块与DMA连接起来的关键步骤。// 1. 设置突发传输长度总共要接收的字节数 HWREG(I2C0_BASE I2C_MBLEN) BUFFER_SIZE; // 例如256 // 2. 清除可能存在的旧中断标志 HWREG(I2C0_BASE I2C_MICR) 0xFFFFFFFF; // 3. 配置主控制寄存器(I2CMCR)如果需要可以配置时钟超时等功能 // 通常保持默认值0x0000.0010使能主控制器即可除非有特殊需求。 // HWREG(I2C0_BASE I2C_MCR) 0x00000010; // 4. 配置中断掩码(I2CMIMR)。我们关心DMA接收完成中断和错误中断。 // 屏蔽禁用TX FIFO空中断因为我们是接收操作。 HWREG(I2C0_BASE I2C_MIMR) 0; HWREG(I2C0_BASE I2C_MIMR) | I2C_MIMR_DMARXIM; // 使能DMA接收完成中断 HWREG(I2C0_BASE I2C_MIMR) | I2C_MIMR_NACKIM; // 使能NACK错误中断 HWREG(I2C0_BASE I2C_MIMR) | I2C_MIMR_ARBLOSTIM; // 使能仲裁丢失中断 // 特别注意根据手册Note在主机进行RX Burst时应屏蔽TXFEIM中断。 // 上述代码中未使能TXFEIM即符合要求。3.3 启动Burst接收传输所有配置就绪后我们通过写入I2CMSA和I2CMCS寄存器来发起一次带DMA的Burst接收操作。// 1. 写入从机地址和方向位1表示读 // 假设从机地址0x48左移一位最低位R/S1表示读操作 uint32_t slave_addr 0x48; HWREG(I2C0_BASE I2C_MSA) (slave_addr 1) | 0x01; // 2. 写入I2CMCS寄存器以启动Burst接收。 // 我们需要设置的位组合是BURST1, ACK1, START1, RUN0 // 根据手册Table 7-6对于Idle状态R/S1, BURST1, ACK1, START1, RUN0 // 对应的是“START condition followed by N FIFO-serviced RECEIVE operations” // 这个组合的十六进制值需要根据寄存器位域计算。假设 // BIT6(BURST)1, BIT4(HS)0, BIT3(ACK)1, BIT2(STOP)0, BIT1(START)1, BIT0(RUN)0 // 其他位CLKTO, QCMD为0。 // 那么 I2CMCS[6:0] b1_0_0_1_0_1_0 0x4A (二进制01001010) // 但注意I2CMCS寄存器复位后BIT5(IDLE)为1我们写入时需要保持不写入的是命令不是状态。 // 查看手册Figure 7-15BIT5在写入时是QCMD位。对于我们的操作QCMD应为0。 // 所以最终写入值BIT61, BIT50, BIT40, BIT31, BIT20, BIT11, BIT00 - 0x4A // 但通常TI的驱动库提供了更清晰的宏定义。 // 使用TI驱动库的宏如果可用或直接使用计算出的值 // 假设我们定义I2C_MCS_BURST | I2C_MCS_ACK | I2C_MCS_START // 注意在Burst模式下RUN位不能置1它与BURST位互斥。 #define I2C_BURST_RECEIVE_CMD (I2C_MCS_BURST | I2C_MCS_ACK | I2C_MCS_START) // 等待总线空闲 while(HWREG(I2C0_BASE I2C_MCS) I2C_MCS_BUSBSY) { // 空循环或短暂延时 } // 发送Burst接收命令 HWREG(I2C0_BASE I2C_MCS) I2C_BURST_RECEIVE_CMD;写入命令后I2C硬件会自动生成起始条件、发送从机地址读、然后开始接收数据。接收到的数据字节会被硬件自动存入RX FIFO。一旦RX FIFO中的数据量达到我们之前设置的触发阈值RX_TRIG_LEVEL6dma_req信号就会有效触发µDMA控制器将数据从I2CFIFODATA寄存器搬移到我们指定的内存缓冲区pui8RxDataBuffer中。DMA会根据I2CMBLEN设定的长度256自动管理传输计数直到所有字节传输完毕。3.4 中断服务程序与传输完成处理当DMA完成了所有I2CMBLEN指定字节的传输或者传输过程中发生错误如NACK、仲裁丢失相应的中断标志位会被置起。// I2C0中断服务函数 void I2C0_IRQHandler(void) { uint32_t status HWREG(I2C0_BASE I2C_MRIS); // 读取原始中断状态 // 处理DMA接收完成中断 if(status I2C_MRIS_DMARXRIS) { // 清除中断标志 HWREG(I2C0_BASE I2C_MICR) I2C_MICR_DMARXIC; // 此时BUFFER_SIZE个字节的数据应已通过DMA完整传输到pui8RxDataBuffer g_bRxComplete true; // 设置全局标志通知主循环 // 可以在这里进行数据处理但注意ISR中应快速处理 } // 处理NACK错误从机无应答 if(status I2C_MRIS_NACKRIS) { HWREG(I2C0_BASE I2C_MICR) I2C_MICR_NACKIC; // 错误处理记录日志、重试、或进入安全状态 g_bError true; g_ui32ErrorCode | I2C_ERROR_NACK; } // 处理仲裁丢失错误多主竞争总线失败 if(status I2C_MRIS_ARBLOSTRIS) { HWREG(I2C0_BASE I2C_MICR) I2C_MICR_ARBLOSTIC; // 错误处理通常可以重新发起传输 g_bError true; g_ui32ErrorCode | I2C_ERROR_ARB_LOST; } // ... 处理其他可能的中断 }在主循环中我们可以检查g_bRxComplete或g_bError标志从而知道传输是否成功完成或发生错误并进行后续操作。重要提示在Burst传输过程中不要在数据传输完成前即I2CMBCNT寄存器未减到0且DMA未触发完成中断去读取I2CMDR寄存器或通过非DMA方式操作FIFO这会导致不可预知的行为。所有数据流应完全由DMA和I2C硬件状态机管理。4. 深度解析关键寄存器位与状态机流转要真正驾驭TI I2C的DMA和Burst模式不能只停留在调用API的层面必须理解几个关键寄存器位的相互作用以及硬件状态机是如何流转的。这能帮助你在调试时通过观察寄存器状态精准定位问题。4.1 I2CMCS寄存器控制与状态的枢纽I2CMCS寄存器是核心中的核心它同时承载了控制命令输入和状态反馈输出。理解其“双重人格”是读懂状态机的关键。写入时控制命令你写入的I2CMCS[6:0]BURST, QCMD, HS, ACK, STOP, START, RUN的特定组合对应着一条让I2C主状态机执行的动作指令。手册中的Table 7-6就是这份“指令编码表”。例如在Idle状态下写入0x4A二进制01001010即BURST1, ACK1, START1, RUN0就命令状态机“发起起始条件然后进行N次FIFO服务的接收操作”。读取时状态反馈你读到的I2CMCS[7:0]CLKTO, BUSBSY, IDLE, ARBLST, DATACK, ADRACK, ERROR, BUSY反映了硬件当前的状态。例如BUSBSY位指示总线是否正被占用包括本机或其他主机BUSY位指示本机I2C控制器是否正在处理一次传输序列ERROR位指示上一次操作是否出错地址或数据NACK。一个常见的调试陷阱在发送命令前没有检查BUSBSY或BUSY状态。如果状态机还未回到Idle状态BUSY0就写入新的命令新命令会被忽略导致程序“卡住”。正确的操作序列应该是1) 等待BUSBSY0总线空闲2) 写入从机地址到I2CMSA3) 等待BUSY0控制器空闲4) 写入命令到I2CMCS启动传输。4.2 Burst模式下的数据流与计数器在Burst模式下两个寄存器I2CMBLEN和I2CMBCNT扮演着“总指挥”和“进度条”的角色。I2CMBLEN(Master Burst Length)这是你设定的总任务量。在启动Burst传输前你必须向它写入本次传输希望发送或接收的总字节数。这个值决定了DMA最终要搬运多少数据。I2CMBCNT(Master Burst Count)这是一个只读寄存器是剩余工作量的实时指示器。当Burst传输启动后I2C硬件每完成一个字节的传输对于TX是从FIFO取走一个字节对于RX是向FIFO存入一个字节这个计数器就会自动减1。你可以通过轮询这个寄存器是否为0来判断Burst传输是否在硬件层面结束尽管更推荐用DMA完成中断。数据流联动你设置I2CMBLEN256并启动Burst接收。I2C硬件开始从总线接收数据存入RX FIFO。当RX FIFO中的数据达到触发阈值如6字节触发DMA请求。µDMA响应请求从I2CFIFODATA寄存器读取1个字节或按仲裁大小读取多个字节到内存每读一个字节I2CMBCNT并不直接减少。I2CMBCNT的减少是由I2C硬件从总线上成功收发一个字节触发的。DMA持续服务直到它累计搬运的字节数等于最初设置的I2CMBLEN256然后触发DMA完成中断。此时I2CMBCNT应该也恰好减为0I2C硬件会认为本次Burst传输结束。4.3 FIFO状态与DMA请求逻辑I2CFIFOSTATUS寄存器提供了FIFO的实时快照而DMA请求信号dma_sreq和dma_req的产生逻辑则决定了DMA何时被唤醒。I2CFIFOSTATUS你可以读取TXFETX FIFO空、TXFFTX FIFO满、RXFERX FIFO空、RXFFRX FIFO满等位以及TXRXLEVEL字段来了解FIFO中有多少数据。在调试FIFO相关问题时这个寄存器非常有用。DMA请求逻辑以从模式RX FIFO为例主模式逻辑类似dma_sreq单次请求只要RX FIFO中有任何数据RXFE0该信号就有效。它用于启动一次DMA传输。dma_req多次请求当RX FIFO中的数据量大于等于I2CFIFOCTL中RXTRIG设置的触发阈值时该信号有效。只要这个条件保持DMA就会以“基本模式”或“Ping-Pong模式”连续传输直到FIFO数据量低于阈值或I2CMBCNT减为0。这种双请求机制允许DMA控制器灵活工作dma_sreq可以触发一次性的小数据量搬运而dma_req则适合在数据流稳定时进行批量搬运提高效率。5. 实战避坑指南与高级技巧纸上得来终觉浅绝知此事要躬行。下面分享几个我在实际项目中踩过的坑和总结的技巧这些在数据手册里往往一笔带过但却能决定项目的成败。5.1 时钟与延时配置的玄机问题I2C通信不稳定时而成功时而失败逻辑分析仪显示SCL/SDA波形有毛刺或时序轻微不符合标准。排查与解决SCL时钟计算复核首先确保I2CMTPR计算绝对正确。使用示波器或逻辑分析仪测量实际的SCL频率。如果偏差较大检查系统时钟配置是否正确。TI的I2CMasterInitExpClk()函数内部计算是可靠的但如果你手动配置寄存器务必仔细核对公式。毛刺抑制I2CMTPR寄存器中的PULSEL字段位18-16专门用于配置SCL和SDA线上的毛刺抑制宽度。如果总线环境嘈杂例如靠近电机、开关电源线上的窄脉冲毛刺可能被误认为是起始/停止条件或数据跳变。适当增加毛刺抑制的时钟数例如从默认的Bypass改为2 clocks或4 clocks可以显著增强抗干扰能力但代价是略微降低了总线最高速率。初始化延时手册明确提到在使能I2C模块时钟后必须等待至少3个系统时钟周期才能访问I2C寄存器。TI的库函数通常已经处理了这个延时但如果你是自己操作寄存器或者在低功耗模式下频繁开关I2C模块时钟必须手动加入延时例如几个__nop()指令。5.2 DMA与FIFO配置的常见陷阱陷阱一DMA传输大小与FIFO触发阈值不匹配现象DMA传输似乎提前结束了或者没有搬完所有数据。分析DMA的仲裁大小ARB_SIZE决定了它一突发传输的数据项数量。例如ARB_4表示DMA会一次性请求传输4个字节。如果你的RX FIFO触发阈值RXTRIG设置为4而DMA的仲裁大小也是4那么当FIFO中刚好有4个字节时触发DMADMA一次搬走4个FIFO立刻变空dma_req信号失效。如果此时I2C总线来不及立刻填充4个新字节DMA就会暂停等待下一次FIFO达到阈值。这可能导致DMA传输被拆分成多次虽然最终能完成但效率不是最优。优化让DMA的单次传输量略小于FIFO深度并设置合理的触发阈值。例如8字节深度的FIFO设置RXTRIG6DMAARB_SIZE4。这样当FIFO有6个字节时触发DMADMA搬走4个还剩2个。I2C硬件有机会在FIFO再次达到6个字节前继续存入数据从而实现更平滑的流水线操作。陷阱二Burst传输未完成时的误操作现象在Burst传输过程中程序因为其他任务如读取一个状态寄存器意外读取了I2CMDR或I2CFIFODATA导致传输数据错乱或DMA计数器异常。根因在Burst模式下数据通路被硬件重定向到FIFO和DMA。I2CMDR寄存器在此模式下被忽略而直接读取I2CFIFODATA会干扰DMA的读指针可能造成数据丢失或重复。铁律一旦启动了Burst传输在传输完成I2CMBCNT0且DMA完成中断发生或主动中止之前禁止通过CPU直接访问I2CMDR或I2CFIFODATA来获取数据。所有数据必须通过预设的DMA通道搬运到指定内存区域后再从内存中访问。陷阱三中断标志清除顺序现象中断服务程序ISR处理完一次中断后立刻又进入了同一个ISR仿佛中断标志没清掉。分析TI I2C模块的中断清除寄存器I2CMICR是“写1清除”的。常见的错误是// 错误写法先读RIS再根据RIS的值去清除 uint32_t status HWREG(I2C0_BASE I2C_MRIS); if(status I2C_MRIS_DMARXRIS) { // 如果此时发生了新的DMARX事件RIS对应位会再次被置1 HWREG(I2C0_BASE I2C_MICR) I2C_MICR_DMARXIC; // 只清除了之前的标志 // ISR退出后新的标志还在导致立即重入 }正确做法在ISR入口处一次性读取原始中断状态RIS然后立即向ICR写入相应的值来清除所有已发生的中断标志然后再进行逻辑处理。void I2C0_IRQHandler(void) { uint32_t status HWREG(I2C0_BASE I2C_MRIS); // 捕获瞬间状态 uint32_t clear_mask 0; if(status I2C_MRIS_DMARXRIS) { clear_mask | I2C_MICR_DMARXIC; // ... 处理逻辑 } if(status I2C_MRIS_NACKRIS) { clear_mask | I2C_MICR_NACKIC; // ... 处理逻辑 } // ... 其他中断 HWREG(I2C0_BASE I2C_MICR) clear_mask; // 一次性清除所有已识别中断 }5.3 混合传输模式与资源管理在实际系统中你可能需要对同一个I2C外设进行混合操作先写一个寄存器地址写操作然后立即读取数据读操作即“写-读”复合操作。这需要用到重复起始条件Repeated START。TI的I2C主状态机完美支持这一点。参考手册中的流程图Figure 7-11, 7-12和命令编码表Table 7-6你可以发现在Master Transmit或Master Receive状态下通过写入特定的I2CMCS命令包含START1但STOP0就可以产生一个重复起始条件并在其后改变数据传输方向。示例流程非Burst模式CPU轮询发送从机地址写方向发送寄存器地址字节单次或多次传输。不发送停止条件而是发送一个带重复起始的命令。发送从机地址读方向然后接收数据。在Burst模式下情况更复杂一些。你不能在同一个Burst序列中混合读和写方向。对于“先写后读”的Burst操作通常需要先配置为Burst发送模式发送命令/地址字节长度I2CMBLEN设为地址长度。等待这次Burst发送完成。重新配置从机地址为读方向设置新的I2CMBLEN为要读取的数据长度启动一次新的Burst接收。这意味着混合操作会涉及两次独立的Burst传输中间有状态切换。你需要妥善管理DMA通道的重新配置源/目标地址、传输长度。一个实用的技巧是使用µDMA的Ping-Pong模式或链表模式预先设置好两个或多个传输任务让DMA在完成一次传输后自动切换到下一个任务减少CPU干预实现更流畅的连续混合操作。但这需要对µDMA控制器有更深入的了解配置也更为复杂。最后在复杂的多任务嵌入式系统中I2C总线是一种共享资源。当使用DMA进行长时间Burst传输时其他任务或中断如果试图访问同一I2C总线会导致冲突。务必使用互斥锁Mutex或信号量Semaphore来保护对I2C模块的访问确保同一时间只有一个上下文任务或中断在操作I2C控制器避免仲裁丢失或数据损坏。