当前位置: 首页 > news >正文

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(电源控制模块)核心功能

  1. 电源管理
    • 负责 STM32 内部 模拟供电(VDDA)、数字供电(VDD,含 1.8V 降压区域)、后备供电(VBAT) 的统筹管理,确保各电路模块(CPU、SRAM、外设、RTC 等)在不同工作模式下的供电稳定。
    • 通过电压调节器(如 1.8V 区域的低功耗模式配置)动态调整供电电压,降低空闲时的静态功耗(如停机 / 待机模式下关闭 1.8V 区域电源,实现微安级功耗)。
  2. 可编程电压监测器(PVD)
    • 功能:实时监控 VDD 电源电压(阈值范围 2.2V~2.9V 可调,通过 PWR_PVDCR 寄存器配置),当电压 低于 / 高于阈值 时触发中断(映射到外部中断线),用于执行紧急任务(如保存数据、关机预警)。
    • 原理:采用迟滞比较器(40mV 迟滞)避免电压波动误触发,确保在电池低电量或电源不稳定时可靠响应(POR/PDR 阈值分别为 1.92V/1.88V,与 PVD 形成电源异常分层处理)。
  3. 低功耗模式实现
    • 睡眠模式(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 清零,需冷启动初始化),适合超长待机设备(如遥控器、低功耗传感器节点)。

电源框图

一、电源分区功能与低功耗模式关联

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 区域无供电(功耗降至纳安级)。
  • 低电压检测器(PVD):监测 VDD,阈值 2.2-2.9V(用于电源异常中断,如电池低电量预警)。

3. 后备供电区域(VBAT)

  • 模块:RTC、LSE(32K 晶振)、备份寄存器,由 VBAT(3V 电池)独立供电。
  • 低功耗模式
    • 睡眠 / 停机模式:VBAT 与 VDD 同时供电(RTC 持续运行,如会议中停机模式 RTC 未关闭)。
    • 待机模式:唯一供电源(VDD 断电),维持 RTC 计时(会议中 RTC 闹钟每 10 秒唤醒,依赖 LSE)。

二、电源架构对低功耗设计的支撑

  1. 分区断电的能效梯度
    • 睡眠模式:仅 CPU 时钟关(1.8V 区域供电,功耗毫安级)→ 快速响应中断(如串口数据)。
    • 停机模式:1.8V 区域时钟关(电源未断,数据保留,功耗微安级)→ 中长待机(如物联网节点周期性休眠)。
    • 待机模式:1.8V 区域断电(仅 VBAT,数据丢失,功耗纳安级)→ 超长待机(如遥控器,数年待机)。
  2. 时钟与供电的联动控制
    • 高速时钟(HSI/HSE):停机 / 待机模式下关闭(VDD 供电区域时钟源切断),唤醒后需重新初始化(停机模式唤醒后恢复 72MHz 主频)。
    • 低速时钟(LSI/LSE)
      • LSI(8kHz):停机模式为 IWDG 供电(框图隐含)。
      • LSE(32.768kHz):VBAT 供电,RTC 依赖其计时(待机模式 RTC 闹钟基于 LSE)。
  3. 复位与 PVD 的电源保护
    • POR/PDR:上电 / 掉电复位(VDDA 供电,阈值 1.92V/1.88V,迟滞 40mV,避免波动误触发)。
    • PVD 中断:VDD 异常时触发(如电池即将耗尽),执行紧急任务(如保存数据)。

上电复位和掉电复位

  • POR:电源上电时,通过电压监测、迟滞和延时,确保系统从已知状态启动,适用于初始化。
  • PDR:电源欠压时,快速复位保护数据,依赖 VBAT 维持关键信息,适用于低功耗设备的电源故障处理。
  • 二者通过 迟滞 增强抗干扰,通过 复位持续时间 确保稳定,与 PVD(电压监测)、VBAT 协同,构成 STM32 电源管理的核心机制(如物联网设备的电源异常防护与低功耗运行)。

可编程电压监测器

PVD 核心功能与原理

  1. 电源监测
    • 实时监控VDD/VDDA电压,阈值范围 2.2V~2.9V(8 级可调),通过迟滞比较器(100mV 迟滞,波形图所示)避免电压波动误触发,确保信号稳定。
    • 当电压 低于 / 高于阈值 时,PVD 输出信号翻转,可触发中断(映射到外部中断线,如 EXTI16),用于执行紧急任务(如数据保存、低电量预警)。
  2. 工作流程
    • 电压上升:超过阈值时,PVD 输出高电平(或低电平,依配置),触发中断(如电源恢复,初始化系统)。
    • 电压下降:低于阈值时,PVD 输出低电平(或高电平),触发中断(如电池低电量,保存数据后进入低功耗模式)。
    • 迟滞设计(100mV)确保仅持续越界时响应,过滤电源纹波(如会议中强调 “避免输出抖动”,提升抗干扰能力)。

低功耗模式

模式选择

睡眠模式

停止模式

待机模式

修改主频

一、准备工作

  1. 解除文件只读属性
    • system_stm32f10x.c 文件为只读(图标带钥匙符号),需手动解除:
      • 右键文件所在文件夹,选择 “属性”,取消 “只读” 勾选。
      • 确认后,文件可编辑(钥匙图标消失)。

二、修改主频的核心步骤:宏定义配置

  1. 定位宏定义位置
    • 打开 system_stm32f10x.c 文件,找到预编译宏定义区域(文件头部)。
    • 核心配置项为 #define SYSCLK_FREQ_XXX,其中 XXX 对应不同主频(如 72MHz、36MHz 等)。
  2. 根据设备型号选择配置分支
    • 超值系列(如 VL 型号):仅支持 8MHz 和 24MHz 主频。
    • 非超值系列(如 F103C8T6):支持 8MHz、24MHz、36MHz、48MHz、56MHz、72MHz 等。
    • 通过预编译指令(#ifdef)判断设备类型,选择对应配置分支(非超值系列查看 #else 分支)。
  3. 修改主频宏定义
    • 示例:从 72MHz 改为 36MHz
      • 注释原默认宏 #define SYSCLK_FREQ_72MHz
      • 取消注释 #define SYSCLK_FREQ_36MHz(或直接修改数值)。
    • 其他主频:按需选择对应宏定义(如 SYSCLK_FREQ_48MHz),原理类似。

三、程序流程:时钟配置函数解析

  1. 系统初始化函数 system_init()

    • 作用:复位后自动调用(在启动文件中触发),配置时钟树。
    • 流程
      1. 开启内部时钟 HSI(默认 8MHz)。
      2. 恢复默认配置(重置外设时钟、禁用不必要功能)。
      3. 调用 set_system_clock() 函数,根据宏定义选择主频配置。
  2. 主频配置函数 set_system_clock()

    • 作用:根据宏定义选择具体的时钟配置函数(如 set_system_clock_to_72()set_system_clock_to_36())。
    • 核心逻辑(以 72MHz 为例)
      1. 使能外部晶振 HSE(8MHz)。
      2. 配置锁相环(PLL):选择 HSE 作为输入,设置倍频系数为 9(8MHz × 9 = 72MHz)。
      3. 等待 HSEPLL 就绪后,将 PLL 输出设为系统时钟(SystemClock)。
    • 其他主频:仅倍频系数不同(如 36MHz 为 HSE ÷ 2 × 9 = 36MHz)。
    • #if 这样的形式叫预编译,就是如果定义 。。。 就执行。。。,否则 就执行 。。 下面如果使用_VL 超值系列,可以选择两种配置,否则可以选择#else的这些配置。

    在这里插入图片描述

四、注意事项

  1. 外设时钟依赖主频
    • 修改主频后,外设时钟(如 AHB、APB1/2)会按分频系数自动调整(默认分频:AHB=72MHz,APB2=72MHz,APB1=36MHz),需确保外设配置与新主频匹配。
  2. 延时函数自适应问题
    • 若延时函数(如 delay_ms)硬编码依赖 72MHz 主频,需根据 system_core_clock 变量动态计算延时参数,避免计时错误。
  3. 谨慎修改主频
    • 非必要情况下保持默认主频(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立即睡眠,无需等待中断结束。
    • 注意:直接操作寄存器需谨慎,需对照芯片手册确认位定义。

四、程序执行流程解析

1. 初始化阶段
  • 配置串口(USART)参数,使能接收中断;
  • 初始化 OLED 显示屏。
2. 主循环流程(无中断时)
  1. 显示 “running” 并延迟,模拟业务逻辑;
  2. 执行 __WFI();,CPU 进入睡眠状态,主循环暂停;
  3. 此时状态:CPU 休眠,USART 外设持续监听串口数据。
3. 中断唤醒流程(收到串口数据时)
  1. USART 接收到数据,触发中断;
  2. CPU 从睡眠中唤醒,暂停当前代码,跳转至 USART 中断处理函数
  3. 中断函数中:读取数据、设置标志位(如 rx_flag = 1)、清除中断标志;
  4. 中断处理完成后,返回主循环,继续执行 WFI 之后的代码(即再次进入循环开头);
  5. 主循环检测到 rx_flag 为真,执行数据回传和显示逻辑;
  6. 逻辑执行完毕后,再次执行 __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 Sensorget操作及外循环刷新,导致不必要的功耗。
  • 优化目标:空闲时进入低功耗模式,仅在外部中断触发时唤醒系统。
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指令  
    
  • 函数内部流程

    1. 读取PWR_CR寄存器值到临时变量。
    2. 清除PDDS位(选择停止模式,非待机模式)。
    3. 根据参数设置LPDS位(0:调节器开启;1:调节器低功耗)。
    4. 写入新配置到PWR_CR寄存器。
    5. 设置内核sleepdeep位(使能深度睡眠,进入停止模式)。
    6. 执行WFI指令,系统进入停止模式,程序暂停。
    7. 唤醒后:自动清除sleepdeep位,程序从暂停处继续执行。

四、测试现象、问题分析与解决

1. 首次测试现象
  • 正常表现
    • 无中断时,“running” 停止闪烁,主循环暂停(进入停止模式)。
    • 遮挡红外传感器(触发中断)时,“running” 闪烁一次(系统唤醒并执行一次主循环)。
  • 异常问题
    • 复位后首次 “running” 闪烁快(正常速度),但中断唤醒后闪烁变慢。
2. 问题原因分析
  • 时钟切换机制
    • 复位后默认使用HSE(高速外部时钟,8MHz 晶振 ×9 倍频 = 72MHz 主频)
    • 停止模式退出后,系统默认切换为HSI(高速内部时钟,8MHz),导致程序运行变慢。
3. 解决方案
  • 修复方法:在退出停止模式后,重新配置系统时钟为 HSE×9 倍频(72MHz)。

    • 代码:在主循环中调用SystemInit()函数(该函数初始化 HSE 时钟配置)。
    PWR_EnterStopMode(...); // 进入停止模式  
    SystemInit(); // 退出后重新配置72MHz主频  
    
  • 验证结果:重新编译下载后,中断唤醒后 “running” 闪烁恢复正常速度。

五、程序执行流程总结

  1. 复位与初始化
    • 执行系统初始化(SystemInit()),配置 HSE 为系统时钟(72MHz)。
    • 初始化外设(如红外传感器对应的 EXTI 中断)。
  2. 进入主循环
    • 显示 “running”→ 延时→ 清除显示→ 准备进入停止模式。
  3. 进入停止模式
    • 调用PWR_EnterStopMode(),执行WFI指令,系统暂停,等待中断。
  4. 中断唤醒
    • 外部中断(如传感器遮挡)触发,系统唤醒。
    • 执行中断处理函数,随后从WFI指令处继续执行主循环。
  5. 时钟恢复
    • 调用SystemInit(),重新配置 HSE 时钟,确保主频为 72MHz。
  6. 循环往复:重复主循环流程,空闲时再次进入停止模式。
//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();}
}

相关文章:

  • 飞算 JavaAI 2.0.0:开启老项目迭代维护新时代
  • SpringBoot自定义EndPoint实现线程池动态管理
  • 【C++系列】模板类型特例化
  • Kotlin REPL初探
  • 多线程语音识别工具
  • 【版本控制】Git 和 GitHub 入门教程
  • 历史数据分析——辽港股份
  • Linux--vsFTP配置篇
  • Python 接口:从协议到抽象基 类(Tombola子类的测试方法)
  • Razor编程中@Helper的用法大全
  • VSCode内网安装插件
  • 【原创】基于视觉模型+FFmpeg+MoviePy实现短视频自动化二次编辑+多赛道
  • stm32-c8t6实现语音识别(LD3320)
  • 【论文阅读29】区间预测CIPM(2025)
  • 读红蓝攻防:技术与策略15手机攻击
  • 华为OD机试-正整数到Excel编号之间的转换-逻辑分析(Java 2025 A卷 100分)
  • STM32[笔记]--1.前置准备
  • DQN算法(详细注释版)
  • 实验三:VGA显示实验
  • 《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (三)数据格式
  • 区域名 网站建设公司的销售好做吗/百度小程序
  • 天津建设培训中心网站/网页设计制作网站html代码大全
  • 南京本地网站/成人培训机构
  • 外贸网站建设排名/昆山seo网站优化软件
  • 网站运行费用预算/谷歌搜索为什么用不了
  • l兰州网站建设/网站seo策划方案实例