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 完全不用管,等数据搬完了去处理就行。

配置流程:

  1. 设置外设地址为源地址(固定不变)
  2. 设置内存地址为目标地址(递增)
  3. 配置传输数据量
  4. 选择外设触发源
  5. 使能 DMA 通道

我曾经犯过的错:外设到内存时,外设地址一定要配置为固定模式。有一次我忘了设置地址递增模式,结果每次数据都写到同一个地址,后面的数据把前面的覆盖了。调试时看到数据全是最后一个值,愣是没反应过来。

3.6 内存到外设传输

和外设到内存相反,这次是把内存里的数据搬到外设。比如把音频数据送到 DAC,把显示数据送到 LCD 控制器,或者通过 SPI 发送数据包。

这个模式的关键在于:外设的发送缓冲区是否准备好。如果外设还没准备好,DMA 就把数据送过去了,数据就会丢失。

常见问题:

  • 外设发送缓冲区满时,DMA 会等待还是丢弃?——取决于外设的握手信号
  • 数据宽度不匹配怎么办?——DMA 通常支持 8/16/32 位数据宽度自动对齐
  • 传输速度跟不上怎么办?——考虑使用 FIFO 或降低传输速率

我的经验:内存到外设时,我习惯先检查外设的状态寄存器,确认它准备好接收数据了,再启动 DMA。虽然 DMA 本身有握手机制,但多一层检查总没坏处。

3.7 六种模式对比总结

模式 触发方式 自动重载 典型应用 优先级
单次传输 外设/软件 否 按键、单次 ADC 低
循环传输 外设 是 音频、连续 ADC 中
突发传输 外设 可选 高速外设、大块数据 高
内存到内存 软件 否 数据拷贝、图像处理 中
外设到内存 外设 可选 数据采集、接收 中
内存到外设 外设 可选 数据发送、波形输出 中

3.8 模式选择决策流程

下面这张图是我自己总结的 DMA 模式选择流程。每次做新项目时,我都会按这个思路走一遍,基本不会选错。

DMA 传输模式选择决策流程 确定传输需求 是否需要外设触发? 是 否 外设到内存 / 内存到外设 内存到内存 需要自动重载? 数据量是否很大? 是 否 循环传输模式 单次传输模式 是 否 考虑突发传输模式 普通单次/循环模式 说明:突发传输可与循环/单次组合使用 实际项目中常根据外设速度和数据量灵活搭配

嗯,这张图基本覆盖了日常开发中 90% 的场景。你只要顺着流程走一遍,就能选出最合适的模式。

核心原则:没有最好的模式,只有最合适的模式。选型时多想想你的外设特性、数据量大小、实时性要求,答案自然就出来了。