本次测试使用的开发板为正点原子的 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 #define DBG_TAG "main" #define DBG_LVL DBG_LOG #include #include "led.h" #include 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 #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 该文件写了进入 standby 模式的测试代码,并打印进入前后的 PM 组件的状态。 // wakeup_test.c #include #include #include #include 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 #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 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
|