MSPM0 I2C DMA触发机制与中断配置实战指南
1. I2C DMA触发机制与中断寄存器配置概述
在嵌入式系统开发中,尤其是涉及传感器数据采集、外设通信等场景,I2C总线的数据传输效率往往是系统性能的瓶颈。传统的轮询或中断驱动方式,每传输一个字节都需要CPU介入,不仅消耗大量时钟周期,在高数据吞吐量时还会导致CPU负载过高,影响系统实时性。直接内存访问(DMA)技术正是解决这一痛点的利器。它允许数据在外设(如I2C的FIFO)和内存之间直接搬运,无需CPU参与每个字节的传输过程,从而将CPU解放出来处理更复杂的任务。
MSPM0 G系列微控制器的I2C模块,在DMA支持上做得相当完善。它提供了两个独立的DMA事件管理寄存器(DMA_TRIG0和DMA_TRIG1),每个都可以灵活配置为多种FIFO触发条件,例如控制器发送FIFO数据量不足时触发DMA填充(MTXFIFOTRG),或者目标设备接收FIFO数据达到阈值时触发DMA读取(SRXFIFOTRG)。这种设计使得我们可以为发送和接收通道分别配置独立的DMA通道和触发策略,实现全双工、高效率的数据流管理。
然而,灵活性的背后是配置的复杂性。仅仅知道如何设置触发条件是不够的,你必须深入理解与之紧密耦合的中断系统。IIDX(中断索引)、IMASK(中断屏蔽)、RIS(原始中断状态)、MIS(屏蔽后中断状态)以及ISET/ICLR(中断置位/清除)这一系列寄存器,共同构成了I2C模块的事件响应机制。DMA传输的完成、FIFO的空满状态、通信错误(如NACK、仲裁丢失)等,都会通过这些寄存器反映出来。配置不当,轻则导致DMA无法正常启动或停止,重则引发数据丢失、总线锁死等难以调试的问题。
因此,掌握I2C DMA的核心,不仅仅是学会填几个寄存器值,而是要建立起“事件触发DMA请求,DMA状态变化触发中断,中断服务程序进行流程控制”的完整认知链条。本文将基于MSPM0的参考手册,拆解这套机制,并分享从零配置一个稳定、高效I2C DMA驱动的实战经验和避坑指南。
2. DMA触发条件与事件管理寄存器深度解析
2.1 DMA触发条件(DMA_TRIG)详解
MSPM0的I2C模块为DMA提供了四个核心的硬件触发条件,它们直接与I2C控制器的FIFO状态挂钩。理解这些条件是配置DMA的基石。
MTXFIFOTRG (0x02): 控制器发送FIFO触发。当控制器发送FIFO中的数据量小于或等于预设的触发阈值时,硬件会产生一个DMA请求事件。这个条件通常用于发送数据。例如,我们将发送FIFO的触发阈值(CFIFOCTL.TXTRIG)设置为2,那么当FIFO中待发送的字节数≤2时,就会触发DMA,要求DMA控制器从内存中搬运新的数据到I2C的发送FIFO中,防止FIFO变空而导致总线空闲,从而维持连续的数据流。
MRXFIFOTRG (0x01): 控制器接收FIFO触发。当控制器接收FIFO中的数据量大于或等于预设的触发阈值时,产生DMA请求。这个条件用于接收数据。例如,设置接收FIFO触发阈值(CFIFOCTL.RXTRIG)为4,那么当FIFO中累积的已接收字节数≥4时,就会触发DMA,将FIFO中的数据批量搬运到指定的内存缓冲区,避免FIFO溢出导致数据丢失。
STXFIFOTRG (0x04): 目标(Target)发送FIFO触发。当I2C模块工作在目标模式(被寻址为从设备)时,其发送FIFO数据量小于或等于阈值时触发。这用于目标设备响应主控制器读请求的场景,当目标设备的发送FIFO快空时,触发DMA从内存(如传感器数据缓冲区)加载待发送数据。
SRXFIFOTRG (0x03): 目标接收FIFO触发。当目标设备的接收FIFO数据量大于或等于阈值时触发。这用于目标设备接收主控制器写命令或数据的场景,数据达到一定量后触发DMA将其存放到内存。
关键理解:发送方向(TX)的触发逻辑是“≤”,这是一种“预防性补充”机制,目的是在FIFO变空、总线停顿之前,提前让DMA补充数据。接收方向(RX)的触发逻辑是“≥”,这是一种“成果性搬运”机制,目的是在积累了一定量的数据后,一次性搬走以提高效率,防止FIFO满而丢失新数据。
2.2 DMA事件管理寄存器(DMA_TRIG0/1)配置
DMA_TRIG0和DMA_TRIG1是两个完全独立的32位事件管理寄存器,它们分别映射到两个DMA通道的触发输入。每个寄存器内部实际上包含了四组“中断”管理寄存器(IIDX,IMASK,RIS,MIS,ISET,ICLR),但其管理的“中断源”仅有上述四个FIFO触发条件。
寄存器组结构:
- DMA_TRIG0: 偏移地址
0x1080-0x10A8。通常我们将其分配给一个DMA通道,例如用于控制器的发送(MTX)或目标的接收(SRX)。 - DMA_TRIG1: 偏移地址
0x1050-0x1078。分配给另一个DMA通道,用于控制器的接收(MRX)或目标的发送(STX)。
配置流程与示例: 假设我们需要配置一个典型的控制器模式下的双向DMA传输:使用DMA通道1自动填充发送数据,使用DMA通道2自动读取接收数据。
规划触发映射:
DMA_TRIG1(通道1) ->MTXFIFOTRG(控制器发送触发)DMA_TRIG0(通道2) ->MRXFIFOTRG(控制器接收触发)
配置I2C FIFO触发阈值:
// 设置控制器发送FIFO触发阈值:当TX FIFO中数据≤2字节时触发DMA I2C0->CFIFOCTL.TXTRIG = 2; // 设置控制器接收FIFO触发阈值:当RX FIFO中数据≥4字节时触发DMA I2C0->CFIFOCTL.RXTRIG = 4;使能DMA_TRIG事件线: 通过
EVT_MODE寄存器,我们需要将对应的事件线模式设置为“硬件模式”(2h),这样当触发条件满足时,硬件会自动产生事件信号给DMA控制器,并在DMA传输完成后自动清除对应的RIS标志位。// INT0_CFG 对应 CPU_INT,保持软件模式(1)或硬件模式(2)处理常规中断 // INT1_CFG 对应 DMA_TRIG1,设为硬件模式,用于TX DMA // EVT2_CFG 对应 DMA_TRIG0,设为硬件模式,用于RX DMA I2C0->EVT_MODE = (2 << 4) | (2 << 2) | (1 << 0); // INT1_CFG=2, EVT2_CFG=2, INT0_CFG=1配置DMA_TRIG1 (发送通道) 的IMASK: 我们需要在
DMA_TRIG1的IMASK寄存器中,使能MTXFIFOTRG对应的位,这样该事件才能被路由到DMA控制器。// DMA_TRIG1 的 IMASK 寄存器偏移为 0x1058 // 使能 CTXFIFOTRG (MTXFIFOTRG) 位 (bit 1) *(volatile uint32_t *)(I2C0_BASE + 0x1058) |= (1 << 1);配置DMA_TRIG0 (接收通道) 的IMASK: 同样,使能
MRXFIFOTRG对应的位。// DMA_TRIG0 的 IMASK 寄存器偏移为 0x1088 // 使能 CRXFIFOTRG (MRXFIFOTRG) 位 (bit 0) *(volatile uint32_t *)(I2C0_BASE + 0x1088) |= (1 << 0);连接DMA通道: 最后,在DMA控制器配置中,将DMA通道1的触发源设置为
I2C0_TX_DMA_TRIG(对应DMA_TRIG1事件),将DMA通道2的触发源设置为I2C0_RX_DMA_TRIG(对应DMA_TRIG0事件)。这部分配置依赖于具体的DMA控制器驱动,通常需要设置通道的源/目标地址、传输量、并启用硬件触发。
实操心得:在调试初期,建议先将
EVT_MODE配置为软件模式(1h),并在中断服务程序中手动清除RIS标志。这样可以先验证FIFO触发条件是否能够正确产生中断,确认逻辑无误后,再切换到硬件模式交给DMA全自动处理。这能有效区分是DMA配置问题还是I2C事件触发问题。
3. 中断寄存器组详解与协同工作流程
DMA负责数据的自动搬运,而传输的启停、错误处理、流程控制则需要通过中断来通知CPU。MSPM0 I2C的中断系统分为三组,分别服务于CPU中断和两个DMA触发事件,结构清晰但需仔细区分。
3.1 核心中断寄存器功能解析
IIDX (Interrupt Index, 中断索引寄存器): 这是中断处理的“导航员”。当多个中断同时发生时,IIDX.STAT字段会给出当前优先级最高且已使能的中断的索引号。例如,0x0C代表控制器发送DMA完成(CDMA_DONE_TX),0x0D代表控制器接收DMA完成(CDMA_DONE_RX)。在CPU中断服务程序(ISR)中,读取IIDX可以快速确定中断源,而无需轮询所有状态位。特别注意:读取IIDX寄存器会自动清除对应中断在RIS和MIS中的标志位,这是硬件自动完成的,简化了软件清中断的流程。
RIS (Raw Interrupt Status, 原始中断状态寄存器): 这是最“诚实”的状态寄存器。它反映了所有中断事件的实际发生状态,不受IMASK寄存器屏蔽影响。即使某个中断被屏蔽了,只要事件发生,对应的RIS位依然会被置1。这个寄存器常用于查询式(Polling)编程,或者在调试时查看所有潜在的中断源。
IMASK (Interrupt Mask, 中断屏蔽寄存器): 中断的“开关”。某一位设置为1,表示允许该中断信号传递到下一级(即影响MIS和IIDX);设置为0,则屏蔽该中断。关键点:DMA触发事件(如MTXFIFOTRG)的屏蔽寄存器在DMA_TRIG0/1寄存器组中;而DMA完成事件(如CDMA_DONE_TX)、FIFO空满、NACK、仲裁丢失等常规中断的屏蔽寄存器在CPU_INT寄存器组(偏移0x1028)中。配置时务必找准位置。
MIS (Masked Interrupt Status, 屏蔽后中断状态寄存器): 这是最终送达CPU中断控制器的信号状态。MIS = RIS & IMASK。只有当事件发生(RIS=1)且未被屏蔽(IMASK=1)时,MIS位才为1,才会可能产生CPU中断请求。在中断服务程序中,可以通过读取MIS来确认当前生效的中断(尽管通常用IIDX更高效)。
ISET/ICLR (Interrupt Set/Clear, 中断置位/清除寄存器):ISET允许软件模拟一个中断事件,主要用于自测试和安全检查。ICLR用于软件手动清除RIS中的标志位。重要规则:对于配置为“硬件模式”清除的事件(如DMA触发事件),其RIS标志会在硬件事件(如DMA应答)完成后自动清除,此时软件写ICLR无效。对于配置为“软件模式”或CPU_INT组中的大多数事件,需要在ISR中手动写ICLR来清除标志,否则会导致中断持续触发。
3.2 DMA与中断的协同工作流程
一个完整的I2C DMA收发流程,是DMA触发、DMA传输、中断通知三者紧密配合的结果。下面以控制器发送(TX)为例,描绘其工作流:
初始化:
- 配置I2C时钟、引脚、模式(控制器)。
- 配置发送FIFO触发阈值(
CFIFOCTL.TXTRIG = 2)。 - 在
DMA_TRIG1.IMASK中使能MTXFIFOTRG中断。 - 在
CPU_INT.IMASK中使能CDMA_DONE_TX中断(用于得知整个DMA传输完成)。 - 配置DMA通道:源地址=内存缓冲区,目标地址=
I2C->CTXDATA,传输总量=N字节,触发源=I2C0_TX_DMA_TRIG。 - 将
EVT_MODE中INT1_CFG(对应DMA_TRIG1)设置为硬件模式(2h)。
启动传输:
- 软件启动I2C控制器,发送START条件和目标地址。
- 初始时,TX FIFO为空,满足“≤2字节”的条件,立即触发
MTXFIFOTRG事件。 - 由于
IMASK已使能且EVT_MODE为硬件模式,该事件产生一个DMA请求。
DMA搬运:
- DMA控制器收到请求,开始从内存搬运数据到
I2C->CTXDATA寄存器。每写入一个字节,TX FIFO计数增加。 - 当DMA搬运了足够多的数据,使得TX FIFO中的数据量大于触发阈值(例如,变成了3个字节)时,
MTXFIFOTRG条件不再满足,DMA请求停止。 - I2C硬件持续从TX FIFO中取出数据发送到总线上。随着数据发送,FIFO计数减少。
- DMA控制器收到请求,开始从内存搬运数据到
循环触发:
- 当TX FIFO中的数据再次被消耗到≤2字节时,
MTXFIFOTRG条件再次满足,产生新的DMA请求,DMA继续搬运后续数据。 - 此过程循环,直到DMA控制器完成了预设的总传输量(N字节)。
- 当TX FIFO中的数据再次被消耗到≤2字节时,
完成通知:
- DMA通道完成全部N字节的传输后,会向I2C模块反馈一个“DMA完成”信号。
- I2C模块据此置位
RIS.CDMA_DONE_TX标志位。 - 由于
CPU_INT.IMASK中已使能该中断,MIS.CDMA_DONE_TX也被置位,进而向CPU申请中断。 - CPU进入中断服务程序,读取
IIDX得到0x0C,得知是控制器发送DMA完成。随后,软件可以执行后续操作,如发送STOP条件、处理发送结果、准备下一次传输等,并手动写ICLR寄存器清除CDMA_DONE_TX中断标志。
目标模式下的流程与此类似,但触发条件使用的是STXFIFOTRG和SRXFIFOTRG,完成中断是TDMA_DONE_TX和TDMA_DONE_RX。
注意事项:务必区分“DMA触发中断”(
MTXFIFOTRG等)和“DMA完成中断”(CDMA_DONE_TX等)。前者是DMA传输的起点(请求搬运数据),频率高,通常配置为硬件自动清除,不打扰CPU;后者是DMA传输的终点(全部数据搬完),频率低,用于通知CPU进行后续处理,需要CPU中断服务程序响应。混淆两者会导致程序逻辑错误。
4. 关键寄存器配置实战与代码示例
理解了原理,我们通过一个具体的实战场景来串联所有配置:使用MSPM0的I2C0作为控制器,通过DMA连续读取一个I2C传感器的128字节数据。
4.1 硬件与软件初始化
首先,进行基础的I2C外设使能、时钟配置和GPIO复用。这部分是标准操作,但却是DMA稳定工作的前提。
// 假设使用I2C0, SCL-PA2, SDA-PA3 void I2C_DMA_Init(void) { // 1. 使能I2C0外设时钟 SYSCTL->CLKEN0 |= SYSCTL_CLKEN0_I2C0; // 2. 配置GPIO为I2C功能 (复用功能 AF1) GPIOA->AFSEL |= (1 << 2) | (1 << 3); // PA2, PA3启用复用功能 GPIOA->CTRL &= ~((0xF << 8) | (0xF << 12)); // 清除原有配置 GPIOA->CTRL |= (1 << 8) | (1 << 12); // 选择AF1 (I2C功能) GPIOA->PULLUP |= (1 << 2) | (1 << 3); // 使能内部上拉(如果外部没有) // 3. 复位并初始化I2C控制器 I2C0->CTRL = 0; // 确保先禁用 // 配置I2C时钟频率,假设系统时钟80MHz,目标100kHz // SCL_PERIOD = (1 + TPR) * (SCL_LP + SCL_HP) * CLK_PRD // SCL_LP=6, SCL_HP=4, CLK_PRD=12.5ns (80MHz) // 目标SCL_PERIOD = 1/100kHz = 10000ns // (1+TPR)*10*12.5ns = 10000ns => TPR = 79 I2C0->CTPR = 79; I2C0->CCR = I2C_CCR_ACTIVE_MASK; // 使能控制器,不启用环路和时钟拉伸 }4.2 FIFO与DMA触发配置
这是DMA自动化的核心配置步骤。我们计划使用DMA通道2来处理接收数据。
void I2C_RX_DMA_Config(uint8_t *rx_buffer, uint32_t data_size) { // 1. 配置接收FIFO触发阈值:当FIFO中有>=4个数据时触发DMA读取 I2C0->CFIFOCTL.RXTRIG = 4; // 2. 配置DMA_TRIG0 (假设它映射到DMA通道2) 的事件使能 // 使能 MRXFIFOTRG (CRXFIFOTRG) 中断 volatile uint32_t *dma_trig0_imask = (volatile uint32_t *)((uint32_t)I2C0 + 0x1088); *dma_trig0_imask |= (1 << 0); // 使能 bit 0 (CRXFIFOTRG) // 3. 将DMA_TRIG0事件线设置为硬件自动清除模式 // EVT_MODE[3:2] = INT1_CFG (DMA_TRIG1), [5:4] = EVT2_CFG (DMA_TRIG0) // 我们只使用DMA_TRIG0,将其设为硬件模式(2),CPU_INT保持软件模式(1) I2C0->EVT_MODE = (2 << 4) | (1 << 0); // EVT2_CFG=2, INT0_CFG=1 // 4. 配置DMA控制器 (伪代码,依赖具体DMA驱动) DMA_ChannelConfig(2, // 通道号 DMA_TRIGGER_I2C0_RX, // 触发源:I2C0 RX DMA事件 (uint32_t)&(I2C0->CRXDATA), // 源地址:I2C接收数据寄存器 (uint32_t)rx_buffer, // 目标地址:内存缓冲区 data_size, // 传输总量:128字节 DMA_ADDR_FIXED, // 源地址固定 DMA_ADDR_INCREMENT, // 目标地址递增 DMA_SIZE_BYTE, // 传输单元:字节 DMA_MODE_BASIC); // 基础模式,传输完停止 // 5. 使能DMA通道(等待触发) DMA_ChannelEnable(2); }4.3 中断配置与启动传输
配置DMA完成中断和可能的错误中断,然后启动I2C传输。
void I2C_Start_DMA_Read(uint8_t target_addr, uint8_t reg_addr) { // 1. 配置并开启相关CPU中断 // 使能控制器接收DMA完成中断(CDMA_DONE_RX)和接收完成中断(CRXDONE) I2C0->IMASK |= (1 << 12) | (1 << 0); // CDMA_DONE_RX, CRXDONE // 可选:使能NACK、仲裁丢失等错误中断用于诊断 // I2C0->IMASK |= (1 << 7) | (1 << 10); // CNACK, CARBLOST // 2. 清除可能存在的旧中断标志 I2C0->ICLR = 0xFFFFFFFF; // 清除所有中断标志 // 3. 配置目标地址和传输方向(读) I2C0->CSA.TADDR = target_addr; // 7位从机地址 I2C0->CSA.DIR = 1; // 方向:接收(读) // 4. 配置传输长度(128字节) I2C0->CCTR.CBLEN = 128 - 1; // 传输字节数 // 5. 启动传输:生成START,并在传输结束后生成STOP,自动ACK I2C0->CCTR.START = 1; I2C0->CCTR.STOP = 1; I2C0->CCTR.ACK = 1; // 自动发送ACK(除了最后一个字节) I2C0->CCTR.BURSTRUN = 1; // 启动传输! }4.4 中断服务程序(ISR)处理
当DMA完成128字节的传输后,CDMA_DONE_RX中断会触发。
void I2C0_IRQHandler(void) { // 1. 读取中断索引,确定中断源 uint8_t int_idx = I2C0->IIDX.STAT; // 读取会自动清除RIS/MIS对应位 switch(int_idx) { case 0x0D: // CDMA_DONE_RX: 控制器接收DMA完成 // DMA传输已完成,数据已在rx_buffer中 DMA_ChannelDisable(2); // 可选:禁用DMA通道 // 检查I2C状态寄存器是否有错误 if (I2C0->CSR.ERR) { // 处理错误:可能是NACK或仲裁丢失 Error_Handler(); } else { // 传输成功,处理接收到的128字节数据 Process_Sensor_Data(rx_buffer); } // 如果需要,可以在这里重新配置并启动下一次DMA传输 break; case 0x00: // CRXDONE: 控制器接收完成(非DMA,单字节) // 如果是非DMA模式或最后一字节处理,会进入这里 // 对于DMA模式,这个中断可能不会发生,或者发生在DMA未覆盖的角落情况 I2C0->ICLR = (1 << 0); // 手动清除CRXDONE标志 break; case 0x08: // CNACK: 地址/数据NACK // 处理NACK错误,例如目标设备无响应 Handle_NACK_Error(); I2C0->ICLR = (1 << 7); // 清除CNACK标志 break; // ... 处理其他可能的中断 default: // 读取未处理的中断,并清除它们(通过读IIDX已清除一部分,但安全起见) I2C0->ICLR = 0xFFFFFFFF; break; } }避坑指南:
- 顺序至关重要:一定要先配置DMA和I2C的触发、中断,最后再启动I2C传输(设置
BURSTRUN)。如果先启动传输,FIFO可能立即满足触发条件,但此时DMA尚未就绪,会导致数据丢失或超时。- FIFO刷新:在每次DMA传输开始前,建议刷新(Flush)TX/RX FIFO,以确保从一个干净的状态开始。
CFIFOCTL.RXFLUSH和TXFLUSH位写1后,需要轮询CFIFOSR中对应的FLUSH位,直到其变为0,表示刷新完成。- DMA传输量对齐:确保DMA配置的传输总量(
data_size)与I2C控制器配置的传输长度(CCTR.CBLEN)一致。CBLEN是传输字节数减1。如果不一致,可能导致DMA提前停止或I2C等待超时。- 中断标志清除:对于
CPU_INT组的中断(如CDMA_DONE_RX),在ISR中必须清除。虽然读IIDX能清除对应的RIS/MIS,但最稳妥的方式是在switch语句的每个case末尾,显式地写ICLR寄存器清除对应位。对于配置为硬件模式清除的DMA_TRIG事件,切勿在ISR中对其写ICLR,否则可能破坏硬件状态机。
5. 调试技巧与常见问题排查
即使按照手册配置,在实际调试中仍会遇到各种问题。以下是一些常见问题的排查思路和调试技巧。
5.1 DMA传输无法启动
- 症状:I2C发送了START和地址后,SCL线被拉低(时钟拉伸),总线卡住,无数据传送。
- 排查步骤:
- 检查触发条件:读取
CFIFOSR.RXFIFOCNT和TXFIFOCNT,确认FIFO的初始状态是否满足你设置的触发条件(例如,TX FIFO是否为空?RX FIFO是否为空?)。 - 检查DMA_TRIG使能:读取
DMA_TRIG0/1的IMASK和RIS寄存器(偏移0x1088/0x1090等),确认对应的FIFO触发位(如CRXFIFOTRG)在IMASK中已使能,并且RIS中是否有标志置起。如果RIS有标志而DMA没动,问题可能在DMA控制器配置。 - 检查EVT_MODE模式:确认
EVT_MODE寄存器中对应事件线(INT1_CFG/EVT2_CFG)是否已设置为硬件模式(2h)。如果误设为禁用(0h)或软件模式(1h),则不会产生DMA请求。 - 检查DMA控制器:确认DMA通道已使能,触发源选择正确,传输量非零,内存地址可写。
- 使用软件模式调试:将
EVT_MODE暂时改为软件模式(1h)。在满足触发条件时,如果对应的RIS位置位,并且在ISR中能处理,说明I2C端事件产生逻辑正确。问题很可能出在DMA控制器的触发信号路由或配置上。
- 检查触发条件:读取
5.2 DMA传输未完成或提前停止
- 症状:只传输了部分数据,
CDMA_DONE_TX/RX中断未产生,或者I2C总线提前产生了STOP。 - 排查步骤:
- 检查传输长度:核对I2C的
CCTR.CBLEN和DMA的传输总量配置。确保它们匹配,且考虑了可能的地址字节。 - 检查FIFO阈值:如果FIFO触发阈值设置得过于激进(例如TX触发阈值设为7,而FIFO深度只有8),可能导致DMA请求过于频繁,在某些时序紧张的系统中,DMA响应不及时,可能造成FIFO下溢(TX)或上溢(RX)。适当降低阈值(如TX设为2,RX设为4)可以增加缓冲余地。
- 检查总线错误:读取
CSR寄存器,检查ERR、ADRACK、DATACK、ARBLOST位。一个NACK或仲裁丢失都会导致I2C状态机提前终止,从而停止从FIFO取数或向FIFO填数,使得DMA触发条件不再满足。 - 检查DMA优先级:如果系统中有多个DMA通道或更高优先级的中断,当前DMA通道可能被长时间抢占。确保I2C DMA通道有足够的优先级,或者检查是否有其他高优先级任务长时间关闭全局中断。
- 检查传输长度:核对I2C的
5.3 中断服务程序无法进入
- 症状:数据传输似乎完成了(通过逻辑分析仪看总线波形正常),但预期的
CDMA_DONE中断没有触发,程序卡住。 - 排查步骤:
- 确认中断向量表与使能:首先确保I2C全局中断在NVIC中已使能,并且中断服务函数地址已正确填入向量表。
- 检查IMASK寄存器:这是最容易被忽略的一步。确认
CPU_INT.IMASK寄存器中,你期望的中断位(如CDMA_DONE_RX, bit 12)确实被置1了。特别注意:DMA_TRIG组的IMASK是使能DMA请求,而CPU_INT组的IMASK是使能CPU中断,两者不同! - 检查RIS寄存器:即使中断未进入,
RIS寄存器也会真实反映事件状态。在疑似完成的地方读取RIS,看对应的完成位是否为1。如果是1但没进中断,肯定是IMASK或NVIC配置问题。如果是0,则说明DMA完成事件根本没产生,需要回溯检查DMA和I2C的配置。 - 清除残留中断标志:在初始化阶段,先写
ICLR = 0xFFFFFFFF清除所有可能残留的中断标志,避免一上电就误入中断。
5.4 目标(Target)模式下的特殊考量
当MSPM0作为I2C从设备时,DMA配置逻辑类似,但有几个关键区别:
- 地址匹配:必须正确配置
TOAR(自身地址寄存器)并确保OAREN=1。如果需要响应通用呼叫地址,还需设置TCTR.GENCALL=1。 - 时钟拉伸:目标模式下,时钟拉伸(
TCTR.TCLKSTRETCH)通常需要使能(=1),以允许从设备在FIFO空/满时拉低SCL线等待DMA服务。 - 触发时机:
TXEMPTY_ON_TREQ和RXFULL_ON_RREQ这两个位(在TCTR中)会影响中断和DMA触发的行为。例如,TXEMPTY_ON_TREQ=1时,TTXEMPTY中断只会在目标状态机因TX FIFO空而等待(TSR.TREQ=1)时产生,这可以更精确地触发DMA填充数据,避免在非传输时段产生无用中断。 - STALE数据:
TXWAIT_STALE_TXFIFO位用于防止陈旧的TX FIFO数据在下一帧被意外发送。在多次不连续的目标发送任务中,建议将此位置1,并在每次传输前刷新TX FIFO。
配置一个稳定可靠的I2C DMA驱动,需要耐心地理解每个寄存器位背后的硬件行为,并通过逻辑分析仪抓取SCL/SDA波形,结合寄存器的实时状态(CSR、TSR、CFIFOSR、TFIFOSR、RIS等)进行交叉验证。从最简单的轮询模式开始,逐步添加FIFO触发中断,最后引入DMA,是降低调试复杂度的有效方法。当一切就绪后,你会看到CPU占用率在大量数据传输时显著下降,系统响应更加流畅,这正是DMA技术带来的价值。
