TM4C I2C从机中断与FIFO配置实战:从寄存器原理到高效数据引擎
1. I2C从机中断机制:从硬件信号到软件响应的完整链路
在嵌入式系统里,I2C总线是连接微控制器和各种传感器、存储器、显示模块的血管。当你的MCU作为从机挂在这条总线上时,它不能像主机那样主动发起通信,只能被动等待主机的召唤。这时候,中断机制就成了从机高效响应、避免CPU空转浪费的关键。想象一下,你正在厨房做饭,每隔几秒就跑去看一眼门铃摄像头有没有人按门铃,这显然很蠢。更聪明的做法是,门铃一响,摄像头就通过一个蜂鸣器(中断)告诉你:“嘿,有人来了,快处理!” I2C从机中断干的就是这个活儿。
I2C从机中断的核心,是让硬件在特定事件发生时(比如主机发来了数据、FIFO快满了、或者一次传输结束了),主动打断CPU当前的工作,让CPU立刻去处理这个紧急的通信任务。这套机制在TM4C这类微控制器上,是通过一组精心设计的寄存器来实现的,它们构成了一个完整的中断管理流水线:屏蔽(Mask)、状态(Status)和清除(Clear)。理解这三者的关系,是玩转I2C从机通信的基石。
屏蔽寄存器(I2CSIMR)就像你手机的通知设置。你收到一条微信(原始中断发生了),但如果你关闭了微信的通知开关(屏蔽了该中断),手机就不会响铃或弹窗(不会产生控制器中断)。在I2C里,I2CSIMR的每一个比特位都对应一种中断源(如数据中断DATAIM、接收FIFO请求中断RXIM等),软件通过写1来“允许通知”,写0来“静音”。
原始中断状态寄存器(I2CSRIS)则是最底层的硬件状态报告器。它不管你的“通知设置”是什么,只要硬件上发生了对应的事件,对应的状态位就会被置1。这就像微信服务器,只要有人给你发消息,它那里就会记录一条“未读消息”,不管你的手机设置成什么样。读取I2CSRIS,你看到的是所有“可能发生”的中断事件。
已屏蔽中断状态寄存器(I2CSMIS)是最终呈现给中断控制器(NVIC)的结果。它等于I2CSRIS & I2CSIMR。只有那些既发生了(I2CSRIS=1)又被允许了(I2CSIMR=1)的中断,才会在这里显示为1,并真正向CPU申请中断。这就像你的手机,只把那些你开了通知的App的未读消息,汇总到通知中心里。
最后,中断清除寄存器(I2CSICR)是“已读回执”。当中断服务程序处理完一个事件后,必须向I2CSICR对应的位写1,来清除I2CSRIS和I2CSMIS中的标志位。这相当于你点开了微信消息,服务器就把“未读”标记去掉了。如果不及时清除,CPU会认为中断一直存在,导致反复进入中断服务程序,系统就卡死了。
注意:
I2CSICR是一个只写(WO)寄存器。尝试读取它不会得到有意义的数据,这是硬件设计决定的,目的是确保清除操作是一个确定的“动作”,而非一个可回读的状态。编程时切忌对它进行“读-修改-写”操作。
2. 核心寄存器深度解析与配置策略
2.1 中断屏蔽寄存器(I2CSIMR):精细化的事件过滤器
I2CSIMR寄存器是中断管理的总闸门。它的复位值是0x0000.0000,意味着所有中断默认都是被屏蔽的。你必须根据你的应用场景,有选择地打开需要的中断源。我们逐位分析其关键位域:
- DATAIM (Bit 0) - 数据中断屏蔽:这是最常用、最基础的中断。当从机被寻址(地址匹配)且主机进行读或写操作时,此中断会触发。它涵盖了“从机事务接收”、“从机事务请求”和“下一字节传输请求”。对于简单的字节传输,开启此中断并配合查询数据寄存器(
I2CSDR)通常就够了。 - STARTIM (Bit 1) 与 STOPIM (Bit 2) - 起始/停止条件中断:这两个中断分别在总线上检测到START或STOP信号时触发。它们对于需要精确控制传输边界、或在帧开始/结束时进行特定初始化和清理工作的应用非常有用。例如,在STOP中断里可以安全地认为一次完整传输结束,可以开始处理接收到的数据包。
- RXIM (Bit 6) 与 TXIM (Bit 5) - 接收/发送FIFO请求中断:这是实现高效DMA或批量数据传输的关键。它们不是在有单个字节时触发,而是当FIFO中的数据量达到你在
I2CFIFOCTL寄存器中预设的触发水平(Trigger Level)时才触发。比如,你可以设置RX FIFO的触发水平为4(RXTRIG=0x4),那么当FIFO中累积了4个或更多字节时,RXIM中断才会产生。这极大地减少了中断频率,提升了系统效率。 - RXFFIM (Bit 8) 与 TXFEIM (Bit 7) - 接收FIFO满/发送FIFO空中断:
RXFFIM在接收FIFO完全填满时触发,这是一个“紧急”信号,提醒你必须立刻读取数据,否则后续数据会丢失。TXFEIM在发送FIFO完全空时触发,告诉你“可以安全地填充下一批数据了”。在流式传输中,常用TXIM(低于触发水平)来提前准备数据,而用TXFEIM作为传输完成的辅助判断。 - DMARXIM (Bit 3) 与 DMATXIM (Bit 4) - DMA中断:当接收或发送DMA传输完成时触发。在配置了DMA进行数据搬运的场景下,这两个中断标志着DMA传输周期的结束,软件可以在此进行缓冲区切换或后续处理。
配置心得:不要盲目打开所有中断。过多的中断会加重CPU负担,并可能因中断嵌套引入复杂性问题。一个典型的传感器读取场景可能只开启DATAIM;而一个需要接收大量数据的应用,则应开启RXIM并配合DMA,同时可能开启RXFFIM作为防溢出的安全网。
2.2 中断状态与清除寄存器:精准的事件捕获与清理
理解了屏蔽寄存器,状态寄存器就很好理解了。I2CSRIS是原始火警传感器,I2CSMIS是传到消防控制室的、经过确认的警报。
一个至关重要的细节在于中断清除。I2CSICR的清除操作是“写1清零”,并且会同时清除I2CSRIS和I2CSMIS中的对应位。这里有一个经典的坑:
踩坑实录:TXFEIM中断的“一次性”特性在
I2CSRIS寄存器中关于TXFERIS(发送FIFO空中断原始状态)的描述里有一行特别说明:“Note that if the TXFERIS interrupt is cleared (by setting the TXFEIC bit) when the TX FIFO is empty, the TXFERIS interrupt does not reassert even though the TX FIFO remains empty in this situation.”这句话的意思是:如果当TX FIFO为空时,你清除了TXFERIS中断标志,那么即使此后TX FIFO继续保持为空,TXFERIS也不会再次被置位。这会导致你依赖TXFEIM来判断发送完成时,如果清除时机不当,会丢失中断。正确的做法是,在TXFEIM中断服务程序中,确认所有数据都已加载(例如,检查一个软件计数器),然后再清除中断标志。或者,更常见的做法是,使用TXIM(FIFO低于触发水平)来驱动数据填充,而将TXFEIM仅作为一个辅助的完成指示,在传输逻辑的最终阶段进行查询而非中断。
中断服务程序(ISR)的标准流程:
- 读取
I2CSMIS寄存器,确定是哪个(或哪些)已屏蔽的中断触发了本次进入。 - 根据
I2CSMIS的值,跳转到相应的处理分支(如处理接收数据、填充发送数据等)。 - 在处理完该中断事件后,立即向
I2CSICR寄存器的对应位写1,以清除中断标志。通常使用HWREG(I2Cx_BASE + I2C_O_SICR) = I2C_SICR_DATAIC;这样的宏或直接寄存器操作。 - 如果需要,在ISR末尾清除中断控制器的挂起位(如ARM Cortex-M的NVIC_ICPR)。
2.3 FIFO控制与状态寄存器:数据流的中枢
I2C模块内置的FIFO是提升吞吐量的利器。I2CFIFOCTL和I2CFIFOSTATUS这对寄存器负责管理它。
I2CFIFOCTL (偏移 0xF04):
- TXASGNMT/RXASGNMT (Bit 15/31):决定TX/RX FIFO是分配给主机控制器还是从机控制器。在从机模式下,我们必须将其设置为1(分配给从机)。
- DMATXENA/DMARXENA (Bit 13/29):使能TX/RX通道的DMA功能。当使能后,达到触发水平时,硬件会自动向DMA控制器发起请求。
- TXTRIG/RXTRIG (Bit[2:0]/Bit[18:16]):这是FIFO中断触发的“水位线”。
TXTRIG定义了发送FIFO中剩余数据量低于或等于多少字节时触发TXIM中断。例如,TXTRIG=2意味着当FIFO中只剩2个或更少字节待发送时,就会请求新数据。RXTRIG则定义了接收FIFO中数据量达到或超过多少字节时触发RXIM中断。例如,RXTRIG=4意味着当FIFO中积累了4个或更多字节时,才通知CPU或DMA来取数据。合理设置这两个值,可以在中断频率和数据实时性之间取得平衡。 - TXFLUSH/RXFLUSH (Bit 14/30):写1可立即清空对应的FIFO。该位会自清除。在通信初始化或错误恢复时非常有用。
I2CFIFOSTATUS (偏移 0xF08): 这是一个只读寄存器,用于实时监控FIFO状态。
- TXFE/TXFF (Bit 0/1):发送FIFO空/满标志。
- RXFE/RXFF (Bit 16/17):接收FIFO空/满标志。
- TXBLWTRIG (Bit 2):发送FIFO数据量低于触发水平标志。当
TXFF不为满且数据量 ≤TXTRIG时置位。这是TXIM中断产生的直接状态依据。 - RXABVTRIG (Bit 18):接收FIFO数据量高于触发水平标志。当
RXFE为空且数据量 ≥RXTRIG时置位。这是RXIM中断产生的直接状态依据。
配置示例:假设我们希望从机在收到4个字节后产生一次中断,并使用DMA将数据搬走。我们会进行如下配置:
- 设置
I2CFIFOCTL:RXASGNMT = 1(从机),RXTRIG = 0x4(4字节触发),DMARXENA = 1(使能DMA)。 - 设置
I2CSIMR:RXIM = 1(使能接收FIFO请求中断)。注意,此时DATAIM可能被禁用,因为数据流由FIFO和DMA管理。 - 配置DMA通道,源地址为
I2CFIFODATA寄存器,目标地址为内存缓冲区,传输宽度为字节,并在每次DMA请求时传输1个数据块(burst size可设)。 - 当主机发送数据达到4字节时,
RXABVTRIG置位,RXRIS置位,由于RXIM=1,RXMIS也置位,向CPU申请中断。在中断服务程序中(或由DMA完成中断),我们可以启动DMA,或者如果数据量不大,直接读取I2CFIFODATA4次。
2.4 从机地址与ACK控制:身份识别与流控
I2CSOAR2 (偏移 0x81C):这个寄存器允许从机响应第二个I2C地址。OAR2字段存储7位从机地址(注意,不包含读/写位),OAR2EN位用于使能这个备用地址。这在某些需要同一个硬件响应多个逻辑地址的复杂应用中很有用。
I2CSACKCTL (偏移 0x820):这是一个高级控制寄存器,允许软件在字节级别覆盖硬件的自动ACK/NACK响应。
- ACKOEN (Bit 0):ACK覆盖使能。置1后,硬件将不再自动发送ACK,而是由
ACKOVAL位的值决定。 - ACKOVAL (Bit 1):当
ACKOEN=1时,此位决定在下一个ACK周期发送什么:0发送ACK(确认),1发送NACK(非确认)。
应用场景:当从机作为接收方,但接收缓冲区已满或数据校验错误时,它可以通过置位ACKOEN并设置ACKOVAL=1,在接收到下一个字节后主动发送一个NACK。这会通知主机“我出问题了,请停止发送”。主机通常会因此终止当前传输。这在实现带流控或错误处理的I2C从机时非常关键。
重要提示:寄存器描述中提到“The I2C clock is pulled low after the last data bit until this register is written.” 这意味着在最后一个数据位之后,SCL线会被拉低(时钟拉伸),直到软件写
I2CSACKCTL寄存器来决定ACK/NACK。这给了软件反应时间,但也要注意,过长的延迟会导致总线超时。
3. 实战:构建一个高效的I2C从机数据接收引擎
让我们结合上述所有知识点,设计一个具体的场景:TM4C129作为从机(地址0x50),需要可靠地接收主机不定时发送的数据包,每个数据包长度不定,但最大不超过256字节。我们要求高可靠性,并能及时通知应用层数据已就绪。
3.1 系统设计与寄存器初始化
我们的设计目标是利用FIFO和中断降低CPU负载,同时防止数据丢失。我们将采用“FIFO水位中断 + DMA搬运”为主,“FIFO满中断”为安全备份的策略。
初始化步骤:
使能外设时钟:首先在系统控制模块中使能I2C模块和对应GPIO端口的时钟。
SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0); SysCtlPeriph-ialEnable(SYSCTL_PERIPH_GPIOA); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0)); // 等待就绪配置GPIO引脚:将SCL和SDA引脚配置为I2C功能。
GPIOPinConfigure(GPIO_PA6_I2C0SCL); GPIOPinConfigure(GPIO_PA7_I2C0SDA); GPIOPinTypeI2C(GPIO_PORTA_BASE, GPIO_PIN_6 | GPIO_PIN_7);初始化I2C从机模式:设置从机地址和时钟速率(从机速率参数通常用于滤波,而非主动时钟)。
I2CSlaveEnable(I2C0_BASE); I2CSlaveInit(I2C0_BASE, 0x50); // 7位地址 0x50配置FIFO:
- 我们希望当接收FIFO中积累到8个字节时,就触发一次中断/DMA请求,以便及时搬走数据。
- 我们将发送FIFO的触发水平设为2,这样当FIFO中只剩2个字节时,就请求新数据,为填充数据留出时间。
// 配置FIFO控制寄存器 I2CFIFOCTL // RX: 分配给从机,触发水平8字节,使能DMA接收 // TX: 分配给从机,触发水平2字节,使能DMA发送(如果也需要发送) uint32_t ui32FIFOConfig = 0; ui32FIFOConfig |= (1 << 31); // RXASGNMT = 1 (Slave) ui32FIFOConfig |= (0x7 << 16); // RXTRIG = 7 (触发当 >=8字节?注意文档描述是“or more”,值7代表>=8字节?这里需要查证,通常值N代表N+1或以上触发,假设我们设0x7) // 更常见的做法是设置RXTRIG=7,表示当FIFO中有>=8个字节时触发。但文档表格显示0x7对应“7 or more”。我们保守点,设为0x3(4字节触发)。 ui32FIFOConfig &= ~(0x7 << 16); // 先清零 ui32FIFOConfig |= (0x3 << 16); // RXTRIG = 3 (触发当 >=4 字节) ui32FIFOConfig |= (1 << 29); // DMARXENA = 1 ui32FIFOConfig |= (1 << 15); // TXASGNMT = 1 (Slave) ui32FIFOConfig |= (0x1 << 0); // TXTRIG = 1 (触发当 <=1 字节,即FIFO将空) ui32FIFOConfig |= (1 << 13); // DMATXENA = 1 (如果需发送) HWREG(I2C0_BASE + I2C_O_FIFOCTL) = ui32FIFOConfig;配置中断:
- 使能接收FIFO请求中断(
RXIM)和接收FIFO满中断(RXFFIM)作为保护。 - 暂时禁用数据中断(
DATAIM),因为我们用FIFO管理数据流。
// 配置中断屏蔽寄存器 I2CSIMR uint32_t ui32IntMask = 0; ui32IntMask |= I2C_SIMR_RXIM; // 使能RX FIFO请求中断 ui32IntMask |= I2C_SIMR_RXFFIM; // 使能RX FIFO满中断(安全备份) // ui32IntMask |= I2C_SIMR_DATAIM; // 禁用数据中断 HWREG(I2C0_BASE + I2C_O_SIMR) = ui32IntMask; // 在NVIC中使能I2C0中断 IntEnable(INT_I2C0); I2CSlaveIntEnable(I2C0_BASE); // 这个TI库函数可能直接操作SIMR,注意不要冲突 // 更推荐直接操作寄存器或使用库函数统一管理- 使能接收FIFO请求中断(
配置DMA(以uDMA为例):
- 设置DMA通道为外设到存储器模式。
- 源地址为
I2C0_BASE + I2C_O_FIFODATA,地址不递增。 - 目标地址为内存缓冲区,地址递增。
- 仲裁大小(Arbitration Size)设为1,每次触发传输1个数据项(字节)。
- 因为
RXTRIG=4,所以每次FIFO中有4个字节时,会产生DMA请求,DMA一次搬走4个字节(通过设置传输次数为4,或使用Ping-Pong缓冲)。
// 伪代码,基于TivaWare uDMA API uDMAChannelAssign(UDMA_CHANNEL_I2C0RX); // 分配通道 uDMAChannelAttributeDisable(UDMA_CHANNEL_I2C0RX, UDMA_ATTR_ALTSELECT); // 使用主数据结构 uDMAChannelControlSet(UDMA_CHANNEL_I2C0RX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4); uDMAChannelTransferSet(UDMA_CHANNEL_I2C0RX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, (void*)(I2C0_BASE + I2C_O_FIFODATA), pucRxBuffer, 64); // 每次传输64字节?需要匹配 // 注意:这里需要仔细设计DMA传输大小与FIFO触发水平的匹配,以及缓冲区的循环管理。 uDMAChannelEnable(UDMA_CHANNEL_I2C0RX);
3.2 中断服务程序(ISR)实现
当中断发生时,我们需要快速判断中断源并处理。
void I2C0_IRQHandler(void) { uint32_t ui32Status; // 读取已屏蔽的中断状态寄存器,确定中断源 ui32Status = HWREG(I2C0_BASE + I2C_O_SMIS); // 处理接收FIFO请求中断(水位触发) if(ui32Status & I2C_SMIS_RXMIS) { // 通常,如果使能了DMA,DMA会自动搬运数据。 // 这里我们可以检查DMA状态,或者如果未用DMA,则手动从FIFO读取数据。 // 假设我们使用DMA,这里可能只需要清除中断标志。 // 但更稳健的做法是,在DMA完成中断中处理数据,此处仅作为触发信号。 // 清除RXIM中断标志 HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_RXIC; } // 处理接收FIFO满中断(紧急情况) if(ui32Status & I2C_SMIS_RXFFMIS) { // FIFO满了!必须立即读取数据,否则会丢失。 // 这可能意味着主机发送速度太快,或者我们的处理太慢。 // 紧急处理:快速读取FIFO中所有数据。 uint8_t ucData; while(!(HWREG(I2C0_BASE + I2C_O_FIFOSTATUS) & I2C_FIFOSTATUS_RXFE)) { ucData = HWREG(I2C0_BASE + I2C_O_FIFODATA) & 0xFF; // 将ucData存入紧急缓冲区或直接处理 g_ucEmergencyBuffer[g_ui32EmergencyIndex++] = ucData; if(g_ui32EmergencyIndex >= EMERGENCY_BUF_SIZE) { // 紧急缓冲区也满了,可能需要进行错误处理,如复位FIFO HWREG(I2C0_BASE + I2C_O_FIFOCTL) |= I2C_FIFOCTL_RXFLUSH; g_ui32EmergencyIndex = 0; g_bRxOverflow = true; } } // 清除RXFFIM中断标志 HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_RXFFIC; } // 处理其他可能的中断,如STOP... if(ui32Status & I2C_SMIS_STOPMIS) { // 检测到STOP条件,一次传输可能结束。 // 可以设置一个标志,通知主循环处理接收到的完整数据包。 g_bPacketReady = true; HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_STOPIC; } // ... 处理其他中断源 }3.3 主循环中的数据包处理
中断服务程序负责数据的快速接收和标志设置,复杂的数据包解析和业务逻辑应放在主循环中,避免在ISR中耗时过长。
int main(void) { // 初始化代码... while(1) { // 检查数据包就绪标志 if(g_bPacketReady) { // 禁用全局中断,安全地访问共享缓冲区 IntMasterDisable(); // 将DMA缓冲区或紧急缓冲区中的数据拷贝到处理缓冲区 memcpy(g_ucProcessBuffer, g_ucDmaRxBuffer, g_ui32DmaRxCount); g_ui32DmaRxCount = 0; // 重置DMA计数器 // 如果有紧急数据,也合并进来 if(g_ui32EmergencyIndex > 0) { memcpy(&g_ucProcessBuffer[g_ui32ProcessIndex], g_ucEmergencyBuffer, g_ui32EmergencyIndex); g_ui32ProcessIndex += g_ui32EmergencyIndex; g_ui32EmergencyIndex = 0; } // 重新使能中断 IntMasterEnable(); // 处理数据包 ProcessI2CPacket(g_ucProcessBuffer, g_ui32ProcessIndex); g_ui32ProcessIndex = 0; g_bPacketReady = false; // 检查溢出标志 if(g_bRxOverflow) { // 处理溢出错误,例如记录日志、复位通信状态等 HandleRxOverflowError(); g_bRxOverflow = false; } } // 其他后台任务... IdleTask(); } }4. 调试技巧与常见问题排查
即使按照手册配置,在实际调试中仍会遇到各种问题。以下是一些常见坑点及排查思路。
4.1 中断根本不触发
- 检查清单:
- 全局中断是否使能?在Cortex-M中,调用
IntMasterEnable()。 - NVIC中断是否使能?使用
IntEnable(INT_I2Cx)。 - I2C从机中断是否使能?确认
I2CSIMR寄存器中对应位已置1。使用调试器直接读取该寄存器地址(如0x4002000C)。 - I2C从机模块是否已使能?
I2CSlaveEnable(I2Cx_BASE)。 - GPIO引脚复用功能是否正确配置?确认
GPIOAFSEL和GPIOPCTL寄存器。 - 总线是否有活动?用逻辑分析仪或示波器抓取SCL/SDA波形,确认主机确实在向你的从机地址发送数据。
- 从机地址是否匹配?检查
I2CSOAR(主地址)和I2CSOAR2(备用地址)的设置。
- 全局中断是否使能?在Cortex-M中,调用
4.2 中断触发一次后不再触发
- 最常见原因:未在中断服务程序中清除中断标志。检查你的ISR,确保对所有处理到的中断源,都向
I2CSICR寄存器的对应位写了1。 - 对于TXFEIM:回顾2.2节提到的“一次性”特性。如果你在FIFO为空时清除了
TXFERIS,即使FIFO继续保持为空,中断也不会再产生。解决方案是使用TXIM(基于触发水平)来驱动数据发送,而非依赖TXFEIM。
4.3 FIFO数据异常或丢失
- 数据错位:确保读取
I2CFIFODATA寄存器的顺序和主机发送的顺序一致。FIFO是先进先出的。 - 数据丢失(溢出):
- 检查
RXFF标志:如果经常置1,说明你的处理速度跟不上接收速度。 - 提高触发水平:增大
RXTRIG值,让每次中断/DMA搬运更多数据,减少中断次数。 - 优化数据处理:将耗时的处理移出ISR,放到主循环。使用DMA代替CPU搬运。
- 增加缓冲区:使用更大的内存缓冲区,并采用乒乓缓冲等策略。
- 检查
- 发送数据被覆盖:在填充TX FIFO前,检查
TXFF(满)标志。如果FIFO已满还强行写入,数据会丢失。
4.4 使用调试器观察寄存器状态
在调试时,学会观察关键寄存器位是基本功。在IDE的寄存器窗口或内存窗口中,添加以下地址的监视:
| 寄存器名称 | 地址偏移 (从I2Cx_BASE) | 关键位观察 |
|---|---|---|
I2CSRIS | 0x810 | 查看RXRIS,TXRIS,DATARIS等,确认硬件是否检测到事件。 |
I2CSMIS | 0x814 | 查看RXMIS,TXMIS等,确认中断是否被屏蔽后仍有效。 |
I2CFIFOSTATUS | 0xF08 | 查看RXFE,RXFF,RXABVTRIG,TXFE,TXFF,TXBLWTRIG,了解FIFO实时状态。 |
I2CSIMR | 0x80C | 确认中断使能位是否按预期设置。 |
4.5 关于DMA与FIFO的协同工作
- 触发对齐:确保DMA的传输大小(Burst Size或Transfer Size)与FIFO的触发水平(
RXTRIG/TXTRIG)合理匹配。例如,RXTRIG=4意味着FIFO有4个字节时请求DMA,那么DMA单次请求最好设置为传输4字节。如果DMA单次传输设置得更大(比如16字节),它会在第一次请求时搬走4字节,但会等待后续请求凑够16字节,这可能不符合预期。 - DMA完成中断:当使用DMA搬运FIFO数据时,除了I2C的FIFO请求中断,通常还会使能DMA通道的传输完成中断。在DMA完成中断中,你可以知道一批数据已经完整地从FIFO搬到了内存,然后进行后续处理。I2C的
RXIM中断在这里更多是作为一个“触发器”,告诉DMA“有活干了”。 - 缓冲区管理:对于连续数据流,需要设计循环缓冲区或双缓冲区。当DMA正在填充缓冲区A时,应用程序处理缓冲区B。在DMA完成中断中,切换缓冲区指针。这可以避免数据竞争,实现流畅的数据流。
通过深入理解I2C从机中断与FIFO寄存器组的工作原理,并结合实际的配置策略和调试经验,你就能构建出稳定、高效的I2C从机通信模块,轻松应对各种嵌入式外设控制的挑战。记住,数据手册是你的地图,示波器/逻辑分析仪是你的眼睛,而严谨的编程和系统的调试思维,则是你抵达目的地的可靠座驾。
