C++本身不实现低功耗模式切换,而是通过调用STM32 HAL/LL库函数(如HAL_PWR_EnterSTOPMode)配合时钟配置、寄存器操作与中断唤醒设置来完成;Stop模式保留SRAM和寄存器状态,唤醒后从中断返回点继续执行,需手动停用SysTick、配置Wakeup引脚并恢复时钟;Standby模式则全片断电仅留备份域,唤醒等效复位;Sleep模式仅停CPU,外设全运行,依赖WFI/WFE指令。

单片机的低功耗模式切换不是靠 C++ 语言本身实现的,而是调用特定芯片厂商提供的外设驱动库(如 STM32 的 HAL 或 LL 库),C++ 在这里只是承载调用逻辑的语法容器。直接写 sleep() 或 enter_stop_mode() 不会生效,必须配合时钟配置、电源控制寄存器操作和中断唤醒设置。
STM32 HAL 库下进入 Stop 模式要关掉 SysTick 并配置 Wakeup Pin
Stop 模式下 CPU 和主时钟停止,但 SRAM 和寄存器内容保留,RTC 和 LSE 可继续运行。HAL 默认启用 SysTick,它依赖 HCLK,进 Stop 前必须手动停用,否则可能无法进入或唤醒异常。
- 调用
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)前,先执行HAL_SuspendTick() - 若用 PA0 做 Wakeup 引脚,需提前调用
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1)(PA0 对应 PIN1) - 退出 Stop 后,必须立刻调用
HAL_ResumeTick()恢复SysTick计数,否则HAL_Delay()等函数会卡死 - 注意:Stop 模式下 HSI/HSE 自动关闭,唤醒后需重新使能并等待稳定,HAL 默认不自动重配系统时钟,需自行处理或启用
__HAL_RCC_HSI_ENABLE()+__HAL_RCC_GET_FLAG()轮询
Standby 模式下所有寄存器丢失,只能靠 RTC Alarm 或 WKUP 引脚唤醒
Standby 是最低功耗模式,1.2V 域断电,SRAM 和寄存器全失,仅备份域(RTC、BKP 寄存器)和待机电路保持供电。这意味着每次唤醒都等效于复位——但又不是真正复位,因为 main() 会重新执行,且 PWR_FLAG_SB 可用于判断是否从 Standby 唤醒。
- 进入前必须启用电源控制时钟:
__HAL_RCC_PWR_CLK_ENABLE() - 调用
HAL_PWR_EnterSTANDBYMode()后程序即终止,不再返回;唤醒后从复位向量开始执行 - 若想区分上电启动和 Standby 唤醒,可在进入前设置
RTC_BKP_DR0,唤醒后读取并清零,再检查__HAL_PWR_GET_FLAG(PWR_FLAG_SB) - WKUP 引脚(如 PC13)需配置为输入浮空,并调用
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN2)(PC13 是 PIN2)
睡眠(Sleep)模式最轻量,但要注意中断优先级和 WFE/WFI 选择
Sleep 是 Cortex-M 内核级指令(WFI 或 WFE),不涉及电源控制器,只停 CPU,所有时钟和外设照常运行。HAL 封装为 HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI),但实际效果取决于你是否已配置好唤醒源。
立即学习“C++免费学习笔记(深入)”;
-
WFI(Wait For Interrupt):任意使能的中断都能唤醒;WFE(Wait For Event)则只响应事件线(如 EXTI line 上的边沿信号),更省电但配置更严 - 若使用串口空闲中断唤醒 Sleep,需确保该中断优先级高于
BASEPRI屏蔽值,否则可能“唤醒了但没响应” - HAL 默认在
HAL_PWR_EnterSLEEPMode()内部调用__WFI(),无需手写汇编;但若用 FreeRTOS,应改用HAL_PWR_EnterSLEEPMode()+osDelay()配合 tickless 模式,而非裸调WFI
停机(Stop)和待机(Standby)的关键区别在唤醒后上下文是否保留
这是最容易混淆的点:Stop 模式唤醒后,堆栈、全局变量、外设寄存器状态(除时钟相关)全部保留,程序从中断返回点继续执行;Standby 唤醒后,整个内核状态丢失,相当于硬复位,只有备份域数据可延续。
- Stop 适合短时低功耗等待(如传感器轮询间隔),唤醒快(微秒级),但功耗比 Standby 高一个数量级
- Standby 适合小时级待机(如远程终端夜间休眠),唤醒慢(毫秒级,含时钟重启、初始化),但电流可压到 1–2 μA
- 两者都要求在进入前关闭不需要的外设时钟(如
__HAL_RCC_USART1_CLK_DISABLE()),否则对应模块仍耗电 - 调试时特别注意:JTAG/SWD 在 Stop/Standby 下通常失效,需在进入前禁用调试接口(
__HAL_AFIO_REMAP_SWJ_NOJTAG()),否则可能卡住无法下载
真正麻烦的从来不是调哪个函数,而是时钟树怎么切、哪些寄存器要手动恢复、唤醒后外设是否还“认得自己”。尤其当用了 DMA、USB 或 SPI 这类依赖精确时序的外设时,Stop 唤醒后往往需要重新初始化其时钟和寄存器,不然看似醒了,实则通信静默。


















