4. DMA完成中断产生过程:传输完成条件、中断状态寄存器、中断清除操作
这一章咱们聊一个很实在的问题:DMA传输完成后,中断到底是怎么“冒出来”的?很多工程师调中断不准,其实不是中断服务程序写错了,而是对中断产生的那几个环节没吃透。我当年在调试一个高速数据采集卡时,就栽过这个跟头——明明DMA已经传完了,中断却死活不来。排查了一天,最后发现是中断状态寄存器的清除时序搞反了。
嗯,所以我们今天就把“传输完成条件→中断状态寄存器置位→CPU响应→中断清除”这条链路彻底拆开看。你把它搞懂了,后面优化响应延迟才有抓手。
4.1 传输完成条件:谁来决定“传完了”?
DMA控制器怎么知道自己传完了?说白了就是看计数器。每个DMA通道内部都有一个传输计数寄存器(Transfer Count Register, TC),初始值是你配置的总传输次数(或者字节数)。
- 单次传输完成:每次传输一个数据单元(比如一个字),TC就减1。当TC减到0,硬件自动判定“传输完成”。
- 块传输完成:在链表模式下,一个块描述符里的所有传输完成,才算一个块完成。此时可能触发块完成中断。
- 循环模式完成:循环模式下TC永远不会降到0,但每次传输回绕时会产生一个“回绕完成”事件。
关键点:大部分DMA控制器在做增量传输时,传输完成条件 = (当前传输地址 == 目标结束地址) 或者 (剩余计数 == 0)。具体实现厂家有差异,但计数归零是通用标准。
我在一个项目中遇到过一种特殊情况:某个厂商的DMA要求传输宽度(字节/半字/字)和地址对齐必须严格匹配,否则TC减半行为会出现异常,导致永远无法归零。嗯,那次的教训是——读手册时一定要看“传输完成条件”那一段的边界条件。
4.2 中断状态寄存器:中断信号的“信息”
DMA传输一完成,硬件会做一件事:把对应的中断状态位写1。这个位放在哪里?就在中断状态寄存器(Interrupt Status Register, ISR)里。
ISR是DMA控制器的核心寄存器之一。常用结构是这样的:
| 位域 | 名称 | 描述 |
|---|---|---|
| Bit 31-16 | 保留 | 厂家自定义 |
| Bit 15 | TCIF15 | 通道15传输完成中断标志 |
| …… | …… | …… |
| Bit 1 | TCIF1 | 通道1传输完成中断标志 |
| Bit 0 | TCIF0 | 通道0传输完成中断标志 |
小提示:有些DMA会将“错误中断”和“传输完成中断”放在同一个ISR的不同位域,但错误标志需要用单独的错误清除序列来清。一定要区分。
我更习惯把ISR读作“中断挂起寄存器”。因为只要对应位是1,就说明这个通道有一个中断在“挂起”状态。CPU的中断控制器(比如NVIC)才能识别到这个挂起信号,从而进入ISR。
你想想看,如果ISR里全是1,你服务了通道0,但通道1、2的中断没处理,那么DMA的中断输出线会一直保持有效。这就是“中断丢失”的根源之一。
4.3 中断清除操作:清不掉的中断才是噩梦
中断服务程序里最常犯的错误就是“中断清除姿势不对”。DMA的中断清除,绝不是简单地往ISR写0就完事。大部分DMA要求:
- 只写1清除:往对应的位写1,该位清零;写0无影响。
- 清除顺序:必须先确认DMA传输已经完全停止(TC归零后硬件会自动暂停),再清除中断标志。
- 清除完成后:有的DMA要求你写一个“确认寄存器”(比如DMA Clear Interrupt Register),ISR才能真正清零。
千万不要在服务程序一开始就清中断。我曾经在项目里犯过:先清标志,再做数据处理。结果DMA又启动了新的传输,在数据处理过程中再次产生中断,标志被以前的清除操作给抹了,导致中断永久丢失。
正确的顺序是:
- 读ISR,找到是哪个通道触发了中断。
- 读该通道的剩余计数寄存器(确认确实传完了)。
- 处理数据(比如把缓冲区指针交给上层)。
- 最后写1到对应的ISR位,清除中断标志。
举个例子,一个典型的Cortex-M平台上的清除代码:
// 假设DMA通道0传输完成中断
void DMA0_IRQHandler(void)
{
uint32_t isr = DMA->ISR; // 读ISR,获取中断源
if (isr & (1 << 0)) // 判断通道0是否完成
{
// 1. 读取剩余计数,确认传输结束
uint32_t remain = DMA->CH0->CNDTR;
if (remain == 0)
{
// 2. 处理数据(比如复制数据到应用缓冲区)
process_dma_data();
// 3. 清除中断标志(写1清除)
DMA->IFCR |= (1 << 0); // 或者写专门的清除寄存器
}
}
}
4.4 三者的时序关系
为了让你更直观地理解,我画了一张流程图。这张图把所有环节串起来了:从传输完成条件触发,到ISR置位,再到CPU响应和清除。
从图上你能看到,整个链路是串行的。每个环节都可能成为延迟瓶颈:
- 传输完成条件检测需要硬件比较器,延迟固定(约1~2个时钟周期)。
- ISR置位通常在一个时钟周期内完成,但总线仲裁可能会拖一拍。
- CPU响应需要看当前有没有更高优先级中断在跑,这是不可控的。
- 清除操作如果放在ISR头部,会导致后续传输被错误清理。
延迟关键:传输完成条件成立到ISR置位之间,几乎无延迟;但ISR置位到CPU响应之间,受中断屏蔽和优先级影响最大。这也是我们后续章节要优化的问题。
4.5 实践中需要注意的三个坑
| 易犯错误 | 现象 | 解决办法 |
|---|---|---|
| 在ISR开头清除中断标志 | 偶发中断丢失,数据传输不完整 | 移到ISR末尾再清,或使用硬件自动清除模式 |
| 未等待TC完全归零就清除 | 清除后又有新传输启动,标志被提前清掉 | 先读剩余计数寄存器确认 |
| 使用错误清除方法(写0) | 中断标志不清除,CPU一直被中断 | 严格按照手册要求写1清除 |
一个小技巧:如果DMA支持“硬件自动清除中断标志”模式(如STM32的DMAMUX),那就在初始化时开启它,省去手动清除步骤,还能避免清除时的竞态。不过要注意,自动清除是在ISR开始执行之前就完成的,你得确保ISR执行时能正确保存状态。