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. 模式选择决策图
下面这张图总结了不同场景下该选哪种模式。我画项目架构图时经常用这个逻辑来判断。
8. 实战建议汇总
最后,我把这些年用 DMA 的经验浓缩成几条建议:
- 单次传输只用于调试和低频场景,别用在生产代码的主路径上。
- 块传输是万金油,大部分场景首选。块大小对齐 cache line。
- 突发传输是性能利器,但突发长度不能超过外设 FIFO 深度。
- 循环模式配合半传输中断,实现 CPU 和 DMA 流水线作业。
- 内存到内存只在大数据量(>256 字节)时用,小数据用 memcpy 更快。
- 外设到内存/内存到外设,触发源一定要配对,否则 DMA 不动。
我曾经犯过的错:在一个项目里,我把 SPI 的 DMA 触发源配成了定时器触发。结果 SPI 数据还没准备好,DMA 就开始搬,搬的全是垃圾数据。排查了两天才发现是触发源错了。所以,每次配完 DMA,先拿逻辑分析仪看一眼触发信号对不对。