1. DMA 基础:从原理到关键参数,一次说透

大家好,我是你们的嵌入式系统讲师。今天咱们正式开讲 DMA——直接存储器访问。说实话,我入行那会儿总觉得 DMA 是个「黑魔法」,搞得神神秘秘的。后来亲手调了几个项目,踩了坑才明白,它就是个让你 CPU 能偷偷懒的好工具。

DMA 的全称是 Direct Memory Access。说白了,它允许外设和存储器之间直接交换数据,不用 CPU 一字节一字节地搬。CPU 只需要告诉 DMA 控制器:「把 A 地址的数据,搬 B 地址去,搬完通知我。」然后 CPU 就可以去干别的活了。

核心思想:让 CPU 从繁重的数据搬运中解放出来,把算力用在刀刃上。

1.1 DMA 工作原理

DMA 控制器本质上是一个硬件状态机。它内部有若干个寄存器,包括源地址寄存器、目标地址寄存器、传输计数器、控制寄存器等。我给你画了个简图,看看数据流过哪些环节。

DMA 工作原理框架图 CPU DMA 控制器 源端(存储器/外设) 目的端(外设/存储器) 总线仲裁器 配置/启动 读请求 数据总线 写请求 总线请求 让出总线 工作流程 1. CPU 配置 DMA 2. DMA 申请总线 3. 仲裁器授权 4. DMA 读取源端 5. DMA 写入目的端 6. 计数减1,重复4-5 7. 完成后发中断 8. CPU 恢复总线

流程不复杂:CPU 先给 DMA 控制器配置好源地址、目的地址、数据长度。然后 DMA 向总线仲裁器申请总线使用权。拿到总线后,它就自动从源端读一个数据,写到目的端,计数器减一。如此反复,直到计数器归零。最后 DMA 向 CPU 发个中断,说「搬完了」。

个人经验:我刚开始用 DMA 时,总忘记检查传输完成标志位就操作数据缓冲区。结果读到的全是旧数据。记住:一定要等 DMA 中断或轮询完成标志再访问目标缓冲区。

1.2 DMA 传输类型

根据数据流向,DMA 传输分三种。我把它们整理了一张表,对比着看更清楚。

传输类型 数据流向 典型应用场景 我踩过的坑
内存→内存 RAM 某区域 → RAM 另一区域 数据拷贝、图像帧缓冲交换、快速排序辅助区 源和目的地址重叠时,数据会被覆盖
内存→外设 RAM → 外设 FIFO/寄存器 DAC 波形输出、LCD 刷新、SPI 发送 外设 FIFO 深度不足,不配合流控会丢失数据
外设→内存 外设 FIFO/寄存器 → RAM ADC 采样数据采集、UART 接收、I²S 音频输入 缓冲区大小没对齐边界,DMA 会卡死

避坑指南:我曾经在一个音频采集项目里,用外设→内存 DMA 采集 I²S 数据。因为没开循环模式,缓冲区溢出后 DMA 直接停止工作,系统死机。后来我改用双缓冲 + 中断切换才解决。

内存→内存的 DMA 在 Cortex-M 系列 MCU 里通常只有少数 DMA 通道支持(比如 STM32 的 DMA2)。外设的 DMA 请求线是固定的——比如 USART1_TX 只能接 DMA1 通道4。查手册时别忘了确认。

1.3 DMA 关键参数

配置 DMA 时,有三个参数必须理解透彻:通道、优先级和传输宽度。搞错一个,轻则数据错乱,重则系统死锁。

1.3.1 通道(Channel)

DMA 控制器通常有多个通道,每个通道可以独立配置源/目的地址。但通道与外设之间的映射是硬连线的。举个例子,STM32F4 的 DMA1 有 8 个通道,每个通道固定连接若干外设请求。你不能把 SPI2_RX 硬塞给 DMA1 通道3——它会毫无反应。

我的习惯:做项目前先画一张「通道-外设映射表」贴在工位上。我见过同事把 ADC1 的 DMA 通道配错了,查了一天 bug。

1.3.2 优先级(Priority)

当多个 DMA 通道同时请求总线时,优先级高的先执行。常见分 4 级:低、中、高、最高(或编程可配)。但注意,优先级不是万能的。

  • 高优先级通道会抢占低优先级通道的传输,但正在进行的低优先级传输不会被打断——要等当前这个字节传完才切换。
  • 同优先级按通道编号轮询(硬件决定)。
  • 小心死锁:如果高优先级通道频繁申请总线,低优先级通道可能永远得不到服务。

曾经有一次:我做多路 ADC 同步采集,把优先级最高的通道给了最快的 ADC。结果其他外设(比如 UART)的 DMA 传输被活活饿死,串口丢包严重。最后我改成轮询模式才稳住。

1.3.3 传输宽度(Transfer Width / Data Size)

每次 DMA 搬运一个单元的数据宽度。常见有 8 位(字节)、16 位(半字)、32 位(字)。宽度必须和源/目的端的数据宽度匹配。比如外设寄存器是 32 位的,你配成 8 位,数据会 truncated,高位丢失。

对齐问题:源地址和目的地址都要按传输宽度对齐。32 位传输要求地址是 4 的倍数,否则硬件会报错或产生对齐异常。我曾在某 ARM 芯片上试过未对齐的 32 位 DMA,结果直接 HardFault。

传输宽度 地址对齐要求 典型外设
8 位(byte) 任意地址 UART、SPI (8-bit 模式)
16 位(half-word) 2 字节对齐 I²S、定时器 PWM 输入捕获
32 位(word) 4 字节对齐 ADC 多通道数据、DAC 输出

一个小技巧:配置传输宽度时,可以把源和目的宽度设成不同值吗?部分 DMA 支持「宽度自动转换」,比如源是 8 位、目的是 32 位,DMA 会自动拼接 4 个字节成一个字。但你要确认芯片手册是否支持该模式,我见过有人没注意这个限制,数据顺序全乱了。

1.4 小结一下

DMA 的基础其实就三件事:
工作原理—— CPU 撒手,硬件搬运。
三种传输—— 内存到内存、内存到外设、外设到内存,各有各的脾气。
关键参数—— 通道要对,优先级别饿着别人,宽度必须对齐。