【STM32 学习笔记】PWR电源控制

发布于:2025-06-27 ⋅ 阅读:(20) ⋅ 点赞:(0)

在电子设备中,待机(Standby)和睡眠(Sleep)是两种不同的省电模式。 1. 待机模式(Standby Mode):在待机模式下,设备仍然保持一定程度的活动,但大部分功能处于暂停状态。在这里插入图片描述

电源

STM32的工作电压(VDD)为2.0~3.6V。通过内置的电压调节器提供所需的1.8V电源
当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源
在这里插入图片描述
这张图展示了STM32微控制器(MCU)内部的电源分配方案。整个系统可分为三大主要供电区域:模拟部分供电(VDDA)、数字部分供电包括VDD供电区域和1.8v供电区域、后备供电(VBAT)。

  • 首先来看模拟部分供电,即VDDA区域,负责为模拟功能如AD转换器、温度传感器、复位模块及PLL锁相环提供电力。这些组件的正极连接至VDDA,而负极则连接至VSSA。特别地,AD转换器的参考电压输入端VREF+和VREF-通常会在在引脚多的型号里会单独引出来;而在像C8T6这样的少引脚型号中,它们已在芯片内部直接连接到VDDA和VSSA上。

  • 接下来是数字部分供电,该部分分为两个子区域:VDD供电区和1.8V供电区。VDD供电区包含了I/O电路、待机电路、唤醒逻辑和独立看门狗等功能。这部分电路的工作电压通常是3.3V。为了提高能效,STM32的设计采用了低压策略,因此大部分关键的内部电路,例如CPU、存储器和数字外设,实际上是以1.8V的较低电压运行。

关于1.8V供电区,它是由VDD通过内置的电压调节器降压得到的,提供给CPU核心、存储器和内置数字外设。当这些外设需要与外界进行交流时,才会通过I/O电路转换到3.3V。这种设计有助于显著降低系统的功耗,因为较低的电压意味着更低的功率消耗。
需要注意的是,STM32的工作电压(VDD)范围为2.0~3.6V,而1.8V电源是通过内置的电压调节器提供的

  • 最后讨论的是后备供电区域。此区域包括了LSE 32K晶体振荡器、后备寄存器、RCC BDCR寄存器和实时时钟(RTC)。RCC BDCR是RTC的控制寄存器之一,也属于后备区域的一部分,因此同样可以通过VBAT供电。此外,图中还显示了一个低电压检测器,它可以监测主电源VDD的状态,并在VDD失效时自动切换到VBAT供电模式,确保RTC和其他关键的后备功能即使在主电源断开的情况下也能继续工作。

电源管理器

上电复位和掉电复位,还有可编程电压监测器这两个内容了解即可

上电复位和掉电复位

在这里插入图片描述
上电复位和掉电复位的功能在于当VDD或VDDA的电压降至一定水平时,内部电路会自动触发复位操作,防止STM32在电压不稳定时进行错误操作。为此,系统设置了一个40毫伏的迟滞电压,以避免电压波动导致的不稳定。具体来说,当电压超过上限(POR阈值,即1.92V)时,系统解除复位状态;而当电压低于下限(PDR阈值,即1.88V)时,系统进入复位状态。这种设计采用了迟滞比较器,通过设定上下两个阈值,有效防止了电压在阈值附近波动时引起的输出抖动。

需要注意的是,复位信号是低电平有效,意味着在电压过低(前后两种情况)时,系统会进入复位状态,而在电压正常(中间状态)时,系统则不会复位。关于具体的电压阈值和复位滞后时间,可以参考STM32的数据手册,具体信息在5.3.3节“内嵌复位和电源控制模块特性”中有详细说明。根据数据手册,PDR的典型下限阈值是1.88V,而POR的典型上限阈值是1.92V,这40毫伏的迟滞阈值确保了电压稳定。简而言之,电压大于1.9V时系统上电,低于1.9V时系统掉电。此外,复位持续时间(TRSTTEMPO)的典型值为2.5毫秒。

这个复位持续时间很重要,实际产品这里经常出偶发问题又难以排查

可编程电压监测器

PVD的工作原理与前述的上电复位和掉电复位类似,都是监测VDD和VDDA的供电电压。然而,PVD的独特之处在于其阈值电压是可以编程设定的,提供了更大的灵活性。
在这里插入图片描述
根据数据手册中的相关表格,可以通过配置PLS寄存器的3个位来选择PVD的阈值。这些阈值的选择范围大约在2.2V到2.9V之间,且PVD的上限和下限阈值之间的迟滞电压为100毫伏。值得注意的是,PVD的监测电压范围高于上电和掉电复位的阈值。
在这里插入图片描述
为了更直观地理解,可以想象一个电压从3.3V的正常供电逐渐降低的情景。当电压降至2.9V至2.2V之间时,就进入了PVD的监测范围。在这个范围内,您可以设置一个警告线,以便在电压进一步降低至1.9V以下,即复位电路的检测范围时,系统可以采取行动。
当PVD被触发时,微控制器仍然可以正常工作,但这是对用户的一个提醒,表明电源电压已经过低。PVD的输出是正逻辑,即电压过低时输出为1,电压正常时输出为0。这个信号可以用来申请中断,在电压上升或下降时触发,从而提醒程序进行相应的处理。
关于PVD的中断申请,它通过外部中断实现。在EXTI(外部中断)的基本结构图中,可以看到PVD的输出信号是如何接入的。因此,如果想要使用PVD,记得正确配置外部中断。
另外,尽管RTC有自己的中断系统,但为了在低功耗模式下唤醒停止模式,只有外部中断能够实现这一点。这也是为什么其他设备,如USB和ETH的唤醒信号,也要通过外部中断来唤醒停止模式。理解这一点对于低功耗设计至关重要。

低功耗模式

在这里插入图片描述
STM32F10xxx有三种低功耗模式:

睡眠模式

在这里插入图片描述

要进入睡眠模式,只需直接调用WFI(等待中断)或WFE(等待事件)指令,这两个都是内核指令,对应的库函数中也提供了相应的调用方法。WFI的作用是让CPU进入睡眠状态,直到有中断发生时唤醒;而WFE则是让CPU等待一个特定的事件来唤醒。唤醒条件分别是:WFI模式下,任何中断都能唤醒CPU;WFE模式下,则需要一个特定的事件来唤醒。唤醒后,WFI通常需要处理中断服务函数,而WFE则可能直接继续执行。

睡眠模式对电路的影响有限,主要表现在关闭了CPU时钟,而其他时钟和ADC时钟不受影响。电压调节器保持开启状态,因此,睡眠模式主要是通过停止CPU时钟来降低功耗。关闭时钟意味着所有的运算和时序操作暂停,但寄存器和存储器中的数据得以保持。睡眠模式的唤醒条件相对宽松,任何中断都能唤醒CPU,因此,它相当于是在保持身体其他部分工作的情况下,大脑稍作休息,省电程度评为一般。

在这里插入图片描述
在这里插入图片描述

停机模式

在这里插入图片描述

要进入停机模式,首先需要将sleepdeep位设置为1,指示CPU进入深度睡眠。PDDS位用于区分停机模式或待机模式,PDDS为0时进入停机模式。之后LPDS位用于控制电压调节器是保持开启还是进入低功耗模式(RPDS等于电压调节器开启,RPDS等于1电压调节器进入低功耗v)。

设置好这些位后,调用WFI或WFE即可进入停机模式。停机模式的唤醒条件比睡眠模式苛刻,只有外部中断能够唤醒。这意味着,如PVD、RTC闹钟、USB唤醒、ETH唤醒等通过外部中断的信号可以唤醒系统。停机模式关闭了所有1.8伏区域的时钟,包括HSI和HSE振荡器,但LSI和LSE振荡器保持运行。电压调节器可以选择开启或低功耗模式,后者更省电但唤醒时间更长。停机模式相当于整个人的工作完全停止,只有外部中断才能唤醒,省电程度评为非常省电。

注意
在这里插入图片描述
系统从停止模式被中断或唤醒事件唤醒时,HSI(内部高速时钟)会被自动选为系统时钟。这是因为,在我们的程序中,默认在SystemInit函数里配置的是使用HSE(外部高速时钟),并通过PLL(锁相环)倍频来获得72MHz的主频。然而,一旦进入停止模式,PLL和HSE都会停止工作。

因此,当系统从停止模式唤醒时,它不会自动通过PLL倍频来恢复到原来的72MHz主频,而是直接使用HSI的8MHz作为主频。如果忽略这一点,就可能会出现以下现象:程序刚上电时运行在72MHz的主频,但进入停止模式并在唤醒之后,主频会降为8MHz。不止慢9倍这么简单,带时序的外设基本上都出问题。

为了避免这种情况,我们通常需要在停止模式唤醒后的第一时间重新启动HSE,并将主频重新配置为72MHz。这一操作并不复杂,因为相关的配置函数已经为我们准备好了。我们只需要在唤醒后调用SystemInit函数,即可完成主频的重新配置。这样,系统就能恢复到停止模式之前的工作状态,确保程序的正常运行。

待机模式

在这里插入图片描述

进入待机模式的步骤与停机模式相似,但PDDS位需设置为1。待机模式的唤醒条件最为严格,普通外设中断和外部中断都无法唤醒,只有特定的信号,如wake up引脚的上升沿、RTC闹钟事件、NRST引脚的外部复位和IWDG独立看门狗复位,才能唤醒。待机模式几乎关闭了所有电路,包括1.8伏区域的时钟和电压调节器,这意味着内部存储器和寄存器的数据会丢失。但与停机模式一样,LSI和LSE振荡器保持运行以支持RTC和独立看门狗。待机模式相当于彻底下班,除非有紧急事项,否则不会返回工作,省电程度评为极为省电。

在这里插入图片描述

在之前的讨论中,我们提到了多个与低功耗模式相关的寄存器位,这些模式还有一些更细致的划分。例如,睡眠模式中有SLEEP-NOW和SLEEP-ON-EXIT的区别,停机模式中则有电压调节器开启与低功耗模式的区别。了解如何配置这些模式对我们理解程序有很大帮助。
首先这里有一句,执行WFI等待中断或者WFE等待事件指令后,STM32进入低功耗模式,就说这两个指令是最终开启低功耗模式的触发条件,配置其他的寄存器都要在这两个指令之前
以下是基于配置流程的详细说明:

  1. 执行WFI或WFE指令:这两个指令是启动低功耗模式的触发点。在执行这两个指令之前,需要配置好相关的寄存器。
  2. 判断sleep deep位:这一位决定了是进入浅睡眠还是深度睡眠模式。
    • 如果sleep deep位为0,则进入睡眠模式。
    • 如果sleep deep位为1,则进入深度睡眠模式,即停机模式或待机模式。
  3. 睡眠模式的细分:在睡眠模式下,SLEEPONEXIT位可以进一步细分模式。
    • 当SLEEPONEXIT位为0时,执行WFI或WFE后立即进入睡眠模式。
    • 当SLEEPONEXIT位为1时,执行WFI或WFE后会等待当前中断处理完成后才进入睡眠模式。这种情况适用于中断处理中还有一些紧急任务需要完成。
  4. 深度睡眠模式的判断:对于深度睡眠模式,需要进一步判断PDDS位。
    • 如果PDDS位为0,则进入停机模式。
    • 如果PDDS位为1,则进入待机模式。
  5. 停机模式的进一步配置:在停机模式下,LPDS位决定了电压调节器的工作状态。
    • 如果LPDS位为0,则电压调节器保持开启状态。
    • 如果LPDS位为1,则电压调节器进入低功耗模式,虽然更省电,但唤醒延迟更长。

手册/天书讲解环节请看VCR

system_stm32f10x.c/.h文件讲解

代码实战:修改主频&睡眠模式&停止模式&待机模式

  • 修改主频
    请添加图片描述
    在这里插入图片描述

最好不要去中途改主频,因为那些外设初始化时都是根据SystemCoreClock来的,就运行一次,主频改完后得重新配置外设初始化那些

  • 睡眠模式+串口发送+接收
    在这里插入图片描述

main.c

#include "stm32f10x.h"                 // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
uint8_t RxData;
uint8_t Pin_9, Pin_10;
int main(void)
{
    OLED_Init();
    Serial_Init();
    OLED_ShowString(1, 1, "RxData:");
    while (1)
    {
        if (Serial_GetRxFlag() == 1)
        {
            RxData = Serial_GetRxData();
            Serial_SendByte(RxData);
            OLED_ShowHexNum(1, 8, RxData, 2);
        }
        // 没有数据要发送但代码一直执行所以可以采用睡眠模式
        OLED_ShowString(2, 1, "Running...");
        Delay_ms(500);
        OLED_ShowString(2, 1, "         ");
        Delay_ms(500);
        __WFI(); // 进入睡眠,中断唤醒
        //执行WFI这时CPU会立刻睡眠,程序就停在了WFI指令这里,但是各个外设比如USRT还是工作状态
		//等到我们用串口助手发送数据时,USRT外设收到数据产生中断,唤醒CPU之后程序在暂停的地方继续运行
    }
}

  • 停止模式+对射式红外传感器计次
    在这里插入图片描述
    在这里插入图片描述

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();			//OLED初始化
	CountSensor_Init();		//计数传感器初始化
	
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟
															//停止模式和待机模式一定要记得开启
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Count:");
	
	while (1)
	{
		OLED_ShowNum(1, 7, CountSensor_Get(), 5);			//OLED不断刷新显示CountSensor_Get的返回值
		
		OLED_ShowString(2, 1, "Running");					//OLED闪烁Running,指示当前主循环正在运行
		Delay_ms(100);
		OLED_ShowString(2, 1, "       ");
		Delay_ms(100);
		
		PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);	//STM32进入停止模式,并等待中断唤醒
		SystemInit();										    //唤醒后,要重新配置时钟,重启HSE配置72M主频
		//退出停止模式时,HSI被选为系统时钟,也就是在我们首次复位后,SystemInit函数里配置的是HSE*9倍频的72M主频
		//所以复位后第一次Running闪烁很快,而之后进入停止模式,再退出时默认时钟就变成HSI了,HSI是8M,所以唤醒之后的程序运行就会明显变慢
	}
}

  • 待机模式+实时时钟
    在这里插入图片描述
    main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	MyRTC_Init();		//RTC初始化
	
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟
															//停止模式和待机模式一定要记得开启,虽然MyRTC_Init里开启了,多次开启无所谓,防止其他没调用MyRTC_Init的场景   但时钟没开启外设就不会工作
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "CNT :");//秒计数器
	OLED_ShowString(2, 1, "ALR :");//闹钟值
	OLED_ShowString(3, 1, "ALRF:");//闹钟标志位
	
	/*使能WKUP引脚*/
	PWR_WakeUpPinCmd(ENABLE);						//使能位于PA0的WKUP引脚,WKUP引脚上升沿唤醒待机模式
	//手册里PWR_CSR的寄存器描述,这里写了使能wake up引脚后,wake up引脚被强制为输入下拉的配置,所以不用再GPIO初始化了
	
	/*设定闹钟*/
	uint32_t Alarm = RTC_GetCounter() + 10;			//闹钟为唤醒后当前时间的后10s
	RTC_SetAlarm(Alarm);							//写入闹钟值到RTC的ALR寄存器 这个寄存器只写不可读,所以使用变量Alarm显示到OLED上
	OLED_ShowNum(2, 6, Alarm, 10);					//显示闹钟值
	
	while (1)
	{
		OLED_ShowNum(1, 6, RTC_GetCounter(), 10);	//显示32位的秒计数器
		OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);		//显示闹钟标志位
		
		OLED_ShowString(4, 1, "Running");			//OLED闪烁Running,指示当前主循环正在运行
		Delay_ms(100);
		OLED_ShowString(4, 1, "       ");
		Delay_ms(100);
		
		OLED_ShowString(4, 9, "STANDBY");			//OLED闪烁STANDBY,指示即将进入待机模式
		Delay_ms(1000);
		OLED_ShowString(4, 9, "       ");
		Delay_ms(100);
		
		OLED_Clear();								//OLED清屏,模拟关闭外部所有的耗电设备,以达到极度省电
													
		PWR_EnterSTANDBYMode();						//STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟)
		/*待机模式唤醒后,程序会重头开始运行*/
		//待机模式之后的代码执行不到,下次继续从头开始 在程序刚开始的时候自动调用SystemInit初始化时钟,所以待机模式我们就不用像停止模式那样,自己调用SystemInit了
		//并且这个while循环,实际上也只有执行一遍的机会,把这个while循环去掉也是可以的
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到