STM32F4 PM 组件 DeepSleep 模式的使用(RT-Thread操作系统)
文章目录
- 相关文章 1 [STM32F4 RTC-Alarm 的使用](https://blog.csdn.net/qq_36310253/article/details/125233711?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125233711%22%2C%22source%22%3A%22qq_36310253%22%7D&ctrtid=YxcOs) 2 [STM32F4 PM组件 DeepSleep 模式的使用](https://blog.csdn.net/qq_36310253/article/details/125225646?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125225646%22%2C%22source%22%3A%22qq_36310253%22%7D&ctrtid=gyJtz) 3 [STM32F4 PM组件 StandBy 模式的使用](https://blog.csdn.net/qq_36310253/article/details/125221801?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125221801%22%2C%22source%22%3A%22qq_36310253%22%7D&ctrtid=um4Hd)
- 1 STM32F4xx 停止模式介绍
- 2 PM 组件的移植
- 3 工程的配置和源码修改
- 3.1 初始化 _rcc_conf 的问题
- 3.2 运行频率切换不正常
- 3.3 stop 模式唤醒后死机
- 4 测试用例
- 4.1 main.c
- 4.2 key.c
- 4.3 led.c
- 4.4 wakeup_test.c
- 5 测试结果
- 6 其他
- 6.1 为什么必须释放 None 模式才能进入休眠模式
- 6.2 使用 RTC_Alarm 唤醒
相关文章
1 STM32F4 RTC-Alarm 的使用
2 STM32F4 PM组件 DeepSleep 模式的使用
3 STM32F4 PM组件 StandBy 模式的使用
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组件的移植主要参考了官方文档 电源管理组件 和文章 RT-Thread进阶之低功耗PM组件应用笔记 ,在移植时主要是在 RT-Thread Settings 里面的设备驱动程序中使能 PM(电源管理)设备驱动程序,并设置空闲任务线程的堆栈空间不小于 1024Bytes。
sunwan 大佬编写了大量的 PM 组件的适配代码,适用于 STM32 各系列芯片,文章链接为 PM组件适配STM32F0/F1/F2/F3/F4/F7/G0/G4/L0/L1/L4/H7 ,适配的代码对应的仓库地址为 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 目录下。要拷贝的文件如下图所示。
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 原理分析 和 RT-Thread精通PM功耗调优 - Tickless篇。
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()
对其进行配置,在这个函数中有如下的代码。
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; // 0x00001000UHAL_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 位。
在文件 drv_pmhw_f4.c 中会对全局变量 _rcc_conf
初始化时 APB2 时钟的分频因子配置的不正确,根据上面的分析,提出的修改方法如下:
// 文件 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 配置的系统频率大小,将高速运行模式和正常运行模式下的系统频率初始化为不同的结果,如下表所示。
// 文件 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 组件时就会把默认的运行模式设置为正常速度,相关代码如下所示。
// 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_SPEEDenum
{/* 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)
,所以在切换模式时会不成功。
// 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;... ... // 省略掉不相关代码
}
上述问题的修改方法如下
// 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 就绪的标志,与前面的时钟源的配置相矛盾,导致进入了死循环。
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]();
}
解决办法:针对上述问题解决办法如下:
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 测试用例
按照上述步骤将源码修改后,参考官方文档 电源管理组件 进行测试用例的编写,编写的测试用例如下
4.1 main.c
main.c 的代码 如下,首先打印一下系统时钟信息,看一下和 CubeMX 配置的 180MHz 是否一致,以判断 PM 组件是否工作在 Normal 模式,然后初始化 LED 和 按键中断,设置 PM 组件的回调函数。
// main.c#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>#include "led.h"
#include "key.h"
#include <drivers/pm.h>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
该文件初始化了两个按键,作为唤醒的唤醒源。
// key.c#include <rtthread.h>
#include "rtdevice.h"
#include "board.h"
#include "led.h"#define DBG_TAG "led.c"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>#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 模式的指示灯。
// led.c#include <rtthread.h>
#include "rtdevice.h"
#include "board.h"#define DBG_TAG "led.c"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>// 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 组件的状态。
#include <board.h>
#include <rtthread.h>
#include <rtdevice.h>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 模式被唤醒后,程序是接着运行的。
\ | /
- RT - Thread Operating System/ | \ 4.0.4 build Jun 10 2022 16:12:122006 - 2021 Copyright by rt-thread team
[I/drv.rtc] RTC hasn't been configured, please use <date> 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 的介绍 的描述“如果请求更低级别的功耗模式,将无法进入,只有释放(解锁)先前请求的模式后,系统才能进入更低的模式;向更高的功耗模式请求则不受此影响”,具体体现在代码中如下所示。分析代码可知使用 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()
切换模式时就不会切换到休眠模式。
// 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 的使用,使用时在 CubeMX 开启 RTC 和 Alarm 及其中断的功能即可,测试用例如下,使用时先开启 RTC Alarm 的功能, 然后再进入 stop 模式便可进行测试,即在控制台分别依次输入命令 alarm_sample
和 stop_mode_test
。
// wakeup_tese.c#include <board.h>
#include <rtthread.h>
#include <rtdevice.h>#define DBG_TAG "wakeup_test.c"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>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,因为前面加了 5setup.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);
STM32F4 PM 组件 DeepSleep 模式的使用(RT-Thread操作系统)相关推荐
- 正点原子delay函数移植到rt thread操作系统(HAL库)
正点原子教程中涉及到的操作系统只涉及了UCOS的教程,其中例程的system文件夹中的delay.c函数只是适配了UCOS. 下面将delay.c函数移植到rt thread中,使用的bsp是rt t ...
- RT-Thread进阶之低功耗PM组件应用笔记
电源管理组件 嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下,尽可能降低系统能耗以延长设备待机时间.高性能与有限的电池能量在嵌入式系统中矛盾最为突出,硬件低功耗设计与软件低功耗管理的联合应用 ...
- C++设计模式 之 “组件协作”模式:Template Method、Strategy、Observer
"组件协作"模式: #现代软件专业分工之后的第一个结果是"框架与应用程序的划分","组件协作"模式通过晚期绑定,来实现框架与应用程序之间的松 ...
- 【Android 组件化】使用 Gradle 实现组件化 ( 组件 / 集成模式下的 Library Module 开发 )
文章目录 一.组件模式下为组件 Module 指定 Java 源码路径 二.主应用的角色 三.BuildConfig 中生成当前 组件 / 集成 模式字段 四.Library Module 中的代码示 ...
- ESP32 使用 RTC_GPIO 唤醒 Deep-Sleep 模式
此例程是使用 GPIO4( 对应 RTC_GPIO10) 触发高电平,来唤醒 Deep-Sleep 模式. ESP-IDF 版本为 v4.3.1 版本. 测试代码如下: /* ESP32 Deep-s ...
- 李建忠设计模式之“组件协作”模式
文章目录 模板方法模式(Template Method) 定义 动机 结构图 代码 要点 总结 策略模式(Strategy) 定义 动机 结构图 代码 要点 总结 观察者模式(Observer/Eve ...
- “组件协作”模式----策略模式(Strategy Pattern)
文章目录 1."组件协作"模式 2.策略模式动机(Motivation) 3.问题引入 4.商场收银系统1.0 5. 使用简单工厂模式 6.简单工厂模式的缺陷 7. 简单工厂模式和 ...
- 李建忠设计模式-组件协作模式-模板方法模式
目录 1.前言 2.模板方法模式(Template Method) 1.动机 2.例子 3.定义 1.结构图 2.模板方法模式适用情形 3.模式特点 参考 1.前言 现代软件专业分工后的第一个结果是& ...
- ESP8266 Deep-Sleep 模式下的唤醒方式
ESP-WROOM-02D 模块进入 Deep-Sleep 睡眠:仅 RTC 处于⼯作状态,芯⽚的其他部分掉电. ESP8266 在 Deep-Sleep 模式下支持两种唤醒方式:自动唤醒和外部唤醒. ...
最新文章
- 西安java招聘_西安招聘 | 陕西安控科技公司招聘(员工宿舍、节日福利、餐补)...
- Tomcat6(含Tomcat6)之后默认没有common,server和shared文件夹,如何配置
- webbrowser 百度列表点击_百度信息流推广后台完整的实操流程!
- boost::log::to_log_manip用法的测试程序
- eks volumn s3_威客电竞 深渊联赛S3欧洲区总决赛,Secret成就八连冠
- eclipse下创建Maven项目
- 【Text_Classification】学习到的语法知识
- filereader php,AJAX_File, FileReader 和 Ajax 文件上传实例分析(php),File FileReader 可以干什么? Ajax...
- 【100题】第十八题(约瑟夫循环)
- ViewFlipper中放入两个ListView不能拖动的情况
- Unity之UGUI初探—按钮动画
- 基于数据库复制的技术架构讨论
- 洛谷试炼场------题目
- 【Android驱动】高通串口驱动,串口驱动中的msm_serial.c
- 康托尔—探索无穷的勇士
- android瀑布流插件,jQuery瀑布流插件 Masonry
- raptor流程图赋值语句_用raptor软件画出以下程序的流程图,将结果的截图复制到答题框中...
- 谈谈创业公司给服务器放在云端的优势和缺陷
- 超级计算机也无法算尽圆周率,圆周率如果被算尽意味着什么?
- 信息学奥赛一本通——1004:字符三角形
热门文章
- PL/SQL Developer 默认是不会自动提交事务的
- project facets中没有dynamic_同人界王牌quot;东方Projectquot;又增爆款?首日Steam畅销前十、好评97%...
- 技术市场分析:闪存阵列中默默服务的SAS SSD
- (六) 6.1 Neurons Networks Representation
- Java 正则表达式 Pattern和Matcher类 Math类 Random类 System类 BigDecimal类
- 23个系列分类网络,10万分类预训练模型,这是飞桨PaddleClas百宝箱
- Trip.io:区块链在旅行住宿预订领域落地
- SQL总结-DDL规范
- 微信小程序纯css实现刻度尺
- 【天禹老师】Vue2个人学习笔记