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 模式的使用

  本次测试使用的开发板为正点原子的 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 目录下。要拷贝的文件如下图所示。
STM32F4 PM 组件 DeepSleep 模式的使用(RT-Thread操作系统)_第1张图片

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;           // 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 位。
STM32F4 PM 组件 DeepSleep 模式的使用(RT-Thread操作系统)_第2张图片
STM32F4 PM 组件 DeepSleep 模式的使用(RT-Thread操作系统)_第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_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; // 修改后
  
    ... ... // 省略掉不相关代码
}

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 

#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

  该文件初始化了两个按键,作为唤醒的唤醒源。

// 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 模式的指示灯。

// 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 组件的状态。

#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 模式被唤醒后,程序是接着运行的。

 \ | /
- 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 >

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 
#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);

你可能感兴趣的:(RTT,stm32,单片机,arm)