在解释 TICKLESS 之前,回顾一些知识点:
这里就出现了一个矛盾点,睡眠时希望能降低功耗,但是每间隔一个毫秒就被唤醒了,就像人要睡觉,你刚把眼睛闭上就被人叫醒了,这怎么能实现功耗的降低呢?所以就出现了 TICKLESS,他的作用是尽可能的降低系统时钟的响应频率的同时,保证系统的实时性和降低系统的功耗。
TICKLESS 的为了保证任务与功耗的兼顾所以这个相关的处理都被放在 IDEL 线程中,所以只需要分析 IDEL 中调用的功耗处理函数即可, 最为核心的就是下面这个函数。
/**
* This function will enter corresponding power mode.
*/
void rt_system_power_manager(void)
{
if (_pm_init_flag == 0)
return;
/* CPU frequency scaling according to the runing mode settings */
_pm_frequency_scaling(&_pm);
/* Low Power Mode Processing */
_pm_change_sleep_mode(&_pm);
}
这里只需分析这里面的两个函数即可:
/**
* This function will update the system clock frequency when idle
*/
static void _pm_frequency_scaling(struct rt_pm *pm)
{
rt_base_t level;
if (pm->flags & RT_PM_FREQUENCY_PENDING)
{
level = rt_hw_interrupt_disable();
/* change system runing mode */
pm->ops->run(pm, pm->run_mode);
/* changer device frequency */
_pm_device_frequency_change(pm->run_mode);
pm->flags &= ~RT_PM_FREQUENCY_PENDING;
rt_hw_interrupt_enable(level);
}
}
首先检查是否有改变系统运行频率的请求被挂起,在关闭中断的情况下来设置 CPU 即将运行的频率。
pm->ops->run(pm, pm->run_mode);
这里的运行模式通过 int rt_pm_run_enter(rt_uint8_t mode)
来让 CPU 进入不同频率的运行模式。
_pm_device_frequency_change(pm->run_mode);
这里去设置 device 的运行频率
清楚改变系统运行频率的标志位。
根据前面文章移植的方法没有设置过改变系统的运行频率,也没有注册 device 设备运行频率改变的接口,所以这里并不会得到执行。如果有这方面的需求,需要去通过 void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops)
去注册对应的回调函数即可。
代码较多逐段的分析
/**
* This function changes the power sleep mode base on the result of selection
*/
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->ops->sleep(pm, PM_SLEEP_MODE_NONE);
rt_pm_exit_critical(level, pm->sleep_mode);
}
else
{
禁止调度,避免在进行睡眠模式的时候进行了任务切换。
检查所有的模块是否都处于空闲状态,如果不是则会退出睡眠模式,允许任务切换,并立刻退出。
判断当前的没有睡眠模式,那么直接切换到无睡眠模式的状态,然后开启允许任务切换
else
{
/* Notify app will enter sleep mode */
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_ENTER_SLEEP, pm->sleep_mode, _pm_notify.data);
/* Suspend all peripheral device */
ret = _pm_device_suspend(pm->sleep_mode);
if (ret != RT_EOK)
{
_pm_device_resume(pm->sleep_mode);
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_EXIT_SLEEP, pm->sleep_mode, _pm_notify.data);
rt_pm_exit_critical(level, pm->sleep_mode);
return;
}
检查是否有 app 通知进入低功耗模式。如果要使用 notify 的功能,需要用户自己通过 void rt_pm_notify_set(void (*notify)(rt_uint8_t event, rt_uint8_t mode, void *data), void *data)
去注册。
将所有的 device 是否已经挂起,如果挂起失败则恢复已经挂起的 device
如果有 notify 需要去通知,则进入 notify 回调,然后恢复任务调度
/* Tickless*/
if (pm->timer_mask & (0x01 << pm->sleep_mode))
{
timeout_tick = rt_timer_next_timeout_tick();
if (timeout_tick == RT_TICK_MAX)
{
if (pm->ops->timer_start)
{
pm->ops->timer_start(pm, RT_TICK_MAX);
}
}
else
{
timeout_tick = timeout_tick - rt_tick_get();
if (timeout_tick < RT_PM_TICKLESS_THRESH)
{
pm->sleep_mode = PM_SLEEP_MODE_IDLE;
}
else
{
pm->ops->timer_start(pm, timeout_tick);
}
}
}
检查 timer_mask
的值,这个值在初始化的时候被设置成了 PM_SLEEP_MODE_DEEP
, 也就是说只有在深度睡眠模式下才会启用 TICKLESS
检查下一个超时事件发生时的 tick
值, 这个事件比如是线程的睡眠时间到了。
如果是 RT_TICK_MAX,且 LPTIM 已经使能,则设置 LPTIM 的唤醒时间为 RT_TICK_MAX
如果下一个超时节点的 tick
不为 RT_TICK_MAX,则计算出下一次超时还需要经过多长时间,如果小于 TICKLESS 的最小粒度,这个值是一个宏,默认是 2 ,用户可以自己修改,就直接设置 sleep_mode
即可,反之就启动 LPTIM
/* enter lower power state */
pm->ops->sleep(pm, pm->sleep_mode);
/* wake up from lower power state*/
if (pm->timer_mask & (0x01 << pm->sleep_mode))
{
delta_tick = pm->ops->timer_get_tick(pm);
pm->ops->timer_stop(pm);
if (delta_tick)
{
rt_tick_set(rt_tick_get() + delta_tick);
}
}
/* resume all device */
_pm_device_resume(pm->sleep_mode);
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_EXIT_SLEEP, pm->sleep_mode, _pm_notify.data);
rt_pm_exit_critical(level, pm->sleep_mode);
if (pm->timer_mask & (0x01 << pm->sleep_mode))
{
if (delta_tick)
{
rt_timer_check();
}
}
让 MCU 进入对应的睡眠模式。
检查当前是不是深度睡眠模式,如果是获取 LPTIM 经过了多少 tick
,关闭 LPTIM,重新设置系统的 tick 数,用来补偿在深度睡眠模式下 systick 停止导致系统时钟的滞后的问题。
恢复素有的 device。
执行 notify.
允许调度。
如果是在深度睡眠模式下被唤醒,就去让所有的 timer 去检查是否已经有超时事件需要去处理
通过以上的分析,可以发现原理似乎没有多么的复杂,但是要更好的实现功耗的控制,还需用户自己做好以下几点: