目录
在学习STM32待机唤醒功能之前,我们首先来系统的学习32单片机的低功耗模式,这将对于我们理解睡眠/停止/待机模式有很大的帮助。
1. 低功耗模式
默认情况下,系统复位或者上电复位后,MCU微控制器进入运行模式。在运行模式下,CPU通过HCLK提供时钟,并且执行程序代码。系统提供了多个低功耗模式,可在CPU不需要运行时(例如等待外部事件时)节省功耗。由用户根据应用选择具体的低功耗模式,以在低功耗、短启动时间和可用唤醒源之间寻求最佳平衡。
STM32有三种低功耗模式:
- 睡眠模式(Cortex -M4F内核停止,外设保持运行)
- 停止模式(所有时钟都停止)
- 待机模式(1.2V域断电)
此外,还可以通过其他方式降低功耗:
1. 降低系统时钟速度
2. 不使用APBx和AHBx外设时,将对应的外设时钟关闭
其中:嵌入式线性调压器为备份域和待机电路以外的所有数字电路供电。调压器输出电压约为1.2V。
1.1 降低系统时钟速度
在运行模式下,可以通过对预分频寄存器编程来降低系统时钟(SYSCLK、HCLK、PCLK1和PCLK2)速度。进入睡眠模式之前,也可以使用这些预分频器降低外设速度。
1.2 外设时钟门控
在运行模式下,可以随时的停止各个外设和存储器的HCLKx和PCLKx以降低功耗。要进一步的降低睡眠模式的功耗,可在执行WFI或者WFE指令之前禁止外设时钟。
外设时钟门控由AHB1外设时钟使能寄存器RCC_AHB1ENR、AHB2外设时钟使能寄存器RCC_AHB2ENR和AHB3外设时钟使能寄存器RCC_AHB3ENR进行控制。
在睡眠模式下,复位RCC_AHBxLPENR和RCC_APBxLPENR寄存器中的对应位可以自动禁止外设时钟。
2. 睡眠模式
2.1 进入睡眠模式
执行WFI(等待中断)或者WFE(等待事件)指令即可进入睡眠模式。根据M4F内核系统控制寄存器中的SLEEPONEXIT位的设置,可以通过两种方案选择睡眠模式进入机制。
立即休眠:如果SLEEPONEXIT位清零,MCU将在执行WFI和WFE指令时立即进入睡眠模式。
退出时休眠:如果SLEEPONEXIT位置1,MCU将在退出优先级最低的ISR时立即进入睡眠模式。(ISR的全称是Interrupt Service Routines,也就是中断服务,中断是有NVIC中断优先级的,这里的意思就是当执行完中断中优先级最低的中断以后进入休眠)
2.2 退出休眠模式
因为进入睡眠模式是由指令WFI和指令WFE控制的。所以退出休眠模式也应该从这两方面入手。
如果使用WFI指令进入睡眠模式,则通过中断控制器NVIC确认的任意外设中断都会将器件从睡眠模式唤醒。
如果使用WFE指令进入睡眠模式,MCU将在有事件发生时立即退出睡眠模式。
唤醒事件可以通过以下方式产生:
在外设的控制寄存器使能一个中断,但不在NVIC中使能,同时使能M4F内核系统控制寄存器中的SEVONPEND位。当MCU从WFE恢复时,需要清除相应外设的中断挂起位和外设NVIC中断通道挂起位(在NVIC中断清除挂起寄存器中)。
配置一个外部或者内部EXTI线为事件模式。当CPU从WFE恢复时,因为对应事件线的挂起位没有被置位,不必清除相应外设的中断挂起位或NVIC中断通道挂起位。
3. 停止模式
停止模式基于M4内核深度睡眠模式与外设时钟门控。调压器既可以配置为正常模式,也可以配置为低功耗模式。在停止模式下,1.2V域中的所有时钟都会停止,PLL、HSI和HSE RC振荡器也会被禁止。内部SRAM和寄存器内容将会被保留。
将PWR_CR电源控制寄存器中的FPDS位置1后,Flash闪存还会在器件进入停止模式时进入掉电状态。Flash处于掉电模式时,将器件从停止模式唤醒将需要额外的启动延时。
3.1 进入停止模式
要进一步降低停止模式的功耗,可将内部调压器设置为低功耗模式。通过对STM32F4的PWR电源控制寄存器(PWR_CR)的LPDS位进行配置。
如果正在进行Flash编程,停止模式的进入将延迟到存储器访问结束后执行。
如果正在访问APB域,停止模式的进入则延迟到APB访问结束后执行。
在停止模式下,可以通过对各个控制位进行编程来选择以下功能:
独立看门狗IWDG:IWDG通过写入其密钥寄存器或使用硬件选项来启动。而且一旦启动便无法停止,除非复位。
实时时钟RTC:通过RCC备份域控制寄存器RCC_BDCR中的RTCEN位进行配置。
内部RC振荡器(LSI RC):通过RCC时钟控制和状态寄存器(RCC_CSR)中的LSION位进行配置。
外部32.768KHz振荡器(LSE OSC):通过RCC备份域控制寄存器RCC_BDCR中的LSEON位进行配置。
在停止模式下,ADC和DAC也会产生功耗,除非在进入停止模式前将其禁止。要禁用这些转换器,必须将ADC_CR2寄存器的ADON位和DAC_CR寄存器中的ENx位都清零。
3.2 退出停止模式
通过发出中断或者唤醒事件退出停止模式时,将选择HSI RC振荡器作为系统时钟。
4. 待机模式
待机模式下可达到最低功耗。待机模式基于M4内核深度睡眠模式,该模式在深度睡眠模式时关闭电压调节器。因此1.2V域断电。PLL、HSI振荡器也将关闭。除备份域(RTC寄存器、RTC备份寄存器和备份SRAM)和待机电路中的寄存器外,SRAM和寄存器内容都将丢失。
4.1 进入待机模式
在待机模式下,可以通过对各控制位进行编程来选择以下功能:
●独立的看门狗 (IWDG):IWDG 通过写入其密钥寄存器或使用硬件选项来启动。而且一 旦启动便无法停止,除非复位。
●实时时钟 (RTC):通过备份域控制寄存器 (RCC_BDCR) 中的 RTCEN 位进行配置。
●内部 RC 振荡器 (LSI RC):通过控制/状态寄存器 (RCC_CSR) 中的 LSION 位进行配置。
●外部 32.768 kHz 振荡器 (LSE OSC):通过备份域控制寄存器 (RCC_BDCR) 中的 LSEON 位进行配置。
4.2 退出待机模式
检测到外部复位(NRST引脚)、IWDG引脚、WKUP引脚上升沿、RTC闹钟、入侵事件或时间戳事件时,微控制器退出待机模式。从待机模式唤醒后,除PWR电源控制/状态寄存器PWR_CSR外,所有寄存器都将复位。
从待机模式唤醒后,程序将按照复位(启动引脚采样、复位向量已获取等)后的方式重新执行。PWR电源控制/状态寄存器PWR_CSR中的SBF状态标志指示MCU已处于待机模式。
从待机模式唤醒后的代码执行等同于复位后的执行(采样启动模式引脚,读取复位向量等)。电源控制/状态寄存器PER_CSR将会指示内核由待机状态退出。意思就是说从待机模式退出后等同于按下开发板上的复位键,执行相对于的代码。
在进入待机模式后,除了复位引脚、RTC_AF1引脚(PC13)(如果针对入侵、时间戳、RTC闹钟输出或RTC时钟校准输出进行了配置)和WK_UP(PA0)(如果使能了)等引脚外,其他所有IO引脚都将处于高阻态。
总结:在这三种低功耗模式中,最低功耗的是待机模式,在此模式下,最低只需要2.2uA左右的电流。停机模式是次低功耗的,其典型的电流消耗在350uA左右。最后就是睡眠模式了。
4.3 电源控制寄存器:PWR_CR
通过对进入待机模式的学习,我们已经清楚了需要将电源控制寄存器PWR_CR的 位1 和 位2 置1,使器件在CPU进入深度睡眠时进入待机模式,并且将WUF唤醒标志清零。
4.4 电源控制/状态寄存器:PWR_CSR
这里我们需要 位8 置1,从待机模式中唤醒器件;也可以通过位0来检测是否收到了唤醒标志。
5. 库函数配置进入待机模式
首先注意:对于已经使能了RTC中断或者RTC唤醒中断的情况,必须先禁止中断,清除相关中断标志位,清除唤醒中断WK_UP,等一切都完成以后,再次使能中断,进入低功耗模式。具体如下:
1. 禁止RTC中断(ALRAIE、ALRBIE闹钟A和B、WUTIE、TAMPIE和TSIE等)
2. 清零对应中断标志位
3. 清除PWR唤醒(WUF)标志(通过设置PWR_CR的CWUF位实现)
4. 重新使能RTC对应中断
5. 进入低功耗模式
1. 使能电源时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //使能PWR外设时钟
2. 设置WK_UP(也就是PA0引脚,KEY_UP按键)引脚作为唤醒源
PWR_WakeUpPinCmd(ENABLE); //使能KEY_UP按键唤醒功能 ,用KEY_UP将CPU从待机模式唤醒
3. 设置SLEERDEEP(深度睡眠)位,设置PDDS位,执行WFI指令,进入待机模式
进入待机模式,首先设置SLEEPDEEP位,接着通过PWR_CR设置PDDS位,使得CPU进入深度睡眠时进入待机模式,最后执行WFI指令开始进入待机模式,并等待WK_UP(KEY_UP)中断的到来;
void PWR_EnterSTANDBYMode(void); //进入待机模式
4. 编写WK_UP中断函数
通过WK_UP中断(PA0中断)来唤醒CPU,同时通过该函数进入待机模式。
6. 实验程序
该实验程序实现功能:长按3秒KEY_UP按键开机,通过LED0指示灯指示程序开始运行,再次长按按键,进入待机模式,LED0关闭,程序停止运行。类似于手机开关机。
6.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "usmart.h"
#include "Key.h"
#include "WKUP.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统分组中断优先级2
delay_init(168);
uart_init(115200);//这里切记先初始化串口,否则无法显示实验现象
LED_Init();
LCD_Init();
WKUP_Init();
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"WKUP Test");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2023/20/23");
LCD_ShowString(30,130,200,16,16,"WK_UP:Standby/WK_UP");
while(1)
{
LED0=!LED0;
delay_ms(250);
}
}
6.2 WKUP.c
#include "stm32f4xx.h"
#include "WKUP.h"
#include "Key.h"
#include "LED.h"
#include "delay.h"
//系统进入待机模式
void Sys_Enter_Standby(void)
{
while(WKUP_KD);//while循环内设置为空,等待KEY_UP按键松开,跳过while循环
RCC_AHB1PeriphResetCmd(0x04FF,ENABLE);//复位所有IO口
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能PWR电源时钟
PWR_BackupAccessCmd(ENABLE);//使能后备域寄存器,RTC实时时钟中介绍过该寄存器
RTC_ITConfig(RTC_IT_ALRB|RTC_IT_ALRA|RTC_IT_TS|RTC_IT_WUT,DISABLE);//禁止RTC相关中断,这些中断可能在RTC实验中开启了
RTC_ClearITPendingBit(RTC_IT_ALRB|RTC_IT_ALRA|RTC_IT_TS|RTC_IT_WUT);//清除相关中断标志位
PWR_ClearFlag(PWR_FLAG_WU);//清除Wake_up唤醒标志
PWR_WakeUpPinCmd(ENABLE);//使能KEY_UP按键唤醒功能 ,用KEY_UP将CPU从待机模式唤醒
PWR_EnterSTANDBYMode();//进入待机模式
}
//检测WKUP脚的信号
//返回值1:连续按下3s以上
// 0:错误的触发
u8 Check_WKUP(void)
{
u8 t=0;//记录时间
u8 tx=0;//记录松开的次数
LED0=0;//LED0点亮
while(1)
{
if(WKUP_KD)//KEY_UP按键按下
{
t++; //记录KEY_UP按键按下的时间,需要去判断这个时间的长短,就跟我们的手机关机一样,不会只是按一下电源键就关机了,需要按下保持一段时间
tx=0; //只要进入if循环语句,那么按键就一直被按下,所以松开次数始终都是零
}
else //松开KEY_UP
{
tx++;//松开的次数++
if(tx>3)//如果松开的次数大于3,超过90s内没有WKUP信号
{
LED0=1; //LED0熄灭
return 0;//错误按键,返回
//这里的错误的意思是:还是我们手机关机的例子,假设需要长按电源键5秒关机,如果我们在5秒内频繁的松开按键,就一定不会关机了;直接一点,松开一次按键就不会关机了
//也就表示用户取消了关机,return 0;
}
}
delay_ms(30);
if(t>=100)//按下超过3秒钟 表示按键超过了预设的时间,那么进入待机模式
{
LED0=0;
return 1;//按键3s以上了,进入待机模式
}
}
}
//中断,检测到PA0脚上的一个上升沿
//中断线0上的中断检测
void EXTI0_IRQHandler(void)
{
EXTI_ClearITPendingBit(EXTI_Line0);//清除LINE0上的中断标志位
if(Check_WKUP())//表示检测3秒以上的函数返回值为1
{
Sys_Enter_Standby();//进入待机模式
}
}
//PA0 KEY_UP 唤醒中断初始化
void WKUP_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);//使能SYSCFG时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN;//按键的模式是输入
GPIO_InitStructure.GPIO_OType=GPIO_OType_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//按键KEY_UP下拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
if(Check_WKUP()==0) //也是和我们手机开机一样,手机开机也是需要长按电源键,如果在长按电源键的期间,频繁的松开电源键,此时手机是开不开机的;
//开发板也是这样,初始化时先检测如果是非正常开机,那么直接进入待机模式
{
Sys_Enter_Standby();//不是正常的开机,进入待机模式
}
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);//PA0连接到中断线0
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
EXTI_Init(&EXTI_InitStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;//外部中断0
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;
NVIC_Init(&NVIC_InitStructure);
}
6.3 WKUP.h
#ifndef _WKUP__H_
#define _WKUP__H_
#define WKUP_KD PAin(0)
void Sys_Enter_Standby(void);
u8 Check_WKUP(void);
void EXTI0_IRQHandler(void);
void WKUP_Init(void);
#endif