深入解析I2C控制器FIFO管理:中断、DMA与Draining机制实战 📅 2026/7/21 13:41:14 1. 项目概述与核心价值在嵌入式系统开发中I2C总线因其简洁的两线制SDA、SCL和主从多设备架构成为了连接传感器、EEPROM、RTC等低速外设的首选协议。然而当数据吞吐量增大或系统实时性要求提高时传统的字节级轮询或中断处理方式会大量占用CPU资源成为系统性能的瓶颈。这时I2C控制器内置的FIFO先进先出缓冲区及其配套的中断、DMA直接内存访问和Draining排空机制就成为了提升效率、解放CPU的关键武器。我接触过不少项目从简单的温湿度采集到复杂的多传感器融合系统但凡涉及到批量数据传输如果没用好FIFO和DMA系统负载很容易就上去了主循环响应变慢甚至出现数据丢失。TI的I2C控制器模块提供了一个相当典型且功能完善的案例其FIFO管理逻辑清晰但手册中的描述往往分散在各个章节初次接触容易摸不着头脑。本文旨在将这些碎片化的知识串联起来结合我实际调试中的踩坑经验深入解析FIFO管理的三大核心操作模式中断、轮询、DMA以及用于处理“零头”数据的Draining机制。理解这些机制不仅能帮你写出更高效、更稳定的I2C驱动更能让你在遇到通信异常时快速定位问题是出在硬件、配置还是软件流程上。2. FIFO管理机制总览与设计思路在深入细节之前我们得先建立起对TI I2C控制器FIFO管理的整体认知。这个模块内部集成了两个独立的32字节FIFO分别用于发送TX和接收RX。FIFO的核心思想是批处理和异步通知其管理逻辑围绕几个关键寄存器展开尤其是I2C_BUF缓冲区配置和I2C_BUFSTAT缓冲区状态。2.1 核心寄存器与阈值控制整个FIFO管理的“开关”和“刻度”都集中在I2C_BUF寄存器。其中RXTRSH和TXTRSH这两个字段是灵魂所在。它们定义了触发事件的阈值水平。RXTRSH(Receive Threshold): 接收FIFO阈值。当RX FIFO中的数据量大于这个阈值时才会满足触发条件产生RRDY中断或DMA请求。例如设置RXTRSH 7意味着当FIFO中存有8个或更多字节数据时才会通知CPU或DMA来取数据。TXTRSH(Transmit Threshold): 发送FIFO阈值。其逻辑与接收相反当TX FIFO中的数据量低于这个阈值即变得更空时才会触发事件产生XRDY中断或DMA请求。例如设置TXTRSH 15意味着当FIFO中剩余空间大于等于16字节即数据量少于16字节时就会通知CPU或DMA来填充数据。这里有一个非常重要的细节阈值设定与触发条件的关系是“阈值1”。手册中多次提到“RXTRSH 1”或“TXTRSH 1”这是因为寄存器存储的是阈值N而触发发生在“FIFO深度 N”接收或“FIFO深度 N”发送更空的时刻。理解这一点对于后续计算DMA传输长度、理解中断频率至关重要。2.2 三种操作模式的选择逻辑TI I2C控制器提供了三种FIFO操作模式选择哪种取决于你的系统对实时性、CPU占用率和开发复杂度的权衡。中断模式 (Interrupt Mode): 通过使能I2C_IRQENABLE_SET寄存器中的RRDY_IE和XRDY_IE来开启。当FIFO达到阈值条件时控制器向CPU发起硬件中断。这是最常用的平衡方案既能及时响应又避免了轮询的CPU空转。你需要编写中断服务程序ISR来读写I2C_DATA寄存器。轮询模式 (Polling Mode): 关闭上述中断使能位CPU定期主动查询I2C_IRQSTATUS_RAW寄存器中的RRDY或XRDY标志位。这种方式实现简单没有中断上下文切换的开销但会持续占用CPU时间。适用于对实时性要求不高、或系统负载极轻的场景。DMA模式 (DMA Mode): 通过设置I2C_BUF中的RDMA_EN/XDMA_EN以及相应的DMA使能寄存器来开启。当FIFO达到阈值条件时控制器直接向DMA控制器发起请求由DMA引擎在内存和I2C_DATA寄存器之间搬运数据完全解放CPU。这是处理大批量、高带宽数据传输的理想选择但需要正确配置DMA通道并处理好与Draining机制的配合。注意模式是互斥的。对于RX或TX方向你只能选择其中一种方式来服务FIFO。例如使能了DMA RX那么RRDY中断就会被强制拉低无效。如果同时使能了中断和DMA行为是未定义的很可能导致数据错乱。2.3 Draining机制的必要性这是最容易出问题的地方。假设你设置RXTRSH 7阈值8并且使用DMA每次DMA传输固定搬运8字节。如果总传输长度是20字节那么DMA会完美地搬运前16字节2次请求。但剩下的4字节20 - 16不足以再次触发DMA请求因为FIFO深度4 阈值8这4个字节就会一直残留在FIFO里导致传输不完整。Draining机制就是为解决这个“零头”问题而生的。当一次I2C传输结束检测到Stop信号后如果RX FIFO中仍有数据但未达到阈值或者TX方向仍有数据需要发送但不足一个阈值量控制器就会产生一个特殊的排空中断RDR(Receive Draining) 或XDR(Transmit Draining)。此时CPU需要介入读取I2C_BUFSTAT.RXSTAT或I2C_BUFSTAT.TXSTAT来获知剩余字节数然后手动读取或写入这些数据或者重新配置DMA进行一次小规模传输。如果没有启用或正确处理Draining就可能触发AERR(Access Error) 中断或者数据永远滞留在FIFO中。3. 中断模式下的FIFO管理详解中断模式是理解所有机制的基础。我们分别从接收和发送两个方向拆解其工作流程和关键陷阱。3.1 接收中断 (RRDY) 流程与配置要点接收流程的目标是高效地从RX FIFO中取出数据。核心中断是RRDY(Receive Ready)。工作流程初始化配置I2C_BUF.RXTRSH例如设为7使能I2C_IRQENABLE_SET.RRDY_IE 1。触发当从设备返回数据使得RX FIFO中的数据字节数大于RXTRSH即 RXTRSH 1时RRDY标志位在I2C_IRQSTATUS_RAW中被置位并向CPU产生中断。响应CPU进入中断服务程序ISR。读取状态ISR首先读取I2C_IRQSTATUS_RAW寄存器确定是RRDY中断。批量读取从I2C_DATA寄存器连续读取RXTRSH 1字节的数据。这是最高效的方式因为一次中断处理尽可能多的数据。清除中断向I2C_IRQSTATUS_RAW.RRDY位写入1清除中断标志。注意如果清除中断后FIFO中的数据量仍然高于阈值RRDY标志会立即再次被置位可能马上触发下一次中断。循环重复步骤2-6直到所有数据接收完成。处理零头如果最后一批数据不足RXTRSH 1字节RRDY中断将永远不会被触发。此时需要依靠Draining机制见第5章或切换到轮询模式读取最后几个字节。关键配置与陷阱阈值选择RXTRSH的选择是一场权衡。设得太小如0每收到1字节就中断效率低下等同于无FIFO。设得太大如31中断频率低但每次中断处理的数据量大可能导致ISR执行时间过长且最后一次传输零头数据的概率和量都会增大。通常根据总传输长度和系统中断响应能力选择8或16是一个不错的起点。中断清除时机务必在读取完数据后再清除RRDY中断标志。如果先清除标志但在读取数过程中新的数据又涌入FIFO并再次满足了阈值条件可能会丢失这次中断事件取决于中断控制器是边沿触发还是电平触发。安全的做法是读数据 - 清除标志。FIFO深度查询在ISR中可以通过读取I2C_BUFSTAT.RXSTAT来实时知道FIFO中有多少字节而不是僵化地只读RXTRSH1个字节。这在处理不定长数据或担心数据堆积时非常有用。3.2 发送中断 (XRDY) 流程与核心差异发送流程的目标是及时向TX FIFO中补充数据避免下溢Underflow。核心中断是XRDY(Transmit Ready)。工作流程初始化配置I2C_BUF.TXTRSH例如设为0使能I2C_IRQENABLE_SET.XRDY_IE 1。注意对于Slave发送模式手册强烈建议设置TXTRSH 0阈值1原因后述。触发当TX FIFO中的数据字节数小于等于TXTRSH即 FIFO 空余空间 TXTRSH 1时XRDY标志位置位产生中断。特别地当FIFO完全为空时一定会触发XRDY。响应CPU进入ISR。批量写入向I2C_DATA寄存器连续写入TXTRSH 1字节的数据。清除中断写入1清除I2C_IRQSTATUS_RAW.XRDY标志。循环重复步骤2-5直到所有数据发送完成。处理零头同接收最后不足一个阈值的数据需要Draining机制处理。发送模式的特殊考量Slave发送模式的陷阱这是重点当I2C作为从设备发送器时主设备随时可能通过发送NACK来终止读取。如果TXTRSH设置得较大比如15那么当FIFO为空时控制器会请求CPU/DMA填充16字节的数据。但如果主设备只读了5字节就发送NACK停止那么剩下的11字节数据就永远留在FIFO里成为“死数据”。因此手册明确建议在Slave TX模式下设置TXTRSH 0阈值1这样主设备每请求一个字节FIFO空就触发一次中断CPU只准备下一个字节避免了数据残留。如果不得不使用大阈值则必须在传输结束后检查NACK中断并手动清除TX FIFO使用I2C_BUF.TXFIFO_CLR。下溢XUDF如果CPU没有及时响应XRDY中断来补充数据导致移位寄存器为空且FIFO也为空但I2C_CNT显示还有数据要发送就会发生下溢。XUDF标志位会被置位控制器会拉低SCL线以挂起总线等待数据。这通常意味着你的中断响应太慢或系统负载过重。4. DMA模式下的高效数据传输DMA模式将CPU从繁重的数据搬运工作中彻底解放出来是实现高效大数据量传输的关键。其核心思想是将FIFO的阈值触发信号 (RRDY/XRDY) 直接转换为DMA请求信号 (I2C_DMA_RX/I2C_DMA_TX)。4.1 DMA接收配置与流程外设端配置设置I2C_BUF.RXTRSH为期望的阈值如7。设置I2C_BUF.RDMA_EN 1启用DMA接收通道。设置I2C_DMARXENABLE_SET.DMARX_ENABLE_SET 1使能DMA请求生成。DMA控制器配置源地址 (Source Address)固定为I2C_DATA寄存器的地址。目的地址 (Destination Address)指向内存中接收缓冲区的地址。传输长度 (Transfer Size)通常配置为(RXTRSH 1) * N其中N是整数表示期望的DMA传输次数。但更关键的是同步类型。同步类型 (Synchronization)必须配置为外设同步。即每次传输都由I2C控制器产生的I2C_DMA_RX请求来触发。单次传输大小 (Element Size)设置为1字节8位。工作流程I2C开始接收数据当RX FIFO中数据量 RXTRSH时I2C_DMA_RX请求信号有效。DMA控制器收到请求执行一次从I2C_DATA到目标内存的1字节传输。关键点DMA控制器需要被配置为每收到一次请求就传输RXTRSH 1个字节一个数据块。这通常通过DMA控制器的“帧Frame”或“突发Burst”概念来实现。例如设置DMA每次触发传输一个包含8个元素的数组。传输完成后DMA控制器可能需要在完成中断中清除I2C的DMA请求使能位 (I2C_DMARXENABLE_CLR)或者由I2C在传输结束时自动处理。Draining处理对于最后不足一个阈值的零头数据DMA模式同样需要处理。当I2C传输结束且RX FIFO中有剩余数据时会触发RDR中断。此时CPU需要读取I2C_BUFSTAT.RXSTAT获得剩余字节数然后重新配置DMA通道将传输长度改为这个剩余值并启动一次性的DMA传输来排空FIFO。或者CPU直接手动读取这些字节。4.2 DMA发送配置与流程外设端配置设置I2C_BUF.TXTRSH主模式可根据需要设置从模式建议为0。设置I2C_BUF.XDMA_EN 1。设置I2C_DMATXENABLE_SET.DMATX_ENABLE_SET 1。DMA控制器配置源地址指向内存中发送缓冲区的地址。目的地址固定为I2C_DATA寄存器的地址。传输长度总发送字节数。同步类型外设同步由I2C_DMA_TX请求触发。单次传输大小1字节。工作流程初始时TX FIFO为空I2C_DMA_TX请求立即有效。DMA控制器收到请求将TXTRSH 1字节的数据写入I2C_DATA。I2C控制器开始发送数据当FIFO中数据量 TXTRSH时再次产生DMA请求。循环直到所有数据写入FIFO。阈值高低的影响手册中的图15-12和15-13清晰地展示了TXTRSH高低的影响。高阈值 (High Threshold)例如TXTRSH15。DMA请求在FIFO较空时数据量15就触发允许DMA一次性写入16字节。这能更快地填满FIFO减少总线空闲提高吞吐率但可能增加数据传输的延迟因为要攒够一定数据才发送。低阈值 (Low Threshold)例如TXTRSH0。DMA请求仅在FIFO完全为空时触发每次只写入1字节。这保证了最快的响应速度但DMA请求频率极高可能增加系统总线竞争降低整体效率。在Slave发送模式下必须使用低阈值0来避免数据残留问题。4.3 DMA模式下的常见问题排查DMA传输卡住不完成首先检查DMA请求信号是否正常产生。用逻辑分析仪或示波器查看I2C_DMA_TX/RX信号线。如果没有请求检查I2C的DMA使能位 (RDMA_EN/XDMA_EN) 和请求使能位 (DMARX_ENABLE_SET等) 是否正确设置。其次检查DMA通道的源/目的地址、传输长度配置是否正确特别是地址是否自增Increment。数据错位或丢失确保DMA的传输数据宽度Element Size与I2C数据寄存器宽度8位匹配。检查内存缓冲区对齐问题。在DMA完成中断中确认DMA传输的字节数与I2CI2C_CNT寄存器设定的值是否一致。Draining中断未触发数据残留确认I2C_IRQENABLE_SET.RDR_IE或XDR_IE是否已使能。检查传输总长度是否确实是FIFO阈值的整数倍。如果不是必须使能Draining中断并正确处理。Access Error (AERR) 中断这是Draining机制未正确处理的最直接表现。当DMA试图读取空RX FIFO或入满TX FIFO时就会触发此中断。这通常发生在传输末尾DMA被配置的传输长度大于实际可用的数据量对于RX或剩余空间对于TX。解决方案就是正确启用和处理RDR/XDR中断。5. Draining机制处理“零头”数据的艺术Draining机制是FIFO管理中最精巧也最容易疏忽的一环。它的存在确保了任何非阈值整数倍的传输都能被完整处理。5.1 接收排空 (RDR) 机制触发条件一次I2C接收传输结束检测到Stop条件且此时RX FIFO中的数据量大于0但小于等于RXTRSH即未达到常规的RRDY或DMA请求触发条件。软件响应流程I2C_IRQSTATUS_RAW.RDR标志位置1如果已使能 (RDR_IE1)则产生中断。CPU在中断服务程序中读取I2C_BUFSTAT.RXSTAT寄存器。这个寄存器的值就是FIFO中剩余的、待读取的字节数。根据当前模式采取行动中断模式CPU直接从I2C_DATA寄存器连续读取RXSTAT次。DMA模式CPU需要重新编程DMA控制器。将DMA传输长度修改为RXSTAT的值然后启动这次特殊的DMA传输。这次传输完成后整个接收过程才算结束。清除RDR中断标志位。5.2 发送排空 (XDR) 机制触发条件在I2C主发送模式下所有待发送数据已通过DMA或中断写入流程提交但最后剩余待写入TX FIFO的字节数小于TXTRSH 1。软件响应流程I2C_IRQSTATUS_RAW.XDR标志位置1如果已使能 (XDR_IE1)则产生中断。CPU读取I2C_BUFSTAT.TXSTAT寄存器。这个值表示还需要向TX FIFO写入多少字节才能完成整个数据块的发送。根据当前模式采取行动中断模式CPU直接向I2C_DATA寄存器连续写入TXSTAT个字节。DMA模式重新配置DMA发起一次长度为TXSTAT的传输。清除XDR中断标志位。5.3 实践中的注意事项与优化技巧使能时机RDR_IE和XDR_IE通常不需要一直开启。可以在启动一次传输前开启在传输完成确认后关闭。这可以减少不必要的中断。Slave发送模式的例外如前所述Slave发送模式无法使用Draining机制因为主机随时可能停止读取传输长度未知。因此在Slave TX模式下要么设置TXTRSH0要么在收到NACK中断后手动清除TX FIFO (I2C_BUF.TXFIFO_CLR)。状态寄存器的原子性在读取RXSTAT/TXSTAT和实际操作FIFO之间如果发生了更高优先级的中断并操作了I2C状态可能改变。在可靠性要求高的场合可以考虑在操作期间短暂关闭全局中断或使用信号量保护。计算替代方案手册提到在主模式下CPU可以通过计算DCOUNT数据计数器模除TXTRSH/RXTRSH来提前知道剩余字节数从而可能避免使用Draining中断。但这需要软件维护精确的计数在复杂的中断和DMA交互场景下容易出错通常直接使用硬件Draining机制更为可靠。6. 实战配置步骤与问题排查实录结合手册第15.2.14节的编程指南我将一个完整的I2C主模式DMA传输配置流程细化并附上常见问题。6.1 完整的主模式DMA传输配置流程以下以主设备发送器使用DMA TX为例目标速率100kbps模块基础配置// 1. 配置时钟预分频器得到约12MHz的模块功能时钟根据系统时钟计算 I2C_PSC (SystemClockInMHz / 12) - 1; // 2. 配置SCL高低时间得到100kbps (12MHz模块时钟下SCLLSCLH60) I2C_SCLL 60; I2C_SCLH 60; // 3. 配置自身地址主模式通常可忽略或配置为不冲突的地址 I2C_OA 0x00; // 4. 使能I2C模块 I2C_CON | I2C_EN;FIFO与DMA配置// 5. 配置TX FIFO阈值假设设为7即阈值8 I2C_BUF.TXTRSH 7; // 6. 使能TX DMA通道 I2C_BUF.XDMA_EN 1; // 7. 使能TX DMA请求生成 I2C_DMATXENABLE_SET 0x1; // 设置DMATX_ENABLE_SET位 // 8. 使能XDR中断用于处理零头数据 I2C_IRQENABLE_SET.XDR_IE 1; // 9. 配置DMA控制器 // - 源地址: 发送数据缓冲区地址 // - 目的地址: I2C_DATA // - 传输长度: 总发送字节数 // - 同步模式: 外设触发触发源为I2C_DMA_TX // - 单次传输大小: 8位 // - 传输模式: 每次触发传输 (TXTRSH1)8 个字节 DMA_ConfigureChannel(I2C_TX_CH, ...);启动传输// 10. 配置从设备地址和传输字节数 I2C_SA SlaveAddress; I2C_CNT TotalBytesToSend; // 11. 等待总线空闲 while(I2C_IRQSTATUS_RAW.BB); // 12. 配置为主发送模式并产生起始条件 I2C_CON | (MST | TRX | STT); // 13. 启动DMA传输 DMA_StartChannel(I2C_TX_CH);处理Draining中断XDRvoid I2C_ISR(void) { if(I2C_IRQSTATUS_RAW.XDR) { uint8_t remaining_bytes I2C_BUFSTAT.TXSTAT; // 重新配置DMA进行一次性小规模传输 DMA_Reconfigure(I2C_TX_CH, remaining_bytes); DMA_StartChannel(I2C_TX_CH); // 或者直接CPU写入剩余字节 // for(int i0; iremaining_bytes; i) I2C_DATA tx_buffer[offset]; I2C_IRQSTATUS_RAW.XDR 1; // 写1清除中断标志 } // ... 处理其他中断 }6.2 常见问题排查速查表现象可能原因排查步骤与解决方案数据发送/接收不完整总是少最后几个字节Draining机制未启用或未正确处理。1. 检查RDR_IE/XDR_IE是否使能。2. 在传输结束中断中检查RDR/XDR标志位是否置位。3. 确保在RDR/XDRISR中正确读取了RXSTAT/TXSTAT并处理了剩余数据。触发AERR (Access Error) 中断软件试图读取空RX FIFO或写入满TX FIFO。1. 在DMA模式下通常是Draining问题见上一条。2. 在中断模式下检查中断服务程序读写I2C_DATA的次数是否超过了FIFO中实际可用的数据量。可以通过查询RXSTAT/TXSTAT来动态决定读写次数而非固定THRSH1。DMA传输启动后毫无动静DMA请求未产生或DMA配置错误。1. 确认I2C_BUF.XDMA_EN和I2C_DMATXENABLE_SET已正确设置。2. 用调试器或指示灯检查I2C_DMA_TX信号是否变高。3. 检查DMA通道的触发源选择是否正确映射到I2C的DMA请求线。4. 检查DMA通道是否已使能。Slave发送时主设备收到错误数据或NACKSlave TX FIFO配置不当导致数据残留。1.确保在Slave发送模式下设置TXTRSH 0。2. 如果必须用大阈值则在检测到NACK中断后执行I2C_BUF.TXFIFO_CLR 1来清空FIFO。中断频率异常高系统负载大FIFO阈值 (RXTRSH/TXTRSH) 设置过小。适当增大阈值。权衡中断处理开销和单次中断处理的数据量。对于大数据量传输优先考虑DMA模式。总线被锁死SCL线持续为低发生下溢 (XUDF) 或仲裁丢失 (AL)。1. 检查XUDF标志。如果是说明CPU/DMA未能及时补充发送数据优化数据供给流程。2. 检查AL标志。如果是说明多主竞争总线失败检查总线冲突或软件启动传输时未检查BBBus Busy标志。使能DMA后对应的RRDY/XRDY中断不再产生这是正常现象。一旦使能RDMA_EN/XDMA_EN对应的RRDY/XRDY中断标志位会被硬件强制拉低中断不会产生取而代之的是DMA请求信号。确保你使用的是DMA请求而不是等待中。调试I2C FIFO相关问题时I2C_BUFSTAT寄存器是你的最佳朋友。时刻关注RXSTAT和TXSTAT可以清晰了解FIFO的实时状态。结合逻辑分析仪查看SCL/SDA波形和DMA请求信号几乎能定位所有硬件交互层面的问题。最后仔细阅读芯片勘误表Errata有些FIFO或DMA相关的异常行为可能是硅片bug需要通过软件 workaround 来解决。