# 引言 在电池供电的物联网设备中,低功耗设计是核心需求。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),要么在进入低功耗前完成传输并妥善处理唤醒后的重新初始化。通过合理配置和代码设计,可以兼顾低功耗与高效数据传输,确保系统稳定可靠。