我们使用手机的时候屏幕亮起,我们页面进入操作不同APP。而当我们不需要使用时,如果手机屏幕一直维持亮起状态,那么手机电量是不是用的很快。所以一般情况下,我们使用手机后,它会自动息屏,或者我们把它关机掉。PWR也是这一相似作用,为了 在性能与功耗之间做出平衡,提供电源管理手段,使系统在满足运行需求的同时,尽可能节能、稳定、安全运行。
从上电到运行:STM32 的供电和启动流程
当 VDD 上电时:
步骤 | 模块/行为 | 说明 |
---|---|---|
1 | 电源电压上升 | VDD 通常为 3.3V,逐步达到稳态 |
2 | 复位电路监控 VDD(POR/PDR) | POR(上电复位)和 PDR(掉电复位)确保电压稳定前不让系统启动 |
3 | 内部复位激活,MCU 初始化 | 时钟未启用,CPU处于复位状态 |
4 | 等待 VDD 稳定后,释放复位 | 系统从复位状态进入主程序启动流程 |
5 | 程序执行 Reset_Handler → main() | 启动汇编初始化,跳转到主函数 |
掉电检测器(PDR)与上电复位(POR)
机制 | 触发条件 | 作用 |
---|---|---|
POR(Power-On Reset) | VDD 从 0V 上升超过阈值(约 1.8V) | 确保芯片只在电压稳定后开始运行 |
PDR(Power-Down Reset) | VDD 跌落低于阈值(~2.0V) | 在掉电或电压不稳时强制系统复位 |
BOR(Brown-Out Reset) | 更精细的电压跌落监控 | 有些 STM32 系列才有(F1 没有) |
POR/PDR 是硬件级别电压检测电路,在 MCU 最开始的电源稳定过程中发挥作用,不依赖软件配置,也无法屏蔽。
PVD(Programmable Voltage Detector)详解
PVD 是一个可编程门限的电压比较器,作用是:
在 VDD 跌落之前发出中断预警
比 PDR 反应更早,允许用户软件自行保存数据/进入低功耗模式
时钟 & PVD 初始化函数
void PVD_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 打开 PWR 时钟
PWR_PVDLevelConfig(PWR_PVDLevel_2V9); // 设置门限电压为 2.9V
PWR_PVDCmd(ENABLE); // 使能 PVD
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_ClearITPendingBit(EXTI_Line16); // 清除 EXTI16 的挂起标志
EXTI_InitStructure.EXTI_Line = EXTI_Line16;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 电压上升/下降都触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
中断服务函数(显示 VDD 状态)
void PVD_IRQHandler(void)
{
if (PWR_GetFlagStatus(PWR_FLAG_PVDO) == SET)
{
OLED_ShowString(2, 1, "LOW VDD!"); // 电压低于门限
}
else
{
OLED_ShowString(2, 1, "VDD OK"); // 电压恢复
}
EXTI_ClearITPendingBit(EXTI_Line16); // 清除中断标志位
}
主函数调用
int main(void)
{
SystemInit();
OLED_Init();
OLED_ShowString(1, 1, "PVD Watchdog");
PVD_Init(); // 初始化 PVD 监测
while (1)
{
// 其他逻辑处理……
}
}
功能 | 库函数 | 说明 |
---|---|---|
打开 PWR | RCC_APB1PeriphClockCmd() |
启动 PWR 模块 |
开启 PVD | PWR_PVDCmd(ENABLE) |
启用电压检测 |
设置门限 | PWR_PVDLevelConfig(PWR_PVDLevel_2V9) |
设置比较器参考电压 |
判断是否电压低 | PWR_GetFlagStatus(PWR_FLAG_PVDO) |
返回比较结果 |
清除中断 | EXTI_ClearITPendingBit(EXTI_Line16) |
清除 EXTI16 中断标志 |
硬件说明
项目 | 说明 |
---|---|
PVD 内部连接 | 连接到 EXTI Line16,不能更换引脚 |
比较参考电压 | 内部固定,有 8 个档位(2.2V ~ 2.9V) |
比较器功能 | 检测 VDD 与参考电压比较结果 |
响应时间 | 非常快,硬件触发中断 |
唤醒功能 | 可以作为唤醒源用于唤醒 Stop 模式下的 MCU(⚠️ 不是 Standby) |
PWR 相关供电结构(STM32F103 为例)
STM32F1 系列包含以下供电模块:
模块 | 说明 |
---|---|
VDD | 主供电(通常 3.3V) |
VDDA | 模拟电源(ADC/Comparator),需滤波 |
VSS | 数字地 |
VBAT | 实时时钟供电(RTC/备份寄存器),用于掉电后保持数据 |
VCAP1 | 稳压输出电容(仅部分 STM32 有) |
低功耗模式
睡眠模式
执行完WFI/WFE指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
SLEEPONEXIT位决定STM32执行完WFI或WFE后,是立刻进入睡眠,还是等STM32从最低优先级的中断处理程序中退出时进入睡眠
在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态
WFI指令进入睡眠模式,可被任意一个NVIC响应的中断唤醒
WFE指令进入睡眠模式,可被唤醒事件唤醒
停止模式
执行完WFI/WFE指令后,STM32进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,SRAM和寄存器内容被保留下来
在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态
当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟
当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时
WFI指令进入停止模式,可被任意一个EXTI中断唤醒
WFE指令进入停止模式,可被任意一个EXTI事件唤醒
待机模式
执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行
整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
举例一个工程包含低功耗所有关键功能
main()
│
├── 初始化外设(OLED、按键)
├── 显示当前状态
├── 判断是否从 Standby 唤醒
├── 初始化 PVD
├── 等待按键触发不同低功耗模式
│ ├── KEY1 → 进入 Sleep 模式
│ ├── KEY2 → 进入 Stop 模式(5秒后 RTC 唤醒)
│ └── KEY3 → 进入 Standby 模式(PA0 唤醒)
└── PVD 检测中断持续监控电压状态
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_exti.h"
#include "misc.h"
#include "OLED.h"
#include "Delay.h"
#include "Key.h"
// 初始化 PVD 电压检测器,使用你库里的函数
void PVD_Init(void)
{
// 1. 开启 PWR 模块时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 2. 设置 PVD 检测电压门限为 2.9V
PWR_PVDLevelConfig(PWR_PVDLevel_2V9);
// 3. 使能 PVD 功能
PWR_PVDCmd(ENABLE);
// 4. 配置 EXTI Line16(PVD 专用中断线)
EXTI_ClearITPendingBit(EXTI_Line16);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line16;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 电压上下沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 5. 配置 PVD 中断 NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 进入 Sleep 模式
void Enter_SleepMode(void)
{
__WFI(); // 等待中断进入 Sleep,使用库函数无须额外操作
}
// 进入 Stop 模式(低功耗调压模式)
void Enter_StopMode(void)
{
// 使用你库里的函数进入 Stop 模式
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// Stop 模式后,时钟需重设
SystemInit();
}
// 进入 Standby 模式(极限低功耗,掉电重启)
void Enter_StandbyMode(void)
{
// 允许唤醒引脚 PA0 唤醒功能
PWR_WakeUpPinCmd(ENABLE);
// 进入 Standby 模式
PWR_EnterSTANDBYMode();
}
// PVD 中断处理函数(在 stm32f10x_it.c 中)
void PVD_IRQHandler(void)
{
if (PWR_GetFlagStatus(PWR_FLAG_PVDO) == SET)
{
OLED_ShowString(4, 1, "LOW VDD! ");
}
else
{
OLED_ShowString(4, 1, "VDD OK ");
}
EXTI_ClearITPendingBit(EXTI_Line16);
}int main(void)
{
SystemInit();
OLED_Init();
Key_Init();
PVD_Init();
OLED_ShowString(1, 1, "Power Mode Demo");
if (PWR_GetFlagStatus(PWR_FLAG_SB) == SET)
{
PWR_ClearFlag(PWR_FLAG_SB);
OLED_ShowString(2, 1, "Wake from STANDBY");
}
else
{
OLED_ShowString(2, 1, "Normal Power On");
}
while (1)
{
uint8_t key = Key_GetNum();
if (key == 1)
{
OLED_ShowString(3, 1, "Enter SLEEP Mode ");
Delay_ms(500);
Enter_SleepMode();
OLED_ShowString(3, 1, "Woke from SLEEP ");
}
else if (key == 2)
{
OLED_ShowString(3, 1, "Enter STOP Mode ");
Delay_ms(500);
Enter_StopMode();
OLED_ShowString(3, 1, "Woke from STOP ");
}
else if (key == 3)
{
OLED_ShowString(3, 1, "Enter STANDBY... ");
Delay_ms(1000);
Enter_StandbyMode();
}
}
}
库函数 | 功能 |
---|---|
PWR_BackupAccessCmd() |
开启对 RTC 和 BKP 寄存器的访问权限(修改 RTC 必须) |
PWR_PVDCmd() |
开启/关闭 PVD 电压检测器 |
PWR_PVDLevelConfig() |
设置电压阈值(2.2V~2.9V) |
PWR_WakeUpPinCmd() |
启用 PA0 唤醒引脚 |
PWR_EnterSTOPMode() |
进入 Stop 模式(可选低功耗 LDO) |
PWR_EnterSTANDBYMode() |
进入 Standby 模式(极低功耗,掉电重启) |
PWR_GetFlagStatus() |
查询 PWR 相关标志位(SB、WU、PVDO) |
PWR_ClearFlag() |
清除 WakeUp 或 Standby 标志位 |