第3章 DMA传输完成中断的作用与意义

DMA传输完成中断,说白了就是DMA干完活后,给CPU发个信号:“老板,货到了!”然后CPU就知道可以来处理数据了。我刚接触DMA时,觉得中断没什么特别的,不就是传输结束通知一下吗?直到有一次项目里,采集摄像头数据,因为忘了把中断服务函数写对,导致连续几帧图像都丢失了……那次调试折腾了整整两天。嗯,从此我对这个中断再也不敢马虎了。

3.1 什么是DMA传输完成中断

DMA传输本身是硬件独立搬数据的过程。CPU只需要告诉DMA:“从地址A搬N个字节到地址B”。传输完成后,DMA控制器会拉高一根中断请求线(IRQ)。CPU检测到中断后,跳转到对应的中断服务函数(ISR)。这个中断就叫“传输完成中断”。

你想想看,如果没有中断,CPU就得不停地轮询DMA的状态寄存器。轮询的时候CPU什么都干不了,白白浪费时钟周期。中断让CPU能在DMA干活期间做其他任务,只有传输完成才打断一下。这相当于把“同步等待”变成了“异步通知”,系统效率一下子就上来了。

核心要点: 传输完成中断是将DMA从“单纯搬数据”升级为“智能数据搬运工”的关键。没有它,DMA就只是个快一点的memcpy。

3.2 核心作用:从轮询到异步的飞跃

中断到底带来了哪些实实在在的好处?我把它归纳为四点,每一个都在实际项目中反复验证过:

  • 释放CPU:传输期间CPU可以执行主逻辑、响应其他中断、或者进入低功耗模式。我在做电池供电的传感器节点时,靠这个特性把CPU占用率从85%降到了12%。
  • 精确同步:中断保证了CPU在传输完成的第一时间得到通知。对于高速数据采集(比如ADC连续采样),这个时间精度决定了数据的实时性。
  • 触发后续动作:ISR里可以启动下一轮DMA、切换双缓冲区、或者把数据送给算法模块。我习惯把DMA中断当作一个“流水线节拍器”,每一拍完成,后续处理自动跟上。
  • 降低平均延迟:相比轮询,中断把CPU的介入时间从“每周期检查”变成“完成时触发”,平均延迟能降低一个数量级以上。
对比项 轮询模式 中断模式
CPU占用率 持续检查,占用高 只有ISR执行时占用
响应实时性 依赖轮询周期(ms级) 微秒级(取决于中断延迟)
能效 CPU无法休眠 可配合WFI等指令休眠
代码复杂度 简单循环 需要中断配置与ISR

从这张表能看出来,中断虽然多了些配置工作,但带来的性能提升是质的飞跃。当然,前提是中断服务函数必须写对。

3.3 典型应用场景

我做了这么多年嵌入式,发现传输完成中断几乎出现在所有中高速数据通路中。下面这几个场景,我都在真实产品里验证过:

  • 高速数据采集(ADC → 内存):每完成一次ADC转换,DMA就把数据搬入缓冲区,中断触发后分析波形。我做的超声探伤仪就是用这个架构,每秒处理10万次采样。
  • 网络数据包处理(以太网 → 内存):一个数据包到达后,DMA搬完,中断通知协议栈。如果不用中断,收包时CPU基本就要空转。
  • 音频播放/录音(I2S → 内存):双缓冲乒乓模式下,DMA完成中断负责切换当前播放缓冲区,保证音频连续不中断。
  • SPI Flash读写:大块数据读写时,中断让CPU可以在等待期间干别的事,比如更新显示或处理按键。
我的小技巧: 在ISR里尽量只做“最小必要操作”——比如设置一个标志位或者启动下一轮DMA。耗时操作(比如协议解析、文件写入)放到主循环或任务里做,否则中断延迟会暴涨,丢数据是小事,系统崩溃都有可能。

3.4 中断延迟的代价(避坑指南)

中断不是免费的。每次中断都有延迟:CPU要保存现场、跳转、执行ISR、恢复现场。传输完成本身很快,但整个中断链条加起来可能几百个时钟周期。

我踩过的一个大坑:有次做4K视频采集,DMA每行数据搬完后发一次中断。CPU的ISR里还写了像素格式转换,结果ISR执行时间比一行DMA传输时间还长。中断还没退出,下一行已经搬完了,新中断被挂起。等CPU处理完第一行,第二行数据已经被后来者覆盖了。画面花得像马赛克。解决方案是把格式转换放到主循环,ISR只做缓冲区切换和计数。

警告: 中断频率超过一定阈值后,CPU会陷入“中断风暴”——大量时间花在进/出中断上,实际有效计算所剩无几。一般建议:保证每次ISR执行时间 ≪ 相邻中断间隔时间(至少1/10)。如果你发现CPU在ISR里花了超过50%的时间,就该考虑合并中断、改用轮询或者调整DMA触发条件了。

3.5 如何评估中断是否必要

不是所有DMA传输都需要中断。比如你只是从SPI Flash搬一段启动代码到内存,搬完直接跳过去执行,那中断就是多余的。这种情况下,可以在启动DMA后,用一条WFI指令让CPU休眠,DMA传输完成后直接唤醒,连ISR都不用写(利用Cortex-M的睡眠唤醒特性)。

我个人习惯用这个判断标准:

  1. 传输完成后,CPU需要立即对数据做处理? → 用中断。
  2. 传输只是预取数据,CPU可以晚一点再处理? → 用中断+标志位,或直接用DMA的自动循环模式(不会发中断)。
  3. 传输完成不代表当前数据可用? (比如在双缓冲切到备用区后,原区数据还在被算法使用中)→ 不要在每个传输完成都中断,改用半满中断或自定义条件。
// 一个典型的DMA传输完成ISR骨架(以STM32为例)
void DMA1_Stream0_IRQHandler(void)
{
    if (DMA1->LISR & DMA_LISR_TCIF0) {  // 检查传输完成标志
        DMA1->LIFCR = DMA_LIFCR_CTCIF0; // 清除标志
        // 启动下一轮DMA(如果需要)
        HAL_DMA_Start(&hdma, src_buf, dst_buf, len);
        // 通知主循环有数据就绪
        data_ready_flag = 1;
    }
}
一句话总结: 传输完成中断是DMA与CPU之间的握手信号。用得好,系统流畅如丝;用得不好,反而是绊脚石。理解它的作用和代价,才能在实战中做出正确取舍。
DMA传输完成中断的核心作用与信号流 外设请求 DMA传输 传输完成中断 CPU中断服务 后续处理 关键:中断使CPU从轮询 中解放,实现异步通知

以上就是我对DMA传输完成中断作用和意义的理解。这东西看似简单,但一旦你理解它不仅仅是“通知”,而是系统效率的杠杆点,你就会在设计中主动用好它。下一次你写DMA驱动的时候,记得想想:这次中断真的必要吗?ISR够短吗?频率匹配吗?希望这些经验能帮你少走我当年走过的弯路。