文章目录

    • 相关文章 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 模式的使用

  本次测试使用的开发板为正点原子的 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组件的移植主要参考了官方文档 电源管理组件 和文章 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_confstm32_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_SPEEDPM_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_samplestop_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操作系统)相关推荐

  1. 正点原子delay函数移植到rt thread操作系统(HAL库)

    正点原子教程中涉及到的操作系统只涉及了UCOS的教程,其中例程的system文件夹中的delay.c函数只是适配了UCOS. 下面将delay.c函数移植到rt thread中,使用的bsp是rt t ...

  2. RT-Thread进阶之低功耗PM组件应用笔记

    电源管理组件 嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下,尽可能降低系统能耗以延长设备待机时间.高性能与有限的电池能量在嵌入式系统中矛盾最为突出,硬件低功耗设计与软件低功耗管理的联合应用 ...

  3. C++设计模式 之 “组件协作”模式:Template Method、Strategy、Observer

    "组件协作"模式: #现代软件专业分工之后的第一个结果是"框架与应用程序的划分","组件协作"模式通过晚期绑定,来实现框架与应用程序之间的松 ...

  4. 【Android 组件化】使用 Gradle 实现组件化 ( 组件 / 集成模式下的 Library Module 开发 )

    文章目录 一.组件模式下为组件 Module 指定 Java 源码路径 二.主应用的角色 三.BuildConfig 中生成当前 组件 / 集成 模式字段 四.Library Module 中的代码示 ...

  5. ESP32 使用 RTC_GPIO 唤醒 Deep-Sleep 模式

    此例程是使用 GPIO4( 对应 RTC_GPIO10) 触发高电平,来唤醒 Deep-Sleep 模式. ESP-IDF 版本为 v4.3.1 版本. 测试代码如下: /* ESP32 Deep-s ...

  6. 李建忠设计模式之“组件协作”模式

    文章目录 模板方法模式(Template Method) 定义 动机 结构图 代码 要点 总结 策略模式(Strategy) 定义 动机 结构图 代码 要点 总结 观察者模式(Observer/Eve ...

  7. “组件协作”模式----策略模式(Strategy Pattern)

    文章目录 1."组件协作"模式 2.策略模式动机(Motivation) 3.问题引入 4.商场收银系统1.0 5. 使用简单工厂模式 6.简单工厂模式的缺陷 7. 简单工厂模式和 ...

  8. 李建忠设计模式-组件协作模式-模板方法模式

    目录 1.前言 2.模板方法模式(Template Method) 1.动机 2.例子 3.定义 1.结构图 2.模板方法模式适用情形 3.模式特点 参考 1.前言 现代软件专业分工后的第一个结果是& ...

  9. ESP8266 Deep-Sleep 模式下的唤醒方式

    ESP-WROOM-02D 模块进入 Deep-Sleep 睡眠:仅 RTC 处于⼯作状态,芯⽚的其他部分掉电. ESP8266 在 Deep-Sleep 模式下支持两种唤醒方式:自动唤醒和外部唤醒. ...

最新文章

  1. 西安java招聘_西安招聘 | 陕西安控科技公司招聘(员工宿舍、节日福利、餐补)...
  2. Tomcat6(含Tomcat6)之后默认没有common,server和shared文件夹,如何配置
  3. webbrowser 百度列表点击_百度信息流推广后台完整的实操流程!
  4. boost::log::to_log_manip用法的测试程序
  5. eks volumn s3_威客电竞 深渊联赛S3欧洲区总决赛,Secret成就八连冠
  6. eclipse下创建Maven项目
  7. 【Text_Classification】学习到的语法知识
  8. filereader php,AJAX_File, FileReader 和 Ajax 文件上传实例分析(php),File FileReader 可以干什么? Ajax...
  9. 【100题】第十八题(约瑟夫循环)
  10. ViewFlipper中放入两个ListView不能拖动的情况
  11. Unity之UGUI初探—按钮动画
  12. 基于数据库复制的技术架构讨论
  13. 洛谷试炼场------题目
  14. 【Android驱动】高通串口驱动,串口驱动中的msm_serial.c
  15. 康托尔—探索无穷的勇士
  16. android瀑布流插件,jQuery瀑布流插件 Masonry
  17. raptor流程图赋值语句_用raptor软件画出以下程序的流程图,将结果的截图复制到答题框中...
  18. 谈谈创业公司给服务器放在云端的优势和缺陷
  19. 超级计算机也无法算尽圆周率,圆周率如果被算尽意味着什么?
  20. 信息学奥赛一本通——1004:字符三角形

热门文章

  1. PL/SQL Developer 默认是不会自动提交事务的
  2. project facets中没有dynamic_同人界王牌quot;东方Projectquot;又增爆款?首日Steam畅销前十、好评97%...
  3. 技术市场分析:闪存阵列中默默服务的SAS SSD
  4. (六) 6.1 Neurons Networks Representation
  5. Java 正则表达式 Pattern和Matcher类 Math类 Random类 System类 BigDecimal类
  6. 23个系列分类网络,10万分类预训练模型,这是飞桨PaddleClas百宝箱
  7. Trip.io:区块链在旅行住宿预订领域落地
  8. SQL总结-DDL规范
  9. 微信小程序纯css实现刻度尺
  10. 【天禹老师】Vue2个人学习笔记