1. 什么是低功耗?
低功耗模式:MCU 暂停部分时钟/外设,降低电流消耗,等待外部事件(中断/复位/唤醒)再恢复运行。
应用场景:电池供电设备(传感器、手持设备、IoT 节点)——延长续航。
2. STM32 电源系统结构(F103)
主电源 VDD:核心供电。
备份电源 VBAT:RTC/备份寄存器在主电源掉电时保持。
电源管理单元 PWR:支持三种低功耗模式:
Sleep:只关 CPU,外设和时钟继续。
Stop:停止主时钟 HSI/HSE/PLL,SRAM/寄存器保持,低功耗调节器供电。
Standby:几乎全关,仅保留 VBAT 域(RTC/BKP)。功耗最低。
3. 三种低功耗模式原理与作用
模式 | 原理 | 功耗 | 数据保持 | 唤醒方式 | 作用场景 |
---|---|---|---|---|---|
Sleep | CPU 内核暂停,外设/时钟继续运行 | 低 | 全部保持 | 任意中断/事件 | CPU 空闲但外设还工作 |
Stop | 主时钟关闭,PLL/HSE/HSI 停止,SRAM+寄存器保持,低功耗调节器供电 | 更低 | SRAM/寄存器保持 | 外部中断、RTC 唤醒等 | 需保留数据但降功耗 |
Standby | 全部掉电,SRAM/寄存器清空,仅 VBAT 域工作(RTC/BKP 可保留) | 最低 | 仅 VBAT 域(RTC/备份寄存器) | 唤醒引脚、RTC 闹钟、复位 | 超低功耗,数据不重要 |
4. 驱动实现步骤(HAL 库示例)
4.1 Sleep 模式
#include "stm32f1xx_hal.h"int main(void)
{HAL_Init(); // 初始化 HALSystemClock_Config(); // 配置系统时钟MX_GPIO_Init(); // GPIO 初始化while (1){// 进入 Sleep 模式:CPU 停止,外设继续,等待中断唤醒HAL_SuspendTick(); // 挂起 SysTick,避免其唤醒HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);HAL_ResumeTick(); // 恢复 SysTick}
}
参数解释
PWR_MAINREGULATOR_ON
:保持主调节器工作(供电正常)。PWR_SLEEPENTRY_WFI
:进入睡眠入口指令,WFI=Wait For Interrupt。
其他:PWR_SLEEPENTRY_WFE
:Wait For Event。
4.2 Stop 模式
#include "stm32f1xx_hal.h"int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();while (1){// 进入 Stop 模式:主时钟停,SRAM/寄存器保持HAL_SuspendTick(); // 停止 SysTick,避免唤醒HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);HAL_ResumeTick(); // 唤醒后恢复 SysTick// 唤醒后需要重新配置系统时钟SystemClock_Config();}
}
参数解释
PWR_LOWPOWERREGULATOR_ON
:低功耗调节器供电(功耗更低)。PWR_MAINREGULATOR_ON
:主调节器供电。PWR_STOPENTRY_WFI
:WFI 指令进入。PWR_STOPENTRY_WFE
:WFE 指令进入。
4.3 Standby 模式
#include "stm32f1xx_hal.h"int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();while (1){// 清除 Wakeup 标志__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);// 进入 Standby 模式:几乎全掉电,仅 VBAT 域保持HAL_PWR_EnterSTANDBYMode();// 唤醒后,系统相当于复位,从 main 重新执行}
}
特点
无参数:一旦进入 Standby,只有复位/RTC Alarm/Wakeup 引脚可唤醒。
唤醒后程序 从复位重新执行,非继续原位置。
5. 库函数与寄存器说明
5.1 HAL 库函数
HAL_PWR_EnterSLEEPMode(regulator, entry)
HAL_PWR_EnterSTOPMode(regulator, entry)
HAL_PWR_EnterSTANDBYMode()
HAL_SuspendTick()
/HAL_ResumeTick()
:暂停/恢复 SysTick。
5.2 StdPeriph 库(老版本)
PWR_EnterSleepMode(PWR_Regulator, PWR_SLEEPEntry)
PWR_EnterSTOPMode(PWR_Regulator, PWR_STOPEntry)
PWR_EnterSTANDBYMode()
5.3 关键寄存器
PWR_CR:控制寄存器
PDDS
:置位进入 StandbyLPDS
:Stop 模式选择低功耗调节器CWUF
:清 Wakeup 标志
PWR_CSR:状态寄存器
WUF
:Wakeup 标志
SCB->SCR:系统控制寄存器
SLEEPDEEP
:=1 表示进入深度睡眠(Stop/Standby),=0 为 Sleep。
6. 总结
Sleep:只关 CPU,最轻量,唤醒最快。
Stop:关时钟,保留 SRAM/寄存器,功耗中等。唤醒后要重新配置时钟。
Standby:几乎全关,仅 RTC/BKP 保持,最低功耗。唤醒等于复位。
库函数的
.
、=
后参数主要用于 选择调节器模式(主/低功耗) 和 进入方式(WFI中断/WFE事件)。
实验:
lpwr.c:
#include "lpwr.h"
#include "led.h"void lpwr_init(void)
{GPIO_InitTypeDef gpio_initstruct;//打开时钟__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOB时钟//调用GPIO初始化函数gpio_initstruct.Pin = GPIO_PIN_0; // 两个LED对应的引脚gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 推挽输出gpio_initstruct.Pull = GPIO_PULLUP; // 上拉gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速HAL_GPIO_Init(GPIOA, &gpio_initstruct);HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 2);HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}void EXTI0_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}void lpwr_enter_sleep(void)
{HAL_SuspendTick();HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
}void lpwr_enter_stop(void)
{//暂停滴答定时器HAL_SuspendTick();//点亮LED2,代表进入停机模式led2_on();//进入停机模式HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);//熄灭LED2,代表退出停机模式led2_off();//从停机模式唤醒,需要重新配置系统时钟(不这样,//灯会闪的很慢,为什么?只有8M)stm32_clock_init(RCC_PLL_MUL9);//没有这一行灯会闪的很慢
}void lpwr_enter_standby(void)
{//使能电源时钟(关闭电压调节器)__HAL_RCC_PWR_CLK_ENABLE();//使能WAKEUP引脚的唤醒功能HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);//清除唤醒标记,否则将持续保持唤醒状态__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);//进入待机模式HAL_PWR_EnterSTANDBYMode();//测试:看看代码会不会运行到下面?//代码复位led2_on();
}
main.c:
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "lpwr.h"
#include "key.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */led_init(); /* 初始化LED灯 */uart1_init(115200);lpwr_init();key_init();printf("hello world!\r\n");uint8_t i = 0;while(1){ if(key_scan() == 2){//lpwr_enter_sleep();//lpwr_enter_stop();lpwr_enter_standby();}if((i % 20) == 0)led1_toggle();i++;delay_ms(10);}
}
一、lpwr.c
—— 低功耗与唤醒
#include "lpwr.h" // 本模块头文件:对外暴露 lpwr_* 接口
#include "led.h" // LED 控制(用于进入/退出低功耗的可视化指示)
1) 低功耗唤醒输入初始化(外部中断)
void lpwr_init(void)
{GPIO_InitTypeDef gpio_initstruct; // HAL 的 GPIO 初始化结构体// 打开与唤醒引脚所在端口相应的时钟(此处使用 PA0 作为唤醒/中断引脚)__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟(← 这里是 A,不是 B)// 若是 F1 系列,使用外部中断还需要 AFIO 时钟(HAL 会在内部设置 EXTI,但保险起见可显式打开)__HAL_RCC_AFIO_CLK_ENABLE(); // 使能 AFIO(可选,但推荐)// 配置 PA0 为上升沿外部中断输入(常接按键,按下产生上升沿唤醒)gpio_initstruct.Pin = GPIO_PIN_0; // 选择 PA0gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 外部中断模式:上升沿触发(IT = Interrupt)gpio_initstruct.Pull = GPIO_PULLUP; // 上拉(未按下时稳定为高/低需结合硬件,常用上拉+下拉按键)gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 速度等级(对输入/中断不关键,可保持默认或任意)HAL_GPIO_Init(GPIOA, &gpio_initstruct); // 调用 HAL 完成 GPIO/EXTI 配置// 配置并使能 EXTI0 的 NVIC 中断(PA0→EXTI0)HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 2); // 抢占优先级=2,子优先级=2(数值越小优先级越高)HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能 EXTI0 中断线(允许进入中断/唤醒)
}
说明:
PA0 一脚有两种“唤醒”用法
EXTI0:从 Sleep / Stop 唤醒(任意可使能的中断都能唤醒);
WKUP(Wakeup Pin1):从 Standby 唤醒(硬件专用唤醒脚,与 EXTI 概念不同)。
F1 用外部中断建议打开
AFIO
时钟;HAL_GPIO_Init()
会帮你写 EXTI/AFIO 寄存器。
2) EXTI0 中断服务函数
void EXTI0_IRQHandler(void) // EXTI0 的中断服务程序(由启动文件向量表跳转到这里)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 交给 HAL:清 pending 标志、再回调 HAL_GPIO_EXTI_Callback()
}
如需在中断发生时做事情(比如点灯/打印),可实现:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {if (GPIO_Pin == GPIO_PIN_0) {// …用户代码(但注意:中断里别 printf,尽量只做轻量操作)}
}
3) 进入 Sleep(睡眠)模式
void lpwr_enter_sleep(void)
{HAL_SuspendTick(); // 挂起 SysTick(否则 SysTick 中断会很快把你唤醒)HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, // regulator 参数:睡眠时主调节器一直供电PWR_SLEEPENTRY_WFI // entry 参数:通过 WFI 指令进入(Wait For Interrupt));// 从 Sleep 唤醒后会继续执行到这里(代码流不中断)HAL_ResumeTick(); // 恢复 SysTick(让系统节拍恢复)
}
参数释义(Sleep)
PWR_MAINREGULATOR_ON
:睡眠下保持主调节器(正常电压)→ 唤醒更快。PWR_SLEEPENTRY_WFI
:通过 WFI 进入睡眠(常用);可选PWR_SLEEPENTRY_WFE
(WFE 事件进入)。
Sleep 模式下:CPU 停止,但 外设/总线时钟继续运行;任意可用中断到来即刻唤醒并从下一条指令继续。
4) 进入 Stop(停机)模式
void lpwr_enter_stop(void)
{HAL_SuspendTick(); // 暂停 SysTick(避免 Tick 把 Stop 唤醒)led2_on(); // 可视化提示:准备进入 StopHAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, // regulator:主调节器供电(也可用 PWR_LOWPOWERREGULATOR_ON 更省电)PWR_STOPENTRY_WFI // entry:通过 WFI 指令进入 Stop(设置 SLEEPDEEP 位));led2_off(); // 运行到这里说明已经“从 Stop 唤醒”了// 重要:从 Stop 唤醒后,系统时钟源被切回 HSI(8MHz),PLL/HSE 关闭// 若不重配,整个系统按 8MHz 运行,你会看到 LED 闪烁变慢、串口波特率异常等现象stm32_clock_init(RCC_PLL_MUL9); // 重新配置系统时钟到 72MHz(HSE→PLL×9)
}
参数释义(Stop)
PWR_MAINREGULATOR_ON
:Stop 下保持主调节器(功耗略高,唤醒稍快);PWR_LOWPOWERREGULATOR_ON
:低功耗调节器(功耗更低,但唤醒更慢)。PWR_STOPENTRY_WFI
/PWR_STOPENTRY_WFE
:WFI/WFE 进入 Stop。
Stop 模式:HSI/HSE/PLL 都停,SRAM/寄存器保持;唤醒后需重新配置系统时钟到期望频率。
5) 进入 Standby(待机)模式
void lpwr_enter_standby(void)
{__HAL_RCC_PWR_CLK_ENABLE(); // 使能 PWR 时钟(对 PWR 寄存器访问必须)HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); // 使能唤醒管脚:WakeUp Pin1(F103 对应 PA0)// Standby 下只认“WakeUp Pin/RTC/复位”等专用唤醒源__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); // 清除“唤醒标志”(否则会立刻退出 Standby)HAL_PWR_EnterSTANDBYMode(); // 进入待机(最低功耗):SRAM/寄存器清空,程序停止// ——— 注意:从 Standby 唤醒相当于“复位启动” ———// 理论上永远到不了这里(进入 Standby 后代码不再往下走;唤醒后 MCU 复位,从 main 重新执行)led2_on(); // 若看到这行被执行,说明并没有真正进入 Standby
}
Standby 只能被:WKUP 引脚上升沿 / RTC 闹钟 / NRST 复位 / 上电 等唤醒;唤醒后相当于上电复位,不是“接着原地运行”。
二、main.c
—— 测试主循环
#include "sys.h" // 系统时钟配置接口(stm32_clock_init)
#include "delay.h" // delay_ms/delay_us
#include "led.h" // LED1/LED2 控制(指示状态)
#include "uart1.h" // 串口调试输出
#include "lpwr.h" // 低功耗接口:初始化/进入各模式
#include "key.h" // 按键扫描(返回 1/2/... 表示不同按键)
int main(void)
{HAL_Init(); /* 初始化 HAL:外设复位、SysTick、NVIC 优先级组等 */stm32_clock_init(RCC_PLL_MUL9); /* 系统时钟 72MHz(HSE→PLL×9) */led_init(); /* LED 引脚初始化(LED1/LED2) */uart1_init(115200); /* 串口1,115200 波特率 */lpwr_init(); /* 低功耗相关引脚初始化:PA0 上升沿 EXTI0 可用于唤醒 */key_init(); /* 按键初始化(内部扫描/消抖) */printf("hello world!\r\n"); /* 上电提示 */uint8_t i = 0; /* 简单的“节拍”计数器 */while (1){if (key_scan() == 2) /* 如果按键2被按下(你的 key_scan 约定) */{//lpwr_enter_sleep(); /* 进入 Sleep:任意中断(比如再按 PA0)即可唤醒 *///lpwr_enter_stop(); /* 进入 Stop:PA0 上升沿(EXTI0)等可唤醒,醒来后会重设时钟 */lpwr_enter_standby(); /* 进入 Standby:PA0(WKUP) 上升沿/RTC/复位可唤醒,醒来相当于复位 */}// 每 200ms 翻转一下 LED1(10ms × 20)if ((i % 20) == 0)led1_toggle();i++;delay_ms(10);}
}
三、三种模式的实验现象与注意点
1) Sleep(睡眠)
现象
进入 Sleep 后,CPU 不执行,但外设时钟仍跑;
只要产生任意可用中断(如:按 PA0 触发 EXTI0),马上唤醒;
唤醒后继续执行
HAL_PWR_EnterSLEEPMode()
之后的代码(非复位);因为我们 SuspendTick(),若不产生中断,LED 翻转会暂停;中断唤醒并 ResumeTick() 后继续以原节奏闪烁。
注意
第二个参数要用
PWR_SLEEPENTRY_WFI
/PWR_SLEEPENTRY_WFE
。Sleep 下 串口、定时器、外设 仍在工作(除非你手动关时钟)。
若不
HAL_SuspendTick()
,SysTick 会不断产生中断从而“睡不安稳”。
2) Stop(停机)
现象
进入 Stop,HSI/HSE/PLL 停止,SRAM/寄存器保持;
通过 EXTI0(PA0)上升沿 或 RTC 闹钟等唤醒;
唤醒后系统时钟退回 HSI=8MHz,如果不
stm32_clock_init()
重设到 72MHz:LED 闪烁会明显变慢(定时用到的时钟也慢了);
串口波特率不对(115200 需要 72MHz 基准)→ 打印乱码或无输出。
我们在函数里唤醒后立即
stm32_clock_init()
,因此 LED/串口都恢复正常速度。
注意
PWR_MAINREGULATOR_ON
vsPWR_LOWPOWERREGULATOR_ON
:后者更省电、唤醒略慢。唤醒后务必重新配置系统时钟(或至少切回你期望的 HSE/PLL)。
EXTI 线要配置好(
lpwr_init()
已设定 PA0 上升沿 EXTI0)。
3) Standby(待机)
现象
进入 Standby,几乎全掉电:SRAM/寄存器丢失,仅 VBAT 域(RTC/BKP) 仍在;
唤醒源:WKUP 引脚 PA0 上升沿 / RTC 闹钟 / NRST/上电;
唤醒后相当于复位,程序从
main()
开始,你会再次看到hello world!
;函数
HAL_PWR_EnterSTANDBYMode()
后面的led2_on()
不会被执行(除非没成功进入 Standby)。
注意
进入前要
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU)
清唤醒标志,否则会立即“假唤醒”。Standby 唤醒不是中断返回,而是系统复位。如果要判断是否来自 Standby,可读取相应标志位(如
PWR_FLAG_SB
)。用 WKUP 引脚 唤醒时,确保硬件按 上升沿 满足门限(典型需要外部上拉/按键接地等正确电路)。
四、可选参数的作用
1) GPIO(GPIO_InitTypeDef
)
Pin = GPIO_PIN_0
…GPIO_PIN_15
:选择端口上的哪一脚。Mode =
GPIO_MODE_INPUT
:普通输入GPIO_MODE_OUTPUT_PP
:推挽输出GPIO_MODE_OUTPUT_OD
:开漏输出GPIO_MODE_AF_PP/AF_OD
:复用功能GPIO_MODE_IT_RISING / IT_FALLING / IT_RISING_FALLING
:外部中断(沿触发)GPIO_MODE_EVT_*
:外部事件(WFE 使用)
Pull = GPIO_NOPULL / GPIO_PULLUP / GPIO_PULLDOWN
:无/上拉/下拉。Speed = GPIO_SPEED_FREQ_LOW / MEDIUM / HIGH
:IO 翻转速率能力(对输入无实质影响)。
2) NVIC
HAL_NVIC_SetPriority(IRQn, preempt, sub)
:设置中断优先级;数字越小优先级越高。HAL_NVIC_EnableIRQ(IRQn)
:使能中断。
3) 低功耗入口(PWR)
Sleep:
HAL_PWR_EnterSLEEPMode(regulator, entry)
regulator = PWR_MAINREGULATOR_ON
entry = PWR_SLEEPENTRY_WFI / PWR_SLEEPENTRY_WFE
Stop:
HAL_PWR_EnterSTOPMode(regulator, entry)
regulator = PWR_MAINREGULATOR_ON / PWR_LOWPOWERREGULATOR_ON
entry = PWR_STOPENTRY_WFI / PWR_STOPENTRY_WFE
Standby:
HAL_PWR_EnterSTANDBYMode()
(无参数)
辅助:
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1)
:开启 Standby 唤醒脚(F103:PA0)。__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU)
:清唤醒标志。HAL_SuspendTick()
/HAL_ResumeTick()
:暂停/恢复 SysTick。