DMA传输类型:内存到内存、外设到内存、内存到外设、外设到外设

DMA传输听起来抽象,说白了就是数据搬运。但往哪儿搬、从哪儿搬,决定了硬件怎么连线、软件怎么配寄存器。我个人习惯把这四种类型画成一张图再动手写代码,不然很容易搞混。

四种DMA传输类型 内存 (Memory) 外设 (Peripheral) 内存(目标) 外设(目标) ① 内存→内存 ② 外设→内存 ③ 内存→外设 ④ 外设→外设 箭头方向:数据流向 左/上为源,右/下为目的

① 内存到内存(Memory-to-Memory)

这是最“无聊”也最常用的一种。说白了就是把A内存块的数据原封不动拷到B内存块。CPU也能干,但DMA不占用CPU时间,适合大块数据的搬移。

典型场景:协议栈做数据缓冲、音视频帧拷贝、双缓冲轮转。我做过一个千兆以太网的项目,收包后要把数据从描述符指向的buffer搬到应用层的ring buffer。初始版本用CPU memcpy,结果带宽一上来CPU直接100%。换成DMA内存到内存传输,CPU占用降到了15%以下。

关键配置点:

  • 源地址寄存器设为起始内存地址A
  • 目的地址寄存器设为起始内存地址B
  • 传输尺寸设为块大小(注意有些DMA要求对齐到字或缓存行)
  • 触发方式一般选软件触发(无需外设请求)

我的小习惯:我会在传输完成后检查剩余计数寄存器,如果非零说明有错误或提前终止。很多芯片的DMA会给出错误状态位,建议读一下——我吃过亏,没查状态结果数据错了一半。

② 外设到内存(Peripheral-to-Memory)

这是DMA最经典的应用。外设“喊”一声,DMA就把数据搬到内存,CPU可以继续干别的事。比如UART、SPI、I2C、ADC连续采样,都适合。

你想想看:串口每收到一个字节就中断一次,CPU要拼命处理。开DMA以后,收到256字节才触发一次中断——CPU效率提升两个数量级。

我曾经调一个AD9361高速射频采集卡,12bit并行接口,数据率超过200MB/s。CPU直接读?不可能的。用DMA外设到内存,配上双缓冲(ping-pong buffer),才算跑通了。嗯,这里要注意外设的握手信号——你是用边沿触发还是电平触发?我之前误配成电平触发,结果DMA一次搬完就卡住了,因为它以为还有数据。

参数推荐配置忌讳
字节对齐外设数据宽度 = 总线宽度宽度不对齐导致总线异常 stall
请求源硬件触发(外设REQ线)软件触发会让外设数据错过
地址增量源固定(外设FIFO),目的自增同时自增会变成覆盖同一地址

③ 内存到外设(Memory-to-Peripheral)

反过来,CPU准备好数据,DMA送给外设。典型:音频I2S播放、LCD显示、GPIO波形控制、DAC发送。

我最早接触的是把一段WAV数据通过DMA灌给I2S发送FIFO。刚开始写代码时,我忘了设置外设地址固定(只写FIFO),结果DMA把数据写到内存里递增去了——完全没声音。后来检查才发现外设地址没锁住。

避坑指南:我曾经遇到过DMA传输的最后一个数据卡在外设FIFO里没发出去。原因是DMA传输完成后自动停止了时钟或片选信号,而外设还需要一个额外的写周期才能把最后的数据推出去。解决办法:配置流控模式或手动额外生成一个边缘。具体查芯片手册的"last transfer"行为——别想当然。

很多DMA控制器支持链路(LinkedList)模式,你可以做多个描述符链起来,让DMA自动切到下一个节点。适合做环形缓冲区——这样你不需要在CPU里打断去重配寄存器。

④ 外设到外设(Peripheral-to-Peripheral)

这个用得最少,但有时是性能法宝。数据从一个外设FIFO直接搬到另一个外设FIFO,完全不沾内存,甚至CPU完全感知不到。

举个实际例子:我需要把SPI从机的数据实时转发到UART输出。传统做法:SPI中断→CPU读→CPU写UART。延迟累加。用DMA外设到外设:SPI的RX请求触发DMA,源是SPI数据寄存器,目的是UART数据寄存器,搬运一个字后自动结束或循环。这样延迟只有几十纳秒,还不用CPU。

不过,这种模式下要注意:两个外设的时钟域可能不同。如果SPI工作在50MHz而UART工作在115200bps,DMA控制器能不能跨越时钟域做流控?我见过的许多DMA不支持这种跨异步场景的握手,需要额外加FIFO或使用共享SRAM做桥接。说白了,硬碰硬容易丢数据。

我个人的评估清单(决定用外设到外设之前):

  • 两个外设是否在同一个时钟域?
  • 它们的数据宽度是否匹配(8/16/32位)?
  • DMA是否支持同时固定源地址和固定目的地址?
  • 是否不需要数据缓存/格式转换?
  • 传输结束后的同步机制是否足够?

如果以上都是YES,大胆用。否则老老实实走内存中转,更稳妥。

四种类型速查表

传输类型源地址模式目的地址模式触发方式典型应用
内存→内存自增自增软件触发数据拷贝、帧搬运
外设→内存固定(FIFO)自增硬件请求ADC采集、UART接收
内存→外设自增固定(FIFO)硬件请求音频播放、LCD刷屏
外设→外设固定固定硬件请求数据重定向、协议桥接

这四种类型覆盖了95%以上的DMA使用场景。在实际项目中,你往往会组合使用——比如先外设到内存做数据采集,再内存到外设做数据输出。搞清楚每种类型下地址怎么变、触发源是谁,写起驱动来心里就有底了。