嵌入式I2C总线DMA触发与中断事件管理机制详解
1. 项目概述与核心价值
在嵌入式系统开发中,I2C总线因其简洁的两线制(SDA、SCL)和主从架构,被广泛用于连接各类低速外设,如传感器、EEPROM和实时时钟。然而,当需要处理大量数据时,传统的轮询或中断驱动方式会大量占用CPU资源,导致系统响应延迟,难以满足实时性要求。这时,直接内存访问(DMA)技术就成了提升系统效率的“王牌”。
DMA的核心思想是“让专业的人做专业的事”。它允许外设和内存之间直接进行数据搬运,CPU只需在传输开始前配置好源地址、目标地址和传输量,之后就可以“撒手不管”,去处理其他任务。传输完成后,DMA控制器会通过一个中断通知CPU。这就像你雇了一个专业的搬运工(DMA)来搬仓库(内存)里的货,而你(CPU)只需要告诉他搬哪里、搬多少,然后就可以去处理更重要的工作(比如计算、决策),等他搬完了喊你一声就行。
本文要深入探讨的,正是如何让I2C这个“外设”与DMA这个“搬运工”高效协同工作的核心机制——DMA触发与中断事件管理。具体来说,我们将聚焦于如何配置I2C模块的FIFO(先进先出缓冲区)状态作为DMA的“开工信号”(触发事件),以及DMA完成后如何通过中断“喊话”CPU。这个过程涉及到几个关键的寄存器组:DMA_TRIG0和DMA_TRIG1事件管理寄存器,以及与之关联的中断状态寄存器。理解这套机制,你就能设计出CPU占用率极低、数据传输流畅的I2C应用,无论是连续读取大量传感器数据,还是向显示设备高速刷屏,都能游刃有余。
2. I2C DMA触发机制深度解析
2.1 DMA触发事件源:从FIFO状态到DMA请求
要让DMA为I2C工作,首先得告诉DMA“什么时候开始搬数据”。在I2C模块中,最自然、最高效的触发源就是其内部的FIFO状态。FIFO就像一个小的数据中转站,发送数据时,CPU或DMA把数据写入发送FIFO(TX FIFO),I2C硬件再依次将数据放到SDA线上;接收数据时则相反。
为什么选择FIFO状态作为触发源?想象一下往一个漏水的桶里倒水。如果你等桶完全空了再倒一整桶水(CPU批量写入),中间会有一段空档期(总线空闲)。如果你每滴一滴水就倒一滴(CPU每字节中断),你会累得手忙脚乱(CPU负载高)。最好的办法是设置一个水位线:当桶里的水低于某个刻度(TX FIFO数据量少于阈值),就自动打开水龙头补充(触发DMA搬运数据);当桶里的水高于某个刻度(RX FIFO数据量多于阈值),就自动抽走一些(触发DMA读取数据)。这样既能保证桶里一直有水(总线持续工作),又不需要你一直盯着(CPU解放)。
在I2C模块中,这个“水位线”的刻度就是通过CFIFOCTL.TXTRIG和CFIFOCTL.RXTRIG(控制器侧),以及TFIFOCTL.TXTRIG和TFIFOCTL.RXTRIG(目标设备侧)来设置的。例如,设置TXTRIG=2,意味着当发送FIFO中的数据量小于等于2字节时,就会产生一个“需要更多数据”的事件。
2.2 事件管理寄存器:DMA_TRIG0与DMA_TRIG1
I2C模块提供了两个独立的事件管理寄存器组:DMA_TRIG0和DMA_TRIG1。你可以把它们理解为两个独立的“门铃”,每个门铃可以连接多个不同的“触发传感器”(事件源)。每个寄存器组都包含一套完整的中断管理逻辑:IIDX(中断索引)、IMASK(中断掩码)、RIS(原始中断状态)、MIS(屏蔽后中断状态)、ISET(中断置位)和ICLR(中断清除)。
关键配置步骤:
选择触发事件:你需要决定哪个FIFO事件去按哪个“门铃”。例如,一个常见的配置是:
- 将控制器的发送FIFO触发事件(
CTXFIFOTRG)映射到DMA_TRIG1。 - 将控制器的接收FIFO触发事件(
CRXFIFOTRG)映射到DMA_TRIG0。 这通过配置DMA_TRIG1和DMA_TRIG0对应的IMASK和RIS等寄存器来实现。实际上,DMA_TRIG1和DMA_TRIG0的IIDX寄存器只列出了有限的几个事件(01h-04h),对应着四个FIFO触发事件。你需要通过IMASK寄存器来使能你关心的事件。
- 将控制器的发送FIFO触发事件(
配置事件模式:
EVT_MODE寄存器决定了事件线的行为模式。对于DMA触发,我们通常选择硬件模式(EVT2_CFG/INT1_CFG/INT0_CFG = 2h)。在此模式下,当DMA控制器完成传输后,它会自动清除对应的RIS标志位,无需软件干预,实现了全硬件的闭环控制。连接DMA通道:
DMA_TRIG0和DMA_TRIG1这两个“门铃”的输出,会连接到芯片DMA控制器的特定触发输入源。你需要在DMA控制器配置中,指定使用I2C_DMA_TRIG0或I2C_DMA_TRIG1作为某个DMA通道的触发信号。这样,当I2C的FIFO达到触发条件时,就会向DMA控制器发出请求,DMA控制器随即启动数据传输。
注意:
DMA_TRIG0和DMA_TRIG1是I2C模块内部的事件集合与路由单元,它们本身不是DMA控制器。它们负责收集I2C内部事件(如FIFO触发),并将其转化为标准的触发信号输出给芯片的DMA控制器模块。
2.3 中断信号生成:DMA完成通知
DMA控制器完成一次数据传输(比如搬完了预设的N个字节)后,需要通知CPU。这时,I2C模块会产生相应的DMA完成中断。
中断信号分类:
- 控制器DMA完成中断:
MDMA_DONE_TX:控制器发送DMA完成。MDMA_DONE_RX:控制器接收DMA完成。
- 目标设备DMA完成中断:
SDMA_DONE_TX:目标设备发送DMA完成。SDMA_DONE_RX:目标设备接收DMA完成。
这些中断标志位位于主中断组(CPU_INT)的RIS、MIS等寄存器中。例如,当DMA_TRIG1关联的DMA通道(假设是通道1)完成传输时,会置位MDMA_DONE_TX或MDMA_DONE_RX;DMA_TRIG0关联的通道(通道2)完成时,则置位SDMA_DONE_TX或SDMA_DONE_RX。
完整的数据流闭环:
- 初始化:CPU配置I2C、FIFO阈值、DMA通道(源/目标地址、传输量)、并使能
DMA_TRIGx中的相应事件掩码。 - 触发:I2C开始工作,当TX FIFO数据量低于
TXTRIG阈值,CTXFIFOTRG事件发生,DMA_TRIG1的RIS相应位置位。 - 搬运:该事件触发DMA控制器,DMA自动从内存向I2C的
CTXDATA寄存器搬运数据,填充FIFO。 - 完成:DMA搬运完预设字节数,停止。I2C模块置位
MDMA_DONE_TX中断标志。 - 响应:CPU收到中断,在中断服务程序(ISR)中清除中断标志,并可选择配置下一次DMA传输或进行后续处理。
3. 关键寄存器详解与配置实战
理解了原理,我们来看具体怎么操作。配置I2C DMA触发主要围绕三组寄存器:FIFO控制寄存器、事件管理寄存器(DMA_TRIG0/1)和主中断控制寄存器。
3.1 FIFO控制寄存器配置
这是设置“水位线”的地方,决定了DMA何时被触发。
控制器FIFO控制寄存器 (CFIFOCTL, 偏移地址 0x1238):
// 假设我们希望: // 1. 发送时:当TX FIFO中数据 <= 4字节时,触发DMA补充数据。 // 2. 接收时:当RX FIFO中数据 >= 4字节时,触发DMA取走数据。 // 3. 初始时清空FIFO。 // 清空TX FIFO和RX FIFO I2C0->CFIFOCTL |= (1 << 7) | (1 << 15); // 设置TXFLUSH和RXFLUSH位 // 等待清空完成 (通常需要检查CFIFOSR中的TXFLUSH/RXFLUSH位,或延时) while((I2C0->CFIFOSR & ((1 << 15) | (1 << 7))) != 0); // 等待刷新完成 // 配置触发阈值 uint32_t temp = I2C0->CFIFOCTL; temp &= ~(0x7 << 0); // 清零TXTRIG[2:0] temp |= (4 << 0); // 设置TXTRIG = 4,即 TX FIFO数据 <= 4 时触发 temp &= ~(0x7 << 8); // 清零RXTRIG[2:0] temp |= (4 << 8); // 设置RXTRIG = 4,即 RX FIFO数据 >= 4 时触发 I2C0->CFIFOCTL = temp;目标设备FIFO控制寄存器 (TFIFOCTL, 偏移地址 0x126C):配置方式与控制器侧类似,用于当I2C模块作为目标设备(从机)时的DMA触发。
实操心得:FIFO深度通常是8字节。设置触发阈值时需要权衡。阈值设得太高(如TXTRIG=6),DMA触发频繁,可能增加总线仲裁开销;设得太低(如TXTRIG=1),则FIFO容易下溢,导致I2C总线等待(时钟拉伸)。对于高速传输,建议TXTRIG设为FIFO深度的一半(如4),RXTRIG设为略高于一半(如5),以平衡效率和可靠性。
3.2 DMA事件管理寄存器配置
这里我们将FIFO触发事件路由到具体的DMA_TRIG通道。
配置DMA_TRIG1用于控制器发送触发:我们打算用DMA_TRIG1来响应控制器的发送FIFO触发事件(CTXFIFOTRG)。
// 1. 清除可能存在的挂起事件 I2C0->DMA_TRIG1.ICLR = (1 << 1); // 写1清除CTXFIFOTRG事件标志 // 2. 取消屏蔽(使能)CTXFIFOTRG事件,使其能触发DMA_TRIG1输出 I2C0->DMA_TRIG1.IMASK |= (1 << 1); // 设置IMASK对应位为1 // 3. (可选)配置为硬件自动清除模式 // 通常EVT_MODE寄存器复位后,INT1_CFG(DMA_TRIG1)可能已是硬件模式(2h)。 // 为确保无误,可显式配置: uint32_t evt_mode = I2C0->EVT_MODE; evt_mode &= ~(0x3 << 2); // 清零INT1_CFG位域[3:2] evt_mode |= (0x2 << 2); // 设置INT1_CFG = 2,硬件模式 I2C0->EVT_MODE = evt_mode;配置DMA_TRIG0用于控制器接收触发:类似地,用DMA_TRIG0响应控制器接收FIFO触发(CRXFIFOTRG)。
I2C0->DMA_TRIG0.ICLR = (1 << 0); // 清除CRXFIFOTRG事件 I2C0->DMA_TRIG0.IMASK |= (1 << 0); // 使能CRXFIFOTRG事件 // EVT_MODE.INT0_CFG 通常也需要配置为硬件模式(2h)3.3 主中断控制寄存器配置
我们需要使能DMA完成中断,以便CPU知道传输结束了。
中断掩码寄存器 (IMASK, 偏移地址 0x1028):
// 使能控制器发送和接收的DMA完成中断 I2C0->IMASK |= (1 << 11) | (1 << 12); // 设置CDMA_DONE_TX和CDMA_DONE_RX掩码位 // 如果需要目标设备的DMA完成中断,则使能TDMA_DONE_TX/RX (位25, 26)中断清除寄存器 (ICLR, 偏移地址 0x1048):在中断服务程序(ISR)中,必须清除已处理的中断标志,否则会持续触发中断。
// 在DMA完成中断的ISR中: if(I2C0->MIS & (1 << 11)) { // 检查CDMA_DONE_TX是否被屏蔽且置位 // ... 处理发送完成 ... I2C0->ICLR = (1 << 11); // 写1清除CDMA_DONE_TX中断标志 } if(I2C0->MIS & (1 << 12)) { // 检查CDMA_DONE_RX // ... 处理接收完成 ... I2C0->ICLR = (1 << 12); }3.4 完整配置流程示例
假设我们要实现I2C控制器模式下的连续发送(使用DMA)。
- I2C基础配置:配置时钟、引脚复用、速度(设置
CTPR)、使能控制器(CCR.ACTIVE=1)。 - FIFO配置:
// 清空FIFO I2C0->CFIFOCTL |= (1<<7); // 清空TX FIFO while(I2C0->CFIFOSR & (1<<15)); // 等待清空完成 // 设置TX触发阈值为2字节 uint32_t fifoctl = I2C0->CFIFOCTL; fifoctl &= ~(0x7); fifoctl |= (2 << 0); // TXTRIG = 2 I2C0->CFIFOCTL = fifoctl; - DMA事件配置:
// 配置DMA_TRIG1响应CTXFIFOTRG I2C0->DMA_TRIG1.ICLR = (1<<1); I2C0->DMA_TRIG1.IMASK |= (1<<1); // 确保为硬件模式 I2C0->EVT_MODE = (I2C0->EVT_MODE & ~(0x3<<2)) | (0x2<<2); - I2C中断配置:
// 使能控制器发送DMA完成中断 I2C0->IMASK |= (1 << 11); // 在NVIC中使能I2C全局中断 NVIC_EnableIRQ(I2C0_IRQn); - DMA控制器配置(伪代码,依赖具体DMA外设):
// 配置DMA通道,例如通道1: // 源地址:内存中的发送缓冲区地址 // 目标地址:I2C0->CTXDATA (0x1220) // 传输宽度:字节 // 传输模式:外设到内存(或内存到外设,根据方向) // 触发源:选择I2C_DMA_TRIG1 // 使能DMA通道 - 启动传输:
一旦I2C开始发送,TX FIFO被消耗,数据量低于阈值2,// 设置目标地址和传输方向 I2C0->CSA = (target_addr << 1) | (0x0); // 7位地址,写方向 // 设置传输字节数(假设为32字节) I2C0->CCTR = (32 << 16); // CBLEN字段 // 启动传输(产生START,并开始) I2C0->CCTR |= (1 << 0) | (1 << 1) | (1 << 2); // BURSTRUN=1, START=1, STOP=1CTXFIFOTRG事件触发,进而触发DMA_TRIG1,DMA通道1开始工作,将数据从内存搬至CTXDATA。当DMA完成32字节传输后,MDMA_DONE_TX中断产生,CPU进入ISR处理。
4. 调试模式行为与寄存器概览
在嵌入式开发中,调试是必不可少的环节。I2C模块的PDBGCTL寄存器(外设调试控制寄存器)决定了当芯片核心被调试器暂停(Halt)时,I2C外设的行为。
PDBGCTL寄存器关键字段:
- FREE (位0):自由运行控制。
0:当核心暂停时,外设也冻结。1:忽略核心暂停状态,外设继续运行。
- SOFT (位1):软暂停边界控制(仅在
FREE=0时有效)。0:外设立即停止,即使可能导致数据损坏。1:外设阻止调试冻结,直到其到达一个可以安全停止而不损坏数据的“边界状态”。
配置建议:
- 实时数据流调试:如果正在调试与DMA、I2C数据流相关的代码,且不希望调试器打断数据采集,可以设置
FREE=1。这样即使你单步执行代码,I2C和DMA仍在后台持续工作。 - 检查寄存器状态:如果需要检查I2C在某个精确时刻的寄存器状态(如FIFO计数、状态位),应设置
FREE=0, SOFT=1。这样当调试器暂停CPU时,I2C会完成当前正在进行的传输(如一个字节的发送/接收)后优雅地停止,避免停在半途导致总线状态错乱或FIFO数据损坏。 - 默认值:通常复位后
FREE=1, SOFT=1,即外设在调试时继续运行,这是比较安全的默认配置。
其他关键寄存器快速索引:除了上述核心寄存器,I2C模块还有大量寄存器用于精细控制。以下表格列出了部分关键寄存器及其功能摘要,方便查阅:
| 偏移地址 | 寄存器缩写 | 全称 | 核心功能简述 |
|---|---|---|---|
| 0x1210 | CSA | 控制器目标地址寄存器 | 设置通信的从机地址和传输方向(读/写)。 |
| 0x1214 | CCTR | 控制器控制寄存器 | 核心控制寄存器。包含启动(START)、停止(STOP)、运行(BURSTRUN)、传输长度(CBLEN)、应答控制(ACK)等。 |
| 0x1218 | CSR | 控制器状态寄存器 | 读取总线忙(BUSBSY)、空闲(IDLE)、仲裁丢失(ARBLST)、应答错误(ACK)、错误(ERR)、忙(BUSY)等状态。 |
| 0x121C | CRXDATA | 控制器接收数据寄存器 | 读取接收到的数据。 |
| 0x1220 | CTXDATA | 控制器发送数据寄存器 | 写入要发送的数据。DMA的目标地址之一。 |
| 0x1224 | CTPR | 控制器定时器周期寄存器 | 设置TPR值,与系统时钟共同决定I2C总线速度(SCL频率)。 |
| 0x1228 | CCR | 控制器配置寄存器 | 使能控制器(ACTIVE)、多主机模式(MCTL)、时钟拉伸(CLKSTRETCH)等。 |
| 0x1234 | CBMON | 控制器总线监控寄存器 | 直接读取SCL和SDA引脚的电平状态,用于底层调试和总线诊断。 |
| 0x1250 | TOAR | 目标自身地址寄存器 | 当I2C模块作为从机时,设置自身地址(OAR)和使能(OAREN)。 |
| 0x1258 | TCTR | 目标控制寄存器 | 从机模式下的核心控制寄存器,配置时钟拉伸、通用呼叫响应等。 |
| 0x125C | TSR | 目标状态寄存器 | 从机模式下的状态寄存器,显示地址匹配、收发模式、请求状态等。 |
| 0x1204 | TIMEOUT_CTL | 超时控制寄存器 | 配置SCL低电平超时(TCNTA)和高电平超时(TCNTB),用于检测总线挂死。 |
5. 常见问题与实战排坑指南
理论配置看似清晰,但实际调试中总会遇到各种“坑”。下面是我在多个项目中总结出的典型问题与解决方案。
5.1 DMA不触发或触发异常
现象:FIFO数据已达到阈值,但DMA没有启动。排查步骤:
- 检查FIFO触发阈值配置:确认
CFIFOCTL.TXTRIG/RXTRIG或TFIFOCTL.TXTRIG/RXTRIG设置是否正确。常见错误是误解了触发条件:TX FIFO是“小于等于”阈值触发,RX FIFO是“大于等于”阈值触发。 - 检查事件管理寄存器状态:
- 读取
DMA_TRIGx.RIS寄存器,查看对应的FIFO触发位(如CTXFIFOTRG)是否已置1。如果置1,说明I2C内部事件已产生。 - 检查
DMA_TRIGx.IMASK,确保对应事件位已被使能(值为1)。如果被屏蔽,事件不会传递出去。 - 检查
EVT_MODE寄存器,确认对应的INTx_CFG是否设置为硬件模式(2h)或软件模式(1h)。如果设置为禁用(0h),事件线无效。
- 读取
- 检查DMA控制器配置:
- 确认DMA通道的触发源(Trigger Source)是否选择正确,例如
I2C0_DMA_TRIG1。 - 确认DMA通道已使能(Enable),并且传输数量(Transfer Size)大于0。
- 检查DMA的源地址和目标地址是否配置正确,特别是外设地址(如
(uint32_t)&(I2C0->CTXDATA))是否无误。
- 确认DMA通道的触发源(Trigger Source)是否选择正确,例如
- 检查I2C总线状态:如果I2C总线本身没有启动传输(
CCTR.BURSTRUN未置1,或总线被占用CSR.BUSBSY=1),FIFO不会被消耗或填充,自然达不到触发条件。
5.2 DMA完成中断不产生
现象:DMA传输似乎完成了(数据已发送/接收),但预期的MDMA_DONE_TX/RX中断没有发生。排查步骤:
- 检查主中断使能:确认
IMASK寄存器中对应的CDMA_DONE_TX/RX或TDMA_DONE_TX/RX位已被置1。这是最容易被忽略的一步!DMA_TRIGx的IMASK使能的是事件到触发信号的路径,而主IMASK使能的是中断到CPU的路径。 - 检查全局中断:确认芯片的全局中断已开启,并且I2C的中断向量在NVIC中已使能。
- 检查中断状态寄存器:
- 读取
RIS寄存器,看对应的DMA完成位是否置1。如果RIS置位而MIS未置位,说明中断被屏蔽了。 - 读取
MIS寄存器,这是最终产生中断请求的状态。如果MIS置位但没进中断,问题可能在NVIC或优先级配置。
- 读取
- 确认DMA传输真正完成:有些DMA控制器需要配置为传输完成后再产生中断。检查DMA通道的“传输完成中断”是否使能。
5.3 数据错乱或丢失
现象:通过DMA收发I2C数据,发现数据内容不对,或数量对不上。排查步骤:
- FIFO溢出/下溢:这是高发区。检查
CSR或TSR寄存器中的错误标志,如ARBLST(仲裁丢失)。但更隐蔽的是FIFO溢出。确保DMA的传输速度能跟上I2C总线速度。- 发送下溢:I2C发送速度太快,DMA来不及填充TX FIFO,导致总线时钟拉伸过长或出错。对策:提高DMA总线优先级,或降低I2C时钟频率,或增大
TXTRIG阈值让DMA更早启动。 - 接收溢出:I2C接收数据太快,DMA来不及从RX FIFO取走数据,导致新数据覆盖旧数据。对策:提高DMA优先级,或提高
RXTRIG阈值(更早触发DMA),或使用更大的DMA传输块。
- 发送下溢:I2C发送速度太快,DMA来不及填充TX FIFO,导致总线时钟拉伸过长或出错。对策:提高DMA总线优先级,或降低I2C时钟频率,或增大
- DMA传输数量与I2C传输长度不匹配:
CCTR.CBLEN字段设置的I2C传输字节数,必须与DMA配置的传输数量(或循环模式下缓冲区大小)严格匹配。如果DMA传输数量少于I2C期望的,会导致I2C等待超时;如果多于,DMA会多写/多读,破坏相邻内存数据。 - 内存对齐与数据宽度:确保DMA源/目标地址符合对齐要求,数据宽度(字节/半字/字)设置正确。I2C数据寄存器通常是字节访问的,DMA也应配置为字节传输。
- 调试模式干扰:如果在调试时单步执行,且
PDBGCTL.FREE=0,I2C会暂停,可能导致总线超时或从设备失去响应。调试与总线通信相关的代码时,建议设置FREE=1。
5.4 配置流程检查清单
为了避免低级错误,请在代码中集成以下检查点:
// 在I2C DMA初始化函数中加入状态检查 bool I2C_DMA_Init_Check(void) { // 1. 检查I2C是否已使能并配置正确 if((I2C0->CCR & 0x1) == 0) { // ACTIVE位 // 错误: I2C控制器未激活 return false; } // 2. 检查FIFO触发配置 uint32_t fifoctl = I2C0->CFIFOCTL; uint8_t tx_trig = (fifoctl >> 0) & 0x7; uint8_t rx_trig = (fifoctl >> 8) & 0x7; if(tx_trig == 0 || tx_trig > 7) { // 0是特殊值(空触发),1-7有效 // 警告或错误: TX触发阈值配置可能无效 } // 3. 检查DMA事件使能 if((I2C0->DMA_TRIG1.IMASK & (1<<1)) == 0) { // 错误: CTXFIFOTRG事件未使能 return false; } // 4. 检查主中断使能 if((I2C0->IMASK & (1<<11)) == 0) { // 以CDMA_DONE_TX为例 // 错误: DMA完成中断未使能 return false; } // 5. 检查总线是否空闲 if(I2C0->CSR & (1<<6)) { // BUSBSY位 // 警告: 总线忙,可能需要等待或处理 } return true; }5.5 性能优化与高级技巧
- 双缓冲(Ping-Pong)DMA:对于连续流数据,可以配置两个DMA通道或一个通道的双缓冲模式。当DMA正在使用缓冲区A传输时,CPU可以处理缓冲区B的数据,实现零等待的数据流水线。这需要仔细处理DMA完成中断,并在中断中切换缓冲区。
- 动态调整触发阈值:在系统负载变化大的场景,可以根据CPU繁忙程度或总线负载,动态调整FIFO触发阈值。负载高时,增大阈值以减少DMA触发频率(降低总线占用);负载低时,减小阈值以降低传输延迟。
- 结合超时中断:使能
TIMEOUTA和TIMEOUTB中断,用于检测总线挂死(SCL被拉低超时)或时钟高速(SCL高电平超时)。在DMA传输超时时,能及时进入中断进行错误恢复,提高系统鲁棒性。 - 使用描述符链:一些高级的DMA控制器支持描述符链(Linked List),允许你预先定义好一系列不连续内存块的传输任务。这对于处理复杂协议帧或非连续数据缓冲区非常有用,可以大大减少CPU干预。
配置I2C的DMA触发与中断,本质上是在构建一个高效、自治的数据搬运流水线。核心思路是:让硬件(FIFO状态)自动触发硬件(DMA)去干活,干完了再通知软件(中断)。掌握这套机制,你的嵌入式系统在处理I2C这类流数据时,就能从“手忙脚乱”的轮询或频繁中断中解放出来,真正实现CPU资源的最大化利用。调试时,务必善用状态寄存器,按照“事件源 -> 事件路由 -> 触发信号 -> DMA动作 -> 完成通知”这条链路逐级排查,大部分问题都能迎刃而解。
