一、低功耗模式简介
系统提供了多个低功耗模式,可在 CPU 不需要运行时(例如等待外部事件时)节省功耗。由用户根据应用选择具体的低功耗模式,以在低功耗、短启动时间和可用唤醒源之间寻求最佳平衡。
睡眠模式、停止模式及待机模式中,若备份域电源正常供电,备份域内的 RTC 都可以正常运行,备份域内的寄存器的数据会被保存,不受功耗模式影响。
从表中可以看到,这三种低功耗模式层层递进,运行的时钟或芯片功能越来越少,因而功耗越来越低。
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 WFI按键外部中断唤醒
3.1.1 流程图
3.1.2 HAL库与标准库代码比较
STM32CubeMX 使用 HAL 库的代码:
int main(void){ ··· while(1) { ··· // 暂停滴答时钟,防止通过滴答时钟中断唤醒 HAL_SuspendTick(); /* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 */ HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI); SYSCLKConfig_STOP(); // 被唤醒后,恢复滴答时钟 HAL_ResumeTick(); ··· }}static void SYSCLKConfig_STOP(void){ RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0}; uint32_t pFLatency = 0; /* 启用电源控制时钟 */ __HAL_RCC_PWR_CLK_ENABLE(); /* 根据内部 RCC 寄存器获取振荡器配置 */ HAL_RCC_GetOscConfig(&RCC_OscInitStruct); /* 从停止模式唤醒后重新配置系统时钟: 启用 HSE 和 PLL */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { while (1) {; } } /* 根据内部 RCC 寄存器获取时钟配置 */ HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency); /* 选择 PLL 作为系统时钟源, 并配置 HCLK、PCLK1 和 PCLK2 时钟分频系数 */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK) { while (1) {; } }}
使用 STM32 标准库的代码:
int main(void){ ··· while(1) { ··· /* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 */ PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI); // 从停止模式下被唤醒后使用的是 HSI 时钟,此处重启 HSE 时钟,使用 PLLCLK SYSCLKConfig_STOP(); ··· }}static void SYSCLKConfig_STOP(void){ /* 停机唤醒后配置系统时钟 */ /* 使能 HSE */ RCC_HSEConfig(RCC_HSE_ON); /* 等待 HSE 准备就绪 */ while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); /* 使能 PLL */ RCC_PLLCmd(ENABLE); /* 等待 PLL 准备就绪 */ while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); /* 选择 PLL 作为系统时钟源 */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* 等待 PLL 被选择为系统时钟源 */ while (RCC_GetSYSCLKSource() != 0x08);}
3.1.3 添加按键
初始化按键 PA0
中断模式,以便当系统进入睡眠模式的时候可以通过按键来唤醒。 查看 STM32CubeMX学习笔记(3)——EXTI(外部中断)接口使用
3.1.4 添加LED灯
添加绿灯 PB0
表示运行状态,红灯 PB5
表示睡眠状态,蓝灯 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 修改中断回调函数
当系统进入停止状态后,我们按下实验板上的 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()
。
/ NOTE: This function Should not be modified, when the callback is needed, the HAL_GPIO_EXTI_Callback could be implemented in the user file /
这个函数不应该被改变,如果需要使用回调函数,请重新在用户文件中实现该函数。
HAL_GPIO_EXTI_Callback()
按照官方提示我们应该再次定义该函数,__weak
是一个弱化标识,带有这个的函数就是一个弱化函数,就是你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;而 UNUSED(GPIO_Pin)
,这就是一个防报错的定义,当传进来的GPIO端口号没有做任何处理的时候,编译器也不会报出警告。其实我们在开发的时候已经不需要去理会中断服务函数了,只需要找到这个中断回调函数并将其重写即可而这个回调函数还有一点非常便利的地方这里没有体现出来,就是当同时有多个中断使能的时候,STM32CubeMX会自动地将几个中断的服务函数规整到一起并调用一个回调函数,也就是无论几个中断,我们只需要重写一个回调函并判断传进来的端口号即可。
接下来我们就在 stm32f1xx_it.c
这个文件的最下面添加 HAL_GPIO_EXTI_Callback()
/* USER CODE BEGIN 1 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ // 亮蓝灯 HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);}/* USER CODE END 1 */
3.1.8 修改main函数
初始化完成后使用 LED 及串口表示运行状态,LED 灯为绿色时表示正常运行,红灯时表示停止状态,蓝灯时表示刚从停止状态中被唤醒。在停止模式下,I/O 口会保持停止前的状态,所以 LED 彩灯在停止模式时也会保持亮红灯。
程序执行一段时间后,调用库函数 HAL_PWR_EnterSTOPMode
把调压器设置在低功耗模式,进入停止状态。由于 WFI
停止模式可以使用任意 EXTI 的中断唤醒,所以我们可以使用按键中断唤醒。
当系统进入停止状态后,我们按下实验板上的 KEY1 按键,即可唤醒系统,当执行完中断服务函数后,会继续执行 HAL_PWR_EnterSTOPMode
函数后的代码。
/*** @brief The application entry point.* @retval int*/int main(void){/* USER CODE BEGIN 1 */uint32_t SYSCLK_Frequency = 0;uint32_t HCLK_Frequency = 0;uint32_t PCLK1_Frequency = 0;uint32_t PCLK2_Frequency = 0;uint32_t SYSCLK_Source = 0;/* 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("stop mode test\r\n"); / USER CODE BEGIN 2 /
/ USER CODE END 2 /
/ Infinite loop / / USER CODE BEGIN WHILE / 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();// 使能PWR时钟__HAL_RCC_PWR_CLK_ENABLE();// 清除唤醒标记__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);// 进入停止模式HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_SET);// 刚从STOP模式唤醒时钟默认使用内部高速8M时钟,所以需要重新配置时钟SystemClock_Config();// 被唤醒后,恢复滴答时钟HAL_ResumeTick();// 获取重新配置后的时钟状态SYSCLK_Frequency = HAL_RCC_GetSysClockFreq();HCLK_Frequency = HAL_RCC_GetHCLKFreq();PCLK1_Frequency = HAL_RCC_GetPCLK1Freq();PCLK2_Frequency = HAL_RCC_GetPCLK2Freq();SYSCLK_Source = __HAL_RCC_GET_SYSCLK_SOURCE();// 重新配置时钟源后始终状态printf("\r\n 重新配置后的时钟状态:\r\n");printf(" SYSCLK 频率:%d,\r\n HCLK 频率:%d,\r\n PCLK1 频率:%d,\r\n PCLK2 频率:%d,\r\n 时钟源:%d (0 表示 HSI,8 表示 PLLCLK)\n",SYSCLK_Frequency,HCLK_Frequency,PCLK1_Frequency,PCLK2_Frequency,SYSCLK_Source);HAL_Delay(2000);HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_SET);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */
} / USER CODE END 3 / }
## 3.2 RTC时钟唤醒### 3.2.1 添加RTC时钟查看 [STM32CubeMX学习笔记(14)——RTC实时时钟使用](https://blog.csdn.net/qq_36347513/article/details/113663264?spm=1001.2014.3001.5501)### 3.1.2 添加LED灯添加绿灯 `PB0` 表示运行状态,红灯 `PB5` 表示睡眠状态,蓝灯 `PB1` 表示刚从睡眠状态中被唤醒。查看 [STM32CubeMX学习笔记(2)——GPIO接口使用](https://blog.csdn.net/qq_36347513/article/details/112464598)### 3.1.3 添加串口打印添加 `USART1` 用于打印信息。查看 [STM32CubeMX学习笔记(6)——USART串口使用](https://blog.csdn.net/qq_36347513/article/details/112654724?spm=1001.2014.3001.5501)### 3.2.4 使能RTC闹钟中断![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3ded6bb92f545df9c6c2fe7b047cac9~tplv-k3u1fbpfcp-zoom-1.image)### 3.2.5 生成代码输入项目名和项目路径![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b9676406939f431ca0793c7a2220e326~tplv-k3u1fbpfcp-zoom-1.image)选择应用的 IDE 开发环境 MDK-ARM V5![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/256878cde4be462b924c7fdd12535bab~tplv-k3u1fbpfcp-zoom-1.image)每个外设生成独立的 `’.c/.h’` 文件不勾:所有初始化代码都生成在 main.c勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c3c25f8201e84006803e5aa07951c940~tplv-k3u1fbpfcp-zoom-1.image)点击 GENERATE CODE 生成代码![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/62fe6ab47caf4f3199e02cc5a9ef0c1c~tplv-k3u1fbpfcp-zoom-1.image)### 3.2.6 修改中断回调函数打开 `stm32f1xx_it.c` 中断服务函数文件,找到 RTC 闹钟中断的服务函数 `RTC_Alarm_IRQHandler()`中断服务函数里面就调用了 RTC 闹钟中断处理函数 `HAL_RTC_AlarmIRQHandler()`![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96da768d80a54ba699c4b893eb3afa35~tplv-k3u1fbpfcp-zoom-1.image)打开 `stm32f1xx_hal_rtc.c` 文件,找到RTC闹钟中断处理函数原型 `HAL_RTC_AlarmIRQHandler()`,其主要作用就是判断是否RTC中断,清除中断标识位,然后调用中断回调函数 `HAL_RTC_AlarmAEventCallback()`。![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f5de7d99cee4b3785e279dcefce37ba~tplv-k3u1fbpfcp-zoom-1.image)> /* NOTE: This function Should not be modified, when the callback is needed, theHAL_RTC_AlarmAEventCallback could be implemented in the user file */这个函数不应该被改变,如果需要使用回调函数,请重新在用户文件中实现该函数。`HAL_RTC_AlarmAEventCallback()` 按照官方提示我们应该再次定义该函数,`__weak` 是一个弱化标识,带有这个的函数就是一个弱化函数,就是你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;而 `UNUSED(hrtc)` ,这就是一个防报错的定义,当传进来的RTC号没有做任何处理的时候,编译器也不会报出警告。其实我们在开发的时候已经不需要去理会中断服务函数了,只需要找到这个中断回调函数并将其重写即可而这个回调函数还有一点非常便利的地方这里没有体现出来,就是当同时有多个中断使能的时候,STM32CubeMX会自动地将几个中断的服务函数规整到一起并调用一个回调函数,也就是无论几个中断,我们只需要重写一个回调函并判断传进来的端口号即可。接下来我们就在 `stm32f1xx_it.c` 这个文件的最下面添加 `HAL_RTC_AlarmAEventCallback()````cpp/* USER CODE BEGIN 1 */void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){ SystemClock_Config(); // STOP模式唤醒后默认时钟主频为内部8M时钟,所以要先初始化时钟配置 // 亮蓝灯 HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET); printf("3s时间到,唤醒!\r\n");}/* USER CODE END 1 */
3.2.7 添加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.8 修改main函数
初始化完成后使用 LED 及串口表示运行状态,LED 灯为绿色时表示正常运行,红灯时表示停止状态,蓝灯时表示刚从停止状态中被唤醒。在停止模式下,I/O 口会保持停止前的状态,所以 LED 彩灯在停止模式时也会保持亮红灯。
程序执行一段时间后,调用库函数 HAL_PWR_EnterSTOPMode 把调压器设置在低功耗模式,进入停止状态。由于 WFI 停止模式可以使用任意 EXTI 的中断唤醒,所以我们可以使用RTC闹钟中断唤醒。
当系统进入停止状态后,隔 3 秒后闹钟唤醒系统,当执行完中断服务函数后,会继续执行 HAL_PWR_EnterSTOPMode 函数后的代码。
/*** @brief The application entry point.* @retval int*/int main(void){/* USER CODE BEGIN 1 */uint32_t SYSCLK_Frequency = 0;uint32_t HCLK_Frequency = 0;uint32_t PCLK1_Frequency = 0;uint32_t PCLK2_Frequency = 0;uint32_t SYSCLK_Source = 0;/* 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("stop mode test\r\n"); / USER CODE BEGIN 2 /
/ USER CODE END 2 /
/ Infinite loop / / USER CODE BEGIN WHILE / 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();// 配置下次唤醒的闹钟时间RTC_AlarmStart();// 使能PWR时钟__HAL_RCC_PWR_CLK_ENABLE();// 清除唤醒标记__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);// 进入停止模式HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_SET);// 被唤醒后,恢复滴答时钟HAL_ResumeTick();// 获取重新配置后的时钟状态SYSCLK_Frequency = HAL_RCC_GetSysClockFreq();HCLK_Frequency = HAL_RCC_GetHCLKFreq();PCLK1_Frequency = HAL_RCC_GetPCLK1Freq();PCLK2_Frequency = HAL_RCC_GetPCLK2Freq();SYSCLK_Source = __HAL_RCC_GET_SYSCLK_SOURCE();// 重新配置时钟源后始终状态printf("\r\n 重新配置后的时钟状态:\r\n");printf(" SYSCLK 频率:%d,\r\n HCLK 频率:%d,\r\n PCLK1 频率:%d,\r\n PCLK2 频率:%d,\r\n 时钟源:%d (0 表示 HSI,8 表示 PLLCLK)\n",SYSCLK_Frequency,HCLK_Frequency,PCLK1_Frequency,PCLK2_Frequency,SYSCLK_Source);HAL_Delay(2000);HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_SET);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */
} / USER CODE END 3 / }
int main(void){ ··· while(1) { ··· // 暂停滴答时钟,防止通过滴答时钟中断唤醒 HAL_SuspendTick(); /* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 */ HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI); SYSCLKConfig_STOP(); // 被唤醒后,恢复滴答时钟 HAL_ResumeTick(); ··· }}static void SYSCLKConfig_STOP(void){ RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0}; uint32_t pFLatency = 0; /* 启用电源控制时钟 */ __HAL_RCC_PWR_CLK_ENABLE(); /* 根据内部 RCC 寄存器获取振荡器配置 */ HAL_RCC_GetOscConfig(&RCC_OscInitStruct); /* 从停止模式唤醒后重新配置系统时钟: 启用 HSE 和 PLL */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { while (1) {; } } /* 根据内部 RCC 寄存器获取时钟配置 */ HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency); /* 选择 PLL 作为系统时钟源, 并配置 HCLK、PCLK1 和 PCLK2 时钟分频系数 */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK) { while (1) {; } }}0原文:https://juejin.cn/post/7101857655001448478