1. DMA 基础概念
聊 DMA 之前,先问一个问题:CPU 一定要亲自搬数据吗?
很多人写嵌入式代码时,习惯用 memcpy 或者 for 循环去拷贝一段数据。CPU 确实能干这事,但代价很大——它本可以去跑算法、处理中断、调度任务,结果却被卡在一条条 LDR/STR 指令上。说白了,CPU 擅长“算”,不擅长“搬”。
DMA(Direct Memory Access),就是专门干“搬”的硬件模块。它跟 CPU 共享总线,但可以自己接管总线控制权,在内存、外设、内存之间直接搬运数据,搬完再通知 CPU:“活干完了,你接着忙你的。”
我在第一个量产项目里就吃过亏:用 CPU 轮询 SPI 接收 1KB 数据,结果 main loop 里的控制周期直接被拖垮。后来换成 DMA,CPU 占用从 37% 降到 2%。那次之后,我养成了一个习惯——但凡有“数据块搬运”的场景,先想想能不能交给 DMA。
1.1 什么是 DMA
DMA 是 SoC 内部的一个专用硬件引擎。它包含几个基本部件:
- DMA 控制器:核心调度单元,负责解析传输描述符、发起总线请求。
- 通道(Channel):每个通道可独立配置源地址、目的地址、传输宽度、增量模式。多通道意味着可并发处理多个搬运任务。
- 请求信号:外设(如 UART、I2C、ADC)通过硬件请求线告诉 DMA:“我有数据了,快来搬”。
- 中断输出:传输完成或出错时,DMA 向 CPU 发出中断。
你看,DMA 和 CPU 的搬运,本质区别在于“谁占着总线”。CPU 搬运时,指令流水线被内存访问阻塞;DMA 搬运时,CPU 可以继续在缓存里跑指令,只要不争用同一段内存,性能几乎能叠加。
关键点:DMA 不是 CPU 的替代品,而是 CPU 的“跑腿小弟”。CPU 负责决策和计算,DMA 负责批量数据移动。两者配合,才能把系统吞吐拉到最高。
1.2 DMA 与 CPU 数据搬运的区别
我画了一个简单的对比表,帮你快速抓住差异:
| 对比维度 | CPU 直接搬运 | DMA 搬运 |
|---|---|---|
| 总线占用 | CPU 全程占用总线,无法执行其他指令 | DMA 周期窃取(Cycle Stealing),CPU 可并行执行 |
| 代码复杂性 | 简单(几行 memcpy) | 需配置寄存器 / 描述符链表,稍复杂 |
| 实时性影响 | 搬运时 CPU 响应中断延迟增加 | 搬运过程几乎不干扰 CPU 中断响应 |
| 适用场景 | 小数据量(< 几十字节)、操作频率低 | 大数据块、高频流式传输、低延时要求 |
| 功耗 | 高(CPU 全速运行 + 总线频繁切换) | 低(专用硬件,频率可独立设置) |
| 灵活性 | 可边搬边处理(如校验、转换) | 纯搬运,如需处理数据必须经 CPU 或外设硬件 |
举个例子:你有一个 ADC 以 1MSPS 采样,每次 2 字节,每秒产生 2MB 数据。如果用 CPU 做 memcpy 到内存缓冲,CPU 几乎被 100% 绑死在总线上。换成 DMA,CPU 只需要在 DMA 完成时处理一个中断,其余时间去跑 FFT、PID 或者 UI 刷新。
避坑:我曾经在一个音频项目中,用 CPU 做 I²S 数据搬运,结果播放声音时按钮扫描完全卡死。后来换成 DMA + ping-pong 缓冲,问题直接消失。记住——凡是跟流式数据打交道,优先考虑 DMA。
1.3 DMA 的应用场景
实际工程中,DMA 最常见的三大舞台:
- 外设到内存(Peripheral-to-Memory)
典型:UART 接收、SPI 读取、ADC 采样。数据从外设 FIFO 连续搬运到 RAM 缓冲区,CPU 只需在缓冲区满或超时时处理一次。 - 内存到外设(Memory-to-Peripheral)
典型:DAC 波形输出、I²C 发送、显示驱动(如 LCD 刷屏)。DMA 从内存里准备好的数据包直接喂给外设,CPU 可同时准备下一帧。 - 内存到内存(Memory-to-Memory)
典型:图像帧拷贝、协议栈数据重组、加密/解密的输入输出缓冲复制。注意:memcpy 场景下 DMA 的开销包括配置时间,只有数据量大于一定阈值(比如 256 字节)才划算。
嗯,还有一个容易被忽略的场景:环形缓冲区(Ring Buffer)搬运。我见过很多工程师用软件维护环缓冲的读写指针,还要处理 wraparound。其实 DMA 支持自动地址回绕(Auto-reload / Circular mode),你只需要配好首地址、长度和循环模式,它自己就会像永动机一样搬运。我在做雷达数据采集时就用过,连续 24 小时采集没丢过一帧。
个人技巧:如果你是新手,建议先从“外设到内存”的 UART RX DMA 入手。配置简单,效果立竿见影。打开串口助手看接收,再对比用 CPU 轮询时的 CPU 负载曲线,你会立刻理解 DMA 的价值。
1.4 DMA 与 CPU 搬运的逻辑对比图
下面这张图把核心流程画出来。左侧是 CPU 亲自搬数据,右侧是 DMA 帮 CPU 搬。你看一眼就明白为什么 DMA 能解放 CPU 了。
从图上能清楚看到:左边的 CPU 一个人扛数据搬运,总线一直占着,任务调度被阻塞。右边呢,CPU 花一点点时间配好 DMA,然后就开始跑应用代码了——DMA 自己悄悄把数据从外设搬到内存,最后用一个中断告诉 CPU “活干完了”。这就是 DMA 的价值核心。
1.5 什么时候该用 DMA
最后给一个简单的决策清单:
- 数据量 > 64 字节 —— 用 DMA,配置开销很快被摊销。
- 数据流速率高(比如 > 100KB/s)—— 必须 DMA,CPU 轮询会严重丢帧。
- 目标外设有硬件请求信号(UART RXNE、SPI RXNE、ADC EOC)—— DMA 能自动按事件触发,零 CPU 干预。
- 传输模式允许延迟(非实时关键)—— DMA 的响应时延可以接受,CPU 可以做更重要的事。
- 需要低功耗—— DMA 搬运时 CPU 可以进睡眠模式,功耗直接砍半。
记住一个原则:CPU 是将军,DMA 是辎重兵。别让将军去扛麻袋。