1. 低功耗基础概念:STM32的几种低功耗模式
做嵌入式开发,尤其是电池供电的项目,低功耗是绕不开的坎。我记得刚入行那会儿,总觉得让单片机“睡觉”很简单,结果产品做出来,待机电流比预期大了十几倍……嗯,从那以后,我才开始认真研究STM32到底是怎么“睡”的。
STM32的低功耗模式,说白了就是三种:Sleep(睡眠)、Stop(停止)、Standby(待机)。它们就像三个不同深度的睡眠状态,功耗越来越低,但唤醒代价也越来越大。
核心原则:功耗越低,唤醒后需要重新初始化的外设就越多。选型就是在这两者之间找平衡。
1.1 Sleep Mode(睡眠模式)
这是最浅的睡眠。CPU核心时钟停了,但所有外设(比如USART、SPI、定时器)都还在跑。你想想看,就像你躺在床上闭着眼,但耳朵还在听周围的声音。
- 电流消耗: 典型值在mA级别,具体取决于外设开启数量
- 唤醒源: 任何中断或事件都能唤醒,包括外设中断、外部中断
- 唤醒时间: 极快,几乎无延迟(几个CPU周期)
- 唤醒后状态: 代码从唤醒中断处继续执行,所有寄存器、SRAM内容完全保留
我在项目中用过Sleep模式做“空闲等待”。比如等待串口接收完一帧数据,CPU没必要空转,进Sleep等中断就行。但说实话,这种模式省电效果有限,适合对功耗不敏感、但要求快速响应的场景。
个人习惯: 如果项目里外设还在频繁工作(比如ADC连续采样),我一般不会用Sleep,因为外设耗电可能比CPU还大。这时候Sleep的意义不大。
1.2 Stop Mode(停止模式)
Stop模式就深了一层。CPU停了,所有外设的时钟也停了。但SRAM和寄存器内容还在,因为主电源没断。这就像你关掉房间的灯和空调,但冰箱还在通电。
这里有个关键点:1.2V域供电还在。所以RTC(实时时钟)和备份寄存器可以继续工作,前提是你配置了LSE或LSI作为RTC时钟源。
| 特性 | Stop模式说明 |
|---|---|
| CPU时钟 | 停止 |
| 外设时钟 | 停止(可配置部分外设保持) |
| SRAM/寄存器 | 保留 |
| 典型电流 | 几十µA到几百µA(取决于RTC是否开启) |
| 唤醒源 | EXTI中断、RTC闹钟、RTC唤醒事件、I2C/USART等特定外设 |
| 唤醒时间 | 几µs到几十µs(需要恢复HSI/HSE振荡器) |
我曾经在一个手持设备上用Stop模式配合RTC闹钟,每秒钟唤醒一次采集传感器数据。电流控制在50µA左右,两节AA电池撑了半年多。嗯,这里要注意:唤醒后系统时钟默认切回HSI,如果你原来用的是HSE+PLL,需要重新配置时钟树。
避坑指南: 我曾经在Stop模式下忘了关闭调试接口(SWD),结果电流死活降不下来。调试时SWD会阻止MCU进入深度睡眠。解决办法是在进入Stop前调用 DBGMCU->CR &= ~(DBGMCU_CR_DBG_STOP)。
1.3 Standby Mode(待机模式)
这是最深度的睡眠。说白了,就是几乎完全断电。1.2V域大部分关闭,SRAM和寄存器内容全部丢失。只有备份域(RTC、备份寄存器、低速振荡器)和唤醒逻辑还在工作。
你想想看,这就像你出门旅行,把家里总闸拉了,只留了一个摄像头(RTC)和门铃(唤醒引脚)。
- 电流消耗: 典型值在1µA以下(关闭RTC时甚至几百nA)
- 唤醒源: RTC闹钟、RTC唤醒事件、WKUP引脚上升沿、NRST复位、BOR欠压复位
- 唤醒时间: 最长,需要芯片重新上电启动(ms级别)
- 唤醒后状态: 相当于复位,代码从main函数开始执行,SRAM内容丢失
Standby模式是我做超低功耗产品的首选。比如电池供电的温湿度传感器,每10分钟采集一次数据,其余时间进Standby。电流可以做到1.5µA左右,一节CR2032纽扣电池能用两年。
关键区别: Standby唤醒后,程序是从头开始跑的。你需要通过检查 PWR->CSR 寄存器的 WUF 标志位来判断是上电复位还是Standby唤醒,然后决定是否恢复现场。
1.4 三种模式对比与选型依据
我把三种模式的核心差异整理成了一张表,方便你快速对比:
| 对比项 | Sleep | Stop | Standby |
|---|---|---|---|
| CPU状态 | 停止 | 停止 | 断电 |
| 外设状态 | 运行 | 停止(可配置) | 断电 |
| SRAM/寄存器 | 保留 | 保留 | 丢失 |
| RTC | 可选 | 可选 | 可选(备份域) |
| 典型电流 | mA级 | 几十µA | 1µA以下 |
| 唤醒时间 | ns级 | µs级 | ms级 |
| 唤醒源数量 | 多(所有中断) | 中等(EXTI+特定外设) | 少(RTC+WKUP引脚) |
| 代码恢复方式 | 中断返回 | 中断返回 | 从头执行 |
选型其实不难,我一般按这个思路来:
- 需要快速响应、外设不能停? 用Sleep。比如等待串口数据、按键消抖。
- 需要中等功耗、保留现场、定时唤醒? 用Stop。比如传感器周期性采集、低功耗蓝牙广播。
- 需要极致省电、唤醒频率很低? 用Standby。比如环境监测、资产追踪、智能表计。
我的经验: 如果项目里需要保留少量数据(比如校准参数、设备ID),但又想用Standby,可以把数据存到备份寄存器里。备份域在Standby模式下不掉电,唤醒后直接读就行,不用重新初始化。
1.5 知识结构总览
下面这张图把三种模式的关系和选型逻辑串起来了,你可以对照着理解:
1.6 进入与退出模式的基本代码框架
最后,我贴三段最基础的进入代码,让你有个直观印象。具体细节后面章节会展开。
进入Sleep模式:
// 最简单的Sleep,执行完WFI就睡,任意中断唤醒
__WFI(); // Wait For Interrupt
进入Stop模式:
// 配置PWR寄存器,进入Stop模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后需要重新配置系统时钟
SystemClock_Config();
进入Standby模式:
// 清除唤醒标志,使能WKUP引脚(如果使用),然后进入Standby
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnterSTANDBYMode();
// 代码不会执行到这里,唤醒后从main重新开始
注意: 进入Standby前,记得把不需要的GPIO全部配置成模拟输入或上拉/下拉,避免引脚浮空导致漏电流。我见过有人没做这一步,Standby电流直接飙到20µA。