第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 如何选择:一个实用决策流程

我总结了一个简单的判断思路,你把它当避坑指南就好:

  1. 数据速率 < 1K次/秒?——用轮询都行,简单可靠。但如果CPU有更重要的事,那还是中断。
  2. 数据速率在1K~100K次/秒?——中断驱动I/O是主流。注意中断处理函数要短平快,别在里面做复杂运算。
  3. 数据速率 > 100K次/秒?——必须上DMA。否则CPU会被中断淹死,系统直接卡死。

我曾经在一个项目中,用DMA从摄像头采集每秒30帧的VGA图像(每帧约300KB)。如果不用DMA,用中断每帧处理,CPU会有70%的时间在搬运数据,剩下的30%根本不够做图像处理。后来换成DMA双缓冲,CPU的占用从70%降到5%,总算跑起来了。嗯,这就是实实在在的DMA威力。

一个常见的误区:有的人觉得DMA配置麻烦,宁可高频中断也不学DMA。我劝你千万别这样。一旦系统复杂了,中断风暴会让你追悔莫及。DMA的配置成本是一次性的,后期收益却是长期的。

2.6 三种方式的关系结构图

下面这张SVG图,我把三种数据传输方式的核心流程画出来了。你可以看到CPU的工作状态如何随方式变化。

三种I/O数据传输方式对比 轮询 I/O CPU循环检查状态 一直占用:不能做其他任务 频繁空转检查 中断驱动 I/O 外设数据就绪→拉中断 CPU暂停当前任务 保存/恢复上下文 每次中断只传1字节/字 DMA CPU配置→启动DMA DMA控制总线,直接搬运 CPU可执行其他任务 传输完成→产生中断 关键选择标准:数据速率 > 100K次/秒 → 必须DMA 中断适用于中等速率,轮询只适合极低速或简单查询

从图上能清楚看到:轮询是CPU全程傻等;中断是CPU来回切换;DMA是CPU只参与初始化和结束,中间全部交给硬件。

我个人的习惯:任何新项目,如果外设能产生数据速率超过1KB/s,我就直接上DMA。哪怕配置麻烦点,后面扩展性会好很多。中断用在一些特殊信号(比如按键、状态变化边沿)上,轮询几乎只在调试阶段临时用用。

2.7 本章小结

三种方式没有的好与坏,只有合不合适。选错了方式,系统性能可能折半甚至宕机。记住这张对比表,下次做设计时拿出来翻一翻。