本次测试使用的开发板为正点原子的 STM32F429IGT6 阿波罗开发板,该款芯片支持睡眠、停止和待机三种低功耗模式。本文以 DeepSleep 模式也就是 停止(Stop) 为例进行组件的使用分析,唤醒方式采用外部中断(按键中断)的方式进行唤醒。
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 振荡器作为系统时钟。 |
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 目录下。要拷贝的文件如下图所示。
拷贝完所需的文件后,需要对工程进行一些修改,因为 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 适配版本,在测试中发现源码有一些问题,导致唤醒失败和唤醒后的工作不正常,问题如下:
问题的发现:停止模式唤醒后,使用的是 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; // 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 位。
在文件 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 |
问题的发现: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_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)
,所以在切换模式时会不成功。
// 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; // 修改后
... ... // 省略掉不相关代码
}
问题的发现:在调试时发现使用外部中断唤醒 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]();
}
按照上述步骤将源码修改后,参考官方文档 电源管理组件 进行测试用例的编写,编写的测试用例如下
main.c 的代码 如下,首先打印一下系统时钟信息,看一下和 CubeMX 配置的 180MHz 是否一致,以判断 PM 组件是否工作在 Normal 模式,然后初始化 LED 和 按键中断,设置 PM 组件的回调函数。
// 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;
}
该文件初始化了两个按键,作为唤醒的唤醒源。
// 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); // 使能中断
}
该文件初始化了两个 LED,其中一个作为心跳灯,另一个作为 stop 模式的指示灯。
// 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();
}
该文件写了进入 stop 模式的测试代码,并打印进入前后的 PM 组件的状态。
#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);
测试结果的日志如下所示,对于运行结果的解释在结果中以注释的方式呈现。从结果中可以看到成功完成了 stop 模式的进入和按键中断唤醒的过程,从中也可以看出进入到 stop 模式被唤醒后,程序是接着运行的。
\ | /
- 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 <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 >
&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
{
... ...
}
}
RTC Alarm 唤醒待机模式,可以参考文章 STM32F4 RTC-Alarm 的使用,使用时在 CubeMX 开启 RTC 和 Alarm 及其中断的功能即可,测试用例如下,使用时先开启 RTC Alarm 的功能, 然后再进入 stop 模式便可进行测试,即在控制台分别依次输入命令 alarm_sample
和 stop_mode_test
。
// 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);