RT-Thread低功耗组件用户手册-移植说明(2)

2020/05/22 22:11
阅读数 1.8K

本篇文章为连载系列第二部分,查看第一部分内容可直接 移步文末 

封面图来源:易图网

转载请注明来源


6 移植说明


基本移植

本节介绍如何移植 PM 组件到新的 BSP 里。

PM组件的底层功能全部都是通过struct rt_pm_ops结构体里的函数完成:

向👉滑动可查看全部代码

 1struct rt_pm_ops
2{

3    void (*enter)(struct rt_pm *pm);
4    void (*exit)(struct rt_pm *pm);
5
6#if PM_RUN_MODE_COUNT > 1
7    void (*frequency_change)(struct rt_pm *pm, rt_uint32_t frequency);
8#endif
9
10    void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout);
11    void (*timer_stop)(struct rt_pm *pm);
12    rt_tick_t (*timer_get_tick)(struct rt_pm *pm);
13};

在新的 BSP 里支持 PM 组件,只需要实现了struct rt_pm_ops里的函数,然后使用rt_system_pm_init()来完成初始化工作即可。

以下是rt_system_pm_init()的使用示例代码:

 1static int drv_pm_hw_init(void)
2
{
3    static const struct rt_pm_ops _ops =
4    {

5        _drv_pm_enter,
6        _drv_pm_exit,
7#if PM_RUN_MODE_COUNT > 1
8        _drv_pm_frequency_change,
9#endif
10        _drv_pm_timer_start,
11        _drv_pm_timer_stop,
12        _drv_pm_timer_get_tick
13    };
14
15    rt_uint8_t timer_mask;
16
17    /* initialize timer mask */
18    timer_mask = 1UL << PM_SLEEP_MODE_TIMER;
19
20    /* initialize system pm module */
21    rt_system_pm_init(&_ops, timer_mask, RT_NULL);
22
23    return 0;
24}

上面的代码,将所有相关的函数保存在_ops变量里,并在变量timer_mask把那些包含了 timer 功能的模式对应的位置1,最后调用rt_system_pm_init()完成初始化工作。

struct rt_pm_ops里面的函数里,必须实现的函数是enter()exit()函数。如果没有打开自动变频功能,就不需要实现frequency_change()函数。如果所有模式里都不包含 timer 功能,那就不需要实现timer_start()timer_stop()timer_get_tick()函数。

下小节将按照API逐一介绍它们的具体实现。

_drv_pm_enter() 和 _drv_pm_exit() 函数的移植

_drv_pm_enter()函数里需要完成的功能是根据当前模式切换到新的运行模式的时钟配置或进入新的休眠模式。

_drv_pm_exit()函数里需要完成的功能是完成模式退出的清理工作,如果没有需要的清理,就不需要做任何事情。

_drv_pm_enter()_drv_pm_exit()函数会在 PM 组件的模式变化的时候会被调用。PM 组件的模式变化有两种情况,一个是在rt_pm_request()里请求了比当前模式更高的模式,一个是在rt_pm_enter()里降低到比当前模式更低的模式。

每次模式变化的时候,PM 组件都会调用`_drv_pm_exit()退出当前模式,然后更新模式变量pm->current_mode,最后调用了_drv_pm_exit()切换到新的模式。

所以_drv_pm_enter()_drv_pm_exit()函数需要根据当前模式的值做不同的判断。以下是 STM32L475 里面的 _drv_pm_enter()的实现:

 1static void _drv_pm_enter(struct rt_pm *pm)
2{
3    RT_ASSERT(pm != RT_NULL);
4    switch (pm->current_mode)
5    {
6    case PM_RUN_MODE_NORMAL:
7        break;
8
9    case PM_SLEEP_MODE_SLEEP:
10        HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
11        break;
12
13    case PM_SLEEP_MODE_TIMER:
14        HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
15        break;
16
17    case PM_SLEEP_MODE_SHUTDOWN:
18        HAL_PWREx_EnterSHUTDOWNMode();
19        break;
20
21    default:
22        RT_ASSERT(0);
23        break;
24    }
25}

由于该 BSP 只有一个运行模式,而时钟已经在系统启动时完成了配置,所以

PM_RUN_MODE_NORMAL不需要做任何事情。剩下的三个休眠模式只需要根据实际情况选择进入不同的休眠模式即可。

以下是 STM32L475 里面的 _drv_pm_exit()的实现:

 1static void _drv_pm_exit(struct rt_pm *pm)
2{
3    RT_ASSERT(pm != RT_NULL);
4    switch (pm->current_mode)
5    {
6    case PM_RUN_MODE_NORMAL:
7        break;
8
9    case PM_SLEEP_MODE_SLEEP:
10        break;
11
12    case PM_SLEEP_MODE_TIMER:
13        rt_update_system_clock();
14        break;
15
16    case PM_SLEEP_MODE_SHUTDOWN:
17        break;
18
19    default:
20        RT_ASSERT(0);
21        break;
22    }
23}

由于PM_SLEEP_MODE_SLEEP不会影响任何外设,所以在这个模式我们不需要做任何事情。我们希望在从PM_SLEEP_MODE_TIMER唤醒之后,就可以马上使用rt_kprintf()输出调试信息,所以在唤醒之后马上更新系统时钟,使得非低功耗的 uart 也能正常工作。

当然如果希望rt_kprintf()可以在唤醒之后马上工作,可以把这个 uart 注册为对模式变化敏感的 PM 设备。具体注册方式可以看后面章节。

_drv_pm_timer_xxx()

PM 组件在进入某个包含了 Timer 功能的休眠模式之前,根据下一次唤醒的时间来调用_drv_pm_timer_start()函数,才进入休眠。所以_drv_pm_timer_start()需要完成该休眠模式的定时器的配置,确保可以在指定的时间醒来,指定的时间是timeout个 OS Tick。

向👉滑动可查看全部代码

1void _drv_pm_timer_start(struct rt_pm *pm, rt_uint32_t timeout);

芯片在休眠一段时间之后被唤醒,可能是由于该休眠模式的定时器的超时中断,也可能是其他的外设中断。所以 PM 组件会在芯片被唤醒之后调用_drv_pm_timer_get_tick()函数,得到真正的休眠时间。最后 PM 组件会调用_drv_pm_timer_stop()函数来停止定时器。

_drv_pm_timer_start()函数和_drv_pm_timer_get_tick()都是使用 OS Tick为单位来决定休眠时间的。但是 OS Tick 和休眠模式的定时器之间的转换可能存在误差,用户可以根据实际情况来决定忽略这部分误差,或者根据多次 OS Tick 的值来修正误差。

以下是 STM32L475 里面的 _drv_pm_timer_start()的实现:

 1static void _drv_pm_timer_start(struct rt_pm *pm, rt_uint32_t timeout)
2{
3    RT_ASSERT(pm != RT_NULL);
4    RT_ASSERT(timeout > 0);
5
6    /* Convert OS Tick to pmtimer timeout value */
7    timeout = stm32l4_pm_tick_from_os_tick(timeout);
8    if (timeout > stm32l4_lptim_get_tick_max())
9    {
10        timeout = stm32l4_lptim_get_tick_max();
11    }
12
13    /* Enter PM_TIMER_MODE */
14    stm32l4_lptim_start(timeout);
15}

该函数首先将timeout变量的值由 OS Tick 转成 PM 模式的低功耗定时器的 Tick 值,然后确定这个值没有超过低功耗定时器的最大访问,最后打开了低功耗定时器。

以下是 STM32L475 里面的 _drv_pm_timer_stop()的实现:

1static void _drv_pm_timer_stop(struct rt_pm *pm)
2{
3    RT_ASSERT(pm != RT_NULL);
4
5    /* Reset pmtimer status */
6    stm32l4_lptim_stop();
7}

该函数只是简单的停止了定时器。

以下是 STM32L475 里面的 _drv_pm_timer_stop()的实现:

 1static rt_tick_t _drv_pm_timer_get_tick(struct rt_pm *pm)
2{
3    rt_uint32_t timer_tick;
4
5    RT_ASSERT(pm != RT_NULL);
6
7    timer_tick = stm32l4_lptim_get_current_tick();
8
9    return stm32l4_os_tick_from_pm_tick(timer_tick);
10}

该函数里,首先去获取了 PM 模式的低功耗定时器的 Tick 值,然后转换成 OS Tick。stm32l4_lptim_get_current_tick()里只是简单的转换。而在stm32l4_os_tick_from_pm_tick()函数里,完成了累积误差的修复:

向👉滑动可查看全部代码

 1static rt_tick_t stm32l4_os_tick_from_pm_tick(rt_uint32_t tick)
2
{
3    static rt_uint32_t os_tick_remain = 0;
4    rt_uint32_t ret, freq;
5
6    freq = stm32l4_lptim_get_countfreq();
7    ret = (tick * RT_TICK_PER_SECOND + os_tick_remain) / freq;
8
9    os_tick_remain += (tick * RT_TICK_PER_SECOND);
10    os_tick_remain %= freq;
11
12    return ret;
13}

该函数每次转行的时候,都将转换时的余数保存到os_tick_remain变量,并用在下次 OS Tick 到 PM Tick 的转行。

_drv_pm_frequency_change()

PM 组件在每次切换到新的运行模式之后,会调用这个函数。所以在驱动里我们可以在这个函数里完成芯片的 CPU 频率调整。

支持对模式变化敏感的 PM 设备

在完成基本的移植之后,BSP 里还可以根据实际情况,注册对PM的模式变化敏感的设备,使得设备在切换到新的运行模式或者新的休眠模式都能正常的工作。

一个新的 PM 设备的功能是通过struct rt_device_pm_ops里的函数完成:

1struct rt_device_pm_ops
2{

3#if PM_RUN_MODE_COUNT > 1
4    void (*frequency_change)(const struct rt_device* device);
5#endif
6
7    void (*suspend)(const struct rt_device* device);
8    void (*resume) (const struct rt_device* device);
9};

在切换到新的运行模式,会逐个调用注册设备的frequency_change()函数。在进入休眠之前,会逐个调用注册设备的suspend()函数。在设备唤醒之后,会逐个调用注册设备的resume()函数。

在完成上述函数之后,我们可以使用rt_pm_register_device()rt_pm_unregister_device()来管理 PM 设备。

注册PM设备,需要传入相应的设备指针以及相应的设备的 pm_ops,以下是一个模板:

向👉滑动可查看全部代码

 1#if PM_RUN_MODE_COUNT > 1
2void _serial1_frequency_change(const struct rt_device* device)
3{
4    /* do something */
5}
6#endif
7void _serial1_suspend(const struct rt_device* device)
8{
9    /* do something */
10}
11void _serial1_resume(const struct rt_device* device)
12{
13    /* do something */
14}
15
16int stm32_hw_usart_init(void)
17
{
18    static struct rt_device_pm_ops _pm_ops = 
19    {

20    #if PM_RUN_MODE_COUNT > 1
21        _serial1_frequency_change,
22    #endif
23        _serial1_suspend,
24        _serial1_resume,
25    };
26    ......
27    rt_pm_register_device(&serial1, &_pm_ops);
28}

取消注册PM设备,只需要传入相应的设备指针就可以了:

1rt_pm_unregister_device(&serial1);

7 API说明


下表是所有 PM 组件里面的 API:

API详解

PM组件初始化

1void rt_system_pm_init(const struct rt_pm_ops *ops,
2                       rt_uint8_t              timer_mask,
3                       void                   *user_data)
;

PM 组件初始化函数,是由相应的 PM 驱动来调用,完成 PM 组件的初始化。

该函数完成的工作,包括底层 PM 驱动的注册、相应的 PM 组件的资源初始化、默认模式的请求,并对上层提供一个名字是 “pm” 的设备,还默认请求了三个模式,包括一个默认运行模式PM_RUN_MODE_DEFAULT、一个默认休眠模式PM_SLEEP_MODE_DEFAULT和最低的模式PM_MODE_MAX。默认运行模式和休眠模式的值,我们可以根据需要来定义,默认值是第一个模式。

请求 PM 模式

1void rt_pm_request(rt_ubase_t mode);

请求 PM 模式函数,是在应用或者驱动里调用的函数,调用之后 PM 组件确保当前模式不低于请求的模式。

释放 PM 模式

1void rt_pm_release(rt_ubase_t mode);

释放PM模式函数,是在应用或者驱动里调用的函数,调用之后 PM 组件并不会立即进行实际的模式切换,而是在rt_pm_enter()里面才开始切换。

注册 PM 模式变化敏感的设备

1void rt_pm_register_device(struct rt_device* device, const struct rt_device_pm_ops* ops);

该函数注册 PM 模式变化敏感的设备,每当 PM 的模式发生变化的时候,会调用设备的相应 API。

如果是切换到新的运行模式,会调用设备里的frequency_change()函数。

如果是切换到新的休眠模式,将会在进入休眠时调用设备的suspend()函数,在进入休眠被唤醒之后调用设备的resume()


取消注册PM模式变化敏感的设备

1void rt_pm_unregister_device(struct rt_device* device);

该函数取消已经注册的 PM 模式变化敏感设备。

PM 模式进入函数

1void rt_pm_enter(void);

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

PM 模式退出函数

1void rt_pm_exit(void);

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

RT-Thread低功耗组件用户手册(1)


RT-Thread 低功耗组件开源,开启IoT 产品智能省电模式




近期活动

1.在公众号后台回复【野火RT-Thread】即可下载《RT-Thread内核实现与应用开发实战指南》基于STM32 完整电子版。

2.RT-Thread 2018开发者大会 深圳站:1、 有机会直接对话 熊大、armink、NXP、腾讯云等大咖  2、看到来自RTT的合作伙伴或社区开发者的最新产品、作品 3、动手实操培训:入门培训、柿饼GUI培训、蓝牙培训任君挑选4、多款开发板抽奖~

长按识别二维码报名

你可以添加微信13924608367为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群

RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。GPLv2+协议,可免费在商业产品中使用。

长按二维码,关注我们


  点“阅读原文” 报名开发者大会

本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部