Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
PM电源管理
stm32F4
STM32F4 PM组件 DeepSleep 模式的使用
发布于 2022-06-10 17:38:01 浏览:1439
订阅该版
文章目录 [toc] -------------------- 相关文章 1 [RT-Thread 电源管理组件官方文档](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/pm/pm) 2 [RT-Thread 进阶之低功耗 PM 组件应用笔记](https://blog.csdn.net/sinat_31039061/article/details/106356068) 3 [PM 组件适配 STM32F0/F1/F2/F3/F4/F7/G0/G4/L0/L1/L4/H7](https://club.rt-thread.org/ask/question/ceccda97ff72daa3.html) 4 [RT-Thread PM 组件 TICKLESS 原理分析](https://club.rt-thread.org/ask/article/a93494016df18951.html) 5 [RT-Thread 精通 PM 功耗调优 - Tickless篇](https://club.rt-thread.org/ask/article/1b39bec9d1fd3818.html) 6 [STM32F4 RTC-Alarm 的使用](https://club.rt-thread.org/ask/article/79d84bfce8d71c4b.html "STM32F4 RTC-Alarm 的使用") 7 [STM32F4 PM 组件 DeepSleep 模式的使用](https://club.rt-thread.org/ask/article/c79667d4d1dd0310.html) 8 [STM32F4 PM 组件 StandBy 模式的使用](https://club.rt-thread.org/ask/article/bae6eba8c15a2376.html "STM32F4 PM组件 StandBy 模式的使用") 9 [STM32 PM组件适配源码 gitee-sunwancn](https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new) -------------------- 本次测试使用的开发板为正点原子的 STM32F429IGT6 阿波罗开发板,该款芯片支持睡眠、停止和待机三种低功耗模式。本文以 DeepSleep 模式也就是 停止(Stop) 为例进行组件的使用分析,唤醒方式采用外部中断(按键中断)的方式进行唤醒。 # 1 STM32F4xx 停止模式介绍 STM32F4xx 停止模式的各种特性如下所示。 | 特性 | 说明 | | ---------------- | ------------------------------------------------------------ | | 调压器低功耗模式 | 在停止模式下调压器可工作在正常模式或低功耗模式,可进一步降低功耗(当调压器在低功耗模式下工作时,将器件从停止模式唤醒将需要额外的延时。在停止模式下一直开启内部调压器虽然可以缩短启动时间,但功耗却增大) | | FLASH 掉 电模式 | 在停止模式下 FLASH 可工作在正常模式或掉电模式,可进一步降低功耗 | | 进入方式 | 内核寄存器的 SLEEPDEEP =1,PWR_CR 寄存器中的 PDDS=0,然后 调用 WFI或WFE 指令即可进入停止模式 ;PWR_CR 寄存器的 LPDS=0 时,调压器工作在正常模式,LPDS=1 时工作在低功耗模式;PWR_CR 寄存器的 FPDS=0 时,FLASH 工作在正常模式,FPDS=1 时进入掉电模式。 | | 唤醒方式 | 如果是使用 WFI指令睡眠的,可使用任意 EXTI线的中断唤醒;如果是使用 WFE 指令睡眠的,可使用任意配置为事件模式的 EXTI 线事件唤醒。 | | 停止时 | 内核停止,片上外设也停止。这个状态会保留停止前的内核寄存器、内存的数据。 | | 唤醒延迟 | 基础延迟为 HSI 振荡器的启动时间,若调压器工作在低功耗模式,还需要加上调压器从低功耗切换至正常模式下的时间,若 FLASH 工作在掉电模式,还需要加上 FLASH 从掉电模式唤醒的时间。 | | 唤醒后 | 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行 WFI 指令后的程序;若由事件唤醒,直接接着执行 WFE 后的程序。唤醒后,STM32 将使用 HSI RC 振荡器作为系统时钟。 | # 2 PM 组件的移植 PM组件的移植主要参考了官方文档 [电源管理组件](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/pm/pm) 和文章 [RT-Thread进阶之低功耗PM组件应用笔记](https://blog.csdn.net/sinat_31039061/article/details/106356068) ,在移植时主要是在 RT-Thread Settings 里面的设备驱动程序中使能 PM(电源管理)设备驱动程序,并设置空闲任务线程的堆栈空间不小于 1024Bytes。 [sunwan](https://club.rt-thread.org/u/627f5a483ff26af0.html) 大佬编写了大量的 PM 组件的适配代码,适用于 STM32 各系列芯片,文章链接为 [PM组件适配STM32F0/F1/F2/F3/F4/F7/G0/G4/L0/L1/L4/H7](https://club.rt-thread.org/ask/question/ceccda97ff72daa3.html) ,适配的代码对应的仓库地址为 [https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new](https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new),分支为 `pm-ports-stm32-new`。本次测试的是 STM32F4xx 系列的芯片因此需要将文件 drv_pm.c、drv_pmhw.h、drv_pmtim.c 和 drv_pmhw_f4.c 拷贝到 Studio 创建的工程的 drivers 目录下。要拷贝的文件如下图所示。 ![111.png](https://oss-club.rt-thread.org/uploads/20220610/17f1e4a7b528ad68388b4c54f578ce54.png.webp "111.png") # 3 工程的配置和源码修改 拷贝完所需的文件后,需要对工程进行一些修改,因为 PM 组件默认使用 RTC 外设(drv_pmtim.c 中的函数 stm32_pmtim_init 初始化了 RTC 外设),所以需要在 CubeMX Setting、RT-Thread Setting 和 board.h 中修改配置 RTC 的功能。 本文不使用 Tickless 的功能,因为使用了 Tickless 功能会导致芯片被不断的唤醒,因此在 drv_pm.c 中将函数 `drv_pm_hw_init()` 中的代码 **`timer_mask = 1UL << PM_SLEEP_MODE_DEEP;` 注释掉**,更多关于 Tickless 功能的解释可以参考论坛的文章 [RT-Thread PM 组件 TICKLESS 原理分析](https://club.rt-thread.org/ask/article/a93494016df18951.html) 和 [RT-Thread精通PM功耗调优 - Tickless篇](https://club.rt-thread.org/ask/article/1b39bec9d1fd3818.html)。 ```c int drv_pm_hw_init(void) { static const struct rt_pm_ops _ops = { stm32_sleep, stm32_run, stm32_pm_timer_start, stm32_pm_timer_stop, stm32_pm_timer_get_tick }; rt_uint8_t timer_mask = 0; /* Enable Power Clock */ __HAL_RCC_PWR_CLK_ENABLE(); /* initialize timer mask */ //timer_mask = 1UL << PM_SLEEP_MODE_DEEP; // 将这一行注释掉 /* initialize system pm module */ rt_system_pm_init(&_ops, timer_mask, RT_NULL); return 0; } ``` 本次测试使用的是 2020-06-05 版本的 PM 组件的 STM32 适配版本,在测试中发现源码有一些问题,导致唤醒失败和唤醒后的工作不正常,问题如下: ## 3.1 初始化 _rcc_conf 的问题 问题的发现:停止模式唤醒后,使用的是 HSI RC 振荡器作为系统时钟,此时需要重新配置系统时钟,发现重新配置系统时钟后,串口1工作不正常(APB2 时钟不正确)。 在文件 drv_pmhw_f4.c 中会对全局变量 `_rcc_conf` 和 `stm32_run_freq` 进行初始化, `_rcc_conf` 的作用为保存了在各种运行速度下的时钟的配置参数,包含了时钟源、PLL倍频参数、AHB和APB总线的分频系数等, `stm32_run_freq` 的作用是记录了各种运行速度下对应的系统时钟,这两个变量在切换系统的工作频率时都会用到。 在配置 AHB 和 APB 总线的时钟频率时都是调用的 HAL 库函数 `HAL_RCC_ClockConfig()` 对其进行配置,在这个函数中有如下的代码。 ```c RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // 0x00001400U RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // 0x00001000U HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) // 配置 AHB、APB1 和 APB2 总线的时钟 |-> MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider); // 写寄存器配置 APB1 总线时钟 |-> MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct->APB2CLKDivider) << 3U)); // 写寄存器配置 APB2 总线时钟,注意这里左移了三位 /* // 该数值实际时 APB1 时钟分频系数的值 #define RCC_HCLK_DIV1 RCC_CFGR_PPRE1_DIV1 // 0x00000000U 该值是写入寄存器的值 #define RCC_HCLK_DIV2 RCC_CFGR_PPRE1_DIV2 // 0x00001000U 该值是写入寄存器的值 #define RCC_HCLK_DIV4 RCC_CFGR_PPRE1_DIV4 // 0x00001400U 该值是写入寄存器的值 #define RCC_HCLK_DIV8 RCC_CFGR_PPRE1_DIV8 // 0x00001800U 该值是写入寄存器的值 #define RCC_HCLK_DIV16 RCC_CFGR_PPRE1_DIV16 // 0x00001C00U 该值是写入寄存器的值 */ ``` 从中可以看到写入到 APB2 总线时钟分频因子寄存器时会将传入的参数左移 3 位,这是因为 HAL 库在设计时使用的是传入的参数 APB2 时钟的分频因子实际用的是 APB1 时钟分频因子的值,而根据寄存器手册显示在寄存器 RCC_CFGR 中,APB1 的分频系数占该寄存器的 [10:12] 位,APB2 的分频系数占该寄存器的 [13:15] 位,所以在上面的代码中在写入 APB2 时钟分频因子寄存器值的时候将其左移了 3 位。 ![222.png](https://oss-club.rt-thread.org/uploads/20220610/275f3e0b5031a8838f6cdb2d79f46f55.png.webp "222.png") ![11.png](https://oss-club.rt-thread.org/uploads/20220611/b190ec0161aaf19dced319427a1345bd.png.webp "11.png") 在文件 drv_pmhw_f4.c 中会对全局变量 `_rcc_conf` 初始化时 APB2 时钟的分频因子配置的不正确,根据上面的分析,提出的修改方法如下: ```c // 文件 drv_pmhw_f4.c 中函数 rcc_conf_init() conf->apb2_div = RCC->CFGR & RCC_CFGR_PPRE2; // 修改前 conf->apb2_div = (RCC->CFGR & RCC_CFGR_PPRE2) >> 3; // 修改后 // 文件 drv_pmhw_f4.c 中函数 clock_tree_config() conf->apb2_div = (div << RCC_CFGR_PPRE2_Pos) & RCC_CFGR_PPRE2; // 修改前 conf->apb2_div = ((div << RCC_CFGR_PPRE2_Pos) & RCC_CFGR_PPRE2) >> 3; // 修改后 ``` 部分代码解析。下面这段代码对 `PM_RUN_MODE_HIGH_SPEED` 和 `PM_RUN_MODE_NORMAL_SPEED` 的系统频率进行了初始化。根据两个之间的大小关系以及CubeMX 配置的系统频率大小,将高速运行模式和正常运行模式下的系统频率初始化为不同的结果,如下表所示。 ```c // 文件 drv_pmhw_f4.c 中函数 rcc_conf_init() case PM_RUN_MODE_HIGH_SPEED: _set_sysclock[mode] = stm32_systemclock_high; if (stm32_run_freq[mode][0] > stm32_run_freq[PM_RUN_MODE_NORMAL_SPEED][0]) { conf->pll_state = RCC_PLL_ON; stm32_run_freq[mode][0] = clock_tree_config(conf, osc->osc_freq, stm32_run_freq[mode][0]); } else { rt_memcpy(conf, &_rcc_conf[PM_RUN_MODE_NORMAL_SPEED], sizeof(struct rcc_conf_struct)); stm32_run_freq[mode][0] = stm32_run_freq[PM_RUN_MODE_NORMAL_SPEED][0]; } break; ``` | | PM_RUN_MODE_HIGH_SPEED | PM_RUN_MODE_NORMAL_SPEED | CubeMX配置的系统频率 | | ----------------------------- | ---------------------- | ------------------------ | -------------------- | | 情形1 初始值 | 180MHz | 168MHz | 180MHz | | 函数 rcc_conf_init() 初始化后 | 180MHz | 180MHz | 180MHz | | | | | | | | | | | | 情形2 初始值 | 180MHz | 168MHz | 168MHz | | 函数 rcc_conf_init() 初始化后 | 180MHz | 168MHz | 168MHz | ## 3.2 运行频率切换不正常 问题的发现:PM组件默认使用时的正常工作频率,为168MHz,工程中设置的高速运行模式的频率为 180MHz,从正常工作频率切换到高度运行频率时不能正常切换。 在文件 drv_pm.c 中的函数 `rt_system_pm_init()` 初始化 PM 组件时就会把默认的运行模式设置为正常速度,相关代码如下所示。 ```c // drv_pm.c 中函数 rt_system_pm_init() pm->run_mode = RT_PM_DEFAULT_RUN_MODE; // pm.h #define RT_PM_DEFAULT_RUN_MODE PM_RUN_MODE_NORMAL_SPEED enum { /* run modes*/ PM_RUN_MODE_HIGH_SPEED = 0, PM_RUN_MODE_NORMAL_SPEED, PM_RUN_MODE_MEDIUM_SPEED, PM_RUN_MODE_LOW_SPEED, PM_RUN_MODE_MAX, }; ``` 而在文件 drv_pmhw_f4.c 中函数 `stm32_run()` 最开始会对当前的运行模式和上一次的运行模式进行比较,发现一致会直接返回,系统初始化后运行模式为 `PM_RUN_MODE_NORMAL_SPEED`(数值为 1) ,切换成 `PM_RUN_MODE_HIGH_SPEED (数值为 0)` ,而下面这个函数定义的变量 `last_mode`为静态局部变量,没有初始化,编译器会将其初始化为0,刚好是 `PM_RUN_MODE_HIGH_SPEED (数值为 0)` ,所以在切换模式时会不成功。 ```c // drv_pmhw_f4.c void stm32_run(struct rt_pm *pm, rt_uint8_t mode) { static rt_uint32_t last_mode; static char *run_str[] = PM_RUN_MODE_NAMES; struct rcc_conf_struct sconf = _rcc_conf[mode]; if (mode == last_mode) return; ... ... // 省略掉不相关代码 } ``` 上述问题的修改方法如下 ```c // drv_pmhw_f4.c void stm32_run(struct rt_pm *pm, rt_uint8_t mode) { // static rt_uint32_t last_mode; // 修改前 static rt_uint32_t last_mode = RT_PM_DEFAULT_RUN_MODE; // 修改后 ... ... // 省略掉不相关代码 } ``` ## 3.3 stop 模式唤醒后死机 问题的发现:在调试时发现使用外部中断唤醒 stop 模式后,程序会死机在文件 drv_pmhw_f4.c 中的函数 `systemclock_reconfig()` 里面的 `HAL_PWREx_ControlVoltageScaling()` 中。 问题分析:stop 模式唤醒后先调用函数 `systemclock_reconfig()` 重新配置时钟执行函数 `systemclock_msi_on()` 后配置了 HSI 直接作为系统时钟源,并且没有配置 PLL 作为系统的时钟源,但是在下面的函数 `HAL_PWREx_ControlVoltageScaling()` 中使能了 PLL,并且在等待 PLL 就绪的标志,与前面的时钟源的配置相矛盾,导致进入了死循环。 ```c static void systemclock_reconfig(rt_uint32_t mode) { systemclock_msi_on(mode); // 执行后是 HSI 为时钟源,并且没有配置 PLL 作为系统的时钟源 #if defined(PWR_CR_ODEN) /* SMT32F42xxx/43xxx/446xx/469xx/479xx */ if (_rcc_conf[mode].pll_state == RCC_PLL_ON) { HAL_PWREx_ControlVoltageScaling(_rcc_conf[mode].volt_scale); // 这里面等待 PLL 就绪导致死循环 #if defined(RT_PM_USING_VDD_2P7_3P6) || defined(RT_PM_USING_VDD_2P4_2P7) || defined(RT_PM_USING_VDD_2P1_2P4) if ((mode == PM_RUN_MODE_HIGH_SPEED) && (stm32_run_freq[mode][0] > OSC_CONF_SYS_FREQ_MAX)) { /* Enter Over-Drive mode */ HAL_PWREx_EnableOverDrive(); } #endif } #endif /* defined(PWR_CR_ODEN) */ _set_sysclock[mode](); } ``` 解决办法:针对上述问题解决办法如下: ```c static void systemclock_reconfig(rt_uint32_t mode) { systemclock_msi_on(mode); #if defined(PWR_CR_ODEN) /* SMT32F42xxx/43xxx/446xx/469xx/479xx */ if (_rcc_conf[mode].pll_state == RCC_PLL_ON) { // HAL_PWREx_ControlVoltageScaling(_rcc_conf[mode].volt_scale); // 修改前 __HAL_RCC_PWR_CLK_ENABLE(); // 修改后 __HAL_PWR_VOLTAGESCALING_CONFIG(_rcc_conf[mode].volt_scale); // 修改后 PWR_REGULATOR_VOLTAGE_SCALE1 配置主内部稳压器输出电压 #if defined(RT_PM_USING_VDD_2P7_3P6) || defined(RT_PM_USING_VDD_2P4_2P7) || defined(RT_PM_USING_VDD_2P1_2P4) if ((mode == PM_RUN_MODE_HIGH_SPEED) && (stm32_run_freq[mode][0] > OSC_CONF_SYS_FREQ_MAX)) { /* Enter Over-Drive mode */ HAL_PWREx_EnableOverDrive(); } #endif } #endif /* defined(PWR_CR_ODEN) */ _set_sysclock[mode](); } ``` # 4 测试用例 按照上述步骤将源码修改后,参考官方文档 [电源管理组件](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/pm/pm) 进行测试用例的编写,编写的测试用例如下 ## 4.1 main.c main.c 的代码 如下,首先打印一下系统时钟信息,看一下和 CubeMX 配置的 180MHz 是否一致,以判断 PM 组件是否工作在 Normal 模式,然后初始化 LED 和 按键中断,设置 PM 组件的回调函数。 ```c // main.c #include
#define DBG_TAG "main" #define DBG_LVL DBG_LOG #include
#include "led.h" #include "key.h" #include
static void pm_botify_callback(uint8_t event, uint8_t mode, void *data) { if(event == RT_PM_ENTER_SLEEP) { led1_on(); // 点灯,表示进入 stop 模式 } else if (event == RT_PM_EXIT_SLEEP) { led1_off(); // 熄灭灯,表示退出 stop 模式 rt_pm_dump_status(); // 打印 PM 组件的状态 rt_pm_run_enter(PM_RUN_MODE_HIGH_SPEED); clock_information(); // 打印时钟频率 rt_pm_release(PM_SLEEP_MODE_DEEP); // 释放 DeepSleep 模式 rt_pm_request(PM_SLEEP_MODE_NONE); // 请求工作模式 } } int main(void) { clock_information(); // 打印系统时钟信息,CubeMX 配置的为 180MHz,打印一下看看 PM 组件是不是工作在 Normal 模式 led_init(); key_irq_init(); rt_pm_notify_set(pm_botify_callback, 0); // 设置回调函数 while(1) { rt_thread_mdelay(1000); } return RT_EOK; } ``` ## 4.2 key.c 该文件初始化了两个按键,作为唤醒的唤醒源。 ```c // key.c #include
#include "rtdevice.h" #include "board.h" #include "led.h" #define DBG_TAG "led.c" #define DBG_LVL DBG_LOG #include
#define KEY0_RTT_PIN (GET_PIN(H, 3)) // 按下后是低电平 #define KEY1_RTT_PIN (GET_PIN(H, 2)) // 按下后是低电平 /* 中断回调函数 */ void key_irq_callback(void *args) { LOG_D("key irq callback"); } void key_irq_init(void) { rt_pin_mode(KEY0_RTT_PIN, PIN_MODE_INPUT_PULLUP); // 配置为输入模式 rt_pin_attach_irq(KEY0_RTT_PIN, PIN_IRQ_MODE_FALLING, key_irq_callback, RT_NULL); // 下降沿触发 rt_pin_irq_enable(KEY0_RTT_PIN, PIN_IRQ_ENABLE); // 使能中断 rt_pin_mode(KEY1_RTT_PIN, PIN_MODE_INPUT_PULLUP); // 配置为输入模式 rt_pin_attach_irq(KEY1_RTT_PIN, PIN_IRQ_MODE_FALLING, key_irq_callback, RT_NULL); // 下降沿触发 rt_pin_irq_enable(KEY1_RTT_PIN, PIN_IRQ_ENABLE); // 使能中断 } ``` ## 4.3 led.c 该文件初始化了两个 LED,其中一个作为心跳灯,另一个作为 stop 模式的指示灯。 ```c // led.c #include
#include "rtdevice.h" #include "board.h" #define DBG_TAG "led.c" #define DBG_LVL DBG_LOG #include
// LED 引脚定义 #define LED0_RTT_PIN (GET_PIN(B, 1)) #define LED1_RTT_PIN (GET_PIN(B, 0)) #define LED_ON (0) // 低电平亮 #define LED_OFF (1) // 高电平熄灭 void led0_on(void) { rt_pin_write(LED0_RTT_PIN, LED_ON); } void led0_off(void) { rt_pin_write(LED0_RTT_PIN, LED_OFF); } void led1_on(void) { rt_pin_write(LED1_RTT_PIN, LED_ON); } void led1_off(void) { rt_pin_write(LED1_RTT_PIN, LED_OFF); } void led_thread_entry(void *parameter) { while(1) { led0_on(); rt_thread_mdelay(500); led0_off(); rt_thread_mdelay(500); } } void start_led_thread(void) { rt_thread_t tid; tid = rt_thread_create("led_thread", led_thread_entry, RT_NULL, 1024, 10, 20); rt_thread_startup(tid); } /* 初始化 LED */ void led_init(void) { rt_pin_mode(LED0_RTT_PIN, PIN_MODE_OUTPUT); // 配置为输出模式 rt_pin_mode(LED1_RTT_PIN, PIN_MODE_OUTPUT); // 配置为输出模式 led0_off(); led1_off(); start_led_thread(); } ``` ## 4.4 wakeup_test.c 该文件写了进入 stop 模式的测试代码,并打印进入前后的 PM 组件的状态。 ```c #include
#include
#include
int stop_mode_test(void) { rt_pm_request(PM_SLEEP_MODE_DEEP); // 请求 stop 模式 rt_pm_dump_status(); // 打印 PM 组件状态 rt_pm_release(PM_SLEEP_MODE_NONE); // 释放正常工作模式,释放后才能进入 stop 模式 rt_pm_dump_status(); // 打印 PM 组件状态 return 0; } MSH_CMD_EXPORT(stop_mode_test, stop_mode_test); ``` # 5 测试结果 测试结果的日志如下所示,对于运行结果的解释在结果中以注释的方式呈现。从结果中可以看到成功完成了 stop 模式的进入和按键中断唤醒的过程,从中也可以看出进入到 stop 模式被唤醒后,程序是接着运行的。 ```c \ | / - RT - Thread Operating System / | \ 4.0.4 build Jun 10 2022 16:12:12 2006 - 2021 Copyright by rt-thread team [I/drv.rtc] RTC hasn't been configured, please use
command to config. [I/board] System Clock information [I/board] SYSCLK_Frequency = 168000000 [I/board] HCLK_Frequency = 168000000 [I/board] PCLK1_Frequency = 42000000 [I/board] PCLK2_Frequency = 84000000 // 根据打印的系统时钟信息,此时处于 PM 组件的 Normal 模式 msh > msh >stop_mode_test // stop 模式测试 | Power Management Mode | Counter | Timer | +-----------------------+---------+-------+ | None Mode | 1 | 0 | | Idle Mode | 0 | 0 | | LightSleep Mode | 0 | 0 | | DeepSleep Mode | 1 | 0 | // 请求 stop 模式成功 | Standby Mode | 0 | 0 | | Shutdown Mode | 0 | 0 | +-----------------------+---------+-------+ pm current sleep mode: None Mode // 当前处于 None 模式 pm current run mode: Normal Speed // 当前处运行在 Normal Speed | module | busy | start time | timeout | +--------+------+------------+-----------+ | 0001 | 0 | 0x00000000 | 0x00000000 | +--------+------+------------+-----------+ | Power Management Mode | Counter | Timer | +-----------------------+---------+-------+ | None Mode | 0 | 0 | // 释放 None 模式成功 | Idle Mode | 0 | 0 | | LightSleep Mode | 0 | 0 | | DeepSleep Mode | 1 | 0 | | Standby Mode | 0 | 0 | | Shutdown Mode | 0 | 0 | +-----------------------+---------+-------+ pm current sleep mode: DeepSleep Mode // 当前处于 DeepSleep 模式,待运行到 IDLE 线程后,系统正式进入 stop 模式 pm current run mode: Normal Speed // 当前处运行在 Normal Speed // 唤醒后 ..................................... | module | busy | start time | timeout | +--------+------+------------+-----------+ | 0001 | 0 | 0x00000000 | 0x00000000 | +--------+------+------------+-----------+ msh >| Power Management Mode | Counter | Timer | +-----------------------+---------+-------+ | None Mode | 0 | 0 | | Idle Mode | 0 | 0 | | LightSleep Mode | 0 | 0 | | DeepSleep Mode | 1 | 0 | | Standby Mode | 0 | 0 | | Shutdown Mode | 0 | 0 | +-----------------------+---------+-------+ pm current sleep mode: DeepSleep Mode pm current run mode: Normal Speed | module | busy | start time | timeout | +--------+------+------------+-----------+ | 0001 | 0 | 0x00000000 | 0x00000000 | +--------+------+------------+-----------+ switch to High Speed mode, frequency = 180 MHz // 切换运行模式到 High Speed warning: The frequency has over than 168 MHz [I/board] System Clock information // 打印切换运行模式后的系统时钟信息 [I/board] SYSCLK_Frequency = 180000000 [I/board] HCLK_Frequency = 180000000 [I/board] PCLK1_Frequency = 45000000 [I/board] PCLK2_Frequency = 90000000 [D/led.c] key irq callback // 按键中断回调函数 msh > msh >pm_dump // 手动打印 PM 组件状态 | Power Management Mode | Counter | Timer | +-----------------------+---------+-------+ | None Mode | 1 | 0 | | Idle Mode | 0 | 0 | | LightSleep Mode | 0 | 0 | | DeepSleep Mode | 0 | 0 | | Standby Mode | 0 | 0 | | Shutdown Mode | 0 | 0 | +-----------------------+---------+-------+ pm current sleep mode: None Mode pm current run mode: High Speed // 当前处运行在 High Speed | module | busy | start time | timeout | +--------+------+------------+-----------+ | 0001 | 0 | 0x00000000 | 0x00000000 | +--------+------+------------+-----------+ msh > ``` # 6 其他 ## 6.1 为什么必须释放 None 模式才能进入休眠模式 &mesp; 根据官方文档 [rt_pm_request 的介绍](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/pm/pm?id=api-%e4%bb%8b%e7%bb%8d) 的描述“如果请求更低级别的功耗模式,将无法进入,只有释放(解锁)先前请求的模式后,系统才能进入更低的模式;向更高的功耗模式请求则不受此影响”,具体体现在代码中如下所示。分析代码可知使用 `rt_pm_request()` 请求新的模式后将对应的模式计数加1,然后调用 `_pm_select_sleep_mode()`对 sleep_mode 进行赋值,如果当前是 None 模式,那么进入 for 循环后 `pm->modes[PM_SLEEP_MODE_NONE]`的值最小为1,所以最终得到的 pm->sleep_mode 也就是 `PM_SLEEP_MODE_NONE`,最终在执行 `_pm_change_sleep_mode()` 切换模式时就不会切换到休眠模式。 ```c // pm.c static rt_uint8_t _pm_select_sleep_mode(struct rt_pm *pm) { int index; rt_uint8_t mode; mode = _pm_default_deepsleep; for (index = PM_SLEEP_MODE_NONE; index < PM_SLEEP_MODE_MAX; index ++) { if (pm->modes[index]) { mode = index; break; } } pm->sleep_mode = mode; return mode; } void rt_pm_request(rt_uint8_t mode) { rt_base_t level; struct rt_pm *pm; if (_pm_init_flag == 0) return; if (mode > (PM_SLEEP_MODE_MAX - 1)) return; level = rt_hw_interrupt_disable(); pm = &_pm; if (pm->modes[mode] < 255) pm->modes[mode] ++; _pm_select_sleep_mode(pm); rt_hw_interrupt_enable(level); } static void _pm_change_sleep_mode(struct rt_pm *pm) { rt_tick_t timeout_tick, delta_tick; rt_base_t level; int ret = RT_EOK; level = rt_pm_enter_critical(pm->sleep_mode); /* module busy request */ if (_pm_device_check_idle() == RT_FALSE) { pm->ops->sleep(pm, PM_SLEEP_MODE_NONE); rt_pm_exit_critical(level, pm->sleep_mode); return; } if (_pm.sleep_mode == PM_SLEEP_MODE_NONE) // 当前是 PM_SLEEP_MODE_NONE 模式时执行 { pm->ops->sleep(pm, PM_SLEEP_MODE_NONE); // PM_SLEEP_MODE_NONE 作为参数执行 slepp 时,函数为空直接返回 rt_pm_exit_critical(level, pm->sleep_mode); } else { ... ... } } ``` ## 6.2 使用 RTC_Alarm 唤醒 RTC Alarm 唤醒待机模式,可以参考文章 [STM32F4 RTC-Alarm 的使用](https://club.rt-thread.org/ask/article/79d84bfce8d71c4b.html),使用时在 CubeMX 开启 RTC 和 Alarm 及其中断的功能即可,测试用例如下,使用时先开启 RTC Alarm 的功能, 然后再进入 stop 模式便可进行测试,即在控制台分别依次输入命令 `alarm_sample` 和 `stop_mode_test` 。 ```c // wakeup_tese.c #include
#include
#include
#define DBG_TAG "wakeup_test.c" #define DBG_LVL DBG_LOG #include
static rt_alarm_t alarm = RT_NULL; /* 闹钟的用户回调函数 */ void user_alarm_callback(rt_alarm_t alarm, time_t timestamp) { struct tm p_tm; localtime_r(timestamp, &p_tm); // 时间戳转换 LOG_D("user alarm callback function."); LOG_D("curr time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec); // 打印闹钟中断产生时的时间,和设定的闹钟时间比对,以确定得到的是否是想要的结果 } /* 闹钟示例 */ void alarm_sample(void) { time_t curr_time; struct tm p_tm; struct rt_alarm_setup setup; curr_time = time(NULL) + 5; // 将闹钟的时间设置为当前时间的往后的 5 秒 localtime_r(&curr_time, &p_tm); // 将时间戳转换为本地时间,localtime_r 是线程安全的 LOG_D("now time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec - 5); // 打印当前时间,其中秒应该减去 5,因为前面加了 5 setup.flag = RT_ALARM_ONESHOT; // 单次闹钟 setup.wktime.tm_year = p_tm.tm_year; setup.wktime.tm_mon = p_tm.tm_mon; setup.wktime.tm_mday = p_tm.tm_mday; setup.wktime.tm_wday = p_tm.tm_wday; setup.wktime.tm_hour = p_tm.tm_hour; setup.wktime.tm_min = p_tm.tm_min; setup.wktime.tm_sec = p_tm.tm_sec; alarm = rt_alarm_create(user_alarm_callback, &setup); // 创建一个闹钟并设置回调函数 if (RT_NULL != alarm) { rt_alarm_start(alarm); // 启动闹钟 } else { LOG_E("rtc alarm create failed"); } rt_alarm_dump(); // 打印闹钟的信息 } MSH_CMD_EXPORT(alarm_sample, alarm sample); int stop_mode_test(void) { rt_pm_request(PM_SLEEP_MODE_DEEP); rt_pm_dump_status(); rt_pm_release(PM_SLEEP_MODE_NONE); rt_pm_dump_status(); return 0; } MSH_CMD_EXPORT(stop_mode_test, stop_mode_test); ```
4
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
crystal266
嵌入式
文章
14
回答
547
被采纳
161
关注TA
发私信
相关文章
1
STM32F103的低功耗
2
最近用rtthread系统下AD采样并低功耗,中断响应不及时
3
rt-thread低功耗休眠应用问题请教
4
关于RTT对低功耗的支持
5
RT-Thread怎么休眠实现低功耗。
6
RT-Thread V3.0支持的低功耗,OS会自行进入吗?
7
关于RTThread3.0低功耗休眠模式
8
RTT3.0的bsp包中哪些MCU自带低功耗定时器?
9
关于低功耗上次说针对L4出个BSP的,怎么迟迟不见呀
10
低功耗问题。
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
出出啊
1518
个答案
343
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
813
个答案
177
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
出出啊
1
篇文章
5
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部