开题
- PM2.0 已经合入最新的RT-Thread,以后RT-Thread 组件使用,以2.0版本为基线。
- 目前PM 组件的平台适配,主要在STM32L4系列上得到适配。
- 工程师想在各个平台上使用PM 组件进行功耗的管理。
PM组件介绍
- 当我们只开启PM 组件后,发现对功耗没有任何的影响!
- PM组件大概分为:【PM组件】、【平台适配】、【业务管理】(引脚、电源管理业务逻辑)三个部分。
- 【备注】PM组件不只能用于STM32L4系列,或低功耗L系列MCU上,可以用于RT-Thread的任何MCU平台上,只是平台适配不同而已。
开启组件
RT-Thread Components
Device Drivers
[*] Using Power Management device drivers
平台适配方法
- 新建平台适配文件(或根据现有的STM32L4系列拷贝一份):
drv_pm.c 【必要】
drv_lptim.c (可选)
- drv_pm 实现的主要函数:
rt_system_pm_init /* 初始化pm组件,【必要】 */
/* 主要实现的ops操作: sleep */
static const struct rt_pm_ops _ops =
{
sleep, /* 【必要】 */
run, /* 可选 */
pm_timer_start, /* 可选 */
pm_timer_stop, /* 可选 */
pm_timer_get_tick /* 可选 */
};
- 必要函数的实现:
/**
* This function will put apollo3 into sleep mode.
*
* @param pm pointer to power manage structure
*/
static void sleep(struct rt_pm *pm, uint8_t mode)
{
switch (mode)
{
case PM_SLEEP_MODE_NONE:
break;
case PM_SLEEP_MODE_IDLE:
pm_bsp_enter_idle();
break;
case PM_SLEEP_MODE_LIGHT:
pm_bsp_enter_light();
break;
case PM_SLEEP_MODE_DEEP:
pm_bsp_enter_deepsleep();
break;
case PM_SLEEP_MODE_STANDBY:
/* Enter STANDBY mode */
break;
case PM_SLEEP_MODE_SHUTDOWN:
/* Enter SHUTDOWNN mode */
pm_bsp_enter_shutdown();
break;
default:
RT_ASSERT(0);
break;
}
}
- 必要的BSP函数实现
void pm_bsp_enter_deepsleep(void)
{
/* 引脚处理:反初始化,gpio_pins_deinit 【必要】*/
/* 外设处理:反初始化,peripherals_deinit 【可选】*/
/* 进入睡眠 【必要】 */
/* 外设唤醒处理:反初始化,peripherals_init */
/* 唤醒后引脚处理:gpio_pins_init */
}
- drv_lptim.c 适配【可选】
- lptimer用于tickless的实现:MCU需要有一个可以在深睡眠情况下,可以唤醒MCU的定时器。
- lptimer用于系统深睡眠下的定时唤醒业务,时钟补偿。
pm_timer_start, //pm_timer_start,
pm_timer_stop, //pm_timer_stop,
pm_timer_get_tick //pm_timer_get_tick
如下为apollo3p 平台的lp_tim.c实现:
#include <board.h>
#include <drv_lptim.h>
/**
* This function get current count value of LPTIM
*
* @return the count vlaue
*/
rt_uint32_t apollo3_lptim_get_current_counter(void)
{
return am_hal_stimer_counter_get();
}
/**
* This function get the max value that LPTIM can count
*
* @return the max count
*/
rt_uint32_t apollo3_lptim_get_max_counter(void)
{
return 0xffffffff;
}
/**
* This function start LPTIM with reload value
*
* @param reload The value that LPTIM count down from
*
* @return RT_EOK
*/
rt_err_t apollo3_lptim_start(rt_uint32_t reload)
{
am_hal_stimer_compare_delta_set(0, reload);
return (RT_EOK);
}
/**
* This function stop LPTIM
*/
void apollo3_lptim_stop(void)
{
uint32_t lptim_cout = 32768 / RT_TICK_PER_SECOND;
am_hal_stimer_compare_delta_set(0, lptim_cout);
return;
}
/**
* This function get the count clock of LPTIM
*
* @return the count clock frequency in Hz
*/
rt_uint32_t apollo3_get_lptim_freq(void)
{
return 32768;
}
对接drv_pm.c的函数实现:
/**
* This function caculate the PM timer counter from OS tick
*
* @param tick OS tick
*
* @return the PM counter
*/
static rt_tick_t apollo3_pm_counter_from_os_tick(rt_tick_t tick)
{
rt_uint32_t freq = apollo3_get_lptim_freq();
return (tick * freq / RT_TICK_PER_SECOND);
}
/**
* This function caculate the OS tick from PM counter
*
* @param tick PM counter
*
* @return the OS tick
*/
static rt_tick_t apollo3_os_tick_from_pm_counter(rt_uint32_t counter)
{
static rt_uint32_t os_tick_remain = 0;
rt_uint32_t ret, freq;
freq = apollo3_get_lptim_freq();
ret = (counter * RT_TICK_PER_SECOND + os_tick_remain) / freq;
os_tick_remain += (counter * RT_TICK_PER_SECOND);
os_tick_remain %= freq;
return ret;
}
/**
* This function start the timer of pm
*
* @param pm Pointer to power manage structure
* @param timeout How many OS Ticks that MCU can sleep
*/
static void pm_timer_start(struct rt_pm *pm, rt_uint32_t timeout)
{
RT_ASSERT(pm != RT_NULL);
RT_ASSERT(timeout > 0);
if (timeout != RT_TICK_MAX)
{
/* Convert OS Tick to pmtimer timeout value */
timeout = apollo3_pm_counter_from_os_tick(timeout);
if (timeout > apollo3_lptim_get_max_counter())
{
timeout = apollo3_lptim_get_max_counter();
}
/* Enter PM_TIMER_MODE */
g_os_old_tick = rt_tick_get();
apollo3_lptim_start(timeout);
}
}
/**
* This function stop the timer of pm
*
* @param pm Pointer to power manage structure
*/
static void pm_timer_stop(struct rt_pm *pm)
{
RT_ASSERT(pm != RT_NULL);
/* Reset pmtimer status */
apollo3_lptim_stop();
}
/**
* This function calculate how many OS Ticks that MCU have suspended
*
* @param pm Pointer to power manage structure
*
* @return OS Ticks
*/
static rt_tick_t pm_timer_get_tick(struct rt_pm *pm)
{
rt_uint32_t timer_counter;
rt_tick_t os_tick;
RT_ASSERT(pm != RT_NULL);
timer_counter = apollo3_lptim_get_current_counter();
os_tick = (apollo3_os_tick_from_pm_counter(timer_counter) - g_os_old_tick);
g_os_old_tick = 0x00;
return os_tick;
}
运行效果:
- idle 线程栈过小编译问题:
【解决方法】增加idle 线程栈大小,如1024或更大。
- 解决上电初始化后,功耗模式为:None,不能自动进入深睡眠的问题:
rt_pm_module_release(PM_POWER_ID, RT_PM_DEFAULT_SLEEP_MODE);
- 上电后想延时一段时间再进入睡眠,保证驱动初始化:
rt_pm_module_delay_sleep(PM_POWER_ID, 5000); /* 5秒后,再进入睡眠 */
- PM组件的控制台命令:
pm_release
pm_release_all
pm_request
pm_module_release
pm_module_release_all
pm_module_request
pm_module_delay
pm_dump
- 功耗相关API接口
void rt_pm_request(rt_uint8_t sleep_mode); /* 请求(不睡眠) */
void rt_pm_release(rt_uint8_t sleep_mode); /* 释放(允许睡眠) */
void rt_pm_release_all(rt_uint8_t sleep_mode); /* 释放此模式的所有请求(清除引用计数) */
void rt_system_pm_init(const struct rt_pm_ops *ops,
rt_uint8_t timer_mask,
void *user_data); /* 初始化PM组件,注册sleep,lptim的操作函数 */
void rt_pm_module_request(uint8_t module_id, rt_uint8_t sleep_mode); /* 请求(不睡眠),带模块id */
void rt_pm_module_release(uint8_t module_id, rt_uint8_t sleep_mode); /* 释放(允许睡眠),带模块id */
void rt_pm_module_release_all(uint8_t module_id, rt_uint8_t sleep_mode); /* 释放此模式的所有请求(清除引用计数),带模块id */
void rt_pm_module_delay_sleep(rt_uint8_t module_id, rt_tick_t timeout); /* 延时睡眠 */
rt_uint32_t rt_pm_module_get_status(void); /* 获取当前的模块id 请求释放状态(用于功耗日志管理,多线程功耗情况的拆解)*/
rt_uint8_t rt_pm_get_sleep_mode(void); /* 获取当前决策的睡眠状态 */
以上为PM2.0平台适配需要注意的,多实践验证,可以用于其他的MCU平台。
总结
- 功耗管理牵涉的工作与基础知识点较多,多实践,才能真正的管理好功耗。
- PM组件使用并不复杂,不代表功耗管理也不复杂(需要详细的功耗拆解、业务优化、整体考虑)
- 更新rt-thread 源码为最新,以便很好的配合操作系统,完善功耗的管理