1. 课程导入

大家好,我是你们这趟性能调优之旅的向导。咱们今天聊的,是嵌入式系统里一个“老黄牛”角色——DMA。说它老黄牛,是因为它默默扛走了 CPU 身上最重的数据搬运活。但很多人对它的理解,还停留在“开了 DMA 就能快”的层面。嗯,这里面的门道,其实不少。

我个人习惯,讲一个技术之前,先把它最朴素的原理讲透。咱们就从 DMA 到底怎么干活开始。

1.1 DMA 工作原理:一个“专职搬运工”

DMA,全称 Direct Memory Access,直接存储器访问。说白了,就是硬件层面给你配了一个“专职数据搬运工”。

它的工作流程,其实就三步:

  1. CPU 下指令:CPU 告诉 DMA 控制器:“嘿,把数据从外设的 FIFO 搬到内存地址 0x8000 去,搬 1024 个字节,搬完了告诉我一声。”
  2. DMA 干活:DMA 接管总线控制权,自己从源地址读数据,往目的地址写数据。这个过程,CPU 完全解放,可以去跑算法、刷界面。
  3. 中断通知:搬完了,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 配置 DMA DMA 控制器 总线仲裁 源端 (外设/内存) 目的端 (内存/外设) 数据流主路径 常见性能瓶颈 ① 总线竞争:DMA 与 CPU 争抢总线 ② 缓存一致性:DMA 直接操作内存,CPU 缓存数据过时 ③ 描述符开销:链表模式下的描述符读取延迟 ④ 外设 FIFO 深度:过浅的 FIFO 导致频繁请求总线

从这张图里,你能看到 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 不是万能的,用不好,它反而会成为系统的拖累。