2、DMA 传输类型归纳总结
DMA 传输类型,说白了就是数据从哪搬到哪。我刚搞 DMA 那会儿,总觉得不都是搬数据吗?后来踩了坑才知道——类型没选对,性能直接砍半。下面我按实际项目最常见的三种来拆解。
2.1 内存到内存(Mem2Mem)
CPU 闲着,SRAM 和 SRAM 之间倒腾数据。这种传输不经过外设,纯靠 DMA 控制器内部读地址 → 写地址循环。硬件上通常不支持流控制,一启动就跑到结束字节数为止。
我在项目中遇到过:用 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,系统早就垮了。
我曾经吃过一次亏: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 位总线宽度,两个外设频率不同时容易出现数据错位。
2.5 传输类型 vs 中断延迟关系
不同传输类型对中断延迟的影响完全不一样:
- Mem2Mem: 连续传输,中断信号只在完成时产生。如果数据量超大(比如 1 MB),你要等很久才能收到完成中断——延迟可达毫秒级。
- Periph2Mem: 每个 burst 都可能触发半传输或传输完成中断。如果 burst 很小(比如每次 2 字节),中断堆积,延迟增加。
- Mem2Periph: 类似,但必须考虑外设的“应答”时间。
所以我做系统设计时,优先把大的 Mem2Mem 放在空闲时段,外设相关的传输用 ping-pong 配合阈值设置,控制中断频率。
这张图里,你可以看到:DMA 控制器是所有传输的中间枢纽。实线代表标准路径(Mem2Mem、Periph2Mem、Mem2Periph),虚线代表 Periph2Periph。注意中断信号都是从 DMA 控制器发出去的——如果是 Mem2Mem 长时间传输,中断延迟自然大;如果是外设相关的短 burst,中断会很密集。
2.6 怎么选?
- 大块数据拷贝(图像、音频缓冲) → Mem2Mem,关中断,一次性完成。
- 流式采集(ADC、摄像头) → Periph2Mem,配 ping-pong buffer。
- 发送固定波形/显示刷屏 → Mem2Periph,用好 FIFO 阈值。
- 外设直通(比如 SPI 直接输出到 DAC) → 谨慎评估后选 Periph2Periph。
做 DMA 设计,一定要先弄清楚你用的是哪种类型。不同类型,中断延迟、吞吐瓶颈的根源完全不同。下回排查性能问题,先问自己一句:数据是从哪搬到哪的?