2、DMA 传输类型归纳总结

DMA 传输类型,说白了就是数据从哪搬到哪。我刚搞 DMA 那会儿,总觉得不都是搬数据吗?后来踩了坑才知道——类型没选对,性能直接砍半。下面我按实际项目最常见的三种来拆解。

2.1 内存到内存(Mem2Mem)

CPU 闲着,SRAM 和 SRAM 之间倒腾数据。这种传输不经过外设,纯靠 DMA 控制器内部读地址 → 写地址循环。硬件上通常不支持流控制,一启动就跑到结束字节数为止。

适用场景: 帧缓冲区复制、图像裁剪、memcpy 加速。

我在项目中遇到过:用 Mem2Mem 做两个摄像头 FIFO 的数据拼接。之前用 CPU memcpy,带宽只跑到 80 MB/s;换成 DMA 之后直接 200 MB/s,而且 CPU 完全解放。

但要注意——不支持自动重载,每传完一次就要重新配置。

// 配置范例:STM32H7 DMA2D 内存到内存
DMA2D_InitTypeDef   dma2d;
dma2d.Init.Mode  = DMA2D_M2M_BLEND;  // 纯搬
dma2d.Init.OutputOffset = 0;
HAL_DMA2D_Init(&dma2d);

2.2 外设到内存(Periph2Mem)

最常见的一种。外设(ADC、UART、SPI、I2S)把数据丢给 DMA,DMA 不停往 SRAM 写。你想想看,如果每次 ADC 中断都进 CPU,系统早就垮了。

我的习惯: 配双 buffer(ping-pong),外设填一个,CPU 处理另一个,0 等待。

我曾经吃过一次亏:UART 接收数据,用了单 buffer,结果 CPU 在搬数据时 DMA 又写进来,数据被覆盖。后来改成双 buffer 再没出过问题。

关键参数:外设地址必须固定(比如 USART1->DR),存储器地址自增。

2.3 内存到外设(Mem2Periph)

拿内存数据喂给外设。比如 DAC 波形生成、SPI 发送显示缓冲区、PWM 更新寄存器。这种传输通常配合硬件请求信号使用——外设说“我空了”,DMA 才给数据。

要注意外设的 FIFO 阈值。比如 SPI 的 TX FIFO 深度是 16,如果每传 4 个字节就请求一次,DMA 中断会密密麻麻,反而拖慢吞吐。

传输类型 源地址变化 目标地址变化 硬件请求 典型带宽
Mem2Mem 自增 自增 无(软件触发) 最高(无协议开销)
Periph2Mem 固定 自增 有(外设请求) 取决于外设速率
Mem2Periph 自增 固定 有(外设请求) 取决于外设速率

2.4 外设到外设(Periph2Periph)

这个用得少,但特定场景很强。比如 SPI 转 UART、定时器直接触发 ADC。不需要中间 SRAM 参与,数据在外设寄存器之间直接移动。

嗯,这里要注意:很多 MCU 不支持 Periph2Periph,或者带宽很低。我之前在 STM32F4 上试过,发现 DMA 只有 32 位总线宽度,两个外设频率不同时容易出现数据错位。

避坑: Periph2Periph 传输时,两端外设的时钟域必须同步,否则丢数据。我曾经花了三天排查,最后发现是 SPI 时钟比 UART 快太多,DMA 根本来不及同步。

2.5 传输类型 vs 中断延迟关系

不同传输类型对中断延迟的影响完全不一样:

  • Mem2Mem: 连续传输,中断信号只在完成时产生。如果数据量超大(比如 1 MB),你要等很久才能收到完成中断——延迟可达毫秒级。
  • Periph2Mem: 每个 burst 都可能触发半传输或传输完成中断。如果 burst 很小(比如每次 2 字节),中断堆积,延迟增加。
  • Mem2Periph: 类似,但必须考虑外设的“应答”时间。

所以我做系统设计时,优先把大的 Mem2Mem 放在空闲时段,外设相关的传输用 ping-pong 配合阈值设置,控制中断频率。

DMA 传输类型关系与中断路径 DMA 传输类型与中断延迟关系图 DMA 控制器 源端内存 源端外设 目标内存 目标外设 中断信号 图例: 实线:标准传输路径 虚线:Periph2Periph 路径(⚠️较少用) 中断信号连接 DMA → 中断控制器 不同类型的中断频率差异很大

这张图里,你可以看到:DMA 控制器是所有传输的中间枢纽。实线代表标准路径(Mem2Mem、Periph2Mem、Mem2Periph),虚线代表 Periph2Periph。注意中断信号都是从 DMA 控制器发出去的——如果是 Mem2Mem 长时间传输,中断延迟自然大;如果是外设相关的短 burst,中断会很密集。

2.6 怎么选?

  • 大块数据拷贝(图像、音频缓冲) → Mem2Mem,关中断,一次性完成。
  • 流式采集(ADC、摄像头) → Periph2Mem,配 ping-pong buffer。
  • 发送固定波形/显示刷屏 → Mem2Periph,用好 FIFO 阈值。
  • 外设直通(比如 SPI 直接输出到 DAC) → 谨慎评估后选 Periph2Periph。
小技巧: 当你发现中断频繁导致系统响应慢时,先看看是不是用了 Periph2Mem 且突发长度太小。设为 16 字节以上通常能降 50% 中断次数。

做 DMA 设计,一定要先弄清楚你用的是哪种类型。不同类型,中断延迟、吞吐瓶颈的根源完全不同。下回排查性能问题,先问自己一句:数据是从哪搬到哪的?