一、低功耗模式简介
系统提供了多个低功耗模式,可在 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
中把这些指令封装成了函数。
/** brief 等待中断
等待中断 是一个暂停执行指令
暂停至任意中断产生后被唤醒
*/
#define __WFI __wfi
/** brief 等待事件
等待事件 是一个暂停执行指令
暂停至任意事件产生后被唤醒
*/
#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 WKUP按键唤醒
3.1.1 流程图
3.1.2 HAL库与标准库代码比较
STM32CubeMX 使用 HAL 库的代码:
int main(void)
{
// 检测复位来源
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n 待机唤醒复位 \r\n");
}
else
{
printf("\r\n 非待机唤醒复位 \r\n");
}
···
while(1)
{
···
HAL_Delay(5000);
/*清除 WU 状态位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引脚的唤醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
···
}
}
使用 STM32 标准库的代码:
int main(void)
{
/* 使能电源管理单元的时钟,必须要使能时钟才能进入待机模式 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 检测复位来源
if(PWR_GetFlagStatus(PWR_FLAG_WU) == SET)
{
printf("\r\n 待机唤醒复位 \r\n");
}
else
{
printf("\r\n 非待机唤醒复位 \r\n");
}
···
while(1)
{
···
Delay(0xFFFF);
/*清除 WU 状态位*/
PWR_ClearFlag(PWR_FLAG_WU);
/* 使能 WKUP 引脚的唤醒功能 ,使能 PA0*/
PWR_WakeUpPinCmd(ENABLE);
/* 进入待机模式 */
PWR_EnterSTANDBYMode();
···
}
}
3.1.3 添加WKUP按键
添加系统唤醒按键 PA0
,以便当系统进入待机模式的时候可以通过按键来唤醒。
或者勾选
System Wake-Up
3.1.4 添加LED灯
添加绿灯 PB0
表示本次复位是上电或引脚复位,蓝灯 PB1
表示本次是待机唤醒的复位。
查看 STM32CubeMX学习笔记(2)——GPIO接口使用
3.1.5 添加串口打印
添加 USART1
用于打印信息。
查看 STM32CubeMX学习笔记(6)——USART串口使用
3.1.6 生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的
’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
3.1.7 修改main函数
程序中首先初始化了系统时钟、LED 灯及串口以便用于指示芯片的运行状态,由于待机模式唤醒使用 WKUP 引脚并不需要特别的引脚初始化。
使用库函数 __HAL_PWR_GET_FLAG 检测 PWR_FLAG_SB 标志位,当这个标志位为 SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。
我们利用这个区分两种复位形式,分别使用蓝色 LED 灯或绿色 LED 灯来指示。在使用库函数 HAL_PWR_EnableWakeUpPin 发送待机命令前,要先使用库函数 __HAL_PWR_CLEAR_FLAG 清除 PWR_FLAG_WU 标志位,并且使用库函数 HAL_PWR_EnableWakeUpPin 使能 WKUP 唤醒功能,这样进入待机模式后才能使用 WKUP 唤醒。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("standby mode test\r\n");
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n standby reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
else
{
printf("\r\n normal reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(5000);
/*清除 WU 状态位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引脚的唤醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.2 RTC时钟唤醒
3.2.1 添加RTC时钟
查看 STM32CubeMX学习笔记(14)——RTC实时时钟使用
3.1.2 添加LED灯
添加绿灯 PB0
表示本次复位是上电或引脚复位,蓝灯 PB1
表示本次是待机唤醒的复位。
查看 STM32CubeMX学习笔记(2)——GPIO接口使用
3.1.3 添加串口打印
添加 USART1
用于打印信息。
查看 STM32CubeMX学习笔记(6)——USART串口使用
3.2.4 使能RTC闹钟中断
3.2.5 生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的
’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
3.2.6 添加RTC闹钟中断启动函数
void RTC_AlarmStart(void)
{
RTC_AlarmTypeDef sAlarm = {0};
RTC_TimeTypeDef tim = {0};
// 获取当前时间
HAL_RTC_GetTime(&hrtc, &tim, RTC_FORMAT_BIN);
sAlarm.AlarmTime.Hours = tim.Hours;
sAlarm.AlarmTime.Minutes = tim.Minutes;
sAlarm.AlarmTime.Seconds = tim.Seconds + 3; /* 设置下次闹钟提醒时间是当前时间的3s之后 */
sAlarm.Alarm = RTC_ALARM_A;
// 启动闹钟中断事件
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
3.2.7 修改main函数
程序中首先初始化了系统时钟、LED 灯及串口以便用于指示芯片的运行状态,由于待机模式唤醒使用 WKUP 引脚并不需要特别的引脚初始化。
使用库函数 __HAL_PWR_GET_FLAG 检测 PWR_FLAG_SB 标志位,当这个标志位为 SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。
我们利用这个区分两种复位形式,分别使用蓝色 LED 灯或绿色 LED 灯来指示。在使用库函数 HAL_PWR_EnableWakeUpPin 发送待机命令前,要先使用库函数 __HAL_PWR_CLEAR_FLAG 清除 PWR_FLAG_WU 标志位,并且使用库函数 HAL_PWR_EnableWakeUpPin 使能 WKUP 唤醒功能,这样进入待机模式后才能使用 WKUP 唤醒。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("standby mode test\r\n");
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n standby reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
else
{
printf("\r\n normal reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(5000);
RTC_AlarmStart();
/*清除 WU 状态位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引脚的唤醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四、注意事项
用户代码要加在
USER CODE BEGIN N
和USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
进入低功耗之前可以将引脚全部配置为浮空输入或者Anglog模式,这样最省电,如果你是用STM32CUBEMX,在这里可以看到这么一项配置就是将没有用到的引脚配置为了Anglog模式:
当系统处于睡眠模式低功耗状态时(包括后面讲解的停止模式及待机模式),使用 DAP 下载器是无法给芯片下载程序的,所以下载程序时要先把系统唤醒。或者使用如下方法:按着板子的复位按键,使系统处于复位状态,然后点击电脑端的下载按钮下载程序,这时再释放复位按键,就能正常给板子下载程序了。
• 由 Leung 写于 2021 年 3 月 11 日
• 参考:STM32CubeMX系列教程14:电源控制器(PWR)
STM32MX电源管理低功耗模式
STM32F1系列使用HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例)
《嵌入式-STM32开发指南》第二部分 基础篇 - 第10章 低功耗(HAL库)