4. DMA传输模式:普通模式(Normal)、循环模式(Circular)、外设流控模式(PFC)
DMA传输模式,说白了就是告诉DMA控制器:「数据传完了之后,你下一步该干嘛?」。三种模式对应三种不同的行为逻辑。我刚开始用HAL库的时候,就因为在循环模式和普通模式之间选错了,导致ADC采集的数据一直不对——嗯,后来才发现是模式没配明白。
咱们一个一个来看。
4.1 普通模式(Normal Mode)
这是最基础的模式。DMA收到启动指令后,从源地址搬数据到目标地址,搬完指定长度后自动停止。如果你还想再传一次,必须手动调用 HAL_DMA_Start() 或者重新触发一次外设请求。
核心特征:
- 传输一次就停,不会自动重载
- 传输完成后产生完成中断(TCIF)
- 适合「一次性」数据传输场景
举个例子。我在做某款工业读卡器的时候,需要把SPI Flash里的一页数据读到内存里做校验。这种场景用普通模式就非常合适——数据读一次就够了,不需要反复搬。
// 普通模式配置示例
DMA_HandleTypeDef hdma_spi_rx;
hdma_spi_rx.Instance = DMA1_Channel3;
hdma_spi_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi_rx.Init.Mode = DMA_NORMAL; // 普通模式
hdma_spi_rx.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_spi_rx);
我曾经踩过的坑:普通模式下,如果你在中断里再次调用 HAL_DMA_Start(),一定要先检查DMA是否真的停止了。否则会出现「上次还没传完,你又让它开始」的混乱状态。建议在TCIF中断里先调用 HAL_DMA_Abort() 再重新启动。
4.2 循环模式(Circular Mode)
循环模式就很有意思了。DMA传完一轮数据后,自动把地址指针重置到缓冲区开头,继续下一轮传输。你想想看,这像什么?像一个环形缓冲区,数据一直在里面转圈。
我个人习惯把循环模式用在「持续采集」的场景里。比如用ADC连续采样,或者用定时器触发DMA搬运数据到DAC输出波形。你只需要配好一次,剩下的DMA自己搞定。
| 对比项 | 普通模式 | 循环模式 |
|---|---|---|
| 传输次数 | 1次 | 无限次(循环) |
| 地址重载 | 不重载 | 自动重载 |
| 中断触发 | 仅完成时触发 | 每轮完成都触发 |
| 典型场景 | SPI Flash读、单次ADC | 连续ADC、DAC波形、串口双缓冲 |
// 循环模式配置示例(ADC连续采集)
hdma_adc1.Init.Mode = DMA_CIRCULAR; // 循环模式
// 启动后,ADC数据会持续填充到 buffer[0]~buffer[99]
// 满了自动回到 buffer[0] 继续
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)buffer, 100);
一个小技巧:循环模式下,你可以利用「半传输中断」(HTIF)和「传输完成中断」(TCIF)实现双缓冲。比如ADC采集100个点,半传输中断时处理前50个,传输完成中断时处理后50个。这样数据采集和处理可以流水线进行,效率很高。
4.3 外设流控模式(PFC - Peripheral Flow Control)
这个模式比较特殊,很多初学者容易忽略。外设流控模式,说白了就是让外设自己决定「什么时候传、传多少」。DMA不再主动控制传输节奏,而是听外设的指挥。
为什么会需要这个模式?你想想看,有些外设的数据速率是不固定的。比如SDIO在读取SD卡时,卡的速度可能因为磨损、温度等因素波动。如果DMA按固定节奏去读,要么读太快丢数据,要么读太慢浪费带宽。
PFC模式的核心机制:
- 外设通过硬件信号(如SDIO的DMA请求线)控制传输
- DMA每传输一个数据单元后,等待外设下一次请求
- 适合SDIO、以太网MAC等速率不固定的外设
我记得在做一个高速数据采集项目时,用到了SDIO接口的SD卡。一开始用的循环模式,结果发现偶尔会出现数据错位。查了半天,发现是SD卡内部缓存满了之后,暂停了数据输出,但DMA还在等数据——这就对不上了。换成PFC模式后,SD卡通过硬件流控信号告诉DMA「我准备好了,你来拿」,问题就解决了。
// PFC模式配置示例(SDIO)
hdma_sdio_rx.Init.Mode = DMA_PFC; // 外设流控模式
// 注意:PFC模式通常需要配合外设的硬件流控引脚一起使用
// SDIO的DMA请求线会自动控制传输节奏
注意:不是所有STM32系列都支持PFC模式。F1系列基本不支持,F4/H7系列的部分DMA通道支持。使用前一定要查参考手册的DMA特性表。我曾经在F103上试过配PFC模式,结果DMA直接不工作——后来才发现F103根本没有这个模式。
4.4 三种模式的选择策略
说了这么多,到底怎么选?我总结了一个简单的判断流程:
- 数据只传一次? → 普通模式
- 数据需要连续采集/输出? → 循环模式
- 外设速率不稳定,需要硬件流控? → PFC模式(如果芯片支持)
你可能会问:「循环模式和PFC模式能不能混用?」嗯,理论上可以,但实际项目中我很少这么干。因为一旦开启循环模式,DMA的地址指针就在循环,再加上外设流控,逻辑会变得很复杂。除非你对DMA的底层机制非常熟悉,否则不建议混用。
最后说一句,模式的选择没有绝对的对错。关键是你得理解你的外设需要什么样的数据流。是「一次性」还是「持续性」?是「固定速率」还是「可变速率」?想清楚了,模式自然就选对了。