在 RT-Thread 潘多拉开发板上实现电源管理

摘要

本文介绍了基于 RT-Thread潘多拉开发板电源管理组件的使用。

简介

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

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

本文的示例都是在潘多拉开发板下运行。潘多拉开发板是 RT-Thread 和正点原子联合推出的硬件平台,该平台上专门为 IoT 领域设计,并提供了丰富的例程和文档。

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

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

本节主要展示了如何开启 PM 组件和相应的驱动,并通过例程来演示常见场景下,应用应该如何管理模式。

配置工程

在潘多拉开发板上运行电源管理组件,需要下载潘多拉开发板的相关资料、RT-Thread 源码和 ENV 工具。

开启 Env 工具,进入潘多拉开发板的 PM例程目录,在 Env 命令行里输入 menuconfig 进入配置界面配置工程。

  • 配置 PM 组件:勾选 BSP 里面的Hareware Drivers Config ---> On-chip Peripheral Drivers ---> Enable Power Management,使能了这个选项后,会自动选择 PM 组件和 PM 组件需要的 HOOK 功能:

配置组件

  • 配置内核选项:使用 PM 组件需要更大的 IDLE 线程的栈,这里使用了1024 字节。例程里还使用 Software timer,所以我们还需要开启相应的配置

配置内核选项

  • 配置完成,保存并退出配置选项,输入命令scons --target=mdk5生成 mdk5 工程;

打开mdk5 工程可以看到相应的源码以及被添加进来:

MDK工程

定时应用

在定时应用里,我们创建了一个周期性的软件定时器,定时器任务里周期性输出当前的 OS Tick。如果创建软件定时器成功之后,使用rt_pm_request(PM_SLEEP_MODE_TIMER)请求TIMER休眠模式。以下是示例核心代码:

#define TIMER_APP_DEFAULT_TICK  (RT_TICK_PER_SECOND * 2)

static rt_timer_t timer1;

static void _timeout_entry(void *parameter)
{
    rt_kprintf("current tick: %ld\n", rt_tick_get());
}

static int timer_app_init(void)
{
    timer1 = rt_timer_create("timer_app",
                             _timeout_entry,
                             RT_NULL,
                             TIMER_APP_DEFAULT_TICK,
                             RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
    if (timer1 != RT_NULL)
    {
        rt_timer_start(timer1);

        /* keep in timer mode */
        rt_pm_request(PM_SLEEP_MODE_TIMER);

        return 0;
    }
    else
    {
        return -1;
    }
}
INIT_APP_EXPORT(timer_app_init);

按下复位按键重启开发板,打开终端软件,我们可以看到有定时输出日志:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Sep  7 2018
 2006 - 2018 Copyright by rt-thread team
[SFUD] Find a Winbond flash chip. Size is 8388608 bytes.
[SFUD] w25q128 flash device is initialize success.
sysclok: 80000000Hz
hclk:    80000000Hz
pclk1:   80000000Hz
pclk2:   80000000Hz
mmc1:    32000000Hz
msh />current tick: 2020
current tick: 4021
current tick: 6022

我们可以在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 组件里所有 PM 模式都被请求了一次,现在正处于Running模式。Running ModeSleep ModeShutdown Mode都是启动的时候已经被默认请求了一次。Timer Mode在定时应用里被请求一次。

我们依次输入命令pm_release 0pm_release 1手动释放 Running 和 Sleep 模式后,将进入Timer Mode。进入Timer Mode之后会定时唤醒。所以我们看到 shell 还是一直在输出:

msh />pm_release 0
msh />
msh />current tick: 8023
current tick: 10024
current tick: 12025

msh />pm_release 1
msh />
msh />current tick: 14026
current tick: 16027
current tick: 18028
current tick: 20029
current tick: 22030
current tick: 24031

我们可以通过功耗仪器观察功耗的变化。下图是基于 Monsoon Solutions Inc 的 Power Monitor 的运行截图,可以看到随着模式变化,功耗明显变化:

功耗变化

休眠时显示2mA是仪器的误差。

按键唤醒应用

在按键唤醒应用里,我们使用 wakeup 按键来唤醒处于休眠模式的 MCU。一般情况下,在 MCU 处于比较深度的休眠模式,只能通过特定的方式唤醒。MCU 被唤醒之后,会触发相应的中断。以下例程是从 Timer 模式唤醒 MCU 并闪烁 LED 之后,再次进入休眠的例程。以下是核心代码:

#define WAKEUP_EVENT_BUTTON                 (1 << 0)

static rt_event_t wakeup_event;

static void wakeup_callback(void)
{
    rt_event_send(wakeup_event, WAKEUP_EVENT_BUTTON);
}

static void wakeup_app_entry(void *parameter)
{
    bsp_register_wakeup(wakeup_callback);

    while (1)
    {
        if (rt_event_recv(wakeup_event,
                          WAKEUP_EVENT_BUTTON,
                          RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
                          RT_WAITING_FOREVER, RT_NULL) == RT_EOK)
        {
            rt_pm_request(PM_RUN_MODE_NORMAL);

            rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
            rt_pin_write(PIN_LED_R, 0);
            rt_thread_delay(rt_tick_from_millisecond(100));
            rt_pin_write(PIN_LED_R, 1);
            _pin_as_analog();

#ifdef WAKEUP_APP_DEFAULT_RELEASE
            rt_pm_release(PM_RUN_MODE_NORMAL);
#endif
        }
    }
}

static int wakeup_app(void)
{
    rt_thread_t tid;

    wakeup_event = rt_event_create("wakup", RT_IPC_FLAG_FIFO);
    RT_ASSERT(wakeup_event != RT_NULL);

    tid = rt_thread_create("wakeup_app", wakeup_app_entry, RT_NULL,
                           WAKEUP_APP_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);

    rt_thread_startup(tid);

    return 0;
}
INIT_APP_EXPORT(wakeup_app);

上面的代码里,我们创建一个线程,这个线程里注册了按键中断唤醒回调函数,每当唤醒中断之后就会调用该函数。回调函数里会发送事件WAKEUP_EVENT_BUTTON。这样我们的线程里接收到这个事件之后,首先请求在 Normal 模式,然后完成 LED 闪烁功能之后,再去释放 Normal 。

功耗变化

上图是我们三次按下 wakeup 按键的运行截图。每次按下按键,MCU 都会被唤醒点亮 LED 2秒之后,再次进入休眠。

参考资料

Qestion && Feedback