一、低功耗模式简介
系统提供了多个低功耗模式,可在 CPU 不需要运行时(例如等待外部事件时)节省功耗。由用户根据应用选择具体的低功耗模式,以在低功耗、短启动时间和可用唤醒源之间寻求最佳平衡。
睡眠模式、停止模式及待机模式中,若备份域电源正常供电,备份域内的 RTC 都可以正常运行,备份域内的寄存器的数据会被保存,不受功耗模式影响。
从表中可以看到,这三种低功耗模式层层递进,运行的时钟或芯片功能越来越少,因而功耗越来越低。
模式名称 | 说明 | 进入方式 | 唤醒方式 | 对1.8V区域时钟的影响 | 对VDD区域时钟的影响 | 调压器 |
---|---|---|---|---|---|---|
睡眠模式 | 内核停止,所有外设包括M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行 | 调用WFI 命令 |
任意中断 | 内核时钟关,对其他时钟和ADC时钟无影响 | 无 | 开 |
睡眠模式 | 内核停止,所有外设包括M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行 | 调用WFE 命令 |
唤醒事件 | 内核时钟关,对其他时钟和ADC时钟无影响 | 无 | 开 |
停止模式 | 所有的时钟都已停止 | 配置PWR_CR寄存器的PDDS +LPDS 位+SLEEPDEEP 位+WFI 或WFE 命令 |
任意外部中断EXTI (在外部中断寄存器中设置) |
关闭所有1.8V区域的时钟 | HSI和HSE的振荡器关闭 | 开启或处于低功耗模式(依据电源控制寄存器的设定) |
待机模式 | 1.8V电源关闭 | 配置PWR_CR寄存器的PDDS +SLEEPDEEP 位+WFI 或WFE 命令 |
WKUP上升沿、引脚的RTC闹钟事件、NRST引脚上的外部复位、IWDG复位 | 关闭所有1.8V区域的时钟 | HSI和HSE的振荡器关闭 | 关 |
1.1 睡眠模式
在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设,CM3 核心的外设全都还照常运行。有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt) 和 WFE(wait for event),即由等待“中断”唤醒和由“事件”唤醒。
特性和说明:
- 立即睡眠: 在执行
WFI
或WFE
指令时立即进入睡眠模式。- 退出时睡眠: 在退出优先级最低的中断服务程序后才进入睡眠模式。
- 进入方式: 内核寄存器的
SLEEPDEEP=0
,然后调用WFI
或WFE
指令即可进入睡眠模式;SLEEPONEXIT=1
时,进入“退出时睡眠”模式。- 唤醒方式: 如果是使用
WFI
指令睡眠的,则可使用任意中断唤醒;如果是使用WFE
指令睡眠的,则由事件唤醒。- 睡眠时: 关闭内核时钟,内核停止,而外设正常运行,在软件上表现为不再执行新的代码。这个状态会保留睡眠前的内核寄存器、内存的数据。
- 唤醒延迟: 无延迟。
- 唤醒后: 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行
WFI
指令后的程序;若由事件唤醒,直接接着执行WFE
后的程序。
1.2 停止模式
在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒,在停止模式中可以选择电压调节器为开模式或低功耗模式。
特性和说明:
- 调压器低功耗模式: 在停止模式下调压器可工作在正常模式或低功耗模式,可进一步降低功耗。
- 进入方式: 内核寄存器的
SLEEPDEEP=1
,PWR_CR 寄存器中的PDDS=0
,然后调用WFI
或WFE
指令即可进入停止模式;PWR_CR 寄存器的LPDS=0
时,调压器工作在正常模式,LPDS=1
时工作在低功耗模式。- 唤醒方式: 如果是使用
WFI
指令睡眠的,可使用任意 EXTI 线的中断唤醒;如果是使用WFE
指令睡眠的,可使用任意配置为事件模式的 EXTI 线事件唤醒。- 停止时: 内核停止,片上外设也停止。这个状态会保留停止前的内核寄存器、内存的数据。
- 唤醒延迟: 基础延迟为 HSI 振荡器的启动时间,若调压器工作在低功耗模式,还需要加上调压器从低功耗切换至正常模式下的时间。
- 唤醒后: 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行
WFI
指令后的程序;若由事件唤醒,直接接着执行WFE
后的程序。唤醒后,STM32 会使用 HSI 作为系统时钟。
1.3 待机模式
待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0)引脚的上升沿,RTC 闹钟事件,NRST 引脚的复位和 IWDG(独立看门狗)复位。
特性和说明:
- 进入方式: 内核寄存器的
SLEEPDEEP=1
,PWR_CR 寄存器中的PDDS=1
,PWR_CR 寄存器中的唤醒状态位WUF=0
,然后调用WFI
或WFE
指令即可进入待机模式。- 唤醒方式: 通过 WKUP 引脚的上升沿,RTC 闹钟、唤醒、入侵、时间戳事件或 NRST 引脚外部复位及 IWDG 复位唤醒。
- 待机时: 内核停止,片上外设也停止;内核寄存器、内存的数据会丢失;除复位引脚、RTC_AF1 引脚及 WKUP 引脚,其它 I/O 口均工作在高阻态。
- 唤醒延迟: 芯片复位的时间。
- 唤醒后: 相当于芯片复位,在程序表现为从头开始执行代码。
1.4 WFI与WFE命令
我们了解到进入各种低功耗模式时都需要调用 WFI
或 WFE
命令,它们实质上都是内核指令,在库文件 core_cm3.h
中把这些指令封装成了函数。
#define __WFI __wfi
#define __WFE __wfe
对于这两个指令,我们应用时一般只需要知道,调用它们都能进入低功耗模式,需要使用函数的格式“__WFI();”和“__WFE();”来调用(因为__wfi 及__wfe 是编译器内置的函数,函数内部调用了相应的汇编指令)。
其中
WFI
指令决定了它需要用中断唤醒,而WFE
则决定了它可用事件来唤醒。
二、新建工程
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
开启 LSE(外部低速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
三、睡眠模式
3.1 WFI任意中断唤醒
3.1.1 流程图
3.1.2 HAL库与标准库代码比较
STM32CubeMX 使用 HAL 库的代码:
// 挂起(暂停)系统时钟中断
HAL_SuspendTick();
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_ResumeTick();
使用 STM32 标准库的代码:
__WFI(); // WFI 指令进入睡眠
3.1.3 添加按键
初始化按键 PA0
中断模式,以便当系统进入睡眠模式的时候可以通过按键来唤醒。
查看 STM32CubeMX学习笔记(3)——EXTI(外部中断)接口使用
3.1.4 添加LED灯
添加绿灯 PB0
表示运行状态,红灯 PB5
表示睡眠状态,蓝灯 PB1
表示刚从睡眠状态中被唤醒。
查看 STM32CubeMX学习笔记(2)——GPIO接口使用
3.1.5 生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的 ’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
3.1.6 修改中断回调函数
当系统进入停止状态后,我们按下实验板上的 KEY1 按键,即可使系统回到正常运行的状态,当执行完中断服务函数后,会继续执行
WFI
指令后的代码。
打开 stm32f1xx_it.c
中断服务函数文件,找到 EXTI0 中断的服务函数 EXTI0_IRQHandler()
中断服务函数里面就调用了 GPIO 外部中断处理函数 HAL_GPIO_EXTI_IRQHandler()
打开 stm32f1xx_hal_gpio.c
文件,找到外部中断处理函数原型 HAL_GPIO_EXTI_IRQHandler()
,其主要作用就是判断是几号线中断,清除中断标识位,然后调用中断回调函数 HAL_GPIO_EXTI_Callback()
。
这个函数不应该被改变,如果需要使用回调函数,请重新在用户文件中实现该函数。
HAL_GPIO_EXTI_Callback()
按照官方提示我们应该再次定义该函数,__weak
是一个弱化标识,带有这个的函数就是一个弱化函数,就是你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;而 UNUSED(GPIO_Pin)
,这就是一个防报错的定义,当传进来的GPIO端口号没有做任何处理的时候,编译器也不会报出警告。其实我们在开发的时候已经不需要去理会中断服务函数了,只需要找到这个中断回调函数并将其重写即可而这个回调函数还有一点非常便利的地方这里没有体现出来,就是当同时有多个中断使能的时候,STM32CubeMX会自动地将几个中断的服务函数规整到一起并调用一个回调函数,也就是无论几个中断,我们只需要重写一个回调函并判断传进来的端口号即可。
接下来我们就在 stm32f1xx_it.c
这个文件的最下面添加 HAL_GPIO_EXTI_Callback()
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 亮蓝灯
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
3.1.7 修改main函数
初始化完成后使用 LED 及串口表示运行状态,LED 灯为绿色时表示正常运行,红灯时表示睡眠状态,蓝灯时表示刚从睡眠状态中被唤醒。
程序执行一段时间后,直接使用
WFI
指令进入睡眠模式,由于WFI
睡眠模式可以使用任意中断唤醒,所以我们可以使用按键中断唤醒。在实际应用中,您也可以把它改成串口中断、定时器中断等。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
// 使用绿灯指示,运行状态
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_SET);
// 任务执行完毕,进入睡眠降低功耗
// 使用红灯指示,进入睡眠状态
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_RESET);
// 暂停滴答时钟,防止通过滴答时钟中断唤醒
HAL_SuspendTick();
// 进入睡眠模式
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
// 被唤醒后,恢复滴答时钟
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_SET);
HAL_ResumeTick();
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_SET);
}
}
四、注意事项
用户代码要加在
USER CODE BEGIN N
和USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
进入低功耗之前可以将引脚全部配置为浮空输入或者Anglog模式,这样最省电,如果你是用STM32CUBEMX,在这里可以看到这么一项配置就是将没有用到的引脚配置为了Anglog模式:
当系统处于睡眠模式低功耗状态时(包括后面讲解的停止模式及待机模式),使用 DAP 下载器是无法给芯片下载程序的,所以下载程序时要先把系统唤醒。或者使用如下方法:按着板子的复位按键,使系统处于复位状态,然后点击电脑端的下载按钮下载程序,这时再释放复位按键,就能正常给板子下载程序了。
• 由 Leung 写于 2021 年 3 月 3 日
• 参考:STM32CubeMX系列教程14:电源控制器(PWR)
STM32MX电源管理低功耗模式
STM32F1系列使用HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例)