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中断延迟不是固定的。我拆解过它的组成:
- DMA内部延迟:从传输完成到挂起中断请求的时间。通常1~2个DMA时钟周期。
- 中断控制器仲裁延迟:多个中断源优先级比较,通常2~5个总线时钟。
- CPU响应延迟:从接收中断请求到开始执行ISR第一条指令。包括当前指令完成、压栈、取向量表。高主频CPU上约10~20个CPU周期。
- 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处理数据结束。你认真看一下,每个环节都可能成为瓶颈。
4.6 我自己常用的调试技巧
排查DMA中断问题,我有几个土办法:
- 挂个GPIO翻转:在ISR入口拉高、出口拉低,用示波器看脉宽。这能直接看到ISR时长。
- 统计中断次数:用循环计数器配合DMA计数值。如果中断次数和预期不符,八成是标志清除有问题。
- 测一次DMA传输的最大间隔:连续采集中断时间戳,看最大间隔。如果远超预期,说明CPU曾被更高优先级中断抢占。
再叮嘱一句:不要在内核中断里使用
printf或malloc。我在一个项目中这么干了,导致死锁,整块板子直接挂死。ISR里只做最小集——比如拷贝一个指针、给任务发送信号量。复杂处理挪到任务上下文里做。
4.7 核心归纳
DMA中断机制,说白了就是“中断类型+触发时机+响应速度”这三件事。我建议你每次配DMA中断前,先画一下类似上面那张流程图的通路,看看每个环节有没有潜在瓶颈。
优化顺序:
- 先确认中断频率在合理范围(别太密)
- 再检查ISR执行时间(去掉耗时操作)
- 最后看CPU响应延迟(优先级、向量表位置)
记住一句话:DMA中断不是越及时越好,而是越“刚好”越好。这个度,需要根据实际吞吐率和延迟需求去算,去测。