DMA 传输模式详解:单次、块、突发与循环

DMA 控制器不是只会一种搬运方式。它有好几种模式,每种模式适合不同的场景。选错了,性能反而会下降。我刚开始用 DMA 时,就吃过这个亏。

咱们把这几种模式掰开揉碎了讲清楚。你理解透了,后面做项目就知道该选哪个。

1. 单次传输模式

这是最基础的模式。每次触发,DMA 只搬运一个数据单元(一个字节、半字或字)。搬完就停,等下一次触发。

核心特点:一次触发,一次传输。简单、可控、开销大。

什么时候用?我一般在调试阶段用这个模式。比如验证外设寄存器映射对不对,或者看某个数据源是否稳定。每次只搬一个,出问题了容易定位。

但别在生产代码里大量用。为啥?因为每次传输都要重新仲裁总线、配置通道,CPU 干预次数多,效率低。说白了,大材小用。

特性 说明
传输粒度 1 个数据单元(1/2/4 字节)
触发方式 软件触发或外设硬件触发
总线占用 极短,每次只占一个总线周期
适用场景 调试、低频外设、单次采样

2. 块传输模式

块传输,也叫数据块传输。一次触发,搬运一整块数据。块的大小可以配置,比如 16 字节、64 字节、1024 字节。

这个模式在实际项目里用得最多。我记得做音频采集时,ADC 每产生 64 个采样点就触发一次 DMA,DMA 一次性把这 64 个数据搬到内存缓冲区。CPU 只需要处理完整的音频帧,不用管每个采样点。

我的习惯:块大小一般设为 cache line 的整数倍。比如 Cortex-M7 的 cache line 是 32 字节,那我就设块大小为 32、64 或 128 字节。这样能避免 cache 一致性问题。

块传输的优点是效率高。一次仲裁,连续搬运多个数据,总线利用率上去了。缺点是如果块太大,会长时间霸占总线,影响其他高优先级任务。

3. 突发传输模式

突发传输是块传输的"加速版"。它允许 DMA 在获得总线控制权后,连续传输多个数据,中间不释放总线。

你想想看,普通块传输虽然一次搬多个,但每个数据之间可能还有总线释放和重新仲裁的过程。突发传输不一样,它一口气搬完,总线一直归它用。

我曾经踩过的坑:突发传输的"突发长度"不能乱设。有些外设的 FIFO 深度只有 4,你把突发长度设成 16,数据会溢出。一定要查外设手册,确认 FIFO 深度再配突发长度。

突发传输的典型应用是高速外设,比如以太网 MAC、高速 USB、摄像头接口。这些外设的 FIFO 一旦满了就得赶紧读走,否则丢数据。突发传输正好满足这个需求。

对比项 块传输 突发传输
总线持有方式 多次仲裁,每次搬一部分 一次仲裁,连续搬完
总线占用时间 分散、较短 集中、较长
对实时性影响 较小 较大(可能阻塞其他 DMA 通道)
适用外设 普通 SPI、UART、I2C 高速 FIFO 外设(ETH、USB、CAM)

4. 循环模式

循环模式,也叫环形缓冲区模式。DMA 搬完一块数据后,自动把地址指针重置到起始位置,继续搬运。无限循环,直到你手动停止。

这个模式太实用了。做数据采集、音频播放、串口接收,几乎离不开它。

我举个例子。你做一个示波器,ADC 一直在采样。用循环模式,DMA 把采样数据源源不断地写入一个环形缓冲区。CPU 只需要在后台读取最新的一帧数据做显示。两边互不干扰。

关键配置:循环模式必须配合"半传输中断"和"传输完成中断"使用。半传输中断告诉你缓冲区前半部分满了,快去处理;传输完成中断告诉你后半部分也满了。这样 CPU 和 DMA 就能流水线作业。

嗯,这里要注意。循环模式的地址回绕必须是 2 的幂次对齐。比如缓冲区大小是 1024 字节,地址必须是 1024 的倍数。否则回绕时地址会算错。我见过有人设了 1000 字节的缓冲区,结果 DMA 跑飞了。

5. 内存到内存传输

这个模式不需要外设触发。DMA 自己就能把数据从内存的一块区域搬到另一块区域。

很多人觉得 memcpy 就够了,为啥要用 DMA?我告诉你,memcpy 会占用 CPU,CPU 在拷贝期间干不了别的。DMA 拷贝是后台进行的,CPU 可以继续执行代码。

什么时候用?大块数据拷贝。比如图像帧缓冲区的交换、协议栈的数据包重组、加密算法的输入输出缓冲区拷贝。数据量超过几百字节时,DMA 的优势就出来了。

我的经验:内存到内存 DMA 的启动开销大约 50-100 个 CPU 周期。如果只拷贝几十个字节,还不如用 memcpy。阈值我一般设在 256 字节以上才用 DMA。

6. 外设到内存 & 内存到外设

这两个模式是 DMA 最常用的场景,放在一起说。

外设到内存:外设产生数据,DMA 自动读取并写入内存。典型应用:ADC 采样、UART 接收、SPI 从设备接收。

内存到外设:CPU 把数据准备好放在内存,DMA 自动读取并发送给外设。典型应用:DAC 输出、UART 发送、SPI 主设备发送、LCD 显示数据刷新。

这两个模式的关键是触发源配置。外设到内存,触发源是外设的"数据就绪"信号。内存到外设,触发源是外设的"数据请求"信号。

方向 触发源 典型外设 注意事项
外设 → 内存 外设 RX 就绪 ADC、UART RX、SPI RX 确保内存缓冲区足够大,防止溢出
内存 → 外设 外设 TX 请求 DAC、UART TX、SPI TX、LCD 外设速度慢时,DMA 可能空转

7. 模式选择决策图

下面这张图总结了不同场景下该选哪种模式。我画项目架构图时经常用这个逻辑来判断。

DMA 传输模式选择决策图 DMA 传输需求 内存 ↔ 内存 外设 ↔ 内存 数据量 > 256 字节? 是 → 用 DMA 块传输 外设 → 内存(接收) 内存 → 外设(发送) 外设 FIFO 深度 > 4? 是 → 突发传输 否 → 块传输 数据连续且高速? 是 → 突发传输 否 → 块传输 💡 连续采集/播放场景 → 在上述模式基础上 + 循环模式

8. 实战建议汇总

最后,我把这些年用 DMA 的经验浓缩成几条建议:

  • 单次传输只用于调试和低频场景,别用在生产代码的主路径上。
  • 块传输是万金油,大部分场景首选。块大小对齐 cache line。
  • 突发传输是性能利器,但突发长度不能超过外设 FIFO 深度。
  • 循环模式配合半传输中断,实现 CPU 和 DMA 流水线作业。
  • 内存到内存只在大数据量(>256 字节)时用,小数据用 memcpy 更快。
  • 外设到内存/内存到外设,触发源一定要配对,否则 DMA 不动。

我曾经犯过的错:在一个项目里,我把 SPI 的 DMA 触发源配成了定时器触发。结果 SPI 数据还没准备好,DMA 就开始搬,搬的全是垃圾数据。排查了两天才发现是触发源错了。所以,每次配完 DMA,先拿逻辑分析仪看一眼触发信号对不对。