STM32超低功耗之移植 RT-Thread PM 组件 TICKLESS 原理分析

一,什么是 TICKLESS

在解释 TICKLESS 之前,回顾一些知识点:

  • RTOS 需要一个周期性的定时器来给操作系统提供一个时间基准,这个定时器会周期性的触发一个中断,一般来说会把这个频率设置为 1000,也就是说每秒每触发 1000 次中断
  • 使用 WFI 指令进入睡眠模式之后,会被任意中断唤醒

这里就出现了一个矛盾点,睡眠时希望能降低功耗,但是每间隔一个毫秒就被唤醒了,就像人要睡觉,你刚把眼睛闭上就被人叫醒了,这怎么能实现功耗的降低呢?所以就出现了 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);
}

这里只需分析这里面的两个函数即可:

  1. 调整 CPU 的运行频率
/**
 * 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) 去注册对应的回调函数即可。

  1. 调整 CPU 的睡眠模式

代码较多逐段的分析

/**
 * 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 回调,然后恢复任务调度

  1. TICKLESS 的实现
        /* 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

  1. 功耗管理的退出
        /* 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 去检查是否已经有超时事件需要去处理

总结

通过以上的分析,可以发现原理似乎没有多么的复杂,但是要更好的实现功耗的控制,还需用户自己做好以下几点:

  1. 根据业务需求去做好 notify
  2. 可以适当的增加或减少不同的功耗模式
  3. tickless 是不能在 待机和关机模式下使用的,这个需要用户自行使用 RTC 的闹钟功能去唤醒
  4. PM 是个框架,用户根据自身的需求去增加 device 等功耗处理逻辑
  5. 熟练掌握其中的参数,随时调整,以备根据不同场景做出更改

你可能感兴趣的:(STM32超低功耗,stm32)