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清除:往对应的位写1,该位清零;写0无影响。
  2. 清除顺序:必须先确认DMA传输已经完全停止(TC归零后硬件会自动暂停),再清除中断标志。
  3. 清除完成后:有的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响应和清除。

DMA完成中断产生与清除流程 DMA传输进行中(TC递减计数) TC减到0 → 传输完成条件成立 ISR对应位置1(中断挂起) CPU响应中断 → 执行ISR ISR末尾写1清除中断标志 注意点 • 有些DMA需要额外 读结束地址寄存器确认 • 循环模式不会清零TC

从图上你能看到,整个链路是串行的。每个环节都可能成为延迟瓶颈:

  • 传输完成条件检测需要硬件比较器,延迟固定(约1~2个时钟周期)。
  • ISR置位通常在一个时钟周期内完成,但总线仲裁可能会拖一拍。
  • CPU响应需要看当前有没有更高优先级中断在跑,这是不可控的。
  • 清除操作如果放在ISR头部,会导致后续传输被错误清理。

延迟关键:传输完成条件成立到ISR置位之间,几乎无延迟;但ISR置位到CPU响应之间,受中断屏蔽和优先级影响最大。这也是我们后续章节要优化的问题。

4.5 实践中需要注意的三个坑

易犯错误 现象 解决办法
在ISR开头清除中断标志 偶发中断丢失,数据传输不完整 移到ISR末尾再清,或使用硬件自动清除模式
未等待TC完全归零就清除 清除后又有新传输启动,标志被提前清掉 先读剩余计数寄存器确认
使用错误清除方法(写0) 中断标志不清除,CPU一直被中断 严格按照手册要求写1清除

一个小技巧:如果DMA支持“硬件自动清除中断标志”模式(如STM32的DMAMUX),那就在初始化时开启它,省去手动清除步骤,还能避免清除时的竞态。不过要注意,自动清除是在ISR开始执行之前就完成的,你得确保ISR执行时能正确保存状态。