3. DMA 传输模式:单次传输、循环传输、突发传输、内存到内存、外设到内存、内存到外设
DMA 的传输模式,说白了就是告诉 DMA 控制器「怎么搬、搬多少、搬完以后怎么办」。我刚开始接触 DMA 时,总觉得模式太多记不住。后来发现,只要抓住两个核心问题,一切就清晰了:触发源是谁?传输结束后要不要自动重来?
这一节,我把六种常用模式拆开来讲。每种模式我都会结合项目中的实际场景,帮你理解它到底用在哪儿。
3.1 单次传输模式
单次传输是最基础的模式。DMA 收到一次请求,搬完设定好的数据量,就停下来了。想再搬,得重新配置或者重新触发。
核心特征:一次触发,一次传输,传输完成后通道自动关闭。
我在一个温湿度传感器项目中用过这个模式。传感器每隔 5 秒产生一次数据,我只需要每次把 4 个字节搬到内存里就行。用单次传输,简单可靠,不会出现数据覆盖的问题。
适用场景:
- 按键扫描、AD 采样等非连续数据采集
- 只需要传输一次的控制命令
- 系统低功耗模式下,避免 DMA 持续占用总线
我的习惯:单次传输完成后,记得检查传输完成标志位。我曾经因为没清标志位,导致下一次触发时 DMA 没反应,排查了半天才发现是标志位还挂着。
3.2 循环传输模式
循环传输,也叫自动重载模式。数据搬完一轮后,DMA 自动把地址和计数器重置到初始值,然后继续等待下一次触发。你想想看,这像不像一个永不停歇的传送带?
这个模式在音频播放、ADC 连续采样、电机控制这些场景里非常常见。我记得做音频播放器时,用循环模式把音频数据从内存搬到 DAC,CPU 完全不用管,声音就流畅地播出来了。
配置要点:
- 使能循环模式位(通常叫 CIRC 或 REPEAT)
- 确保源地址和目标地址的递增方向正确
- 注意缓冲区大小要能整除传输数据块大小
我曾经踩过的坑:循环模式下,如果缓冲区地址没有对齐,DMA 可能会在边界处跳回错误的位置。尤其是用双缓冲(ping-pong)时,地址对齐一定要用 __align(32) 强制对齐。
3.3 突发传输模式
突发传输,就是 DMA 一次请求搬多个数据,而不是搬一个就释放总线。说白了,就是「攒够了再搬」。这样做的好处很明显:减少了总线仲裁的次数,提高了传输效率。
突发传输通常和 FIFO 配合使用。DMA 先把数据攒到 FIFO 里,攒够一次突发长度(比如 4、8、16 个数据),再一口气搬出去。
| 突发长度 | 总线占用时间 | 适用场景 |
|---|---|---|
| 1(单次) | 短 | 低速外设、实时性要求高的场景 |
| 4 | 中等 | SPI 数据传输、中等速率 ADC |
| 8 | 较长 | 高速外设、内存拷贝 |
| 16 | 长 | 大块数据搬运、显示刷新 |
我的建议:突发长度不是越大越好。突发太长,会长时间霸占总线,影响其他外设的响应。我一般从 4 开始试,够用就行。
3.4 内存到内存传输
内存到内存,就是从一个内存地址区域搬到另一个内存地址区域。不需要外设触发,配置好以后软件启动,DMA 自己就把数据搬完了。
你可能会问:用 memcpy 不就行了吗?为什么还要用 DMA?嗯,这里要注意:memcpy 是 CPU 在搬,DMA 搬的时候 CPU 可以干别的事。在大数据量拷贝时,DMA 的优势非常明显。
典型应用:
- 图像数据从缓冲区搬到显存
- 协议栈中的数据包拷贝
- 双缓冲区的数据交换
配置要点:内存到内存模式通常需要手动触发。配置完成后,写一次通道使能位,DMA 就开始搬了。搬完会产生传输完成中断。
我记得在一个摄像头项目中,需要把采集到的图像数据从临时缓冲区搬到处理缓冲区。用 DMA 内存到内存模式,CPU 可以同时做图像预处理,效率提升了一倍。
3.5 外设到内存传输
这是最常用的模式。外设(比如 ADC、UART、SPI)产生数据,DMA 自动把数据搬到内存里。CPU 完全不用管,等数据搬完了去处理就行。
配置流程:
- 设置外设地址为源地址(固定不变)
- 设置内存地址为目标地址(递增)
- 配置传输数据量
- 选择外设触发源
- 使能 DMA 通道
我曾经犯过的错:外设到内存时,外设地址一定要配置为固定模式。有一次我忘了设置地址递增模式,结果每次数据都写到同一个地址,后面的数据把前面的覆盖了。调试时看到数据全是最后一个值,愣是没反应过来。
3.6 内存到外设传输
和外设到内存相反,这次是把内存里的数据搬到外设。比如把音频数据送到 DAC,把显示数据送到 LCD 控制器,或者通过 SPI 发送数据包。
这个模式的关键在于:外设的发送缓冲区是否准备好。如果外设还没准备好,DMA 就把数据送过去了,数据就会丢失。
常见问题:
- 外设发送缓冲区满时,DMA 会等待还是丢弃?——取决于外设的握手信号
- 数据宽度不匹配怎么办?——DMA 通常支持 8/16/32 位数据宽度自动对齐
- 传输速度跟不上怎么办?——考虑使用 FIFO 或降低传输速率
我的经验:内存到外设时,我习惯先检查外设的状态寄存器,确认它准备好接收数据了,再启动 DMA。虽然 DMA 本身有握手机制,但多一层检查总没坏处。
3.7 六种模式对比总结
| 模式 | 触发方式 | 自动重载 | 典型应用 | 优先级 |
|---|---|---|---|---|
| 单次传输 | 外设/软件 | 否 | 按键、单次 ADC | 低 |
| 循环传输 | 外设 | 是 | 音频、连续 ADC | 中 |
| 突发传输 | 外设 | 可选 | 高速外设、大块数据 | 高 |
| 内存到内存 | 软件 | 否 | 数据拷贝、图像处理 | 中 |
| 外设到内存 | 外设 | 可选 | 数据采集、接收 | 中 |
| 内存到外设 | 外设 | 可选 | 数据发送、波形输出 | 中 |
3.8 模式选择决策流程
下面这张图是我自己总结的 DMA 模式选择流程。每次做新项目时,我都会按这个思路走一遍,基本不会选错。
嗯,这张图基本覆盖了日常开发中 90% 的场景。你只要顺着流程走一遍,就能选出最合适的模式。
核心原则:没有最好的模式,只有最合适的模式。选型时多想想你的外设特性、数据量大小、实时性要求,答案自然就出来了。