一、什么是低功耗?
低功耗是指通过优化设计和采用特定的技术手段,降低电子设备在运行过程中消耗的能量,从而延长电池寿命、提高性能和减少发热。低功耗设计主要从芯片设计和系统设计两个方面进行,旨在减少所有器件的功率损耗,从而降低总体功耗。
低功耗_百度百科
STM32的低功耗(low power mode)特性是其嵌入式处理器系列的一个重要优势,特别适用于需要长时间 运行且功耗敏感的应用场景,如便携式设备、物联网设备、智能家居系统等。
在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手,提高设备的续行时间。
二、STM32电源系统结构
三、低功耗模式介绍
STM32具有运行、睡眠、停止和待机四种工作模式。
上电后默认是在运行模式,当内核不需要继续运行时,可以选择后面三种低功耗模式。
3.1 睡眠模式(sleep mode)
在睡眠模式下,CPU停止工作,但所有外设(如ADC、通信接口等)仍然运行,时钟继续运转。这适用于需要暂时关闭CPU但外围设备需要继续工作的场景。
模式特点:
- 对系统影响小,但是节能效果最差。
- 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。
进入条件:
- 当系统控制寄存器中的SLEEPDEEP位被清除(通常为0),并且SLEEPONEXIT位根据需求设置时(如果设置为1,则在最低优先级中断处理程序退出时进入Sleep模式;如果为0,则执行WFI或WFE时立即进入)。
- 执行WFI(Wait For Interrupt)或WFE(Wait For Event)指令来进入。
唤醒条件:
- 任意一个中断都能将系统从Sleep模式唤醒。
- 如果执行WFE指令进入Sleep模式,则一旦发生唤醒事件时,MCU将唤醒。
3.2 停机模式(stop mode)
在停机模式下,CPU和核心外围设备的时钟会停止,但部分唤醒源(如外部中断和某些定时器)仍然运行。这适用于需要长时间等待外部事件唤醒的应用,如等待用户输入或外部信号。Stop模式实现了非常低的功耗,同时保留了SRAM和寄存器的内容。
模式特点:
- 节能效果好,程序不会复位。但恢复时间较长(比如震荡器需要重新起震等)。
- 在停机模式下,所有的I/O引脚都保持它们在运行模式时的状态。
- 退出停止模式,HSI RC振荡器被选为系统时钟
进入条件:
- 需要将SLEEPDEEP位设置为1以进入深度睡眠模式,然后通过设置电源控制/状态寄存器(PWR_CSR)中的PDDS位为0来选择进入Stop模式。
- 根据需求设置LPDS位(LPDS = 0:表示在深睡眠模式下,电压调节器保持开启状态;LPDS = 1:表示在深睡眠模式下,电压调节器进入低功耗模式。)。
- 执行WFI(Wait For Interrupt)或WFE(Wait For Event)指令来进入。
- 在进入Stop模式之前,通常需要关闭不必要的外设时钟,并保存需要保留的状态信息。
唤醒条件:
- Stop模式可以通过外部中断(如按键中断、USART接收中断等)唤醒。
- RTC闹钟事件、USB唤醒、以太网(ETH)唤醒等也可以作为唤醒源,但这些通常需要通过外部中断来触发。
3.3 待机模式(standby mode)
在该模式下,CPU、外围设备和时钟都被关闭,只保留唤醒逻辑和备份寄存器。这适用于不需要保留RAM内容且可以从复位状态恢复的设备,常见于需要极低功耗且稀疏唤醒的应用。Standby模式是STM32中功耗最低的模式之一。
模式特点:
- 节能效果最好,但程序会复位,只有少数条件唤醒。
- 在Standby模式下,大部分IO引脚处于高阻态,只有复位引脚、TAMPER引脚(如果配置为防侵入或校准输出)和WKUP引脚可用作唤醒源。
进入条件:
- Standby模式进入前,需要清除电源控制/状态寄存器(PWR_CSR)中的WUF位,以确保没有未处理的唤醒标志。
- 将SLEEPDEEP位设置为1以进入深度睡眠模式,并设置PDDS位为1来选择进入Standby模式。
- 执行WFI或WFE指令进入Standby模式。
唤醒条件:
- Standby模式可以通过WKUP引脚的上升沿唤醒。
- RTC闹钟事件也可以作为唤醒源。
- 独立看门狗(IWDG)复位和NRST引脚上的外部复位也可以唤醒STM32,但这通常用于系统复位而非低功耗唤醒。
图片展示如下:
四、寄存器及库函数介绍
关于低功耗模式都集中在电源控制模块下
4.1 电源控制寄存器(PWR_CR)
4.2 电源控制/状态寄存器(PWR_CSR)
4.3 系统控制寄存器(SCB_SCR)
4.4 库函数
//使能电源时钟(关闭电压调节器)
__HAL_RCC_PWR_CLK_ENABLE();
//使能WKUP引脚唤醒功能
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
//清除唤醒标记,否则会持续唤醒,无法进入待机模式
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
五、低功耗实验
实验目的:
打开项目
加载文件
5.1 睡眠模式:
在lpwr.c函数中
//睡眠模式
void lpwr_enter_sleep(void)
{
HAL_SuspendTick();//使用WFI,必须关闭滴答定时器中断,否则会把整个程序唤醒
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
}
在主函数中
uint8_t i=0;
while(1)
{
if(key_scan() == 2)
{
lpwr_enter_sleep();
}
if(i%20 == 0)
led1_Toggle();
i++;
delay_ms(10);
}
测试睡眠模式时,会出现什么现象?
烧录代码之后led1以200ms的频率疯狂闪烁,此时摁下按键2,就会进入睡眠模式,led1可能会瞬间保持当前状态进入睡眠模式(可能是亮灯,也可能是灭灯状态),然后摁下按键1(WKUP引脚产生上升沿),就会通过中断退出睡眠模式,这时led1再次以200ms的状态疯狂闪烁。
在lpwr.c文件中HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);函数,查看库文件发现:
翻译是:
(电压)调节器:在睡眠模式下是不使用(电压)调节器的,但是为了保持一致这里要使用这个参数。
当使用WFI入口时,如果不希望滴答定时器中断唤醒源,则必须取消勾选滴答中断。
5.2 停机模式:
在lpwr.c函数中
void lpwr_enter_stop(void)
{
//关闭滴答定时器
HAL_SuspendTick();
//点亮led2,使其知道进入了停机模式
led2_ON();
//直接进入停机模式
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
//熄灭led2,使其知道退出了停机模式
led2_OFF();
}
在主函数中
uint8_t i=0;
while(1)
{
if(key_scan() == 2)
{
lpwr_enter_stop();
}
if(i%20 == 0)
led1_Toggle();
i++;
delay_ms(10);
}
测试停机模式,会出现什么现象?
烧录上面代码会发现led1以200ms的速度疯狂闪烁,此时摁下按键2,led1会立刻以当前状态进入停机模式(可能是亮灯,也可能是灭灯状态),并且led1常亮,当摁下按键1时,led1从常亮状态变成熄灭状态,led2会从以200ms的闪烁的状态变成慢速度闪烁。
为什么led2会从以200ms的闪烁的状态变成慢速度闪烁?为什么不是原速度闪烁?
因为停机模式第三条:退出停止模式,HSI RC振荡器被选为系统时钟。在原来振荡器是以系统时钟的频率进行震荡(72MHZ),当进入停止模式之后,再退出一次停止模式,振荡器会选择HSI RC(8MHZ),所以led2灯闪烁的状态会下降,如果需要变回原来的频率,需要在退出停止模式之后,再次配置系统时钟为振荡器。代码如下:
5.3 待机模式:
在lpwr.c函数中
//待机模式
void lpwr_enter_standby(void)
{
//使能电源时钟(关闭电压调节器)
__HAL_RCC_PWR_CLK_ENABLE();
//使能WKUP引脚唤醒功能
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
//清除唤醒标记,否则会持续唤醒,无法进入待机模式
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
//进入待机模式
HAL_PWR_EnterSTANDBYMode();
//测试:看看代码会不会运行到下面
led2_ON();
}
在主函数中
while(1)
{
if(key_scan() == 2)
{
lpwr_enter_standby();
}
if(i%20 == 0)
led1_Toggle();
i++;
delay_ms(10);
}
测试待机模式时,会出现什么现象?
烧录代码之后,led1会以200ms的频率疯狂闪烁,当摁下按键2时,led1会立刻以当前状态进入待机模式(可能是亮灯,也可能是灭灯状态),然后摁下按键1之后,led1再次以200ms的频率疯狂闪烁,并且,在此期间,led2没有亮。
led2为什么没有亮?
退出一次待机模式之后,代码会从主函数重新执行,而不是继续执行,所以led2没有亮。