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。