tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(扩展篇)V1.0.0 — 第1章:电源控制 + 配套代码15~20" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:STM32 有多种低功耗模式就像你家里的电器——运行模式是所有电器全开(费电),睡眠模式是关灯但空调继续开(CPU 停,外设继续),停止模式是只留冰箱(维持寄存器),待机模式是拉总闸(只剩下 Vbat 供 RTC)。越省电的模式唤醒越慢。
STM32 的电源系统分为多个供电区域:
| 区域 | 电压 | 供电对象 | 特点 |
|---|---|---|---|
| VDD 区域 | 3.3V | I/O 电路、待机电路、电压调节器 | 数字部分主电源 |
| VDDA 区域 | 3.3V | ADC、温度传感器、复位模块、PLL | 模拟部分独立电源(去耦更好) |
| 1.8V 区域 | 1.8V | CPU 内核、SRAM、内部数字外设 | 由电压调节器从 3.3V 降压得到 |
| 备份区域 (VBAT) | 3.3V/电池 | RTC、LSE 振荡器、BKP 寄存器、PC13~15 | VDD 断电时由 VBAT 维持 |
参考:参考手册 §5(电源控制 PWR 寄存器)、原理图电源部分 参考:AMS1117 数据手册(板上 5V→3.3V LDO)
电压调节器将 VDD(3.3V) 转换为内核所需的 1.8V,有 3 种工作模式:
| 调节器模式 | 输出状态 | 对应的 MCU 模式 |
|---|---|---|
| 运转模式 (MR) | 正常输出 1.8V | 运行/睡眠 |
| 停止模式 (LPR) | 低功耗输出 1.8V(电流小) | 停止 |
| 待机模式 | 关闭(无输出) | 待机(内核掉电) |
内部复位电路监控 VDD/VDDA 电压:
电压升高到 VPOR(~1.8V) → 解除复位 → MCU 启动
电压下降到 VPDR(~1.5V) → 进入复位 → MCU 停止
两者之间有 40mV 的迟滞,防止电压在阈值附近抖动时频繁复位。
参考:参考手册 §5.2(上电/掉电复位特性)
PVD(Programmable Voltage Detector)监控 VDD 电压,当电压低于可编程阈值时,可以产生 中断(不是复位)。
| PVD 阈值选择 (PLS[2:0]) | 阈值电压 |
|---|---|
| 000 | 2.2V |
| 001 | 2.3V |
| 010 | 2.4V |
| 011 | 2.5V |
| 100 | 2.6V |
| 101 | 2.7V |
| 110 | 2.8V |
| 111 | 2.9V |
// PVD 配置示例:阈值 2.7V,下降沿中断
PWR->CR |= PWR_CR_PLS_2 | PWR_CR_PLS_1; // PLS[2:0]=110 → 2.8V
PWR->CR |= PWR_CR_PVDE; // PVDE=1 使能PVD
// PVD 中断: EXTI16 线
EXTI->RTSR &= ~EXTI_RTSR_TR16; // 下降沿触发(电压下降)
EXTI->FTSR |= EXTI_FTSR_TR16;
EXTI->IMR |= EXTI_IMR_MR16;
NVIC_EnableIRQ(PVD_IRQn);
// PVD 中断处理
void PVD_IRQHandler(void)
{
if (EXTI->PR & EXTI_PR_PR16)
{
// 电压低于 2.8V → 紧急保存数据到备份寄存器
SaveEmergencyData();
EXTI->PR |= EXTI_PR_PR16;
}
}
| 特性 | 睡眠 (Sleep) | 停止 (Stop) | 待机 (Standby) |
|---|---|---|---|
| CPU 内核 | 停止 | 停止 | 断电 |
| SRAM/寄存器 | 保持 | 保持 | 丢失 |
| 备份域 | 保持 | 保持 | 保持 |
| 电压调节器 | 运转模式 | 低功耗模式 | 关闭 |
| 典型功耗 | ~10mA | ~30μA | ~2μA |
| 唤醒源 | 任意中断/事件 | EXTI 中断 | RTC 闹钟、WKUP 引脚、NRST |
| 唤醒时间 | ~0.9μs | ~5μs | ~50ms(需重新初始化) |
CPU 停止,外设继续工作。任何中断或事件都能唤醒。
进入方式:
// 执行 WFI 指令(Wait For Interrupt)
__WFI(); // 等待中断唤醒
// 或执行 WFE 指令(Wait For Event)
__WFE(); // 等待事件唤醒
SLEEP-NOW vs SLEEP-ON-EXIT:
SCB->SCR &= ~SCB_SCR_SLEEPONEXIT_Msk; // SLEEPDEEP=0, SLEEPONEXIT=0
// SLEEP-NOW: 执行 WFI 立即进睡眠
// SLEEP-ON-EXIT: 进中断→退出中断时自动睡眠
SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk; // SLEEPONEXIT=1
// SLEEP-ON-EXIT: 执行 WFI → 最低优先级 ISR 退出后进睡眠
CPU + 所有外设时钟都停止,但 SRAM 和寄存器内容保留。电压调节器切到低功耗模式。
配置与进入:
// 进入停止模式的函数
void enter_stop_mode(void)
{
// 1. 设置深度睡眠模式
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 2. 开启PWR模块时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 3. 设置PDDS = 0,表示停止模式
PWR->CR &= ~PWR_CR_PDDS;
// 4. 设置电压调节器的工作模式:低功耗模式
PWR->CR |= PWR_CR_LPDS;
// 5. 使用WFI指令,进入停止模式
__WFI();
}
唤醒后:触发 EXTI 中断后,系统从停止模式恢复,执行 ISR,恢复运行主程序。
最省电模式。1.8V 内核域完全断电(SRAM 和寄存器内容丢失),仅备份域(VBAT)和待机电路保持。
配置与进入:
// 进入待机模式的函数
void enter_standby_mode(void)
{
// 1. 设置深度睡眠模式
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 2. 设置PDDS = 1,表示待机模式
PWR->CR |= PWR_CR_PDDS;
// 3. 使能 PA0 的唤醒功能
PWR->CSR |= PWR_CSR_EWUP;
// 4. 使用WFI指令,进入待机模式
__WFI();
}
// 唤醒后相当于系统复位!从 startup 重新执行。
唤醒后检测:
// 开启PWR模块时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 判断标志位来判断是否从待机模式唤醒
if (PWR->CSR & PWR_CSR_SBF)
{
printf("从待机模式唤醒:\n");
PWR->CR |= PWR_CR_CSBF;
}
if (PWR->CSR & PWR_CSR_WUF)
{
printf("产生了唤醒事件:\n");
PWR->CR |= PWR_CR_CWUF;
}
| 实验 | 寄存器版 | HAL 库版 |
|---|---|---|
| 睡眠模式 | stm32/15_lowpower_sleep_register |
stm32/16_lowpower_sleep_hal |
| 停止模式 | stm32/17_lowpower_stop_register |
stm32/18_lowpower_stop_hal |
| 待机模式 | stm32/19_lowpower_standby_register |
stm32/20_lowpower_standby_hal |
文件:stm32/17_lowpower_stop_register/User/main.c(停止模式,进入停止模式后通过 KEY 唤醒)
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
void enter_stop_mode(void);
void get_clock_freq(uint32_t *sys_clock, uint32_t *AHB_clock, uint32_t *APB2_clock, uint32_t *APB1_clock);
int main(void)
{
// 初始化
USART_Init();
LED_Init();
KEY_Init();
printf("尚硅谷低功耗实验:停止模式...\n");
// 1. 点亮LED灯,延时2s,模拟正常运行程序
LED_On(LED_2);
Delay_s(2);
while (1)
{
// 2. 进入停止模式
printf("正常运行执行完毕,3s后进入停止模式...\n");
Delay_s(3);
printf("进入停止模式...\n");
Delay_ms(1); // 稍作延时确保串口信息成功发送
enter_stop_mode();
uint32_t sys_clock, ahb_clock, apb1_clock, apb2_clock;
get_clock_freq(&sys_clock, &ahb_clock, &apb2_clock, &apb1_clock);
// 3. 重新进行系统初始化,配置系统时钟
SystemInit();
printf("唤醒前的时钟频率:\nsys_clock = %d, ahb_clock = %d, apb1_clock = %d, apb2_clock = %d\n\n",
sys_clock, ahb_clock, apb1_clock, apb2_clock);
get_clock_freq(&sys_clock, &ahb_clock, &apb2_clock, &apb1_clock);
printf("唤醒后的时钟频率:\nsys_clock = %d, ahb_clock = %d, apb1_clock = %d, apb2_clock = %d\n\n",
sys_clock, ahb_clock, apb1_clock, apb2_clock);
// 4. 以下代码只有在唤醒之后才会执行
printf("从停止模式唤醒...\n");
Delay_s(2);
}
}
// 进入深度睡眠模式的函数
void enter_stop_mode(void)
{
// 1. 设置深度睡眠模式
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 2. 开启PWR模块时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 3. 设置PDDS = 0,表示停止模式
PWR->CR &= ~PWR_CR_PDDS;
// 4. 设置电压调节器的工作模式:低功耗模式
PWR->CR |= PWR_CR_LPDS;
// 5. 使用WFI指令,进入停止模式
__WFI();
}
// 自定义函数,查询各时钟频率(系统时钟sysclock、AHB、APB2、APB1)
void get_clock_freq(uint32_t *sys_clock, uint32_t *AHB_clock, uint32_t *APB2_clock, uint32_t *APB1_clock)
{
// 1. 获取时钟源
uint32_t clock_src = RCC->CFGR & RCC_CFGR_SWS;
// 2. 根据时钟源获取系统时钟频率
if (clock_src == RCC_CFGR_SWS_HSE)
{
*sys_clock = HSE_VALUE;
}
else if (clock_src == RCC_CFGR_SWS_HSI)
{
*sys_clock = HSI_VALUE;
}
else if (clock_src == RCC_CFGR_SWS_PLL)
{
// 取倍频系数值
uint32_t mul = ((RCC->CFGR & RCC_CFGR_PLLMULL) >> 18) + 2;
*sys_clock = HSE_VALUE * mul;
}
// 3. 根据分频系数确定最终时钟频率
// 3.1 AHB
// 3.1.1 获取分频系数
uint32_t hpre = (RCC->CFGR & RCC_CFGR_HPRE) >> 4;
// 3.1.2 检查最高位来判断是否有分频
if (hpre & 0x8)
{
// 有分频,取低三位
uint32_t n = hpre & 0x7;
// 与4比较,选则n+1位或n+2位
if (n < 4)
{
*AHB_clock = *sys_clock >> (n+1);
}
else
{
*AHB_clock = *sys_clock >> (n+2);
}
}
else
{
// 没有分频
*AHB_clock = *sys_clock;
}
// 3.2 APB1
// 3.2.1 获取分频系数
uint32_t ppre1 = (RCC->CFGR & RCC_CFGR_PPRE1) >> 8;
// 3.2.2 检查最高位来判断是否有分频
if (ppre1 & 0x4)
{
*APB1_clock = *AHB_clock >> ((ppre1 & 0x3) + 1);
}
else
{
// 没有分频
*APB1_clock = *AHB_clock;
}
// 3.3 APB2
// 3.3.1 获取分频系数
uint32_t ppre2 = (RCC->CFGR & RCC_CFGR_PPRE2) >> 11;
// 3.3.2 检查最高位来判断是否有分频
if (ppre2 & 0x4)
{
*APB2_clock = *AHB_clock >> ((ppre2 & 0x3) + 1);
}
else
{
// 没有分频
*APB2_clock = *AHB_clock;
}
}
文件:stm32/19_lowpower_standby_register/User/main.c(待机模式,通过 PA0 WKUP 引脚唤醒)
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
void enter_standby_mode(void);
int main(void)
{
// 初始化
USART_Init();
LED_Init();
KEY_Init();
// 开启PWR模块时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 判断标志位来判断是否从待机模式唤醒
if (PWR->CSR & PWR_CSR_SBF)
{
printf("从待机模式唤醒:\n");
PWR->CR |= PWR_CR_CSBF;
}
if (PWR->CSR & PWR_CSR_WUF)
{
printf("产生了唤醒事件:\n");
PWR->CR |= PWR_CR_CWUF;
}
printf("尚硅谷低功耗实验:待机模式...\n");
// 1. 点亮LED灯,延时2s,模拟正常运行程序
LED_On(LED_2);
Delay_s(2);
while (1)
{
// 2. 进入待机模式
printf("正常运行执行完毕,3s后进入待机模式...\n");
Delay_s(3);
printf("进入待机模式,按下KEY唤醒...\n");
Delay_ms(1); // 稍作延时确保串口信息成功发送
enter_standby_mode();
// 4. 以下代码不会执行
printf("从待机模式唤醒...\n");
Delay_s(2);
}
}
// 进入待机模式的函数
void enter_standby_mode(void)
{
// 1. 设置深度睡眠模式
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 2. 设置PDDS = 1,表示待机模式
PWR->CR |= PWR_CR_PDDS;
// 3. 使能 PA0 的唤醒功能
PWR->CSR |= PWR_CSR_EWUP;
// 4. 使用WFI指令,进入待机模式
__WFI();
}
| 操作 | 寄存器操作 | HAL 库函数 |
|---|---|---|
| 进入睡眠 | __WFI()(或 __WFE) |
HAL_PWR_EnterSLEEPMode() |
| 进入停止 | PWR.LPDS=1 + SCR.SLEEPDEEP=1 + __WFI() |
HAL_PWR_EnterSTOPMode() |
| 进入待机 | SCR.SLEEPDEEP=1 + PWR.PDDS=1 + __WFI() |
HAL_PWR_EnterSTANDBYMode() |
| 配置 PVD | PWR.PLS + PWR.PVDE + EXTI16 |
HAL_PWR_ConfigPVD() |
| 清除唤醒标志 | PWR.CSR.WUF=1 |
__HAL_PWR_CLEAR_FLAG() |
| 检测待机唤醒 | PWR.CSR.SBF |
__HAL_PWR_GET_FLAG() |
RCC->APBxENR=0)