1、DMA基础概念:什么是DMA,为什么需要DMA
大家好,我是你们的嵌入式系统讲师。今天咱们聊一个经常被忽视、却又极其重要的老朋友——DMA。你肯定会想,不就是直接存储器访问吗?有什么好讲的?但说实话,我入行头两年也把它当成一个“配置好就能用”的黑盒子。直到有一次在电机控制项目里,CPU 被中断占满导致丢步,我才意识到:不懂 DMA,你永远写不出高性能嵌入式代码。
这一章我们先回到原点。不急着玩双缓冲、乒乓操作那些花哨的东西。咱们先搞清楚 DMA 到底是什么,为什么它值得你花时间学。
什么是 DMA
DMA,全称 Direct Memory Access,直接存储器访问。说白了,就是一个硬件搬运工。它在 CPU 眼皮底下,接管了内存和外设之间的数据移动,而且不需要 CPU 逐字节参与。
你想想看,传统的数据搬运是什么样子?CPU 从外设读一个字节,存到寄存器,再写到内存,循环往复。这就像大老板亲自去仓库搬箱子,浪费人才。而 DMA 控制器就是那个专门搬箱子的工人,老板(CPU)只需要告诉它:“把这一箱数据从外设搬到地址 0x20001000,搬完叫我一声。”然后 CPU 就可以去干更重要的事了。
我习惯把 DMA 比作一个可编程的硬件状态机。它有源地址、目标地址、传输长度,以及传输结束后要触发的中断。这几个寄存器配置好了,DMA 就能独立跑完整个传输过程。
为什么需要 DMA
没有 DMA 的时候,嵌入式系统是怎么处理数据流的?我列一下常见的方式:
- 轮询(Polling):CPU 不断检查外设状态寄存器,有数据就读走。CPU 被占满,还浪费电能。
- 中断驱动(Interrupt-driven):外设准备好后触发中断,ISR 里搬运数据。中断频繁时,上下文切换开销巨大。
- CPU 直接搬运(PIO):用循环指令逐字节读写,上面两种都是这个模式的特例。
所有这些方式都有一个共同问题:CPU 参与了每一字节的传输。当你需要 1MB/s 的数据吞吐时,CPU 可能 80% 以上的时间都在搬数据。那系统的实时性、响应速度全都会崩掉。
举个例子。我之前做的一个音频采集项目,ADC 采样率 48kHz,每个采样 16 位,每秒就是 96KB 的数据。如果用中断,48k 次中断/秒,每次中断起码消耗几十微秒。CPU 根本来不及做音频算法。改用 DMA 后,CPU 每采集一秒才处理一次数据,负载直接降到个位数。
| 方式 | CPU 参与程度 | 适用场景 | 典型吞吐 |
|---|---|---|---|
| 轮询 | 全程 | 低速外设 | < 1KB/s |
| 中断驱动 | 每帧触发一次 | 中低速,突发传输 | 几十 KB/s |
| PIO (寄存器拷贝) | 逐字节 | 简单 SPI/I2C 传输 | 几 MB/s |
| DMA | 仅发起和完成 | 高速、大块、连续数据 | 百 MB/s~GB/s |
看到没?C 罗再厉害也不该去当守门员。DMA 的价值就是释放 CPU,让专业的人做专业的事。
DMA 的基本工作流程
虽然不同芯片的 DMA 寄存器千差万别,但工作流程大同小异。我把它总结为四步:
- 配置:设定源地址、目的地址、传输长度、传输模式(单次/循环)、数据宽度等。
- 启动:CPU 写使能位,或者外设发出 DMA 请求,DMA 控制器开始工作。
- 传输:DMA 在系统总线上逐个周期搬运数据。这个过程不占用 CPU,CPU 可以运行其他代码或进入低功耗模式。
- 完成:达到计数后,DMA 停止并可选地触发中断。CPU 在中断服务函数里处理这批数据。
嗯,这里要注意,DMA 传输期间会占用系统总线。如果 CPU 也需要访问总线(比如取指令),会出现总线仲裁。大多数 MCU 的 DMA 优先级高于 CPU,所以密集型 DMA 会拉长 CPU 的指令执行周期。这就是为什么做实时系统时,要留尽量避开 DMA 对关键时序的影响——我曾在某个电机控制项目里吃过这个亏,后来加了一级指令缓存才解决。
DMA 在系统中的位置
为了让你有个直观的印象,我画了一张 DMA 数据传输示意图。CPU、DMA 控制器、内存和外设都挂在系统总线上,DMA 是独立的总线主控。
好了,通过这一章你应该明白了:DMA 的本质是一个硬件数据搬运引擎,它存在的理由就一个——让 CPU 从低效的数据搬运中解放出来,去处理真正需要复杂逻辑的事情。接下来我们会一步步深入 DMA 的配置细节、双缓冲与乒乓策略,以及如何在工程中安全高效地使用它。
(对了,如果你现在手头有一个正在开发的嵌入式项目,建议先翻翻芯片手册的 DMA 章节,看看有几个通道、支持什么样的触发源。心里有个底,后面学习会更顺手。)