4、DMA中断机制概述归纳总结

DMA中断,说简单点就是DMA干完活后通知CPU一声。但这一声“通知”,在实时系统里可能价值千金,也可能捅出大篓子。

我刚开始搞嵌入式时,以为DMA中断就是“传输完成→进中断→取数据”,简单粗暴。后来在某个高速数据采集项目里,就因为没摸清中断机制,导致吞吐率直接腰斩。今天我把DMA中断机制的核心要点掰开揉碎,给大伙儿讲讲。

4.1 中断类型——不止“完成”这一种

很多人的认知里,DMA只有一个“传输完成中断”。实际上,现代DMA控制器至少支持以下几种中断类型:

中断类型触发条件典型应用
传输完成中断整个块传输结束普通数据搬移后通知CPU处理
半传输中断传输达到总长度的一半乒乓缓冲切换、提前处理数据
错误中断总线错误、超时、对齐错误等异常恢复、系统保护
链表中断链表描述符执行完毕SG(散聚)传输的场景
通道完成中断多通道DMA中特定通道完成复杂外设驱动(如USART、SPI多通道)
我的习惯:能不用完成中断就尽量不用。比如高速ADC采集,我会用半传输中断 + 乒乓缓冲,让CPU处理前半段缓冲的同时,DMA继续往后半段填数据。这样延迟直接砍半。

4.2 中断标志与清除——血泪教训

有一类坑特别容易踩——中断标志的清除方式。不同厂家的DMA设计不一样:

  • 写1清除(W1C):向标志位写1来清除中断。常见于ARM Cortex-M系列。
  • 读清除:读取状态寄存器后自动清除。常见于一些老式DMA或FPGA外设。
  • 写0清除:少见,但有些专用芯片会这么干。
千万不要:在中断服务程序里使用“读-改-写”的方式来清除标志。我曾经看到一个同事用REG |= (1 << BIT)去清除标志,结果因为读和写之间有中断嵌套,把硬件自动置位的其他标志也给覆盖了。调试了两天才找到原因。

正确的做法:直接向清除寄存器写需要的比特,不要依赖读出的值。比如:

void DMA_IRQHandler(void)
{
    // 先读取中断状态,判断原因
    uint32_t status = DMA->ISR;
    if (status & (1 << DMA_CH1_TCIF)) {
        // 处理传输完成
        process_data();
        // 写1清除完成标志
        DMA->IFCR = (1 << DMA_CH1_TCIF);
    }
    if (status & (1 << DMA_CH1_TEIF)) {
        // 处理错误
        error_handler();
        DMA->IFCR = (1 << DMA_CH1_TEIF);
    }
}

4.3 中断延迟——谁在吃时间?

DMA中断延迟不是固定的。我拆解过它的组成:

  1. DMA内部延迟:从传输完成到挂起中断请求的时间。通常1~2个DMA时钟周期。
  2. 中断控制器仲裁延迟:多个中断源优先级比较,通常2~5个总线时钟。
  3. CPU响应延迟:从接收中断请求到开始执行ISR第一条指令。包括当前指令完成、压栈、取向量表。高主频CPU上约10~20个CPU周期。
  4. ISR执行时间:这个就看代码质量了。如果你在ISR里做大量计算,延迟会指数级增长。
环节典型耗时(@100MHz CPU)优化方向
DMA内部10~20 ns提高DMA时钟
中断控制器仲裁20~50 ns合理设置优先级
CPU响应80~200 ns使用零延迟中断、快速压栈特性
ISR执行几百ns~几μs拆出中断底半部、使用任务通知
关键数字:即使只优化ISR,减少一次内存访问就能省下几十ns。在千兆以太网或4K视频采集场景下,一次DMA中断延迟如果超过1μs,吞吐率就会明显掉下来。

4.4 中断频率与吞吐率——平衡的艺术

DMA中断不是越频繁越好。假设你这么配:每次传2字节就发一次中断——那CPU大部分时间都在进中断和清标志,数据搬移速度反而上不去。

我一般遵循两条原则:

  • 中断间隔 > 中断服务开销×10(保证CPU使用率 < 10%)
  • 缓冲大小 = 期望吞吐率 × (中断延迟 + 服务时间)(保证不丢数据)

举个例子:某ADC采样率10MSPS,16位。如果我设定DMA传输256样本后触发一次中断,那么中断频率大约是40KHz。ISR处理时间必须小于25μs。如果ISR需要50μs,那就要么把缓冲减半到128样本(中断频率80KHz),要么用乒乓缓冲配合半传输中断来降低单次处理压力。

4.5 架构总览——一张图说清楚

下面这张SVG图把DMA中断的完整通路画出来。从外设请求开始,到CPU执行ISR处理数据结束。你认真看一下,每个环节都可能成为瓶颈。

DMA中断机制知识体系图 外设/外设请求 DMA控制器 完成标志 / 错误标志 / 半传标志 内存/数据缓冲 中断控制器(NVIC/INTC) 优先级仲裁 / 挂起 CPU 压栈 → 向量表 → ISR 数据处理 / 任务通知 ⚠ 延迟/吞吐瓶颈排查路线 1. 外设侧:请求速率是否超过DMA带宽? 2. 中断侧:优先级设置、标志清除方式、ISR执行时间是否超标? 3. 处理侧:数据是否还在ISR里做耗时运算?应推到底半部。

4.6 我自己常用的调试技巧

排查DMA中断问题,我有几个土办法:

  • 挂个GPIO翻转:在ISR入口拉高、出口拉低,用示波器看脉宽。这能直接看到ISR时长。
  • 统计中断次数:用循环计数器配合DMA计数值。如果中断次数和预期不符,八成是标志清除有问题。
  • 测一次DMA传输的最大间隔:连续采集中断时间戳,看最大间隔。如果远超预期,说明CPU曾被更高优先级中断抢占。
再叮嘱一句:不要在内核中断里使用printf或malloc。我在一个项目中这么干了,导致死锁,整块板子直接挂死。ISR里只做最小集——比如拷贝一个指针、给任务发送信号量。复杂处理挪到任务上下文里做。

4.7 核心归纳

DMA中断机制,说白了就是“中断类型+触发时机+响应速度”这三件事。我建议你每次配DMA中断前,先画一下类似上面那张流程图的通路,看看每个环节有没有潜在瓶颈。

优化顺序:

  1. 先确认中断频率在合理范围(别太密)
  2. 再检查ISR执行时间(去掉耗时操作)
  3. 最后看CPU响应延迟(优先级、向量表位置)

记住一句话:DMA中断不是越及时越好,而是越“刚好”越好。这个度,需要根据实际吞吐率和延迟需求去算,去测。