深入解析I2C中断与DMA协同机制:FIFO阈值管理与排水实战
2026/7/20 10:29:59 网站建设 项目流程

1. 项目概述与核心价值

在嵌入式系统开发中,I2C总线因其简洁的两线制(SDA、SCL)和主从架构,成为了连接微控制器与各类传感器、EEPROM、RTC等外设的“血管”。然而,当数据吞吐量增大或系统对实时性、功耗有严格要求时,传统的轮询(Polling)方式就显得力不从心了。它就像让CPU这个“大管家”不停地跑去门口查看快递(数据)是否到了,效率低下且占用大量计算资源。此时,中断(Interrupt)和直接内存访问(DMA)这两种机制就成为了提升系统性能、解放CPU的关键。

但仅仅知道“用中断”或“用DMA”是远远不够的。很多开发者在使用像TI这类厂商的I2C控制器时,常常会遇到一些令人困惑的问题:为什么FIFO(先入先出缓冲区)设了阈值,中断却不像预期那样触发?为什么DMA传输到最后几个字节会卡住或出错?数据量不是FIFO深度的整数倍时,该如何优雅地收尾?这些问题的答案,都深藏在控制器内部关于FIFO管理、中断/DMA事件生成逻辑以及“排水”(Draining)机制的细节之中。

本文将从一线工程师的视角,深入解析I2C控制器中中断与DMA协同工作的核心机制。我们不会停留在手册的简单翻译上,而是结合真实的寄存器配置、时序图以及我踩过的“坑”,重点拆解FIFO阈值管理如何精确控制RRDY/XRDY中断的触发DMA请求事件与中断的异同与联动,以及如何处理“非整数倍”数据传输的Draining Feature。目标是让你不仅知道如何配置,更透彻理解为什么这样配置,从而在设计高可靠、高效率的嵌入式通信系统时,能够游刃有余,写出既稳定又优雅的驱动代码。

2. I2C中断机制深度解析:从信号到服务

中断的本质是一种硬件级别的“回调”机制。当外设(如I2C)发生某个特定事件(如收到数据、发送完成)时,它会拉高一根连接到CPU中断控制器的信号线。CPU收到这个“呼叫”后,会暂停当前任务,跳转到预设的中断服务程序(ISR)来处理这个事件。对于I2C来说,理解每个中断标志在什么条件下被置位,以及如何正确地清除它,是稳定通信的基础。

2.1 核心中断类型及其触发条件

根据技术手册,I2C模块的中断事件都汇总在I2C_IRQSTS_RAW寄存器中。我们重点关注与数据传输最相关的几个:

  1. 接收就绪中断(RRDY):这是接收数据的“门铃”。当接收FIFO中的数据量达到或超过你通过I2C_BUF.RXTRSH寄存器设定的阈值时,此标志位被硬件置1。手册中的图示清晰地表明,RRDY信号(低电平有效)会在FIFO深度超过RXTRSH时拉低,直到CPU读取数据使FIFO深度低于阈值后才拉高。这意味着,如果你设置RXTRSH=7(即阈值8字节),那么当FIFO中累积到第8个字节时,就会产生中断。在中断服务程序中,你可以一次性读取8字节(RXTRSH+1)或更多数据,高效地清空缓冲区。

  2. 发送就绪中断(XRDY):这是发送数据的“催单”。在发送模式下,当发送FIFO为空,或者FIFO中的数据量低于I2C_BUF.TXTRSH设定的阈值,并且总线上还有数据需要发送时,此标志位置1。这里有个关键点:在主模式发送时,XRDY的触发与剩余待发送数据量(TXSTAT)有关。仅当TXSTAT > TXTRSH时,才会产生中断来请求填充数据。而在从模式发送时,由于主机随时可能结束通信(通过发送NACK),通常建议将TXTRSH设为0(即阈值1字节),让每个字节的请求都触发一次中断或DMA,避免FIFO中滞留不会被主机读取的数据。

  3. 接收排水中断(RDR)与发送排水中断(XDR):这是处理“零头”数据的利器。当一次传输的总数据长度不是FIFO阈值的整数倍时,在传输末尾,FIFO中会剩下少于阈值的数据量。此时,RRDYXRDY的条件可能永远不会满足。RDR(接收)或XDR(发送)中断就是为此而生,它专门通知CPU:“传输快结束了,FIFO里还有点‘零钱’,快来处理一下。” 我们会在第4章详细讨论它的用法。

  4. 其他关键状态中断

    • ARDY(访问就绪):标志一次寄存器访问(如配置从机地址、数据长度)已完成,可以配置下一次操作了。在轮询式编程中常用。
    • NACK(无应答):从机未应答,通常意味着寻址错误或从机忙。
    • AL(仲裁丢失):在多主系统中,本机在竞争总线时失败。
    • AERR(访问错误):在FIFO已满时写入,或FIFO为空时读取,会发生此错误。合理使用Draining机制可以避免此错误。

2.2 中断服务程序(ISR)的编写要点与避坑指南

编写I2C中断服务程序,最忌讳的就是“想当然”。必须严格遵循硬件规定的流程,否则极易导致中断丢失、数据错乱甚至死锁。

标准的中断处理流程如下:

  1. 进入ISR:保存上下文(编译器通常自动完成)。
  2. 识别中断源:读取I2C_IRQSTS_RAW寄存器,判断是哪个(或哪几个)标志位被置起。重要提示:该寄存器是“原始”状态寄存器,无论中断是否使能(I2C_IRQEN_SET),事件发生都会置位。判断中断源应使用它。
  3. 处理事件
    • 如果是RRDY,则从I2C_DATA寄存器连续读取(RXTRSH + 1)个字节(或读取RXSTAT指示的当前FIFO数据量)。
    • 如果是XRDY,则向I2C_DATA寄存器连续写入(TXTRSH + 1)个字节(或写入足够数据使FIFO非空)。
    • 如果是RDR/XDR,则读取I2C_BUFSTAT.RXSTAT/TXSTAT获取剩余字节数,并进行相应处理。
  4. 清除中断标志:向I2C_IRQSTS寄存器(注意,不是I2C_IRQSTS_RAW)的对应位写入1,以清除中断标志。这是最关键也最容易出错的一步!清除操作必须在处理完事件之后进行。如果先清除标志,但处理事件过程中又满足了中断条件,可能会丢失一次中断。
  5. 退出ISR:恢复上下文,返回。

避坑经验一:中断使能与标志清除的顺序在初始化时,应先配置好所有参数(地址、时钟等),最后再使能中断(设置I2C_IRQEN_SET)。防止配置过程中产生的不期望中断干扰系统。在ISR中,一定要先处理数据,再清除标志。我曾遇到过在XRDY中断中,先清标志再填充FIFO,结果因为填充速度慢于总线发送速度,FIFO再次变空满足条件,但标志已被清除,导致后续数据发送停滞的诡异问题。

避坑经验二:共享变量的保护如果ISR和主程序之间通过全局变量(如数据缓冲区指针、状态标志)通信,必须考虑临界区保护。对于单核MCU,通常可以在访问共享变量前关闭全局中断,访问后再打开。更优雅的做法是使用原子操作或信号量(如果RTOS支持)。

避坑经验三:ISR执行时间ISR应尽可能短平快。只做最必要的数据搬运和标志操作,复杂的逻辑(如协议解析、大量计算)应放到主循环或任务中。长时间停留在ISR会阻塞其他低优先级中断,影响系统实时性。

3. DMA机制与FIFO的协同:解放CPU的搬运工

如果说中断是把CPU从轮询的苦力中解放出来,让它从“主动询问”变为“被动响应”,那么DMA则是雇了一个专门的“数据搬运工”。DMA控制器可以在不打扰CPU的情况下,在外设(如I2C的I2C_DATA寄存器)和内存之间直接搬运数据。对于I2C这种速率相对较慢但数据量可能不小的总线,使用DMA能大幅降低CPU占用率,让CPU专注于更重要的应用逻辑。

3.1 DMA事件与中断的对应关系

I2C模块可以产生两个DMA请求事件:I2C_DMA_RX(读请求)和I2C_DMA_TX(写请求)。手册明确指出,这两个请求的生成条件,与RRDYXRDY中断的生成条件完全相同。也就是说:

  • I2C_DMA_RX在接收FIFO数据量 >= (RXTRSH+ 1) 时触发。
  • I2C_DMA_TX在发送FIFO为空,或数据量 < (TXTRSH+ 1) 且还有数据要发送时触发。

它们的核心区别在于响应者:中断需要CPU跳转执行ISR来搬运数据;而DMA请求会直接触发DMA控制器,由DMA硬件自动完成数据搬运。当使能了DMA(设置I2C_BUF.RDMA_EN/XDMA_EN及相应的DMA通道使能寄存器),对应的RRDY/XRDY中断就会被屏蔽,防止两者冲突。

3.2 DMA模式下的FIFO阈值配置策略

配置DMA时,FIFO阈值 (TXTRSH/RXTRSH) 的选择直接影响DMA请求的频率和总线效率,需要与DMA传输的突发长度(Burst Size)一起考量。

接收模式(RX): DMA请求在FIFO数据量达到阈值时产生。假设RXTRSH=7(8字节阈值),FIFO深度为32字节。那么当FIFO收到第8、16、24、32个字节时,都会产生一次DMA请求。DMA控制器每次应至少搬运8字节(RXTRSH+1)才能解除请求。你可以将DMA的传输长度配置为总数据量,让DMA控制器自动处理多次请求;也可以配置为单次8字节,在DMA完成中断中重新配置下一次传输。阈值设置越大,DMA请求频率越低,但要求FIFO深度足够,否则容易溢出。

发送模式(TX): DMA请求在发送FIFO为空时产生(这是最常见且推荐的方式,对应TXTRSH配置为FIFO深度-1)。以32字节FIFO为例,设置TXTRSH=31。当CPU或DMA填满FIFO后,I2C硬件开始向外发送。一旦FIFO完全变空,立即产生DMA请求,DMA控制器再次填充一批数据。这种“空中加油”式的流水线操作,能最大限度地保持总线忙碌,提高吞吐量。手册中的图22-14(高阈值)和图22-15(低阈值)生动展示了不同阈值下,DMA请求信号 (DMA TX Active) 和FIFO水平的变化关系。高阈值能减少DMA请求次数,但可能增加初始延迟;低阈值(甚至为0)则让DMA更频繁地填充少量数据。

一个关键配置公式: 为了使DMA高效工作,建议满足:DMA传输长度(字节) = N × (FIFO阈值 + 1),其中N为正整数。例如,FIFO深度32,设置TXTRSH=15(阈值16),那么DMA传输长度可以是16、32、48...字节。这样每次DMA都能恰好填满一个阈值周期,避免出现最后的“零头”需要特殊处理(当然,Draining机制就是用来处理这个零头的)。

3.3 DMA与中断的混合使用模式

在实际项目中,纯DMA并非万能。一个经典的混合使用模式是:主数据块使用DMA传输,传输完成或错误时使用中断处理

  1. 初始化:配置I2C为主机,设置好时钟、从机地址。配置DMA通道的源/目标地址、传输长度、触发源为I2C_DMA_TX/I2C_DMA_RX
  2. 启动传输:使能I2C的DMA(I2C_BUF.XDMA_EN等),然后启动I2C传输(设置START位)。
  3. DMA搬运数据:I2C模块根据FIFO状态自动触发DMA请求,DMA控制器在后台默默搬运数据。
  4. 完成与异常中断:使能ARDY(传输完成)、NACKAL等中断。当DMA搬运完所有数据,I2C完成整个传输序列后,会产生ARDY中断,通知CPU“任务完成”。如果中途发生错误(如无应答),则产生NACK等中断,CPU需要在ISR中停止DMA、清理现场,并重试或上报错误。

这种模式结合了DMA的大数据搬运能力和中断的异步事件响应能力,是构建高效可靠通信系统的常见做法。

4. FIFO管理精要:阈值、排水与实战配置

FIFO是I2C控制器内部的数据缓冲池,是中断和DMA机制得以高效工作的基础。管理好FIFO,就管理好了数据流的节奏。

4.1 阈值(Threshold)的深层含义与配置原则

阈值不是一个简单的“开关”,而是一个水位线。它决定了何时向CPU或DMA控制器发出“补水”(发送)或“抽水”(接收)的请求。

  • 接收阈值 (RXTRSH):代表“抽水”警报水位。当FIFO中数据量达到RXTRSH + 1,说明缓冲区有足够数据了,可以一次性读取一批,减少频繁操作的 overhead。设置建议:对于低延迟要求,可设小(如0-3);对于批量高效传输,可设大(如FIFO深度的一半或更大)。但必须确保RXTRSH < FIFO深度,否则永远不会触发。
  • 发送阈值 (TXTRSH):代表“补水”预警水位。当FIFO中数据量低于或等于TXTRSH,且还有数据要发,就需要补充数据。一个关键技巧:如果你想在FIFO完全空时才请求数据(实现最大程度的连续发送),则应设置TXTRSH = FIFO深度 - 1。这样只有当FIFO从有到无(空)时,才会触发请求,DMA可以一次性填入大量数据。

配置步骤示例(以32字节FIFO为例,目标为批量发送):

  1. 确定操作模式:主发送、DMA使能。
  2. 设置I2C_BUF.TXTRSH = 31。这意味着FIFO为空(0字节)时,数据量(0)<= 阈值(31),条件满足,触发DMA请求。
  3. 配置DMA传输长度为32字节(或32的倍数)。
  4. 启动传输后,I2C开始发送FIFO中的数据(初始为空,但会等待DMA填充)。FIFO空触发DMA,DMA写入32字节填满FIFO。I2C发送这些数据,当发送完最后一个字节,FIFO再次变空,触发下一次DMA请求,如此循环。

4.2 Draining Feature(排水机制)详解与实战

这是手册中非常精妙但容易被忽略的部分。它专门解决“最后一次搬运的数据量不是整数倍”的问题。

为什么需要排水机制?假设FIFO阈值设为15(即16字节触发一次请求),你需要传输50字节数据。50除以16等于3次满请求(48字节),还余2字节。这最后的2字节不足以触发下一次XRDY或DMA请求(因为需要16字节)。如果没有排水机制,这2字节会永远留在FIFO里(对于发送)或无法被读取(对于接收),导致传输不完整或超时。

排水机制如何工作?

  1. 检测非整数倍情况:当传输接近尾声,剩余待处理字节数TXSTATRXSTAT小于配置的阈值(TXTRSH+1 或RXTRSH+1)时,硬件不会触发标准的XRDY/RRDY或DMA请求。
  2. 触发排水中断:取而代之,它会触发专门的XDR(发送排水)或RDR(接收排水)中断。这个中断就像一个特殊的“收尾通知”。
  3. 获取剩余字节数:在XDR/RDR的中断服务程序中,CPU必须去读取I2C_BUFSTAT.TXSTATRXSTAT寄存器。这个寄存器会明确告诉你,还剩多少字节需要写入(发送)或读取(接收)。
  4. 执行排水操作
    • 中断模式:CPU根据TXSTAT/RXSTAT的值,执行相应次数的读写操作。
    • DMA模式:CPU需要重新配置DMA控制器,将下一次DMA传输的长度修改为TXSTAT/RXSTAT的值,然后启动这次特殊的“排水传输”。这是关键!DMA不会自动知道最后要传多少,需要CPU介入调整。
  5. 清除中断标志:完成���水操作后,清除XDR/RDR中断标志。

排水机制配置要点:

  • 该功能默认是禁用的。需要通过设置I2C_IRQEN_SET.XDR_IERDR_IE来使能。
  • 它仅在你预知或处理传输长度非阈值整数倍时才需要使能。如果传输长度总是整数倍,可以禁用以简化中断处理。
  • 从机发送模式下,手册特别指出不应使用排水机制。因为从机不知道主机要读多少字节,主机可能随时用NACK结束读取。此时应将TXTRSH设为0,让每个字节请求都独立处理。

4.3 FIFO的清除与异常处理

FIFO管理还包括异常情况下的清理工作。寄存器I2C_BUF.RXFIFO_CLRTXFIFO_CLR提供了软件复位FIFO的能力。

典型使用场景:

  1. 传输中止:在发送过程中,如果从机回复了NACK,主机应中止传输。此时,发送FIFO中可能还有未送出的数据。在重新开始传输前,应使用TXFIFO_CLR清除这些残留数据,避免与下一次传输的数据混淆。
  2. 模式切换:从接收模式切换到发送模式前,最好清除接收FIFO,反之亦然。
  3. DMA错误恢复:如果DMA传输出错,在重新配置DMA和I2C前,清除FIFO可以确保状态机从干净的状态开始。

操作注意:清除FIFO位是“瞬间”操作,通常向对应位写1即可。在DMA模式下,清除FIFO也会复位内部的DMA状态机,因此需要在清除后重新检查或配置DMA。

5. 完整配置流程与代码实战要点

理解了原理,我们将其串联成一个完整的配置流程。这里以主模式、中断+DMA混合、使能排水功能为例,展示关键的步骤和代码逻辑。

5.1 初始化与模块配置流程

  1. 时钟与基础配置

    // 1. 配置预分频器,产生约12MHz的I2C模块工作时钟(依据系统时钟计算) I2C_PSC = (SystemClock / 12000000) - 1; // 2. 配置SCL高电平和低电平时间,以产生目标速率(如100kHz) // SCL周期 = (SCLH + 1 + SCLL + 1) * 模块时钟周期 // 例如,模块时钟12MHz,目标100kHz,则总计数应为120。通常SCLH=SCLL=59。 I2C_SCLL = 59; I2C_SCLH = 59; // 3. 配置自身地址(从机模式时需要) I2C_OA = MY_SLAVE_ADDRESS; // 4. 使能I2C模块(退出复位) I2C_CON |= I2C_EN;
  2. FIFO与中断/DMA配置

    // 假设FIFO深度为32字节,我们设置阈值 #define FIFO_DEPTH 32 #define RX_THRESHOLD 15 // 接收16字节触发 #define TX_THRESHOLD 31 // 发送FIFO空触发(高阈值模式) // 配置FIFO阈值 I2C_BUF = (RX_THRESHOLD << RXTRSH_SHIFT) | (TX_THRESHOLD << TXTRSH_SHIFT); // 使能DMA(如果使用) I2C_BUF |= RDMA_EN; // 使能接收DMA I2C_BUF |= XDMA_EN; // 使能发送DMA // 注意:还需要配置DMA控制器的通道,指定源/目标地址、传输长度、触发源为I2C_DMA_RX/TX // 使能所需的中断(如ARDY完成中断、NACK错误中断、排水中断) I2C_IRQEN_SET = ARDY_IE | NACK_IE | RDR_IE | XDR_IE; // 使能传输完成、无应答、排水中断 // 注意:使能了DMA后,RRDY和XRDY中断通常被自动屏蔽,无需使能。
  3. 启动传输(主模式)

    // 1. 配置从机地址和传输数据总数 I2C_SA = TARGET_SLAVE_ADDR; I2C_CNT = TOTAL_DATA_BYTES_TO_TRANSFER; // 2. 检查总线是否繁忙 while (I2C_IRQSTS_RAW & BB); // 等待BB位为0 // 3. 配置为主发送模式,并产生START条件 I2C_CON |= (MST | TRX | STT); // 主模式、发送、启动 // 随后,如果DMA已配置并启用,硬件会自动根据FIFO状态触发DMA请求搬运数据。

5.2 中断服务程序(ISR)实现示例

void I2C_ISR(void) { uint32_t status = I2C_IRQSTS_RAW; // 读取原始中断状态 // 处理接收排水中断 if (status & RDR) { uint32_t bytes_left = I2C_BUFSTAT & RXSTAT_MASK; // 读取剩余字节数 // 如果是DMA模式,需要重新配置DMA传输长度为 bytes_left // 如果是中断模式,则循环读取 bytes_left 次 I2C_DATA for(int i=0; i<bytes_left; i++) { rx_buffer[offset++] = I2C_DATA; } I2C_IRQSTS = RDR; // 清除RDR中断标志 } // 处理发送排水中断 if (status & XDR) { uint32_t bytes_left = (I2C_BUFSTAT & TXSTAT_MASK) >> TXSTAT_SHIFT; // 如果是DMA模式,重新配置DMA传输长度为 bytes_left // 如果是中断模式,循环写入 bytes_left 次 I2C_DATA for(int i=0; i<bytes_left; i++) { I2C_DATA = tx_buffer[offset++]; } I2C_IRQSTS = XDR; // 清除XDR中断标志 } // 处理传输完成中断 if (status & ARDY) { // 一次传输(可能包含多次DMA搬运)完成 transfer_complete_flag = 1; I2C_IRQSTS = ARDY; // 清除ARDY中断标志 } // 处理错误中断 if (status & NACK) { // 从机无应答,处理错误(如重试、报错) handle_nack_error(); I2C_IRQSTS = NACK; // 清除NACK中断标志 // 可能需要清除FIFO: I2C_BUF |= TXFIFO_CLR; } if (status & AL) { // 仲裁丢失,通常重试即可 handle_arbitration_lost(); I2C_IRQSTS = AL; } }

5.3 常见问题排查与调试技巧

  1. 中断不触发

    • 检查中断使能:确认I2C_IRQEN_SET寄存器相应位已置1。
    • 检查全局中断:确认CPU全局中断已打开,且中断向量表配置正确。
    • 检查FIFO阈值:确认RXTRSH/TXTRSH设置合理,数据量能达到触发条件。用调试器监控I2C_BUFSTAT寄存器查看实时FIFO深度。
    • DMA冲突:如果使能了DMA,对应的RRDY/XRDY中断会被屏蔽。确认你期待的中断不是这两个。
  2. DMA传输卡在最后几个字节

    • 大概率是排水问题:检查总传输字节数是否是FIFO阈值+1的整数倍。如果不是,且未使能XDR/RDR中断或未正确处理该中断,DMA会在等待永远无法满足的请求条件时挂起。
    • 解决方案:使能并正确处理XDR/RDR中断,在中断中重新配置DMA进行最后一次“零头”传输。
  3. 数据错乱或丢失

    • FIFO溢出/下溢:检查ROVR(接收溢出)和XUDF(发送下溢)标志。溢出通常因为CPU或DMA读取太慢;下溢通常因为填充数据太慢。调整阈值或优化数据供应/消费速度。
    • 访问错误:检查AERR标志。这通常发生在FIFO已满时强行写入,或FIFO为空时强行读取。确保你的读写操作与RRDY/XRDY状态或DMA请求同步。
    • 时序问题:在高速模式下(400kHz或更高),确保CPU/DMA响应中断或请求的速度跟得上总线速度。必要时提升CPU时钟或使用更高性能的DMA。
  4. 调试建议

    • 善用状态寄存器:在调试初期,不要完全依赖中断。可以轮询I2C_IRQSTS_RAW寄存器,打印RRDY,XRDY,ARDY,RDR,XDR等关键位的状态,观察其变化是否符合预期。
    • 逻辑分析仪是利器:使用逻辑分析仪抓取SDA和SCL波形,可以直观看到起始位、地址、数据、应答、停止位,是排查物理层问题和协议时序问题的终极手段。
    • 简化测试:先使用轮询模式实现最基本的读写,确保硬件连接和基础配置正确。然后再逐步加入中断、DMA、FIFO阈值等高级功能。

通过深入理解中断与DMA的触发机制、熟练掌握FIFO阈值与排水功能的配置,你就能让I2C总线在嵌入式系统中真正高效、可靠地运转起来,将CPU从繁琐的数据搬运中解放出来。记住,所有的配置都要服务于具体的应用场景:对实时性要求高的���阈值设小点;对吞吐量要求大的,阈值设大点并结合DMA;数据量不定的,用好排水机制。多思考、多实践、多调试,这些机制就会成为你手中得心应手的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询