pm组件是适配于rtthread的电源功耗管理组件,移植需要进行一定适配,并且有些问题需要注意,现在做一个记录
1.PM组件移植
1.PM组件初始化
需要在rtthread的设置里面开启相关功能,并且在hal_config里面开启相应宏定义(lptim),之后添加初始化代码
static int drv_pm_hw_init(void)
{
static const struct rt_pm_ops _ops =
{
sleep,
RT_NULL,
_drv_pm_timer_start,
_drv_pm_timer_stop,
_drv_pm_timer_get_tick
};
rt_uint8_t timer_mask;
/* 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;
}
INIT_BOARD_EXPORT(drv_pm_hw_init);
创建了一个结构体,第一个参数是不同功耗模式下的具体操作,pm组件只是会调用该函数并不会自动降低功耗。之后是pm组件的时钟(这里用stm32l4系列的低功耗时钟lptim)的配置。还有就是在低功耗模式下计算节拍数来补偿系统节拍。然后启动tick_less,这可以降低操作系统的定时中断造成的唤醒数。
关于 Tickless 功能的解释可以参考论坛的文章 RT-Thread PM 组件 TICKLESS 原理分析 和RT-Thread精通PM功耗调优 - Tickless篇。
之后是注册该结构体
关于上述填充的函数的实现
static void sleep(struct rt_pm *pm, uint8_t mode)
{
switch (mode)
{
case PM_SLEEP_MODE_NONE:
break;
case PM_SLEEP_MODE_IDLE:
// __WFI();
break;
case PM_SLEEP_MODE_LIGHT:
/* Enter SLEEP Mode, Main regulator is ON */
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
break;
case PM_SLEEP_MODE_DEEP:
/* Enter STOP 2 mode */
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
/* Re-configure the system clock */
rt_update_system_clock();
break;
case PM_SLEEP_MODE_STANDBY:
/* Enter STANDBY mode */
HAL_PWR_EnterSTANDBYMode();
break;
case PM_SLEEP_MODE_SHUTDOWN:
/* Enter SHUTDOWNN mode */
HAL_PWREx_EnterSHUTDOWNMode();
break;
default:
RT_ASSERT(0);
break;
}
}
static rt_tick_t stm32l4_pm_tick_from_os_tick(rt_tick_t tick)
{
rt_uint32_t freq = stm32l4_lptim_get_countfreq();
return (freq * tick / RT_TICK_PER_SECOND);
}
/**
* This function caculate the OS tick from PM tick
*
* @param tick PM tick
*
* @return the OS tick
*/
static rt_tick_t stm32l4_os_tick_from_pm_tick(rt_uint32_t tick)
{
static rt_uint32_t os_tick_remain = 0;
rt_uint32_t ret, freq;
freq = stm32l4_lptim_get_countfreq();
ret = (tick * RT_TICK_PER_SECOND + os_tick_remain) / freq;
os_tick_remain += (tick * 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 _drv_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 = stm32l4_pm_tick_from_os_tick(timeout);
if (timeout > stm32l4_lptim_get_tick_max())
{
timeout = stm32l4_lptim_get_tick_max();
}
/* Enter PM_TIMER_MODE */
stm32l4_lptim_start(timeout);
}
}
/**
* This function stop the timer of pm
*
* @param pm Pointer to power manage structure
*/
static void _drv_pm_timer_stop(struct rt_pm *pm)
{
RT_ASSERT(pm != RT_NULL);
/* Reset pmtimer status */
stm32l4_lptim_stop();
}
到这里pm初始化基本完成
2.pm唤醒设置
在大部分低功耗模式中部分外设中断如EXTI仍然可以使用,在EXTI中断中手动唤醒进入正常运行模式。
static void (*_wakeup_hook)(void);
void bsp_register_wakeup(void (*hook)(void))
{
RT_ASSERT(hook != RT_NULL);
_wakeup_hook = hook;
}
static void _wakeup_button_update(void)
{
/* The Following Wakeup sequence is highly recommended prior to each Standby mode entry
mainly when using more than one wakeup source this is to not miss any wakeup event.
- Disable all used wakeup sources,
- Clear all related wakeup flags,
- Re-enable all used wakeup sources,
- Enter the Standby mode.
*/
/* Disable all used wakeup sources: WKUP pin */
HAL_PWR_DisableWakeUpPin(DRV_WKUP_PIN);
/* Clear wake up Flag */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* Enable wakeup pin WKUP2 */
HAL_PWR_EnableWakeUpPin(DRV_WKUP_PIN_MODE);
}
static void _wakeup_callback(void *args)
{
/* Wait that user release the User push-button */
//while (rt_pin_read(USER_WAKEUP_PIN) == 0);
_wakeup_button_update();
if (_wakeup_hook)
_wakeup_hook();
}
static void wakeup_callback(void)
{
rt_kprintf("key irq callback");
rt_pm_release_all(3);
rt_pin_write(PIN_LED_R,PIN_LOW);
rt_pm_request(PM_SLEEP_MODE_NONE);
}
static int rt_hw_wakeup_init(void)
{
rt_pin_mode(USER_WAKEUP_PIN, PIN_MODE_INPUT);
rt_pin_attach_irq(USER_WAKEUP_PIN, DRV_WKUP_PIN_IRQ_MODE, _wakeup_callback, RT_NULL);
rt_pin_irq_enable(USER_WAKEUP_PIN, 1);
/* Configure Wakeup pin 2 */
/* Enable Power Clock*/
__HAL_RCC_PWR_CLK_ENABLE();
/* Ensure that MSI is wake-up system clock */
__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_MSI);
/* Update Wakeup Button */
_wakeup_button_update();
bsp_register_wakeup(wakeup_callback);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_wakeup_init);
到这为止就实现了可以进入低功耗并且提供EXTI唤醒。
2.自定义设备的低功耗动作
设备可以提供注册在进入低功耗和离开时进行操作。值得注意的是进入低功耗的时候会把设备停止和恢复的函数都调用,因此在具体实现的时候要判断当前的低功耗模式然后做出相应动作,以下为一个led设备的实例
void mled_regisiter(void){
struct mled_device *device = rt_malloc(sizeof(struct mled_device));
if (device == RT_NULL)
{
rt_kprintf("Failed to allocate memory\n");
return;
}
device->parent.type = RT_Device_Class_Char;
device->parent.init = mled_init;
device->parent.open = mled_open;
device->parent.close = mled_close;
rt_device_register(&device->parent, "mled", RT_DEVICE_FLAG_RDWR);
static struct rt_device_pm_ops mled_pm_ops = {
.suspend = mled_suspend,
.resume = mled_resume,
.frequency_change = mled_frequency_change,
};
rt_pm_device_register(&device->parent, &mled_pm_ops);
}
INIT_DEVICE_EXPORT(mled_regisiter);
注册了一个自定义的led设备并且将其注册到pm组件中
static int mled_suspend(const struct rt_device *device, uint8_t mode)
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, GPIO_PIN_SET);
return 0;
}
static void mled_resume(const struct rt_device *device, uint8_t mode)
{
if(mode<3){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, GPIO_PIN_RESET);
}
}
这是设备的挂起和恢复回调函数。
3.注意点
1.在rtthread中系统节拍时会产生中断,因此实际上单片机实在经常被短暂唤醒然后再睡眠,切换模式的回调会反复调用。
2.模式的选择实际上是用的投票模式,如果存在高功耗的票不将其释放就无法进入低的功耗模式。因此在进入低功耗模式时需要使用release函数释放高的票数。具体可以参考RTT官方文档