STM32 低功耗模式
注意事项
烧录程序
- 芯片在3中低功耗模式下,是没法直接再下载程序的,如果直接点击下载,就会提示报错,这是因为芯片现在在睡眠,不会理调试端口,解决方法也很简单
- 第一步,按住复位键不放
- 第二步,点下载按钮
- 第三步,即使松开复位键
- 点击下载按钮后一定要即使松开复位键,否则会报错了。实测按一下复位键,马上点下载也是可以的
判断寄存器读写特征
- 下图 W 表示只能写入不能读出
- 下图 rw 表示可读可写
- 下图 r 表示只能读不能写
睡眠模式+串口收发数据
- 假设我们目前要用STM32做一个下位机,下位机接收电脑串口发过来的指令,然后执行相应的功能,电脑随时都可能通过串口发送指令,当然也可以几个小时、几天都不发指令,为了随时能相应指令,STM32就得时刻准备着,比如在主循环中,不断地检查标志位,但是如果一直不发指令,这些检查标志位等的操作就没有什么意义,还比较费电。
- 如果把代码放入中断执行,但是即使主循环是空的,CPU也是在不断耗电的,所以对于这种靠中断触发,没有中断的时候,就没什么事的代码,我们就可以加入低功耗模式,没事的时候就低功耗,中断来了,再唤醒运行即可。
模式选择
- 睡眠模式(可以) CPU时钟关闭,程序不再执行,但是外设的时钟不会关,USART硬件电路还是可以接收数据的,USART收到数据后,产生中断,唤醒CPU。
- 停机模式或停止模式(不可以) 所有1.8V区域的时钟均关闭,CPU和外设都不能运行,USART也就不能收到数据,无法产生中断,并且USART的中断也不能唤醒停止模式。
- 待机模式(不可以)
代码示例(USART代码略)
mian.c
SLEEPDEEP和SLEEPONEXIT位使用默认配置,默认为0.
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint16_t Num;
uint8_t RxData;
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"RxData");
Serial_Init();
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();//立刻进入睡眠模式 对应CPU的一个汇编指令 #define __WFI __wfi
}
}
程序执行流程
- 程序开始,初始化,把串口配置好
- 进入主循环,检查标志位,Running闪烁一次
- 在主循环的最后,执行WFI,这时CPU就会立刻睡眠,程序停在WFI指令处。这时CPU睡眠,但是各个外设,比如USART还是工作状态。
- 等到我们用串口助手发送数据时,USART外设收到数据,产生中断,唤醒CPU
- 睡眠模式唤醒之后,程序在暂停的地方继续运行,所以程序会运行到WFI之后,但是唤醒之后,中断也是会立刻申请的,所以程序再跳回到while循环开头之前,先进入USART的中断函数,在中断函数中,读取数据,置RxFlag,清除RXNE。
- 回到while循环的开头,这时RxFlag刚刚置1,所以if成立,执行数据回传和显示的功能,唤醒的功能执行完之后,Running闪烁一次。
- 最后程序又来到WFI的位置,CPU再次进入睡眠。等待下一个数据发过来时,程序再重新执行一遍这个流程。
- 睡眠模式,一般可以在while循环的最后加一个WFI指令,每来一个中断,先进中断函数,再执行一遍程序功能。
待机模式+实时时钟
- 待机模式对标的是极度省电,光STM32自己一个省电,那也不行,所以一般在进入待机模式之前,我们都要尽可能地把外挂模块都关闭,如果外挂模块的耗电远超STM32本身,这时再给STM32待机,不管外部模块的话,那不就是捡了芝麻,丢了西瓜。
- 所以STM32进入待机模式之前,一定要把外部接的模块,能停的都停掉,能断的都断掉,这样才能最大化省电。
程序流程
- 第一步,设置RTC时钟
- 第二步,进入待机模式
- 第三步,使用闹钟信号,唤醒待机模式
待机模式唤醒后,程序是从头开始执行的,进入待机模式之后,高速时钟也都会关闭,在退出待机模式时,程序从头开始执行,在程序刚开始的时候,自动调用SystemInit()初始化时钟,所以待机模式,就不用像停止模式那样,手动调用SystemInit()了。
代码示例
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "MyRTC.h"
#include "Delay.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
//虽然在RTC初始化已经开启了PWR时钟,就把开启PWR时钟的代码省略,那么我们待机模式的代码就会对RTC代码具有依赖性,增大了代 码之间的耦合性,没有RTC待机模式就不能正常工作。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启PWR时钟,保持代码独立性
OLED_ShowString(1,1,"CNT :");
OLED_ShowString(2,1,"ALR :");
OLED_ShowString(3,1,"ALRF:");
PWR_WakeUpPinCmd(ENABLE);//WAKE_UP引脚唤醒 WAKE_UP引脚默认是下拉的,WAKE_UP接高电平的时刻,待机模式就被唤醒了
uint32_t Alarm = RTC_GetCounter()+10;//设定闹钟为10S后
RTC_SetAlarm(Alarm);//设置闹钟值
OLED_ShowNum(2,6,Alarm,10);//显示闹钟值
while(1)
{
OLED_ShowNum(1,6, RTC_GetCounter(),10);//显示秒数 随着CNT的增大,CNT会和ALR相等,然后触发闹钟标志位置1
OLED_ShowNum(3,6, RTC_GetFlagStatus(RTC_FLAG_ALR),10);//显示闹钟标志位
OLED_ShowString(4,1,"Running");
Delay_ms(500);
OLED_ShowString(4,1," ");
Delay_ms(500);
OLED_ShowString(4,9,"STANDBY");
Delay_ms(1000);
OLED_ShowString(4,1," ");
Delay_ms(500);
OLED_Clear();//清屏
PWR_EnterSTANDBYMode();//进入待机模式
}
}
PWR_EnterSTANDBYMode()
- 清除WakeUP标志位
- 选择STANDBY模式,实际上就是把PDDS置1
- SLEEPDEEP置1,进入深度睡眠
- 调用WFI指令,进入待机模式
- 对于待机模式,其实并没有区分WFI和WFE,库函数里也不能选择,统一调用WFI,WFI和WFE的区别就是唤醒方式不同。对于待机模式,它的唤醒条件是指定的4个信号,没有对中断唤醒和事件唤醒做出区分,所以我们就也不用区分了。
void PWR_EnterSTANDBYMode(void)
{
/* Clear Wake-up flag */
PWR->CR |= PWR_CR_CWUF;
/* Select STANDBY mode */
PWR->CR |= PWR_CR_PDDS;
/* Set SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR |= SCB_SCR_SLEEPDEEP;
/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM )
__force_stores();
#endif
/* Request Wait For Interrupt */
__WFI();
}
//在执行完这个函数进入待机模式后,这个函数之后的代码,就再也执行不到了,因为待机模式退出,程序从头开始。