1. 课程导入
大家好,我是你们这趟性能调优之旅的向导。咱们今天聊的,是嵌入式系统里一个“老黄牛”角色——DMA。说它老黄牛,是因为它默默扛走了 CPU 身上最重的数据搬运活。但很多人对它的理解,还停留在“开了 DMA 就能快”的层面。嗯,这里面的门道,其实不少。
我个人习惯,讲一个技术之前,先把它最朴素的原理讲透。咱们就从 DMA 到底怎么干活开始。
1.1 DMA 工作原理:一个“专职搬运工”
DMA,全称 Direct Memory Access,直接存储器访问。说白了,就是硬件层面给你配了一个“专职数据搬运工”。
它的工作流程,其实就三步:
- CPU 下指令:CPU 告诉 DMA 控制器:“嘿,把数据从外设的 FIFO 搬到内存地址 0x8000 去,搬 1024 个字节,搬完了告诉我一声。”
- DMA 干活:DMA 接管总线控制权,自己从源地址读数据,往目的地址写数据。这个过程,CPU 完全解放,可以去跑算法、刷界面。
- 中断通知:搬完了,DMA 拉一根中断线给 CPU。“老板,活干完了。”CPU 收到信号,过来处理这批数据。
这里有个关键点:DMA 本身没有“大脑”,它只是个执行器。所有的传输参数——源地址、目的地址、数据长度、传输模式——都得由 CPU 提前配好。配错了,它也会老老实实地把数据写到错误的地方去。
核心认知: DMA 的本质是“硬件化的 memcpy + 外设控制”。它用硬件逻辑代替了 CPU 的循环搬运指令,从而实现了真正的并行。
1.2 传统 PIO 与 DMA 对比:一个干,一个看
没有 DMA 的时候,我们用什么?PIO,Programmed I/O。就是 CPU 亲自下场,一个字节一个字节地从外设寄存器里读,再一个字节一个字节地写到内存里。
我当年做第一个项目,用的是一个没有 DMA 的低端 MCU,驱动一个 SPI 屏幕刷图片。CPU 主频 72MHz,刷一张 320x240 的图片,CPU 占用率直接飙到 95% 以上。系统几乎干不了别的事。这就是 PIO 的典型困境。
咱们来做个直观对比:
| 对比维度 | PIO (程序控制) | DMA (直接存储器访问) |
|---|---|---|
| CPU 参与度 | 全程参与,逐字搬运 | 仅初始化配置,传输时完全释放 |
| 传输效率 | 低,受限于 CPU 指令执行速度 | 高,接近总线理论带宽 |
| 适用场景 | 小数据量、低频外设(如按键、GPIO) | 大数据量、高速外设(如 ADC、SDIO、以太网) |
| 代码复杂度 | 简单,直接读写寄存器 | 中等,需配置 DMA 描述符链 |
| 实时性影响 | 阻塞 CPU,影响中断响应 | 非阻塞,CPU 可处理高优先级任务 |
你想想看,如果你的系统里有一个高速 ADC,每秒产生 1M 个采样点。用 PIO,CPU 每 1us 就要被中断一次去读数据。用 DMA,CPU 只需要每 1ms 处理一次批量数据。这中间的差距,是 1000 倍的效率差异。
我的经验: 判断一个外设是否该用 DMA,有个简单标准:看它产生数据的速率是否超过 CPU 主频的 1/100。如果超过了,别犹豫,上 DMA。否则你的 CPU 会变成纯粹的“数据搬运工”,什么算法都跑不动。
1.3 DMA 传输性能瓶颈分析:理想很丰满,现实很骨感
很多人以为配好 DMA 就万事大吉了。其实不然。我在项目中遇到过好几次,开了 DMA 反而比 PIO 还慢的情况。为什么会这样?因为 DMA 的性能瓶颈,往往藏在你看不见的地方。
咱们用一张图来梳理 DMA 传输的完整链路和潜在瓶颈点:
从这张图里,你能看到 DMA 传输不是一条直线。它要经过总线仲裁、源端读取、目的端写入等多个环节。任何一个环节出问题,都会拖慢整体速度。
我总结了几条最常见的性能瓶颈,也是咱们后续课程要重点攻克的方向:
- 总线竞争:DMA 和 CPU 共享系统总线。如果 CPU 也在频繁访问内存(比如跑 RTOS 的任务调度),两者就会抢总线。轻则 DMA 传输速率下降,重则 CPU 的实时任务被卡顿。
- 缓存一致性问题:这是个大坑。CPU 有 L1/L2 Cache,DMA 直接读写物理内存。如果 CPU 在 Cache 里改了数据但还没写回内存,DMA 去读内存就会读到旧数据。反过来,DMA 往内存写了新数据,CPU 的 Cache 里还是旧数据。我曾经在一个音频项目里被这个问题折磨了三天,声音断断续续,最后发现是 DMA 和 CPU 的 Cache 打架了。
- 描述符链开销:高级 DMA 支持链表模式,可以连续传输多个数据块。但每次传输完一个块,DMA 都要去内存里读下一个描述符。如果描述符放在慢速内存区域,这个读取延迟就会成为瓶颈。
- 外设 FIFO 深度:很多外设(如 UART、SPI)内部有个小 FIFO。FIFO 太浅,DMA 刚启动传输,FIFO 就满了/空了,DMA 不得不频繁请求总线,效率大打折扣。
避坑指南: 我曾经在一个项目中,把 DMA 描述符放在了外部 SDRAM 里。结果每次 DMA 传输完成,去读下一个描述符时,都要经过漫长的 SDRAM 访问延迟。后来我把描述符挪到内部 SRAM,传输吞吐量直接提升了 40%。记住:描述符和缓冲区的位置,决定了 DMA 性能的上限。
好了,这一章咱们把 DMA 的底牌翻了个底朝天。从它的工作原理,到和 PIO 的对比,再到那些藏在暗处的性能瓶颈。你心里应该有个谱了:DMA 不是万能的,用不好,它反而会成为系统的拖累。