电源管理组件

随着物联网 (IoT) 的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的 SOC 也需要有快速的响应功能和较低的功耗。

在产品开发的起始阶段,首先考虑是尽快完成产品的功能开发。在产品功能逐步完善之后,就需要加入电源管理 (Power Management,以下简称 PM) 功能。为了适应 IoT 的这种需求,RT-Thread 提供了电源管理组件。电源管理组件的理念是尽量透明,使得产品加入低功耗功能更加轻松。

PM 组件介绍

RT-Thread 的 PM 组件主要特点如下所示:

  • PM 组件是基于模式来管理功耗。
  • PM 组件可以根据模式自动更新设备的频率配置,确保在不同的运行模式都可以正常工作。
  • PM 组件可以根据模式自动管理设备的挂起和恢复,确保在不同的休眠模式下可以正确的挂起和恢复。
  • PM 组件支持可选的休眠时间补偿,让依赖 OS Tick 的应用可以透明使用。
  • PM 组件向上层提供设备接口,如果打开了 devfs 组件,那么也可以通过文件系统接口访问。

工作原理

芯片的运行模式

MCU 通常提供了多种时钟源供用户选择。例如 STM32L475 就可以选择 LSI/MSI/HSI 等内部时钟,还可以选择 HSE/LSE 等外部时钟。MCU 内通常也集成了 PLL(Phase-locked loops),使用不同的时钟源,向 MCU 的其他模块提供更高频率的时钟。

为了支持低功耗功能,MCU 里也会提供不同的休眠模式。例如 STM32L475 里,可以分成 SLEEP 模式、STOP 模式、STANDBY 模式。这些模式还可以有进一步的细分,以适应不同的场合。

以上只是 STM32L475 的时钟和休眠的情况。在不同的 MCU 之间,它们的时钟和低功耗可能会有很大的差异。高性能的 MCU 可以运行在 600M 以上或者更高,低功耗的 MCU 可以在 1~2M 以极低的功耗运行。

根据实际情况,应用里根据任务的需要,可以选择让芯片运行在高性能、普通性能或者极低性能模式;在当前没有需要处理的任务时,就可以让芯片进入休眠模式,休眠模式可以选择停止不同的外设,支持不同的外设在休眠模式里唤醒。

功耗简介

上节介绍了芯片可以运行在不同的频率,可以进入不同的休眠模式。如果将芯片在不同时间的功耗展示出来,如下图:

功耗变化

该图的横轴是时间,纵轴是芯片的当前功率,那么功耗就是被横轴和纵轴和功率曲线包裹起来的面积了。降低功耗也就是使得相应的面积更小。完成一个任务,如果可以在更低的频率下或者在更低休眠模式下进行,就可以降低功耗;如果可以在更短的时间内完成,也可以降低功耗。

模式简介

我们将 MCU 的时钟频率和休眠模式,统一叫做模式。如果当前模式下,CPU 还在工作的,叫做运行模式。如果 CPU 停止工作了,就叫做休眠模式。

在运行模式里,CPU 还是处于运行状态,根据运行的频率我们可以细分不同的运行模式。而在休眠模式里,CPU 已经停止工作,根据不同的外设是否还在工作我们可以细分不同的休眠模式。不同的 MCU 根据实际情况可以自定义不同的模式。

RT-Thread PM 组件的模式分类如下枚举值所示:

enum
{
    PM_RUN_MODE_NORMAL = 0,     /* 运行模式 */
    PM_SLEEP_MODE_SLEEP,        /* 休眠模式 */
    PM_SLEEP_MODE_TIMER,        /* 休眠定时器模式,此模式下 OS Tick 仍然在正常工作 */
    PM_SLEEP_MODE_SHUTDOWN,     /* 关闭模式 */
};

每一个模式都有自己的计数器。如果某个模式的值不是 0,那就说明至少有一个请求希望工作在这个模式。根据一票否决的原则, PM 组件选择最高的模式运行,在 PM 的实现里就是第一个计数器非 0 的模式。

模式的一票否决

在多个线程里,不同的线程可能请求不同的模式。例如线程 A 里请求运行在高性能运行模式,线程 B 和线程 C 里都请求运行在普通运行模式里。这种情况,PM 组件应该选择哪个模式?

这时候应该选择尽可能满足所有线程的请求的模式。高性能模式通常可以更好的完成在普通运行模式的功能,如果选择了高性能模式,那么线程 A/B/C 都可以正确运行。如果选择了普通模式,那么线程 A 的需求就无法得到满足。

因此在 PM 组件里,只要有一个模式请求了更高优先级的模式,就不会切换到比它低的模式。这就是模式的一票否决。

对模式变化敏感的设备

在 PM 组件里,切换到新的运行模式可能会导致 CPU 频率发生变化,如果外设和 CPU 共用一部分时钟,那外设的时钟就会受到影响;在进入新的休眠模式,大部分时钟源会被停止,如果外设不支持休眠的冻结功能,那么从休眠唤醒的时候,外设的时钟就需要重新配置外设。所以 PM 组件里支持了 PM 模式敏感的 PM 设备。使得设备在切换到新的运行模式或者新的休眠模式都能正常的工作。该功能需要底层驱动实现相关的接口并注册为对模式变化敏感的设备。

PM 组件 API 介绍

PM 组件相关接口如下所示:

函数 描述
rt_pm_enter() 进入模式
rt_pm_exit() 退出模式
rt_pm_request() 请求模式
rt_pm_release() 释放模式

进入模式

void rt_pm_enter(void);

该函数尝试进入更低的模式,如果没有请求任何运行模式,就进入休眠模式。这个函数已经在 PM 组件初始化函数里注册到 IDLE HOOK 里,所以不需要另外的调用。

退出模式

void rt_pm_exit(void);

该函数在从休眠模式唤醒的时候被在 rt_pm_enter() 调用。在从休眠唤醒时,有可能先进入唤醒中断的中断处理函数里由。用户也可以在这里主动调用 rt_pm_exit()。从休眠唤醒之后可能多次调用 rt_pm_exit()

请求模式

void rt_pm_request(rt_ubase_t mode);
参数 描述
mode 请求的模式

模式 mode 可取以下枚举值:

enum
{
    PM_RUN_MODE_NORMAL = 0,     /* 运行模式 */
    PM_SLEEP_MODE_SLEEP,        /* 睡眠模式 */
    PM_SLEEP_MODE_TIMER,        /* 休眠定时器模式 */
    PM_SLEEP_MODE_SHUTDOWN,     /* 关闭模式 */
};

调用该函数模式计数器会加一,如果请求模式比当前的模式更高,就会立即切换到新的模式,同时当前模式被修改成新的模式。

释放模式

void rt_pm_release(rt_ubase_t mode);
参数 描述
mode 释放的模式

调用该函数模式计数器会减一。如果释放的模式是当前模式,而且当前模式的计数器值变成 0,就意味着可以切换到更低的模式。在 PM 的实现里,这个切换并不会立即进行,而是在所有任务空闲的时候,在 IDLE HOOK 里调用 rt_pm_enter() 来完成。

FinSH 命令

请求模式

可以使用 pm_request 命令请求模式,使用示例如下所示:

msh />pm_request 0
msh />

参数取值为 0-3,分别对应以下枚举值:

enum
{
    PM_RUN_MODE_NORMAL = 0,     /* 运行模式 */
    PM_SLEEP_MODE_SLEEP,        /* 睡眠模式 */
    PM_SLEEP_MODE_TIMER,        /* 休眠定时器模式 */
    PM_SLEEP_MODE_SHUTDOWN,     /* 关闭模式 */
};

释放模式

可以使用 pm_release 命令释放模式,第 2 个参数取值为 0-3,使用示例如下所示:

msh />pm_release 0
msh />

查看模式状态

可以使用 pm_dump 命令查看 PM 组件的模式状态,使用示例如下所示:

msh />pm_dump
| Power Management Mode | Counter | Timer |
+-----------------------+---------+-------+
|          Running Mode |       1 |     0 |
|            Sleep Mode |       1 |     0 |
|            Timer Mode |       1 |     1 |
|         Shutdown Mode |       1 |     0 |
+-----------------------+---------+-------+
pm current mode: Running Mode

pm_dump 的模式列表里,优先级是从高到低排列,所以现在正处于 Running Mode。Counter 表示模式被请求的次数,上面代码表示 PM 组件里所有 PM 模式都被请求了一次,Timer Mode 在定时应用里被请求一次。

Qestion && Feedback