本次测试使用的开发板为正点原子的 STM32F429IGT6 阿波罗开发板,该款芯片支持睡眠、停止和待机三种低功耗模式。本文以 StandBy 模式也就是待机模式为例进行组件的使用分析,唤醒方式采用 RTC WakeUP 的方式进行唤醒。
1 STM32F4xx 待机模式介绍
STM32F4xx 待机模式的各种特性如下所示。在待机模式下除了关闭所有的时钟,还把 1.2V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。
2 PM 组件的移植
PM组件的移植主要参考了官方文档 电源管理组件 和文章 RT-Thread进阶之低功耗PM组件应用笔记 及 STM32 PM组件 DeepSleep 模式的使用,在移植时主要是在 RT-Thread Settings 里面的设备驱动程序中使能 PM(电源管理)设备驱动程序,并设置空闲任务线程的堆栈空间不小于 1024Bytes,移植步骤在此不再叙述。
3 源码的修改
为了测试方便将 RTC 的唤醒时钟选择配置为 1Hz,修改方式如下
// drv_pmtim.c 函数 stm32_pmtim_start()
HAL_RTCEx_SetWakeUpTimer_IT(&RtcHandle, reload, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 修改前
HAL_RTCEx_SetWakeUpTimer_IT(&RtcHandle, reload, RTC_WAKEUPCLOCK_CK_SPRE_16BITS); // 修改后
4 测试用例
依照上文中参考文档对 PM 组件移植后,参考官方文档 电源管理组件 进行测试用例的编写,本次主要测试待机模式,因为待机模式唤醒之后相当于程序复位,所以测试起来比较简单,编写的测试用例如下。
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 <time.h>
int main(void)
{
time_t now;
now = time(RT_NULL); // 获取当前的系统时间
rt_kprintf("now time: %.*s", 25, ctime(&now)); // 打印系统时间
led_init();
while(1)
{
rt_thread_mdelay(1000);
}
return RT_EOK;
}
4.2 led.c
该文件初始化了两个 LED,其中一个作为心跳灯,在正常工作模式下心跳灯闪烁,在待机模式下心跳灯不会闪烁。
// 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
该文件写了进入 standby 模式的测试代码,并打印进入前后的 PM 组件的状态。
// wakeup_test.c
#include <board.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <time.h>
int rtc_wakeup_test(void)
{
time_t now;
stm32_pmtim_start(5); // 5秒后唤醒
now = time(RT_NULL); // 获取当前的系统时间
rt_kprintf("%.*s", 25, ctime(&now)); // 打印系统时间
return 0;
}
MSH_CMD_EXPORT(rtc_wakeup_test, rtc_wakeup_test);
int standby_mode_test(void)
{
rtc_wakeup_test(); // 设置 RTC wakeup 时间
rt_pm_request(PM_SLEEP_MODE_STANDBY);
rt_pm_dump_status();
rt_pm_release(PM_SLEEP_MODE_NONE);
rt_pm_dump_status();
return 0;
}
MSH_CMD_EXPORT(standby_mode_test, standby_mode_test);
5 测试结果
测试结果的日志如下所示,对于运行结果的解释在结果中以注释的方式呈现。从结果中可以看到成功完成了 standby 模式的进入和 RTC wakeup 唤醒的过程。可以看到进入 standby 模式的系统时间 和 唤醒后打印的系统时间之间相差了 6 秒,考虑到必须等到空闲线程才能真正进入到 standby 模式,加上系统初始化耗费的时间,相差 6 秒表明测试结果和预期吻合。
\ | /
- RT - Thread Operating System
/ | \ 4.0.4 build Jun 10 2022 16:37:41
2006 - 2021 Copyright by rt-thread team
[I/drv.rtc] RTC hasn't been configured, please use command to config.
now time: Sat Jan 1 06:50:44 2000 // 打印进入 standby 模式时的系统时间
msh >
msh >standby_mode_test // standby 模式测试
Sat Jan 1 06:50:48 2000
| Power Management Mode | Counter | Timer |
+-----------------------+---------+-------+
| None Mode | 1 | 0 |
| Idle Mode | 0 | 0 |
| LightSleep Mode | 0 | 0 |
| DeepSleep Mode | 0 | 0 |
| Standby Mode | 1 | 0 | // 请求 standby 模式成功
| 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 | 0 | 0 |
| Standby Mode | 1 | 0 |
| Shutdown Mode | 0 | 0 |
+-----------------------+---------+-------+
pm current sleep mode: Standby Mode // 当前处于 standby 模式,待运行到 IDLE 线程后,系统正式进入 standby 模式
pm current run mode: Normal Speed // 当前处运行在 Normal Speed
| module | busy | start time | timeout |
+--------+------+------------+-----------+
| 0001 | 0 | 0x00000000 | 0x00000000 |
+--------+------+------------+-----------+
msh >
\ | /
RT - Thread Operating System
/ | \ 4.0.4 build Jun 10 2022 16:37:41
2006 - 2021 Copyright by rt-thread team
[I/drv.rtc] RTC hasn't been configured, please use command to config.
now time: Sat Jan 1 06:50:54 2000 // 唤醒后打印当前的系统时间
msh >
6 其他
6.1 RTC Alarm 唤醒
RTC Alarm 唤醒待机模式,可以参考文章 STM32F4 RTC-Alarm 的使用,使用时在 CubeMX 开启 RTC 和 Alarm 及其中断的功能即可,测试用例如下,使用时先开启 RTC Alarm 的功能, 然后再进入 standby 模式便可进行测试,即在控制台分别依次输入命令 alarm_sample 和 standby_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,因为前面加了 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 rtc_wakeup_test(void)
{
time_t now;
stm32_pmtim_start(5); // 5秒后唤醒
now = time(RT_NULL); // 获取当前的系统时间
rt_kprintf("%.*s", 25, ctime(&now)); // 打印系统时间
return 0;
}
MSH_CMD_EXPORT(rtc_wakeup_test, rtc_wakeup_test);
6.2 wakeup 引脚唤醒(PA0)
StandBy 模式还可以由 WAKEUP(PA0) 引脚唤醒,本小节讲述当使用 WAKEUP(PA0) 引脚做唤醒源时的使用方法。
根据 STM32F4xx 的芯片手册(如上图所示),当开启了 RTC 相关中断后,必须先关闭 RTC 中断,再清中断标志位,然后重新设置 RTC 中断,再进入待机模式才可以正常唤醒,否则会有问题。所以需要添加对 RTC 中断的处理,本次将相关代码添加在文件
drv_pmtim.c 中,代码如下:
void clear_rtc_irq(void)
{
__HAL_RCC_AHB1_FORCE_RESET(); // 复位所有IO口
__HAL_RCC_PWR_CLK_ENABLE(); // 使能PWR时钟
//__HAL_RCC_BACKUPRESET_FORCE(); // 复位备份区域
//HAL_PWR_EnableBkUpAccess(); // 后备区域访问使能
// STM32F4,当开启了RTC相关中断后,必须先关闭RTC中断,再清中断标志位,然后重新设置RTC中断,
// 再进入待机模式才可以正常唤醒,否则会有问题.
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); // 关闭RTC写保护
// 关闭RTC相关中断,可能在RTC实验打开了
__HAL_RTC_WAKEUPTIMER_DISABLE_IT(&hrtc, RTC_IT_WUT);
__HAL_RTC_TIMESTAMP_DISABLE_IT(&hrtc, RTC_IT_TS);
__HAL_RTC_ALARM_DISABLE_IT(&hrtc, RTC_IT_ALRA | RTC_IT_ALRB);
// 清除RTC相关中断标志位
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF | RTC_FLAG_ALRBF);
__HAL_RTC_TIMESTAMP_CLEAR_FLAG(&hrtc, RTC_FLAG_TSF);
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
__HAL_RTC_WAKEUPTIMER_ENABLE_IT(&hrtc, RTC_IT_WUT);
__HAL_RTC_TIMESTAMP_ENABLE_IT(&hrtc, RTC_IT_TS);
__HAL_RTC_ALARM_ENABLE_IT(&hrtc, RTC_IT_ALRA | RTC_IT_ALRB);
//__HAL_RCC_BACKUPRESET_RELEASE(); // 备份区域复位结束
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); // 使能RTC写保护
}
在文件 drv_pmhw_f4.c 中对进入待机模式的代码进行修改,修改后的代码如下:
case PM_SLEEP_MODE_STANDBY:
case PM_SLEEP_MODE_SHUTDOWN:
clear_rtc_irq(); // 清除 RTC 中断
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); // 清除 Wake_UP 标志
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); // 设置 WKUP 引脚用于唤醒 (PA0)
HAL_PWR_EnterSTANDBYMode(); // 进入待机模式
break;
待机模式的测试代码如下,测试时在命令行输入 standby_mode_test使开发板处于待机模式,然后按下 WAKEUP(PA0) 按键,唤醒开发板。
int standby_mode_test(void)
{
rt_pm_request(PM_SLEEP_MODE_STANDBY);
rt_pm_dump_status();
rt_pm_release(PM_SLEEP_MODE_NONE);
rt_pm_dump_status();
return 0;
}
MSH_CMD_EXPORT(standby_mode_test, standby_mode_test);
原作者:crystal266