第2章:DMA与CPU数据搬运对比
做嵌入式系统这么多年,我见过太多新人在I/O数据传输方式上栽跟头。
真正想明白这三种方式——轮询、中断、DMA,你才算摸到了系统性能的门槛。这一章我们不绕弯子,直接上干货。
2.1 轮询I/O:最原始的方式
你想想看,CPU像个傻子一样不断问外设:“数据到了吗?数据到了吗?”
这就是轮询。每次读状态寄存器,检查就绪位,然后读数据。循环往复。
核心特征:CPU 100%占用,直到数据传输完成。
// 典型的轮询读操作(伪代码)
while (STATUS_REG & READY_BIT == 0) {
// 空转,CPU 烧在这里
}
data = DATA_REG;
我有个血泪教训。刚工作时做一款工业数据采集卡,CPU是ARM7,外设ADC每100微秒产生一个新值。我图省事用了轮询,结果CPU被占得死死的,连中断都响应不了。后来一上逻辑分析仪,CPU在95%的时间都在那条while循环里转悠。为什么?因为ADC转换速度很快,但CPU轮询频率必须比ADC更快才行,否则会丢数。可即便这样,CPU也没空干其他事了。
千万别在实时性要求高、CPU需要处理多个任务时用轮询。除非你只有这一件事可做,或者外设响应速度极快、CPU失速可忽略。
2.2 中断驱动I/O:解放CPU的好帮手
中断的出现,让CPU能“暂停手头工作,先处理重要的事”。
外设完成一次数据准备后,拉一根中断信号。CPU保存现场,跳入中断服务程序,读数据,返回。
// 中断服务程序示例
void UART_IRQHandler(void) {
if (USART->SR & USART_FLAG_RXNE) {
uint8_t byte = USART->DR;
buffer[head++] = byte; // 存到缓冲区
}
}
相比轮询,CPU不用空等了——外设不拉中断时,CPU可以睡大觉或者干别的。不过,中断也是有代价的。进入中断需要压栈、跳转、恢复上下文,一般几十到几百个时钟周期。如果数据量很大、中断频率很高,开销依然可观。
记住一个经验法则:如果外设每产生一个数据就要触发一次中断,且数据速率超过每秒几万次,中断处理的开销就会成为瓶颈。这时候,就该DMA上场了。
2.3 DMA:真正的离线搬运工
DMA的全称是直接存储器访问。说白了,它让外设和内存之间能直接传数据,CPU只负责启动、收尾,不用在传输过程中操碎心。
- 发起传输:CPU配置DMA控制器(源地址、目的地址、长度、触发源),然后启动。
- 自动搬运:外设发出请求,DMA控制器从外设读取数据,写入内存。每次传输一个字节/字,然后地址递增、计数递减。
- 通知完成:当计数归零,DMA控制器产生一个中断,告诉CPU“活干完了”。
// DMA配置示例(简化版)
DMA->source = peripheral_addr;
DMA->dest = memory_buffer;
DMA->size = 1024;
DMA->ctrl = enable_inc_src | enable_inc_dst | start;
while (DMA->busy); // 可做其他事,或响应其他中断
// 传输完成后检查标志
if (DMA->completed) { /* 处理数据 */ }
注意了:在DMA搬运期间,CPU可以跑其他代码、响应其他中断、甚至休眠。只有开始和结束时CPU被轻微打扰一下。
2.4 三种方式对比:一张表说清楚
我在做培训时,经常让大家自己填这个表。填完就懂了。
| 对比维度 | 轮询I/O | 中断驱动I/O | DMA |
|---|---|---|---|
| CPU占用率 | 高(100%消耗) | 中(仅中断处理时用) | 低(启动和完成时短暂用) |
| 数据传输单位 | 每次一个字节/字 | 每次一个字节/字 | 批量(块传输) |
| 响应延迟 | 取决于轮询频率,不可控 | 取决于中断响应延迟(一般几μs) | 极低(硬件直接操作) |
| 硬件复杂度 | 无(仅需GPIO) | 需中断控制器、外设中断支持 | 需DMA控制器、总线仲裁 |
| 适用数据速率 | 极低(<1 KHz) | 中等(<100 KHz) | 高(可达MHz以上) |
| 多任务友好度 | 极差 | 良好 | 优秀 |
| 典型场景 | 按键、简单传感器 | UART、SPI、通用中断 | 高速ADC、DAC、音频、视频、存储 |
核心结论:轮询是“死等”,中断是“提醒你取”,DMA是“替你去取并放好”。大多数现代嵌入式系统,高速数据流都离不开DMA。
2.5 如何选择:一个实用决策流程
我总结了一个简单的判断思路,你把它当避坑指南就好:
- 数据速率 < 1K次/秒?——用轮询都行,简单可靠。但如果CPU有更重要的事,那还是中断。
- 数据速率在1K~100K次/秒?——中断驱动I/O是主流。注意中断处理函数要短平快,别在里面做复杂运算。
- 数据速率 > 100K次/秒?——必须上DMA。否则CPU会被中断淹死,系统直接卡死。
我曾经在一个项目中,用DMA从摄像头采集每秒30帧的VGA图像(每帧约300KB)。如果不用DMA,用中断每帧处理,CPU会有70%的时间在搬运数据,剩下的30%根本不够做图像处理。后来换成DMA双缓冲,CPU的占用从70%降到5%,总算跑起来了。嗯,这就是实实在在的DMA威力。
一个常见的误区:有的人觉得DMA配置麻烦,宁可高频中断也不学DMA。我劝你千万别这样。一旦系统复杂了,中断风暴会让你追悔莫及。DMA的配置成本是一次性的,后期收益却是长期的。
2.6 三种方式的关系结构图
下面这张SVG图,我把三种数据传输方式的核心流程画出来了。你可以看到CPU的工作状态如何随方式变化。
从图上能清楚看到:轮询是CPU全程傻等;中断是CPU来回切换;DMA是CPU只参与初始化和结束,中间全部交给硬件。
我个人的习惯:任何新项目,如果外设能产生数据速率超过1KB/s,我就直接上DMA。哪怕配置麻烦点,后面扩展性会好很多。中断用在一些特殊信号(比如按键、状态变化边沿)上,轮询几乎只在调试阶段临时用用。
2.7 本章小结
三种方式没有的好与坏,只有合不合适。选错了方式,系统性能可能折半甚至宕机。记住这张对比表,下次做设计时拿出来翻一翻。