1. DMA 基础概念:为什么我们需要它?

各位同学,咱们今天聊一个嵌入式开发里绕不开的话题——DMA。很多刚入行的朋友觉得 DMA 是个高大上的东西,其实说白了,它就是一块专门负责搬数据的硬件。

我刚开始做嵌入式那会儿,也犯过糊涂。总觉得 CPU 自己搬数据挺快的,干嘛还要多一个 DMA?直到有一次,我在一个音频采集项目里,CPU 忙着搬 ADC 数据,结果按键响应慢了半拍,用户一按按钮,声音要等 200 毫秒才出来……嗯,从那以后,我就再也不敢小看 DMA 了。

1.1 什么是 DMA?

DMA,全称 Direct Memory Access,直接存储器访问。它的核心能力就一句话:不需要 CPU 参与,就能在内存和外设之间搬数据。

你想想看,CPU 本来是要干大事的——跑算法、处理逻辑、响应中断。结果它把大量时间花在从外设寄存器里读一个字节,再写到内存里,这种重复劳动,说白了就是浪费。

DMA 控制器就像是一个专门的“搬运工”。你告诉它:从哪儿搬、搬到哪儿、搬多少、搬完怎么通知我。然后它自己就干完了,CPU 可以腾出手来做更重要的事。

核心要点: DMA 是一个独立于 CPU 的硬件模块,专门负责数据搬运,搬运过程中 CPU 可以并行执行其他任务。

1.2 DMA 与 CPU 的数据搬运对比

咱们直接上对比表,这样更直观。我习惯用这张表给团队新人做培训,一看就明白。

对比维度 CPU 搬运 DMA 搬运
搬运方式 逐字节/逐字读取→写入 硬件自动批量传输
CPU 占用 100% 占用,无法做其他事 仅初始化时占用,传输过程释放
传输速度 受限于 CPU 主频和指令周期 接近总线极限速度
适用场景 小数据量、随机访问 大数据块、周期性传输
功耗 CPU 全速运行,功耗高 CPU 可进入低功耗模式
编程复杂度 简单,直接读写寄存器 需要配置 DMA 控制器

举个例子你就懂了。假设你要从 UART 接收 1024 个字节:

  • CPU 搬运: 每收到一个字节,CPU 就要进一次中断,读寄存器,写内存。1024 个字节就是 1024 次中断。CPU 啥也别干了,光伺候 UART 就够呛。
  • DMA 搬运: 配置好 DMA 后,UART 每收到一个字节,DMA 自动把它搬到内存缓冲区。等 1024 个字节收完,DMA 才发一次中断通知 CPU。CPU 中间该干嘛干嘛。

我的经验: 在 STM32F4 上实测,用 CPU 搬运 1KB 数据到 SPI Flash,耗时约 2.3ms;换成 DMA 后,同样数据量只要 0.4ms,CPU 占用率从 100% 降到 5% 以下。这个差距,在高频数据采集场景里就是能不能跑得动的区别。

1.3 DMA 在嵌入式系统中的价值

DMA 的价值,我总结为三个关键词:解放 CPU、提升吞吐、降低功耗。

解放 CPU

这是最直接的价值。CPU 不用再当“搬运工”了,可以专心跑算法、处理协议栈、响应用户交互。我在一个工业控制项目里,用了 DMA 之后,CPU 的负载从 85% 降到了 30%,多出来的算力刚好用来跑一个 PID 控制算法。

提升吞吐

DMA 的传输速度接近总线极限。比如在 Cortex-M4 上,DMA 可以做到 4-8 字节/时钟周期的传输速率。而 CPU 用 LDR/STR 指令搬数据,通常要 2-3 个时钟周期才能搬一个 32 位字。差距摆在这儿。

降低功耗

这个很多人容易忽略。DMA 传输时,CPU 可以进入睡眠模式或者低功耗模式。对于电池供电的设备,比如 IoT 传感器节点,这个特性太重要了。我曾经做过一个低功耗数据记录仪,用 DMA 采集传感器数据,CPU 大部分时间都在睡,整机功耗从 50mA 降到了 5mA。

注意: DMA 不是万能的。小数据量(比如几十个字节)用 DMA 反而有开销——配置 DMA 通道、设置传输参数、等待启动,这些步骤本身就要花时间。我一般以 64 字节为分界线:小于 64 字节用 CPU 搬,大于 64 字节用 DMA。

1.4 DMA 传输的核心流程

咱们用一张图来理解 DMA 是怎么工作的。这张图我画了很多次,每次给新同事讲 DMA,都是先画这个。

DMA 传输核心流程 CPU DMA 控制器 内存 (SRAM) 外设 (UART/SPI/ADC) ① 配置源/目的/长度 ② 启动 DMA 传输 ③ 读取外设数据 ④ 写入内存缓冲区 ⑤ 传输完成中断 流程说明: 1. CPU 配置 DMA 的源地址、目的地址、传输长度和触发源 2. DMA 启动后,CPU 可以去做其他任务或进入低功耗模式 3. 传输完成后,DMA 通过中断通知 CPU 取数据

这张图里,我特意把 CPU 和 DMA 画成了两条并行的线。你看,CPU 只在第一步和第五步参与,中间的数据搬运全是 DMA 自己搞定的。这就是 DMA 的精髓——让 CPU 从数据搬运的苦力活里解脱出来。

1.5 什么时候该用 DMA?

我根据自己的项目经验,整理了一个简单的判断清单:

  • 大数据块传输: 比如从 ADC 采集 1K 样本、从摄像头读取一帧图像
  • 周期性数据流: 比如音频播放、传感器连续采样
  • 高速外设通信: 比如 SPI 屏幕刷新、SD 卡读写
  • 低功耗场景: 需要 CPU 长时间睡眠,DMA 后台搬运数据
  • 多通道并发: 多个外设同时需要数据搬运,DMA 可以分时处理

避坑指南: 我曾经在一个项目里,把 DMA 用在了 GPIO 中断里——每次按键触发 DMA 搬一个字节。结果发现配置 DMA 的时间比 CPU 直接搬还长。后来改成 CPU 直接读寄存器,反而更快。记住:DMA 适合“搬得多、搬得勤”,不适合“搬一次、搬得少”。

1.6 小结

这一章咱们把 DMA 的基础概念讲清楚了。说白了,DMA 就是一个专门的硬件搬运工,让 CPU 从重复的数据搬运中解放出来。后面我们会深入 Rust 的 DMA 驱动开发,但基础概念一定要先吃透。