1. 课程导入
做嵌入式项目,尤其是电池供电的设备,功耗管理永远是绕不开的坎。我记得刚入行那会儿,有个手持设备项目,电池续航死活撑不过两天。后来一查,主控大部分时间都在空转,根本没进低功耗模式。从那以后,我养成了一个习惯:设计初期就把功耗模式规划好。
STM32 的低功耗模式,说白了就三种:睡眠、停止、待机。它们不是简单的“省电程度不同”,而是内核、外设、时钟、寄存器的保留策略完全不同。选错了,要么功耗降不下来,要么唤醒后系统跑飞。
1.1 三种模式的本质区别
我们先看一张对比表,把核心差异摆出来。你想想看,这就像你晚上睡觉:
- 睡眠模式:相当于“闭目养神”——CPU 停了,但所有外设和时钟都还开着,随时能响应中断。
- 停止模式:相当于“深度睡眠”——CPU 和大部分外设时钟都关了,只有少数几个唤醒源还在工作。
- 待机模式:相当于“关机”——除了 RTC 和备份域,整个芯片几乎掉电,唤醒等于复位。
| 特性 | 睡眠模式 (Sleep) | 停止模式 (Stop) | 待机模式 (Standby) |
|---|---|---|---|
| CPU 时钟 | 关闭 | 关闭 | 关闭 |
| 外设时钟 | 保持(可配置关闭) | 大部分关闭 | 全部关闭 |
| SRAM 内容 | 保留 | 保留 | 丢失 |
| 寄存器内容 | 保留 | 保留 | 丢失(备份域除外) |
| 唤醒源 | 任何中断/事件 | 特定外设中断(EXTI、RTC等) | WKUP 引脚、RTC 闹钟、复位 |
| 唤醒时间 | ~几个 CPU 周期 | ~几个 μs | ~几个 ms |
| 典型功耗 | ~mA 级 | ~μA 级 | ~nA 级 |
核心结论:睡眠模式省电有限但响应快;待机模式最省电但代价是“失忆”;停止模式是两者的折中,也是实际项目中最常用的模式。
1.2 睡眠模式:别小看它,用对地方很香
很多人觉得睡眠模式省不了多少电,就直接跳过。其实不然。我在一个高速数据采集项目里用过它——CPU 大部分时间在等 DMA 传输完成,这时候进睡眠,DMA 传输完自动唤醒,功耗降了 40%,而且零延迟。
进入方式很简单:
// 进入睡眠模式(WFI 指令)
__WFI();
// 或者用 SEV 指令唤醒
// __WFE();
这里有个坑:默认睡眠模式是“立即睡眠”,也就是执行 WFI 后 CPU 立刻停。但如果你在中断里调了 WFI,可能会出问题。我建议用 SLEEPONEXIT 位来控制——让 CPU 在退出所有中断后再睡,更安全。
我的习惯:在 RTOS 的 Idle 任务里放 WFI,配合 SysTick 定时唤醒,既省电又不影响任务调度。裸机项目则用 WFE + 事件机制,避免中断频繁进出。
1.3 停止模式:项目里的主力军
停止模式是我用得最多的。它保留了 SRAM 和寄存器,唤醒后程序能接着跑,不像待机那样要重新初始化。但要注意:进入停止模式前,必须处理好外设时钟。
我曾经犯过一个错:进停止前没关 USART 时钟,结果唤醒后串口数据全乱了。后来我总结了一套标准流程:
- 关闭不用的外设时钟(RCC 寄存器操作)
- 配置唤醒源(EXTI 线 + RTC 或 GPIO)
- 设置电压调节器为低功耗模式(LPR 位)
- 执行 PWR 进入停止指令
// 进入停止模式示例(STM32F4)
void Enter_Stop_Mode(void)
{
// 1. 关闭不用的外设时钟
RCC->AHB1ENR &= ~(RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN);
// 2. 配置 EXTI 唤醒(PA0 上升沿)
SYSCFG->EXTICR[0] = SYSCFG_EXTICR1_EXTI0_PA;
EXTI->IMR |= EXTI_IMR_MR0;
EXTI->RTSR |= EXTI_RTSR_TR0;
// 3. 设置电压调节器为低功耗模式
PWR->CR |= PWR_CR_LPR;
// 4. 进入停止模式
PWR->CR |= PWR_CR_CWUF;
PWR->CR |= PWR_CR_CSBF;
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
}
注意:停止模式下,HSE 和 HSI 都会停掉。唤醒后系统会自动切回 HSI,如果你的代码依赖 HSE 精度(比如 USB),必须在唤醒后重新配置时钟。我吃过这个亏,USB 设备唤醒后枚举失败,查了两天才发现是时钟没切回来。
1.4 待机模式:最省电,但代价最大
待机模式是“终极省电方案”,功耗可以低到几十 nA。但代价是:SRAM 和大部分寄存器内容全部丢失,唤醒后相当于复位,程序从头执行。
什么场景用?我举两个例子:
- 电池供电的传感器节点:每分钟醒来采集一次数据,发完就睡。中间不保留任何状态。
- 产品休眠锁死恢复:系统检测到异常(比如看门狗没喂),直接进待机,让硬件彻底复位。
进入待机很简单,但有个关键点:必须设置 RTC 闹钟或 WKUP 引脚作为唤醒源,否则芯片就“长眠”了。
// 进入待机模式
void Enter_Standby_Mode(void)
{
// 使能电源管理时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 清除唤醒标志
PWR->CR |= PWR_CR_CWUF;
// 设置待机模式
PWR->CR |= PWR_CR_PDDS;
// 设置 WKUP 引脚(PA0)
PWR->CSR |= PWR_CSR_EWUP1;
// 进入深度睡眠
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
}
避坑指南:我曾经在待机唤醒后,直接访问了未初始化的全局变量——结果可想而知。解决方案是:在 SystemInit() 里判断复位原因,如果是待机唤醒,只恢复关键外设,跳过全量初始化。
1.5 如何选择?一张决策图帮你搞定
说了这么多,到底怎么选?我画了一张流程图,你照着走就行:
1.6 实际项目中的选择策略
最后,我分享几个真实项目的选型思路:
- 智能手表:平时用停止模式,保留计步数据;抬手亮屏时用睡眠模式,快速响应;关机进待机。
- 工业传感器:大部分时间待机,RTC 每 10 分钟唤醒一次,采集完数据再睡回去。
- 音频播放器:播放时用睡眠模式(DMA 传输),暂停时进停止模式,省电又不丢播放进度。
我的经验:不要一开始就追求极致功耗。先让功能跑通,然后用电流表实测,逐级降低功耗。我见过太多人一上来就进待机,结果功能调不通,回头查发现是唤醒源没配好。嗯,循序渐进才是正道。