STM32 低功耗模式下的 DMA 传输失效问题与解决策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式开发中,STM32 的低功耗模式与 DMA 传输常被结合使用以降低功耗并提升效率,但二者协同工作时往往出现 DMA 传输失效的棘手问题。本文深入剖析低功耗模式下 DMA 失效的根因,涵盖 Stop、Standby 模式下的时钟与电源域变化,并给出基于 HAL 库的完整配置策略与代码示例,帮助开发者规避陷阱,实现可靠的低功耗数据搬运。
# 引言
在电池供电的物联网设备中,低功耗设计是核心需求。STM32 提供了多种低功耗模式(Sleep、Stop、Standby),而 DMA(直接内存访问)则能在外设与内存间高效搬运数据,减少 CPU 干预。然而,当两者结合时,开发者常遇到 DMA 传输在低功耗模式下失效的问题,导致数据丢失或系统挂起。本文将深入分析失效原因,并提供切实可行的解决策略。
# 低功耗模式与 DMA 的冲突根源
## 1. 时钟与电源域的变化
- **Stop 模式**:关闭大部分外设时钟,仅保留低功耗定时器(LPTIM)和部分唤醒源。DMA 依赖的系统时钟(如 AHB/APB)被关闭,导致 DMA 控制器停止工作。
- **Standby 模式**:几乎全部电路断电,仅保留备份域和唤醒引脚。DMA 完全失效。
- **Sleep 模式**:CPU 停止,但外设时钟仍运行,DMA 可正常工作,但功耗降低有限。
## 2. DMA 传输的触发源
DMA 传输通常由外设事件触发(如 UART 接收、ADC 转换完成)。在低功耗模式下,这些外设可能被关闭或时钟停止,无法产生触发信号,导致 DMA 无法启动。
## 3. 缓冲区与内存的电源域
在 Stop 模式下,SRAM 内容保留,但若 DMA 配置的缓冲区位于被断电的域(如备份 SRAM),则访问失败。
# 解决策略
## 策略一:选择合适的低功耗模式
- 若需 DMA 持续工作,应使用 **Sleep 模式** 或 **低功耗运行模式(Low-power Run)**,此时系统时钟仍运行。
- 若必须使用 Stop 模式,则需在进入低功耗前完成必要的 DMA 传输,或使用支持低功耗的外设(如 LPUART)配合 DMA,但需注意时钟源。
## 策略二:使用低功耗定时器(LPTIM)触发 DMA
LPTIM 在 Stop 模式下仍可运行(使用 LSI 时钟),可配置为周期性触发 DMA。但需确保 DMA 控制器在 Stop 模式下保持时钟(部分系列支持,如 STM32L4 的 DMA 可配置为保持时钟)。
## 策略三:进入低功耗前完成 DMA 传输并等待完成
最稳妥的方法:在进入低功耗前,确保所有 DMA 传输已完成,并关闭 DMA 中断,避免在低功耗模式下产生中断唤醒。
## 策略四:利用事件唤醒后重新初始化 DMA
在唤醒后重新配置 DMA,确保其状态正确。
# 完整代码示例(基于 STM32L4 + HAL 库)
以下示例演示如何安全地使用 DMA 进行 UART 接收,并在进入 Stop 模式前完成传输。
```c
// 全局变量
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
uint8_t rx_buffer[128];
volatile uint8_t rx_complete = 0;
// UART DMA 接收初始化
void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_usart2_rx.Instance = DMA1_Channel5;
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_rx.Init.Mode = DMA_NORMAL;
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_usart2_rx);
__HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);
}
// 启动 DMA 接收
void Start_DMA_Receive(void)
{
rx_complete = 0;
HAL_UART_Receive_DMA(&huart2, rx_buffer, sizeof(rx_buffer));
}
// DMA 传输完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
rx_complete = 1;
}
}
// 进入低功耗前调用此函数
void Enter_StopMode_Safely(void)
{
// 停止 DMA 接收,防止在低功耗模式下触发
HAL_UART_DMAStop(&huart2);
// 等待当前传输完成(若正在传输)
while (HAL_DMA_GetState(&hdma_usart2_rx) != HAL_DMA_STATE_READY) {
// 超时保护
}
// 关闭 UART 时钟(可选)
__HAL_UART_DISABLE(&huart2);
// 进入 Stop 模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后重新配置系统时钟(需调用 SystemClock_Config())
SystemClock_Config();
// 重新初始化 UART 和 DMA
MX_UART2_Init();
MX_DMA_Init();
// 重新启动 DMA 接收
Start_DMA_Receive();
}
```
# 注意事项
- **时钟配置**:进入 Stop 模式前,确保系统时钟配置正确,唤醒后必须重新配置时钟,否则外设工作异常。
- **DMA 中断**:在低功耗模式下,DMA 中断可能无法唤醒 CPU,因此不要在低功耗期间依赖 DMA 中断。
- **缓冲区位置**:若使用备份 SRAM,需确认其电源域在低功耗模式下是否保留。
- **外设时钟门控**:在 Stop 模式下,外设时钟被关闭,但部分外设(如 LPUART)有独立时钟源,可配合 DMA 使用,但需查阅参考手册确认。
- **调试建议**:使用调试器观察 DMA 状态寄存器,确认传输是否启动。
# 总结
STM32 低功耗模式与 DMA 的冲突主要源于时钟和电源域的变化。解决思路是:要么选择允许 DMA 工作的低功耗模式(如 Sleep),要么在进入低功耗前完成传输并妥善处理唤醒后的重新初始化。通过合理配置和代码设计,可以兼顾低功耗与高效数据传输,确保系统稳定可靠。