目录
PWR简介
- PWR(Power Control)电源控制
- PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
- 可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD阀值之上时,PVD会触发中断,用于执行紧急关闭任务
- 低功耗模式包括睡眠模式(Sleep)、停机模式(Stop)和待机模式(Standby),可在系统空闲时,降低STM32的功耗,延长设备使用时间
电源框图
上电复位和掉电复位
这个了解即可,这个意思是当VDD或者VDDA电压过低时内部电路直接产生复位,这个复位与不复位的界限之间设置了一个40mV的迟滞电压,大于上限POR时解除复位,小于下限PDR时复位。设置两个阈值就是防止电压在某个阈值附近波动时造成输出也来回抖动。下面的复位信号低电平有效。
可编程电压监测器
了解即可,与上面差不多,都是监测VDD与VDDA的供电电压,PVD的区别就是首先它的阈值电压是可以使用程序指定的,PVD触发之后芯片还能正常工作,只不过电源电压过低该提醒一下用户了。电压过低为1,电压正常为0,是正逻辑。这个信号可以申请中断,在上升沿或者下降沿触发中断,提醒程序进行适当处理。中断申请通过外部中断实现。
低功耗模式
首先睡眠模式,直接调用WFI或者WFE即可进入,这两个东西是内核的指令,对应库函数里有对应的函数,直接调用函数即可。其中WFI的意思是Wait For Interrupt等待中断,对应的唤醒条件是任一中断,调用WFI进入的睡眠模式任何外设发生任何中断,芯片都会立刻醒来。WFE意思是Wait For Event等待事件,对应的唤醒条件是唤醒事件,这个事件可以是外部中断配置为时间模式也可以是使能了中断但是没有配置NVIC,调用WFE进入的睡眠模式产生唤醒事件时就会立刻醒来。睡眠模式对电路的影响是只把CPU时钟关了,对其他电路没有任何操作,CPU时钟关了程序就会暂停,芯片功耗就会降低。
停机模式,如何进入呢?首先SLEEPDEEP位设置为1,告诉CPU你可以放心睡进入深度睡眠模式,另外PDDS这一位用来区分它是停机模式还是下面的待机模式,PDDS=0进入停机模式,PDDs=1进入待机模式,所以要进入停机模式PDDS要事先设置为0。LPDS用来设置最后这个电压调节器,是开启还是进入低功耗模式,LPDS=0电压调节器开启。这些位设置好最后再调用WFI或者WFE就可以进入停机模式了,然后停止模式唤醒是任一外部中断,前文睡眠模式是任一中断,选择停止模式要求就是只有外部中断才能唤醒了。对电路的影响是不仅CPU不能运行了,外设也运行不了,定时器正在定时的会暂停,串口收发数据也会暂停,不过由于没关闭电源,所以CPU和外设的寄存器数据都是维持原状的,HSI(内部高速时钟)和HSE(外部高速时钟)的振荡器关闭,LSI(内部低速时钟)和LSE(内部低速时钟)这两个并不会主动关闭,如果开启过这两个时钟还可以进行运行。
待机模式,进入和停机模式差不多,首先SLEEPDEEP位也是设置为1,即深度睡眠,然后RDDS置1,表示即将进入待机模式,最后调用WFI或者WFE就可以进入待机模式了。唤醒条件普通外设的中断和外部中断都无法唤醒待机模式,待机模式只有这几个指定的信号才能唤醒,第一个是WKUP引脚上升沿,第二个是RTC闹钟事件,第三个是NRST引脚上的外部复位意思就是按一下复位按键它也是能唤醒的,最后一个IWDG独立看门狗复位。对电路的操作基本上是能关的都关了,内部存储器和寄存器的数据全部丢失,但是和停止模式一样不会主动关闭LSI和LSE两个低速时钟,因为这两个时钟还要维持RTC和独立看门狗运行。
模式选择
- 执行WFI(Wait For Interrupt)或者WFE(Wait For Event)指令后,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复位退出待机模式
低功耗模式应用
睡眠模式
在串口发送+接收工程的基础上加入低功耗代码,串口发送+接收见:STM32学习笔记07-USART串口通信_stm32串口接收数据-CSDN博客
假设要用STM32做一个下位机,下位机接收电脑串口发送过来的指令,然后执行相应的功能,电脑随时都可能通过串口发送指令,也可以很久的不发指令,为了随时能响应指令,STM32就得时刻准备着,比如原来的代码主循环就一直在不断检查标志位,但是一直不发指令这样就没啥意义还费电,当然你可能说把这段代码放中断就好了,但是即使主循环是空的它CPU也是在不断耗电的,所以对这种靠中断触发,没有中断的时候就没什么事的代码我们就可以给它加入低功耗模式。
我们只需要在main.c的while循环最后加上__WFI()即可:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData;
int main(void)
{
OLED_Init();
Serial_Init();
Serial_SendByte(0x41);
uint8_t MyArray[] = {0x42,0x43,0x44,0x45};
Serial_SendArray(MyArray, 4);//发送数组
Serial_SendString("\r\nNum1=");//发送字符串
Serial_SendNumber(111,3);//发送数字(十进制显示)
printf("\r\nNum2=%d",222);//printf封装1
char String[100];
sprintf(String, "\r\nNum3=%d",333);//printf封装2
Serial_SendString(String);
Serial_Printf("\r\nNum4=%d",444);//printf封装3
Serial_Printf("\r\n");
OLED_ShowString(1, 1, "RxData:");
while(1)
{
/* 查询方法 */
// if (USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
// {
// RxData = USART_ReceiveData(USART1);
// OLED_ShowHexNum(1, 1, RxData, 2);
// }
if(Serial_GetRxFlag() == 1)//接收
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);//数据回传,接收到的数据回传到电脑
OLED_ShowHexNum(1, 8, RxData, 2);
}
OLED_ShowString(2, 1, "Runing");
Delay_ms(100);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
__WFI();
}
}
执行流程是首先初始化把串口配置好,接着发送各个数据一次,进入主循环后,检查标志位,Running闪烁一次,主循环的最后执行WFI,这时候CPU就会立刻进入睡眠模式,程序停在WFI指令这里,这时CPU睡眠但是各个外设比如USART还在工作状态,等到我们用串口助手发送数据时USART外设收到数据产生中断唤醒CPU,睡眠模式唤醒之后程序在暂停的地方继续运行,唤醒之后中断立刻申请,再跳回while循环开头之前先进入USART中断函数,中断函数里读取数据、置RxFlag、清除RXNE,之后回到主循环,这时RxFlag刚刚置1所以if成立,执行数据回传和显示,唤醒的功能执行之后Runing闪烁一次,最后程序又来到WFI的位置,这是一个新的睡眠指令,CPU再次进入睡眠模式。
停止模式
停止模式只能通过外部中断触发唤醒,所以和停止模式相关的代码肯定得用外部中断。我们在对射式红外传感器计次的代码上进行:STM32学习笔记03-EXTI外部中断-CSDN博客
下载程序要按住复位键,按下载,再松手复位键。
先看几个库函数:
void PWR_WakeUpPinCmd(FunctionalState NewState);//使能位于PA0位置的WKUP引脚,配合待机模式使用
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);//进入停止模式,调用这个函数就可以进入停止模式了
void PWR_EnterSTANDBYMode(void);//进入待机模式,调用这个函数就可以进入待机模式了
接下来我们加入停止模式的代码,首先要开启PWR时钟:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
uint8_t Key_Num;
int main(void)
{
OLED_Init();
CountSensor_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//开启时钟
OLED_ShowString(1,1,"count:");
while(1)
{
OLED_ShowNum(1,7,Get_CountSensor_count(),5);
OLED_ShowString(2, 1, "Runing");
Delay_ms(100);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
//退出停止模式时,HSI被选为系统时钟,也就是在我们首次复位后SystemInit函数里的配置是HSE*9的72MHz主频
SystemInit();//进入停止模式再退出时默认时钟就变成HSI了,HSI是8M,所以我们退出停止模式后重新调用SystemInit配置72MHz主频
}
}
执行流程与上一个类似。
待机模式
我们在实时时钟代码上加入待机模式:STM32学习笔记10-RTC实时时钟-CSDN博客
我们的主要任务就是,第一设置RTC闹钟,第二进入待机模式,第三使用闹钟信号唤醒待机模式。
首先精简一下main布局:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
OLED_ShowString(1, 1, "CNT :");
OLED_ShowString(2, 1, "ALR :");
OLED_ShowString(3, 1, "ALRF:");
while(1)
{
OLED_ShowNum(1, 6,RTC_GetCounter(), 10);
}
}
首先设定闹钟为10秒后,随着CNT增大,CNT会与ALR相等,然后触发闹钟标志位置1,如果开启了闹钟中断那还会进一步进入中断函数,这个代码我们就不开中断了,直接显示闹钟标志位,看看闹钟响的时候标志位会不会置1。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
OLED_ShowString(1, 1, "CNT :");
OLED_ShowString(2, 1, "ALR :");
OLED_ShowString(3, 1, "ALRF:");
uint32_t Alarm = RTC_GetCounter() + 10;//设定闹钟为10秒后
RTC_SetAlarm(Alarm);
OLED_ShowNum(2, 6, Alarm, 10);//闹钟显示
while(1)
{
OLED_ShowNum(1, 6,RTC_GetCounter(), 10);
OLED_ShowNum(3, 6,RTC_GetFlagStatus(RTC_FLAG_ALR), 1);//显示闹钟标志位
OLED_ShowString(4, 1, "Runing");
Delay_ms(100);
OLED_ShowString(4, 1, " ");
Delay_ms(100);
}
}
接下来加入待机模式代码:
要进入待机模式,我们需要使用PWR外设,使用PWR外设之前要开启时钟。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//开启PWR时钟
OLED_ShowString(1, 1, "CNT :");
OLED_ShowString(2, 1, "ALR :");
OLED_ShowString(3, 1, "ALRF:");
uint32_t Alarm = RTC_GetCounter() + 10;//设定闹钟为10秒后
RTC_SetAlarm(Alarm);
OLED_ShowNum(2, 6, Alarm, 10);//闹钟显示
while(1)
{
OLED_ShowNum(1, 6,RTC_GetCounter(), 10);
OLED_ShowNum(3, 6,RTC_GetFlagStatus(RTC_FLAG_ALR), 1);//显示闹钟标志位
OLED_ShowString(4, 1, "Runing");
Delay_ms(100);
OLED_ShowString(4, 1, " ");
Delay_ms(100);
PWR_EnterSTANDBYMode();//进入待机模式
}
}
经过实测,很多芯片唤醒后ALEF并不会置1,不必纠结能唤醒就没问题。每次唤醒闹钟值都重新设定,通过这一现象我们可以确定待机模式唤醒后程序是从头开始执行,因为我们闹钟设定的代码在while循环之前,如果不是从头开始那闹钟值也不会更新。在进入待机模式之后,高速时钟也都会关闭,在退出待机模式时,程序从头开始执行在程序刚开始的时候自动调用SystemInit初始化时钟,所以待机模式我们就不用像停止模式那样自己调用SystemInit了。
待机模式对标的是极度省电,光STM32自己一个省电那也不行,所以一般进入待机模式之前我们要尽可能把外挂模块都关掉,这个需要精心设计硬件电路,可以选一个带有使能端的稳压器实现或者自己设计电路把其他模块的供电加一个开关。程序我们就简单模拟一下,在进入待机模式之前OLED_Clear清屏一下。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//开启PWR时钟
OLED_ShowString(1, 1, "CNT :");
OLED_ShowString(2, 1, "ALR :");
OLED_ShowString(3, 1, "ALRF:");
uint32_t Alarm = RTC_GetCounter() + 10;//设定闹钟为10秒后
RTC_SetAlarm(Alarm);
OLED_ShowNum(2, 6, Alarm, 10);//闹钟显示
while(1)
{
OLED_ShowNum(1, 6,RTC_GetCounter(), 10);
OLED_ShowNum(3, 6,RTC_GetFlagStatus(RTC_FLAG_ALR), 1);//显示闹钟标志位
OLED_ShowString(4, 1, "Runing");
Delay_ms(100);
OLED_ShowString(4, 1, " ");
Delay_ms(100);
OLED_ShowString(4, 9, "STANDBY");//待机模式提示信息
Delay_ms(1000);
OLED_ShowString(4, 9, " ");
Delay_ms(1000);
OLED_Clear();//清屏模拟OLED关电
PWR_EnterSTANDBYMode();//进入待机模式
}
}