4. DMA传输模式

STM32 的 DMA 控制器并不是“只会搬数据”的黑盒,它真正灵活的地方在于传输方向与工作模式可以自由组合。方向决定数据从哪来、到哪去;模式决定这次搬运是一次性完成,还是自动循环、持续不断。理解这两组概念,是配置 DMA 时最容易出错、也最值得先弄清楚的基础。

从方向上看,DMA 支持三种基本传输类型:外设到存储器(Peripheral-to-Memory)、存储器到外设(Memory-to-Peripheral)、存储器到存储器(Memory-to-Memory)。从工作模式上看,又分为单次模式(Normal)和循环模式(Circular)。方向与模式是正交的,也就是说,外设到存储器既可以是单次,也可以是循环,这一点在配置寄存器时体现得非常直接。

DMA 传输方向与工作模式组合 外设 → 存储器 ADC / USART_RX / SPI_RX 数据源:外设数据寄存器 目的:SRAM 缓冲区 存储器 → 外设 USART_TX / SPI_TX / DAC 数据源:SRAM 缓冲区 目的:外设数据寄存器 存储器 → 存储器 SRAM → SRAM / Flash → SRAM 仅 DMA1/DMA2 支持 常用于数据搬移、填充 单次模式 Normal 传输 N 个数据后停止 TC 置位,需软件重新使能 适合一次性采集、发送 循环模式 Circular 传输 N 个数据后自动重装 地址与计数器复位,持续搬运 适合 ADC 连续采样、音频流 关键配置位: DIR[1:0] 决定传输方向;CIRC 位决定单次/循环;MINC/PINC 决定地址是否递增; NDTR 设置传输数量;TC 中断在传输完成时触发,循环模式下每次重装都会触发。

一、三种传输方向的核心原理

外设到存储器是最常见的采集路径。以 ADC 连续采样为例,ADC 每次转换完成会把结果写入 ADC_DR,DMA 检测到外设请求后,自动把 ADC_DR 的内容搬到 SRAM 数组里。此时外设地址固定为 ADC_DR,存储器地址递增,传输数量等于采样点数。USART 接收、SPI 接收、I2S 接收都属于这一类。

存储器到外设则是发送路径。比如 USART 发送一段字符串,DMA 从 SRAM 缓冲区逐个读取字节,写入 USART_DR,硬件自动完成发送。外设地址固定为 USART_DR,存储器地址递增。SPI 发送、DAC 输出、定时器 PWM 占空比更新也常用这种方式。

存储器到存储器比较特殊,它不依赖外设请求,而是由 DMA 控制器自己产生触发,把一块 SRAM 或 Flash 区域搬到另一块 SRAM。它常用于数据搬移、缓冲区填充、图像数据复制等场景。需要注意的是,只有 DMA1 和 DMA2 的特定通道支持存储器到存储器,且不能与循环模式随意组合使用。

方向判断口诀:看“谁产生请求、谁被动接收”。外设产生请求、DMA 把数据搬进内存,就是外设到存储器;内存准备好数据、DMA 推给外设,就是存储器到外设;没有外设参与、纯内存搬移,就是存储器到存储器。

二、单次模式与循环模式的工作流程

单次模式下,DMA 传输完 NDTR 指定的数据量后,硬件自动清除 EN 位,传输完成标志 TC 置位。如果还需要继续传输,必须由软件重新配置 NDTR 并使能通道。它适合“采集一批、处理一批”的间歇式场景,比如按键触发一次 ADC 采集 100 个点。

循环模式下,NDTR 减到 0 后不会停止,而是自动重装为初始值,源地址和目的地址也回到起始位置,DMA 继续下一轮搬运。整个过程无需 CPU 干预,TC 标志每轮都会置位,可以配合中断做“半传输/全传输”双缓冲处理。它适合 ADC 连续扫描、音频流播放、串口不定长接收等持续数据流场景。

// 外设到存储器 + 循环模式(ADC1 + DMA1_Channel1)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC; // 外设→存储器
DMA_InitStructure.DMA_BufferSize         = 256;
DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode               = DMA_Mode_Circular;     // 循环模式
DMA_InitStructure.DMA_Priority           = DMA_Priority_High;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);

三、典型场景与配置对照

场景 传输方向 工作模式 关键配置
ADC 连续采样 256 点 外设 → 存储器 循环 PINC 禁止,MINC 使能,半字对齐
USART 发送字符串 存储器 → 外设 单次 PINC 禁止,MINC 使能,字节对齐
SRAM 缓冲区搬移 存储器 → 存储器 单次 PINC/MINC 均使能,仅特定通道支持
音频 I2S 播放 存储器 → 外设 循环 半传输中断 + 双缓冲
串口不定长接收 外设 → 存储器 循环 配合 IDLE 中断读取 NDTR 差值

四、常见误区与复盘

误区一:把循环模式当成“无限传输”。循环模式只是自动重装计数器,传输长度仍然是 NDTR 指定的值,只是反复执行。如果缓冲区大小和 NDTR 不匹配,会出现数据覆盖或错位。
误区二:存储器到存储器随意开循环。部分 STM32 型号的 M2M 模式不支持循环,或者循环时行为异常,配置前必须查参考手册的通道限制表。
复盘建议:调试 DMA 时先确认三件事——方向对不对、地址递增对不对、NDTR 和缓冲区大小是否一致。再打开 TC 中断观察是否按预期触发,循环模式下重点看半传输中断是否稳定。只要这三步走通,绝大多数 DMA 异常都能快速定位。

总结来说,传输方向决定“数据流向”,工作模式决定“搬完是否继续”。外设到存储器、存储器到外设、存储器到存储器是三条不同的数据通路,单次与循环则是两种生命周期管理方式。把它们组合起来,就能覆盖从单次采集到连续流处理的绝大多数应用场景。