第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干活期间做其他任务,只有传输完成才打断一下。这相当于把“同步等待”变成了“异步通知”,系统效率一下子就上来了。
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可以在等待期间干别的事,比如更新显示或处理按键。
3.4 中断延迟的代价(避坑指南)
中断不是免费的。每次中断都有延迟:CPU要保存现场、跳转、执行ISR、恢复现场。传输完成本身很快,但整个中断链条加起来可能几百个时钟周期。
我踩过的一个大坑:有次做4K视频采集,DMA每行数据搬完后发一次中断。CPU的ISR里还写了像素格式转换,结果ISR执行时间比一行DMA传输时间还长。中断还没退出,下一行已经搬完了,新中断被挂起。等CPU处理完第一行,第二行数据已经被后来者覆盖了。画面花得像马赛克。解决方案是把格式转换放到主循环,ISR只做缓冲区切换和计数。
3.5 如何评估中断是否必要
不是所有DMA传输都需要中断。比如你只是从SPI Flash搬一段启动代码到内存,搬完直接跳过去执行,那中断就是多余的。这种情况下,可以在启动DMA后,用一条WFI指令让CPU休眠,DMA传输完成后直接唤醒,连ISR都不用写(利用Cortex-M的睡眠唤醒特性)。
我个人习惯用这个判断标准:
- 传输完成后,CPU需要立即对数据做处理? → 用中断。
- 传输只是预取数据,CPU可以晚一点再处理? → 用中断+标志位,或直接用DMA的自动循环模式(不会发中断)。
- 传输完成不代表当前数据可用? (比如在双缓冲切到备用区后,原区数据还在被算法使用中)→ 不要在每个传输完成都中断,改用半满中断或自定义条件。
// 一个典型的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传输完成中断作用和意义的理解。这东西看似简单,但一旦你理解它不仅仅是“通知”,而是系统效率的杠杆点,你就会在设计中主动用好它。下一次你写DMA驱动的时候,记得想想:这次中断真的必要吗?ISR够短吗?频率匹配吗?希望这些经验能帮你少走我当年走过的弯路。