STM32 低功耗设计全攻略:PWR 模块原理 + 睡眠 / 停止 / 待机模式实战(串口 + 红外 + RTC 应用全解析)
文章目录
- PWR
- PWR(电源控制模块)核心功能
- 电源框图
- 上电复位和掉电复位
- 可编程电压监测器
- 低功耗模式
- 模式选择
- 睡眠模式
- 停止模式
- 待机模式
- 修改主频
- 一、准备工作
- 二、修改主频的核心步骤:宏定义配置
- 三、程序流程:时钟配置函数解析
- 四、注意事项
- 总结流程图
- 睡眠模式+串口的接收与发送数据
- 一、需求背景:为什么需要低功耗模式?
- 1. 项目功能描述
- 2. 低功耗模式的目标
- 二、低功耗模式对比与选择
- 1. 睡眠模式(Sleep Mode)
- 2. 停机模式(Stop Mode)/ 停止模式
- 3. 待机模式(Standby Mode)
- 最终选择:睡眠模式
- 三、睡眠模式代码实现步骤
- 1. 初始代码:未优化的耗电场景
- 2. 加入睡眠模式:使用 `WFI` 指令
- 3. 睡眠模式的配置细节
- 四、程序执行流程解析
- 1. 初始化阶段
- 2. 主循环流程(无中断时)
- 3. 中断唤醒流程(收到串口数据时)
- 4. 关键现象验证
- 停止模式+红外对射传感器计次
- 一、停止模式引入背景与原理
- 1. 耗电问题分析
- 2. 停止模式工作原理
- 二、PWR 外设库函数详解
- 1. 相关函数列表与功能
- 2. 停止模式核心函数 `PWR_EnterStopMode()`
- 三、停止模式代码实现步骤
- 1. 添加运行状态指示(非必需,便于调试)
- 2. 开启 PWR 外设时钟
- 3. 调用函数进入停止模式
- 四、测试现象、问题分析与解决
- 1. 首次测试现象
- 2. 问题原因分析
- 3. 解决方案
- 五、程序执行流程总结
- 待机模式+实时时钟
- 待机模式(Standby Mode)实现
- 1. 时钟配置
- 2. 进入待机模式的函数调用
- 3. 外部模块省电处理
- 唤醒功能测试
- 1. WAKEUP 引脚唤醒
- 2. 闹钟唤醒
PWR
PWR(电源控制模块)核心功能
- 电源管理
- 负责 STM32 内部 模拟供电(VDDA)、数字供电(VDD,含 1.8V 降压区域)、后备供电(VBAT) 的统筹管理,确保各电路模块(CPU、SRAM、外设、RTC 等)在不同工作模式下的供电稳定。
- 通过电压调节器(如 1.8V 区域的低功耗模式配置)动态调整供电电压,降低空闲时的静态功耗(如停机 / 待机模式下关闭 1.8V 区域电源,实现微安级功耗)。
- 可编程电压监测器(PVD)
- 功能:实时监控 VDD 电源电压(阈值范围 2.2V~2.9V 可调,通过
PWR_PVDCR
寄存器配置),当电压 低于 / 高于阈值 时触发中断(映射到外部中断线),用于执行紧急任务(如保存数据、关机预警)。 - 原理:采用迟滞比较器(40mV 迟滞)避免电压波动误触发,确保在电池低电量或电源不稳定时可靠响应(POR/PDR 阈值分别为 1.92V/1.88V,与 PVD 形成电源异常分层处理)。
- 功能:实时监控 VDD 电源电压(阈值范围 2.2V~2.9V 可调,通过
- 低功耗模式实现
- 睡眠模式(Sleep)
- 进入:调用
WFI()
/WFE()
,仅关闭 CPU 时钟,外设(如串口、定时器)保持运行。 - 唤醒:任意中断(如串口数据接收、定时器溢出),唤醒后从暂停处继续执行(后面会实现演示程序通过串口数据唤醒,实现 “空闲睡眠 - 工作唤醒” 的节能逻辑)。
- 功耗:毫安级(约 30mA,比正常运行省 40% 以上),适合需快速响应的轻量级低功耗场景。
- 进入:调用
- 停机模式(Stop)
- 进入:设置
SLEEPDEEP=1
+PDDS=0
+WFI()
/WFE()
,关闭 1.8V 区域时钟、HSI/HSE 高速时钟,仅保留 LSI/LSE 低速时钟。 - 唤醒:外部中断(如对射红外传感器触发、PVD 中断、RTC 闹钟映射到外部中断),唤醒后需重新初始化主频(默认 HSI 8MHz,需恢复 HSE/PLL 到 72MHz,程序要通过此步骤恢复正常运行)。
- 功耗:微安级(14-24μA,接近 “深度睡眠”),适合中长时间空闲且需保留 SRAM 数据的场景(如物联网节点周期性休眠 - 唤醒)。
- 进入:设置
- 待机模式(Standby):
- 进入:设置
SLEEPDEEP=1
+PDDS=1
+WFI()
/WFE()
,关闭 1.8V 区域电源、所有时钟,仅 VBAT 供电维持 RTC / 备份寄存器。 - 唤醒:WAKEUP 引脚上升沿、RTC 闹钟、复位信号(会议中演示程序通过 RTC 每 10 秒唤醒,执行任务后再次待机)。
- 功耗:纳安级(2-3μA,极致省电),数据仅备份寄存器保留(SRAM 清零,需冷启动初始化),适合超长待机设备(如遥控器、低功耗传感器节点)。
- 进入:设置
- 睡眠模式(Sleep)
电源框图
一、电源分区功能与低功耗模式关联
1. VDDA 供电区域(模拟电路)
- 模块:ADC、温度传感器、PLL 等,独立供电确保模拟信号纯净(如 ADC 精度依赖 VDDA 稳定性)。
- 低功耗模式:始终供电(睡眠 / 停机 / 待机模式均不关闭),因模拟电路功耗占比低,优先保证信号质量。
2. VDD 供电区域(数字电路,含 1.8V 降压)
- I/O 电路:直接由 VDD(3.3V)供电,支持高电压 IO(如 5V 容忍),低功耗模式下:
- 睡眠模式:仅关 CPU 时钟,I/O 外设(串口)仍工作(可唤醒,如串口数据唤醒)。
- 停机模式:关 1.8V 区域时钟(CPU / 内存 / 数字外设时钟停,电源未断,数据保留),仅 LSI/LSE 运行(外设中断可唤醒)。
- 待机模式:关 1.8V 区域电源(数据丢失,仅 VBAT 供电),I/O 高阻态。
- 电压调节器
- 睡眠 / 停机模式:降压 1.8V 持续供电(停机模式低功耗,
LPDS=1
)。 - 待机模式:断电(
PDDS=1
),1.8V 区域无供电(功耗降至纳安级)。
- 睡眠 / 停机模式:降压 1.8V 持续供电(停机模式低功耗,
- 低电压检测器(PVD):监测 VDD,阈值 2.2-2.9V(用于电源异常中断,如电池低电量预警)。
3. 后备供电区域(VBAT)
- 模块:RTC、LSE(32K 晶振)、备份寄存器,由 VBAT(3V 电池)独立供电。
- 低功耗模式
- 睡眠 / 停机模式:VBAT 与 VDD 同时供电(RTC 持续运行,如会议中停机模式 RTC 未关闭)。
- 待机模式:唯一供电源(VDD 断电),维持 RTC 计时(会议中 RTC 闹钟每 10 秒唤醒,依赖 LSE)。
二、电源架构对低功耗设计的支撑
- 分区断电的能效梯度:
- 睡眠模式:仅 CPU 时钟关(1.8V 区域供电,功耗毫安级)→ 快速响应中断(如串口数据)。
- 停机模式:1.8V 区域时钟关(电源未断,数据保留,功耗微安级)→ 中长待机(如物联网节点周期性休眠)。
- 待机模式:1.8V 区域断电(仅 VBAT,数据丢失,功耗纳安级)→ 超长待机(如遥控器,数年待机)。
- 时钟与供电的联动控制:
- 高速时钟(HSI/HSE):停机 / 待机模式下关闭(VDD 供电区域时钟源切断),唤醒后需重新初始化(停机模式唤醒后恢复 72MHz 主频)。
- 低速时钟(LSI/LSE)
- LSI(8kHz):停机模式为 IWDG 供电(框图隐含)。
- LSE(32.768kHz):VBAT 供电,RTC 依赖其计时(待机模式 RTC 闹钟基于 LSE)。
- 复位与 PVD 的电源保护:
- POR/PDR:上电 / 掉电复位(VDDA 供电,阈值 1.92V/1.88V,迟滞 40mV,避免波动误触发)。
- PVD 中断:VDD 异常时触发(如电池即将耗尽),执行紧急任务(如保存数据)。
上电复位和掉电复位
- POR:电源上电时,通过电压监测、迟滞和延时,确保系统从已知状态启动,适用于初始化。
- PDR:电源欠压时,快速复位保护数据,依赖 VBAT 维持关键信息,适用于低功耗设备的电源故障处理。
- 二者通过 迟滞 增强抗干扰,通过 复位持续时间 确保稳定,与 PVD(电压监测)、VBAT 协同,构成 STM32 电源管理的核心机制(如物联网设备的电源异常防护与低功耗运行)。
可编程电压监测器
PVD 核心功能与原理
- 电源监测
- 实时监控
VDD
/VDDA
电压,阈值范围 2.2V~2.9V(8 级可调),通过迟滞比较器(100mV 迟滞,波形图所示)避免电压波动误触发,确保信号稳定。 - 当电压 低于 / 高于阈值 时,PVD 输出信号翻转,可触发中断(映射到外部中断线,如 EXTI16),用于执行紧急任务(如数据保存、低电量预警)。
- 实时监控
- 工作流程
- 电压上升:超过阈值时,PVD 输出高电平(或低电平,依配置),触发中断(如电源恢复,初始化系统)。
- 电压下降:低于阈值时,PVD 输出低电平(或高电平),触发中断(如电池低电量,保存数据后进入低功耗模式)。
- 迟滞设计(100mV)确保仅持续越界时响应,过滤电源纹波(如会议中强调 “避免输出抖动”,提升抗干扰能力)。
低功耗模式
模式选择
睡眠模式
停止模式
待机模式
修改主频
一、准备工作
- 解除文件只读属性
- system_stm32f10x.c 文件为只读(图标带钥匙符号),需手动解除:
- 右键文件所在文件夹,选择 “属性”,取消 “只读” 勾选。
- 确认后,文件可编辑(钥匙图标消失)。
- system_stm32f10x.c 文件为只读(图标带钥匙符号),需手动解除:
二、修改主频的核心步骤:宏定义配置
- 定位宏定义位置
- 打开
system_stm32f10x.c
文件,找到预编译宏定义区域(文件头部)。 - 核心配置项为
#define SYSCLK_FREQ_XXX
,其中XXX
对应不同主频(如 72MHz、36MHz 等)。
- 打开
- 根据设备型号选择配置分支
- 超值系列(如 VL 型号):仅支持 8MHz 和 24MHz 主频。
- 非超值系列(如 F103C8T6):支持 8MHz、24MHz、36MHz、48MHz、56MHz、72MHz 等。
- 通过预编译指令(
#ifdef
)判断设备类型,选择对应配置分支(非超值系列查看#else
分支)。
- 修改主频宏定义
- 示例:从 72MHz 改为 36MHz
- 注释原默认宏
#define SYSCLK_FREQ_72MHz
。 - 取消注释
#define SYSCLK_FREQ_36MHz
(或直接修改数值)。
- 注释原默认宏
- 其他主频:按需选择对应宏定义(如
SYSCLK_FREQ_48MHz
),原理类似。
- 示例:从 72MHz 改为 36MHz
三、程序流程:时钟配置函数解析
-
系统初始化函数
system_init()
- 作用:复位后自动调用(在启动文件中触发),配置时钟树。
- 流程
- 开启内部时钟
HSI
(默认 8MHz)。 - 恢复默认配置(重置外设时钟、禁用不必要功能)。
- 调用
set_system_clock()
函数,根据宏定义选择主频配置。
- 开启内部时钟
-
主频配置函数
set_system_clock()
- 作用:根据宏定义选择具体的时钟配置函数(如
set_system_clock_to_72()
、set_system_clock_to_36()
)。 - 核心逻辑(以 72MHz 为例)
- 使能外部晶振
HSE
(8MHz)。 - 配置锁相环(PLL):选择
HSE
作为输入,设置倍频系数为 9(8MHz × 9 = 72MHz
)。 - 等待
HSE
和PLL
就绪后,将PLL
输出设为系统时钟(SystemClock
)。
- 使能外部晶振
- 其他主频:仅倍频系数不同(如 36MHz 为
HSE ÷ 2 × 9 = 36MHz
)。 - #if 这样的形式叫预编译,就是如果定义 。。。 就执行。。。,否则 就执行 。。 下面如果使用_VL 超值系列,可以选择两种配置,否则可以选择#else的这些配置。
- 作用:根据宏定义选择具体的时钟配置函数(如
四、注意事项
- 外设时钟依赖主频
- 修改主频后,外设时钟(如 AHB、APB1/2)会按分频系数自动调整(默认分频:AHB=72MHz,APB2=72MHz,APB1=36MHz),需确保外设配置与新主频匹配。
- 延时函数自适应问题
- 若延时函数(如
delay_ms
)硬编码依赖 72MHz 主频,需根据system_core_clock
变量动态计算延时参数,避免计时错误。
- 若延时函数(如
- 谨慎修改主频
- 非必要情况下保持默认主频(72MHz),随意修改可能导致程序兼容性问题或外设异常。
总结流程图
复制工程 → 解除文件只读 → 修改宏定义(SYSCLK_FREQ_XXX)→ 编译下载 → 验证主频显示与代码运行速度↓配置流程:system_init() → set_system_clock() → 选择 PLL 倍频系数 → 系统时钟更新
首先调用 system_init() 使用修改寄存器的写法,作用就是恢复缺省配置,恢复完之后会调用 SetSysClock(); 函数该函数会根据所选宏的不同,调用不用的函数,这些函数对时钟进行配置。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"int main(void)
{OLED_Init();OLED_ShowString(1, 1, "Sysclock");OLED_ShowNum(2, 1, SystemCoreClock, 8);while(1){OLED_ShowString(3, 1, "Running");Delay_ms(500);OLED_ShowString(3, 1, " ");Delay_ms(500);}
}
睡眠模式+串口的接收与发送数据
一、需求背景:为什么需要低功耗模式?
1. 项目功能描述
- 使用 SDK 32 开发下位机,功能为:
- 接收电脑通过串口发送的指令,并执行相应操作;
- 电脑指令发送时间不确定(可能随时发送,也可能长时间无操作)。
- 核心矛盾
- 为了随时响应指令,主循环需持续检查标志位或等待中断,但无指令时,CPU 空转导致功耗浪费。
2. 低功耗模式的目标
- 空闲时降低功耗:无指令时让 CPU 进入低功耗状态,仅在中断(如串口接收数据)到来时唤醒,恢复工作。
二、低功耗模式对比与选择
STM32 支持多种低功耗模式,会议重点分析了以下三种:
1. 睡眠模式(Sleep Mode)
- 特性
- CPU 停止运行,但外设时钟(如 USART)保持开启,硬件电路可正常接收数据;
- 中断唤醒:当 USART 接收到数据时,产生中断唤醒 CPU,恢复主循环执行。
- 适用场景:需要外设持续监听信号,仅 CPU 休眠的场景(如本项目的串口通信)。
2. 停机模式(Stop Mode)/ 停止模式
- 特性
- 关闭 1.8V 区域所有时钟,CPU 和外设均停止运行;
- 无法通过 USART 中断唤醒:因外设已断电,无法接收数据或产生中断。
- 结论:不适用于本项目(需持续监听串口指令)。
3. 待机模式(Standby Mode)
- 特性:功耗更低,但唤醒时间更长,且需重新初始化外设。
- 结论:同样不满足 “快速响应串口指令” 的需求。
最终选择:睡眠模式
原因:仅 CPU 休眠,外设保持工作,可通过串口中断快速唤醒,平衡功耗与响应速度。
三、睡眠模式代码实现步骤
1. 初始代码:未优化的耗电场景
-
功能:通过 OLED 显示 “running” 字样,主循环中循环显示和清除,模拟无指令时的空转。
-
代码片段
while (1) {OLED_ShowString(2, 1, "running"); // 显示“running”delay_ms(100); // 延迟 100 毫秒OLED_ShowString(2, 1, " "); // 清除显示 }
-
现象:无指令时,“running” 持续闪烁,主循环不断运行,CPU 持续耗电。
2. 加入睡眠模式:使用 WFI
指令
-
核心指令
__WFI();
(Wait For Interrupt,等待中断唤醒)- 作用:使 CPU 进入睡眠模式,直到中断发生。
-
代码优化
while (1) {// 原有逻辑:检查标志位、处理业务OLED_ShowString(2, 1, "running");delay_ms(100);OLED_ShowString(2, 1, " ");// 新增:主循环末尾加入睡眠指令__WFI(); // CPU 进入睡眠,等待中断唤醒 }
WFI vs WFE
WFI
:通过中断唤醒,适用于带中断的程序(如串口通信),配置简单;WFE
:通过事件唤醒,需额外配置事件寄存器,复杂度较高
3. 睡眠模式的配置细节
-
默认配置
- 睡眠模式涉及两个寄存器位:sleepdeep和sleep on exit,库函数未提供便捷配置接口,暂使用默认值0即
- 进入默认睡眠模式(非深度睡眠);
- 执行
WFI
后立即睡眠,无需等待中断结束。
- 睡眠模式涉及两个寄存器位:sleepdeep和sleep on exit,库函数未提供便捷配置接口,暂使用默认值0即
-
-
注意:直接操作寄存器需谨慎,需对照芯片手册确认位定义。
-
四、程序执行流程解析
1. 初始化阶段
- 配置串口(USART)参数,使能接收中断;
- 初始化 OLED 显示屏。
2. 主循环流程(无中断时)
- 显示 “running” 并延迟,模拟业务逻辑;
- 执行
__WFI();
,CPU 进入睡眠状态,主循环暂停; - 此时状态:CPU 休眠,USART 外设持续监听串口数据。
3. 中断唤醒流程(收到串口数据时)
- USART 接收到数据,触发中断;
- CPU 从睡眠中唤醒,暂停当前代码,跳转至 USART 中断处理函数;
- 中断函数中:读取数据、设置标志位(如
rx_flag = 1
)、清除中断标志; - 中断处理完成后,返回主循环,继续执行
WFI
之后的代码(即再次进入循环开头); - 主循环检测到
rx_flag
为真,执行数据回传和显示逻辑; - 逻辑执行完毕后,再次执行
__WFI();
,CPU 重新进入睡眠,等待下一次中断。
4. 关键现象验证
- 无数据时:“running” 不再闪烁,表明主循环停止,CPU 休眠;
- 有数据时:每发送一次数据,“running” 闪烁一次(主循环执行一次),数据正常回传,证明中断唤醒机制有效。
//main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int Data;
int main(void)
{OLED_Init();Serial_Init();while(1){//如果接收到接收数据寄存器中的非空标志位,就可以读取数据if(Serial_GetFlag() == 1){Data = Serial_GetData();Serial_SendBtye(Data);OLED_ShowHexNum(1, 1, Data, 2);//不用手动清除标志位,读取数据会自动清除}OLED_ShowString(2, 1, "Running");Delay_ms(100);OLED_ShowString(2, 1, " ");Delay_ms(100);__WFI();}
}
//Serial.c 串口的主要逻辑
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>int Serial_Data;
int Serial_flag;
void Serial_Init(void)
{//1.开启时钟 //开启GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//初始化gpioinit参数中的结构体GPIO_InitTypeDef GPIO_InitStructure;//将gpio口设置为推挽输出,因为非工作模式默认为高电平,所以配置为上拉GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//初始化发送引脚 9号引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//接收数据初始化 10号引脚 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//初始化发送引脚 9号引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;USART_InitTypeDef USART_InitStruct;//要配置的波特率USART_InitStruct.USART_BaudRate = 9600;//有没有启动硬件数据流控USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//模式输出模式USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//奇偶校验位USART_InitStruct.USART_Parity = USART_Parity_No;//停止位USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//如果想要使用中断,就开启中断,然后配置NVICUSART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//先设置中断分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);//开始USART1的总开关USART_Cmd(USART1, ENABLE);
}//封装发送数据的函数
void Serial_SendBtye(uint8_t byte)
{USART_SendData(USART1, byte);//等待数据发送完成,读取标志位while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//标志位置一之后不需要手动清零,进行写操作会自动清零}
void Serial_SendArray(uint8_t *Array, uint8_t length)
{uint8_t i;for(i = 0; i < length; i++){Serial_SendBtye(Array[i]);}
}void Serial_SendString(char *string)
{for(uint8_t i = 0; string[i] != '\0'; i++){Serial_SendBtye(string[i]);}
}
uint32_t Pow(uint8_t x, uint8_t y)
{uint32_t result = 1;while(y--){result *= x;}return result;
}
void Serial_SendNum(uint32_t Number, uint8_t length)
{for(uint8_t i = 0; i < length; i++){Serial_SendBtye(Number / Pow(10, length - i - 1) % 10 + '0');}
}
/*** 函 数:使用printf需要重定向的底层函数* 参 数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendBtye(ch); //将printf的底层重定向到自己的发送字节函数return ch;
}
/*** 函 数:自己封装的prinf函数* 参 数:format 格式化字符串* 参 数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100]; //定义字符数组va_list arg; //定义可变参数列表数据类型的变量argva_start(arg, format); //从format开始,接收参数列表到arg变量vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg); //结束变量argSerial_SendString(String); //串口发送字符数组(字符串)
}
int Serial_GetFlag(void)
{//实现读取标志位后自动清除的功能if(Serial_flag == 1){Serial_flag = 0;return 1;}return 0;
}
int Serial_GetData(void)
{return Serial_Data;
}void USART1_IRQHandler(void)
{if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){Serial_Data = USART_ReceiveData(USART1);Serial_flag = 1;//不用手动清除标志位,读取数据会自动清除}
}
停止模式+红外对射传感器计次
一、停止模式引入背景与原理
1. 耗电问题分析
- 当前代码在无外部中断信号时,持续执行
counter Sensor
的get
操作及外循环刷新,导致不必要的功耗。 - 优化目标:空闲时进入低功耗模式,仅在外部中断触发时唤醒系统。
2. 停止模式工作原理
- 低功耗特性
- 关闭 1.8V 区域时钟,CPU 和外设停止工作,但外部中断(EXTI)无需时钟即可工作。
- 内核停止运行,但寄存器和 SRAM 内容保留。
- 关键依据
- 初始化时未开启 EXTI 时钟,证明 EXTI 在时钟关闭时仍可响应中断(硬件特性)。
二、PWR 外设库函数详解
1. 相关函数列表与功能
函数名 | 功能描述 |
---|---|
PWR_DeInit() | 恢复 PWR 外设默认配置 |
PWR_BackupAccessCmd(ENABLE) | 使能后备区域访问(用于待机模式等场景) |
PWR_PVDCmd(ENABLE) | 使能电源电压检测(PVD)功能 |
PWR_PVDLevelConfig() | 配置 PVD 阈值电压 |
PWR_WakeUpPinCmd(ENABLE) | 使能 PA0 引脚作为唤醒引脚(配合待机模式) |
PWR_EnterStopMode() | 进入停止模式(核心函数) |
PWR_EnterStandbyMode() | 进入待机模式 |
PWR_GetFlagStatus() | 获取 PWR 状态标志位(如唤醒标志) |
PWR_ClearFlag() | 清除 PWR 状态标志位 |
2. 停止模式核心函数 PWR_EnterStopMode()
- 参数说明
- 第一个参数:指定电压调节器状态
PWR_Regulator_ON
:调节器开启(功耗略高,恢复速度快)PWR_Regulator_LowPower
:调节器低功耗模式(更省电,但唤醒后时钟需重新配置)
- 第二个参数:选择进入模式的指令
PWR_StopMode_WFI
:通过WFI
(等待中断)指令进入停止模式PWR_StopMode_WFE
:通过WFE
(等待事件)指令进入停止模式
- 第一个参数:指定电压调节器状态
三、停止模式代码实现步骤
1. 添加运行状态指示(非必需,便于调试)
-
在主循环中添加代码:
OVERIDE_ShowString(2, 1, "running"); // 在指定位置显示"running" delay(100); // 延时100ms OVERIDE_ClearString(); // 清除显示
-
作用:通过 “running” 闪烁判断主循环是否运行,辅助观察低功耗效果。
2. 开启 PWR 外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 开启PWR时钟
- 注意:若未开启时钟,对 PWR 寄存器的读写操作无效(寄存器值全为 0)。
3. 调用函数进入停止模式
-
在主循环末尾添加:
PWR_EnterStopMode(PWR_Regulator_ON, PWR_StopMode_WFI); // 选择调节器开启,WFI指令
-
函数内部流程
- 读取
PWR_CR
寄存器值到临时变量。 - 清除
PDDS
位(选择停止模式,非待机模式)。 - 根据参数设置
LPDS
位(0:调节器开启;1:调节器低功耗)。 - 写入新配置到
PWR_CR
寄存器。 - 设置内核
sleepdeep
位(使能深度睡眠,进入停止模式)。 - 执行
WFI
指令,系统进入停止模式,程序暂停。 - 唤醒后:自动清除
sleepdeep
位,程序从暂停处继续执行。
- 读取
四、测试现象、问题分析与解决
1. 首次测试现象
- 正常表现
- 无中断时,“running” 停止闪烁,主循环暂停(进入停止模式)。
- 遮挡红外传感器(触发中断)时,“running” 闪烁一次(系统唤醒并执行一次主循环)。
- 异常问题
- 复位后首次 “running” 闪烁快(正常速度),但中断唤醒后闪烁变慢。
2. 问题原因分析
- 时钟切换机制
- 复位后默认使用HSE(高速外部时钟,8MHz 晶振 ×9 倍频 = 72MHz 主频)。
- 停止模式退出后,系统默认切换为HSI(高速内部时钟,8MHz),导致程序运行变慢。
3. 解决方案
-
修复方法:在退出停止模式后,重新配置系统时钟为 HSE×9 倍频(72MHz)。
- 代码:在主循环中调用
SystemInit()
函数(该函数初始化 HSE 时钟配置)。
PWR_EnterStopMode(...); // 进入停止模式 SystemInit(); // 退出后重新配置72MHz主频
- 代码:在主循环中调用
-
验证结果:重新编译下载后,中断唤醒后 “running” 闪烁恢复正常速度。
五、程序执行流程总结
- 复位与初始化
- 执行系统初始化(
SystemInit()
),配置 HSE 为系统时钟(72MHz)。 - 初始化外设(如红外传感器对应的 EXTI 中断)。
- 执行系统初始化(
- 进入主循环
- 显示 “running”→ 延时→ 清除显示→ 准备进入停止模式。
- 进入停止模式
- 调用
PWR_EnterStopMode()
,执行WFI
指令,系统暂停,等待中断。
- 调用
- 中断唤醒
- 外部中断(如传感器遮挡)触发,系统唤醒。
- 执行中断处理函数,随后从
WFI
指令处继续执行主循环。
- 时钟恢复
- 调用
SystemInit()
,重新配置 HSE 时钟,确保主频为 72MHz。
- 调用
- 循环往复:重复主循环流程,空闲时再次进入停止模式。
//main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{OLED_Init();OLED_ShowString(1, 1, "count:");CountSensor_Init();//开启PWR时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);while(1){OLED_ShowNum(1, 7, Count_Get(), 5);OLED_ShowString(2, 1, "Running");Delay_ms(100);OLED_ShowString(2, 1, " ");Delay_ms(100);PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFE);//停止模式结束会默认时钟为LSI时钟 8 MHZ 所以结束停止模式要重新设置时钟频率SystemInit();}
}
//红外计数传感器
#include "stm32f10x.h" // Device header
#include "CountSensor.h"uint16_t count;
void CountSensor_Init(void)
{//配置时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//初始化io口GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);//AFIO选择GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//配置EXTIEXTI_InitTypeDef EXTI_InitStruct;EXTI_InitStruct.EXTI_Line = EXTI_Line14;EXTI_InitStruct.EXTI_LineCmd = ENABLE;EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_Init(& EXTI_InitStruct);//先配置中断分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVICNVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(& NVIC_InitStruct);}
uint16_t Count_Get(void)
{return count;
} //中断函数的名字是固定的,可以参考启动文件中的函数名
void EXTI15_10_IRQHandler(void)
{//在中断函数中一般要查看是不是我们想要的中断进来的if( EXTI_GetITStatus(EXTI_Line14) == SET){count++;//使用完成后将中断标志位手动清零EXTI_ClearITPendingBit(EXTI_Line14);}
}
待机模式+实时时钟
待机模式(Standby Mode)实现
1. 时钟配置
- 必要性:使用 PWR 外设需开启其时钟(
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE)
)。 - 代码独立性:即使 RTC 初始化中已开启 PWR 时钟,仍需在待机模式代码中单独开启,避免代码耦合(若后续删除 RTC 代码,可能导致待机模式失效)。
2. 进入待机模式的函数调用
- 函数选择:调用
PWR_EnterSTANDBYMode()
,内部执行步骤:
① 清除 WAKEUP 标志位;
② 设置 PWR 控制寄存器(PWR_CR)的 SBF 位,进入待机模式;
③ 置位SLEEPDEEP
位,调用WFI
指令进入深度睡眠。 - 关键点:待机模式不区分
WFI
/WFE
,唤醒条件为指定 4 个信号(如闹钟、WAKEUP 引脚等)。
3. 外部模块省电处理
- 原则:待机模式下 STM32 本身仅耗微安级电流,但外部模块(如显示屏、电机)可能耗电数十毫安,需彻底关闭。
- 硬件方案
- 使用带使能端的稳压器,或在外部模块供电回路中添加开关电路。
- 软件模拟:进入待机前执行
OID_Clear()
清屏,并在 4 行 9 列显示 “standby”(停留 1 秒),模拟模块关闭动作。
唤醒功能测试
1. WAKEUP 引脚唤醒
- 代码配置:调用
PWR_WakeUpPinCmd(ENABLE)
使能唤醒引脚,无需 GPIO 初始化(手册规定:使能后自动配置为输入下拉)。 - 测试方法
- 引脚默认下拉(悬空为低电平),接高电平时触发唤醒。
- 可接入外部传感器信号,实现自动化唤醒。
2. 闹钟唤醒
- 逻辑:待机模式下,RTC 闹钟触发后,STM32 自动退出待机,从程序起始位置重新执行(因闹钟设定代码在
while
循环前,唤醒后闹钟值会重新初始化)。
//main.c
//闹钟唤醒#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{OLED_Init();RTC_Init();//开启PWR时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);OLED_ShowString(1, 1, "CNT:");OLED_ShowString(2, 1, "ALR:");OLED_ShowString(3, 1, "FLAG_ALR:");uint32_t ALR = RTC_GetCounter() + 10;RTC_SetAlarm(ALR);OLED_ShowNum(2, 5, ALR, 10);while(1){OLED_ShowNum(1, 5, RTC_GetCounter(), 10); //显示32位的秒计数器OLED_ShowNum(3, 10, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);OLED_ShowString(4, 1, "Running");Delay_ms(100);OLED_ShowString(4, 1, " ");Delay_ms(100);OLED_Clear();PWR_EnterSTANDBYMode();}
}// wakeup唤醒
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{OLED_Init();RTC_Init();//开启PWR时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//开启wakeup使能PWR_WakeUpPinCmd(ENABLE);OLED_ShowString(1, 1, "CNT:");OLED_ShowString(2, 1, "ALR:");OLED_ShowString(3, 1, "FLAG_ALR:");uint32_t ALR = RTC_GetCounter() + 10;RTC_SetAlarm(ALR);OLED_ShowNum(2, 5, ALR, 10);while(1){OLED_ShowNum(1, 5, RTC_GetCounter(), 10); //显示32位的秒计数器OLED_ShowNum(3, 10, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);OLED_ShowString(4, 1, "Running");Delay_ms(100);OLED_ShowString(4, 1, " ");Delay_ms(100);OLED_Clear();PWR_EnterSTANDBYMode();}
}
相关文章:

STM32 低功耗设计全攻略:PWR 模块原理 + 睡眠 / 停止 / 待机模式实战(串口 + 红外 + RTC 应用全解析)
文章目录 PWRPWR(电源控制模块)核心功能 电源框图上电复位和掉电复位可编程电压监测器低功耗模式模式选择睡眠模式停止模式待机模式 修改主频一、准备工作二、修改主频的核心步骤:宏定义配置三、程序流程:时钟配置函数解析四、注意…...

持续交付的进化:从DevOps到AI驱动的IT新动能
文章目录 一、持续交付的本质:从手动到自动的交付飞跃关键特性案例:电商平台的高效部署 二、持续交付的演进:从CI到AI驱动的未来发展历程 中国…...
Linux信号保存与处理机制详解
Linux信号的保存与处理涉及多个关键机制,以下是详细的总结: 1. 信号的保存 进程描述符(task_struct):每个进程的PCB中包含信号相关信息。 pending信号集:记录已到达但未处理的信号(未决信号&a…...

OpenHarmony标准系统-HDF框架之I2C驱动开发
文章目录 引言I2C基础知识概念和特性协议,四种信号组合 I2C调试手段硬件软件 HDF框架下的I2C设备驱动案例描述驱动Dispatch驱动读写 总结 引言 I2C基础知识 概念和特性 集成电路总线,由串网12C(1C、12C、Inter-Integrated Circuit BUS)行数据线SDA和串…...

LeetCode - 148. 排序链表
目录 题目 思路 基本情况检查 复杂度分析 执行示例 读者可能出的错误 正确的写法 题目 148. 排序链表 - 力扣(LeetCode) 思路 链表归并排序采用"分治"的策略,主要分为三个步骤: 分割:将链表从中间…...

多模态大语言模型arxiv论文略读(110)
CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文标题:CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文作者:Hidehisa Arai, Keita Miwa, Kento Sasaki, Yu Yamaguchi, …...

React、Git、计网、发展趋势等内容——前端面试宝典(字节、小红书和美团)
React React Hook实现架构、.Hook不能在循环嵌套语句中使用 , 为什么,Fiber架构,面试向面试官介绍,详细解释 用户: React Hook实现架构、.Hook不能在循环嵌套语句中使用 , 为什么,Fiber架构,面试向面试官介绍&#x…...

Web APIS Day01
1.声明变量const优先 那为什么一开始前面就不能用const呢,接下来看几个例子: 下面这张为什么可以用const呢?因为复杂数据的引用地址没变,数组还是数组,只是添加了个元素,本质没变,所以可以用con…...

关于 ffmpeg设置摄像头报错“Could not set video options” 的解决方法
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/148515355 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...

Linux系统:进程间通信-匿名与命名管道
本节重点 匿名管道的概念与原理匿名管道的创建命名管道的概念与原理命名管道的创建两者的差异与联系命名管道实现EchoServer 一、管道 管道(Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在不…...

使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…...

使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...
VUE3 ref 和 useTemplateRef
使用ref来绑定和获取 页面 <headerNav ref"headerNavRef"></headerNav><div click"showRef" ref"buttonRef">refbutton</div>使用ref方法const后面的命名需要跟页面的ref值一样 const buttonRef ref(buttonRef) cons…...

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境
如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境? 在 Python 开发中,为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具,能高效创建不同 Python 版本的 Poetry 虚拟环境,接下来…...

多模态学习路线(2)——DL基础系列
目录 前言 一、归一化 1. Layer Normalization (LN) 2. Batch Normalization (BN) 3. Instance Normalization (IN) 4. Group Normalization (GN) 5. Root Mean Square Normalization(RMSNorm) 二、激活函数 1. Sigmoid激活函数(二分类&…...

[10-1]I2C通信协议 江协科技学习笔记(17个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17...

AWSLambda之设置时区
目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可,即Asia/Shanghai。 参考 使用 Lambda 环境变量...

RFID推动新能源汽车零部件生产系统管理应用案例
RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域,电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式,存在单点位单独头溯源、网关布线…...

[C++错误经验]case语句跳过变量初始化
标题:[C错误经验]case语句跳过变量初始化 水墨不写bug 文章目录 一、错误信息复现二、错误分析三、解决方法 一、错误信息复现 write.cc:80:14: error: jump to case label80 | case 2:| ^ write.cc:76:20: note: crosses initialization…...

Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...

uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...

7种分类数据编码技术详解:从原理到实战
在数据分析和机器学习领域,分类数据(Categorical Data)的处理是一个基础但至关重要的环节。分类数据指的是由有限数量的离散值组成的数据类型,如性别(男/女)、颜色(红/绿/蓝)或产品类…...

【字节拥抱开源】字节团队开源视频模型 ContentV: 有限算力下的视频生成模型高效训练
本项目提出了ContentV框架,通过三项关键创新高效加速基于DiT的视频生成模型训练: 极简架构设计,最大化复用预训练图像生成模型进行视频合成系统化的多阶段训练策略,利用流匹配技术提升效率经济高效的人类反馈强化学习框架&#x…...

本地部署drawDB结合内网穿透技术实现数据库远程管控方案
文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下,数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统,还是初创…...

可视化预警系统:如何实现生产风险的实时监控?
在生产环境中,风险无处不在,而传统的监控方式往往只能事后补救,难以做到提前预警。但如今,可视化预警系统正在改变这一切!它能够实时收集和分析生产数据,通过直观的图表和警报,让管理者第一时间…...

多模态大语言模型arxiv论文略读(112)
Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文标题:Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文作者:Jea…...

【向量库】Weaviate概述与架构解析
文章目录 一、什么是weaviate二、High-Level Architecture1. Core Components2. Storage Layer3. 组件交互流程 三、核心组件1. API Layer2. Schema Management3. Vector Indexing3.1. 查询原理3.2. 左侧:Search Process(搜索流程)3.3. 右侧&…...
PostgreSQL 对 IPv6 的支持情况
PostgreSQL 对 IPv6 的支持情况 PostgreSQL 全面支持 IPv6 网络协议,包括连接、存储和操作 IPv6 地址。以下是详细说明: 一、网络连接支持 1. 监听 IPv6 连接 在 postgresql.conf 中配置: listen_addresses 0.0.0.0,:: # 监听所有IPv4…...
python数据结构和算法(1)
数据结构和算法简介 数据结构:存储和组织数据的方式,决定了数据的存储方式和访问方式。 算法:解决问题的思维、步骤和方法。 程序 数据结构 算法 算法 算法的独立性 算法是独立存在的一种解决问题的方法和思想,对于算法而言&a…...
视觉slam--框架
视觉里程计的框架 传感器 VO--front end VO的缺点 后端--back end 后端对什么数据进行优化 利用什么数据进行优化的 后端是怎么进行优化的 回环检测 建图 建图是指构建地图的过程。 构建的地图是点云地图还是什么信息的地图? 建图并没有一个固定的形式和算法…...