RT-Thread省电部件用户指南 - 移植教程第二篇
本篇文章为连载系列第二部分,查看第一部分内容可直接 移步文末 。
封面图来源:易图网
转载请注明来源
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()
的实现: