1、DMA基础概念:什么是DMA、DMA在嵌入式系统中的作用、DMA与CPU的数据搬运对比
各位同学,咱们今天正式开讲DMA。说实话,我刚开始做嵌入式那会儿,对DMA的理解也就停留在“一个能自己搬数据的东西”这个层面。直到有一次做一个高速数据采集项目,CPU被中断占得死死的,系统几乎卡死——嗯,从那以后我才真正重视起DMA来。
1.1 什么是DMA?
DMA,全称Direct Memory Access,直接存储器访问。说白了,就是硬件层面专门干“数据搬运”的一个独立模块。它不需要CPU一条条指令地去读、去写,而是自己就能把数据从源地址搬到目的地址。
核心要点:DMA是一个独立于CPU的硬件数据搬运引擎,它拥有自己的总线接口和地址控制逻辑,可以在不占用CPU计算资源的情况下完成内存与外设、内存与内存之间的数据传输。
我习惯把DMA想象成一个“快递小哥”。CPU是公司的CEO,负责决策和计算;而DMA就是那个专门跑腿送文件的快递员。CEO只需要说一句“把这份文件从A送到B”,快递员就自己搞定,CEO可以继续干正事。
1.2 DMA在嵌入式系统中的作用
嵌入式系统里,DMA的作用可以用三个词概括:解放CPU、提升吞吐、降低延迟。咱们一个一个说。
- 解放CPU:没有DMA的时候,每次外设来数据,CPU都得进中断,把数据从外设寄存器搬到内存。数据量一大,CPU就光忙着搬砖了。有了DMA,CPU只需要配置好传输参数,然后该干嘛干嘛。
- 提升吞吐:DMA的传输速度通常比CPU逐字节搬运快得多。我记得有个项目用SPI读取ADC数据,CPU轮询模式只能跑到2Msps,换成DMA后直接飙到5Msps——差距就是这么明显。
- 降低延迟:DMA可以在后台持续传输,数据一到就能搬走,不需要等CPU响应中断。对于实时性要求高的场景,比如音频流、视频采集,这几乎是必须的。
| 应用场景 | DMA的作用 | 典型传输方向 |
|---|---|---|
| UART/SPI/I2C通信 | 自动接收/发送数据帧,减少中断频率 | 外设 → 内存 / 内存 → 外设 |
| ADC采样 | 连续采集数据到缓冲区,CPU定期处理 | 外设 → 内存 |
| DAC输出 | 从缓冲区自动输出波形数据 | 内存 → 外设 |
| 内存拷贝 | 快速复制大块数据(如帧缓冲) | 内存 → 内存 |
| 显示刷新 | 从显存搬运像素数据到LCD接口 | 内存 → 外设 |
我的经验:判断一个外设是否需要DMA,就看它的数据速率。如果每秒中断次数超过1000次,或者单次传输的数据量超过64字节,我建议你认真考虑用DMA。否则CPU会被频繁打断,系统响应会变差。
1.3 DMA与CPU的数据搬运对比
咱们来做个直观对比。假设要从外设接收1024字节的数据,看看CPU和DMA分别怎么干。
CPU搬运模式(无DMA)
// 伪代码示意
for i in 0..1024 {
// CPU 每次都要执行:读外设寄存器 → 写内存
while !(UART->SR & TXE) {} // 等待标志位
buffer[i] = UART->DR; // CPU亲自搬
}
这段代码里,CPU每搬一个字节都要:检查状态、读数据、写内存、循环计数。1024次下来,CPU几乎被完全占用。
DMA搬运模式
// 伪代码示意
// 配置DMA:源地址=UART数据寄存器,目的地址=buffer,长度=1024
dma.set_source(ADDR_UART_DR, Peripheral);
dma.set_destination(ADDR_BUFFER, Memory);
dma.set_length(1024);
dma.start();
// CPU 可以去做别的事
while (adc_is_busy()) {
process_sensor_data(); // CPU 并行处理其他任务
}
看到了吗?CPU只需要配置一次,然后就可以去干别的活了。DMA在后台默默把1024字节搬完,最后发一个完成中断通知CPU。
| 对比维度 | CPU搬运 | DMA搬运 |
|---|---|---|
| CPU占用率 | 100%(搬运期间无法做其他事) | 仅配置和完成中断时占用(通常<1%) |
| 传输速度 | 受限于CPU主频和指令周期 | 接近总线理论带宽 |
| 功耗 | CPU全速运行,功耗高 | DMA专用硬件,功耗远低于CPU |
| 灵活性 | 可对每个字节做处理(如校验) | 纯搬运,不能修改数据 |
| 适用场景 | 小数据量、需要逐字节处理 | 大数据量、周期性传输 |
避坑指南:我曾经在一个项目中,把所有外设都配了DMA,结果发现DMA通道不够用,而且多个DMA请求同时触发时出现了总线仲裁冲突。后来才明白——DMA不是万能的。小数据量(比如几十字节)或者需要实时处理每个字节的场景,用CPU轮询反而更简单可靠。
1.4 DMA的核心工作流程
下面这张图展示了DMA一次典型传输的完整流程。我建议你仔细看看,后面配置寄存器的时候会反复用到这些概念。
这个流程里,最容易被忽略的是第二步——DMA等待触发信号。很多新手以为配置完DMA就开始传了,其实不是。DMA配置好之后是“待命”状态,必须等外设发出请求信号(比如UART收到一个字节、ADC转换完成),DMA才会真正开始搬数据。这个机制保证了DMA和外设的同步。
1.5 什么时候该用DMA?
我总结了一个简单的判断标准,供你参考:
- 数据量大:单次传输超过64字节,或者持续传输速率超过1KB/s
- 周期性强:数据以固定频率到达,比如音频采样、定时ADC
- CPU有更重要的事:比如在做控制算法、协议栈处理、UI渲染
- 低功耗要求:DMA搬运时CPU可以进入睡眠模式
反过来,如果只是偶尔读一个传感器值、或者每次只传几个字节,用CPU轮询或中断反而更简单。别为了用DMA而用DMA,这是很多初学者容易犯的毛病。
一句话总结:DMA的本质是用硬件资源换CPU时间。当你的CPU时间比硬件资源更宝贵时,就该上DMA了。