项目基本完成了,就剩下降功耗了。参考官方的文档,首先理解下pm管理架构。
配置
1、先建立个最简单的工程。
2、打开组件层的电源管理选项。
3、新加了三个文件:
4、编译会出错: error: unknown type name 'LPTIM_HandleTypeDef'; did you mean 'EXTI_HandleTypeDef'?
这里其实是没有定义Lptime或RTC导致的,系统需要补偿对模式变化敏感的设备的时钟。具体看官网。
开启低功耗时钟,即可。如下图:
到这里为止,基本的官网的PM管理框架基本完成。
使用:
1、增加IDLE空闲线程的栈空间:
通过ENV配置:
具体函数的接口,参见官方教程。
https://www.rt-thread.org/document/site/programming-manual/pm/pm/
剩下的就是根据自己业务需求,编写程序了。
如:项目基本情况:
雷达,配置成了外部中断触发,有断电引脚,恢复需要一定时间
红外,与雷达一样,有外部中断。
MP3,有断电引脚,电流大概20MA,恢复需要一定的时间
功放,有断电引脚
4g通信模块,只有在特定的时间需要工作,电源也有引脚控制
温湿度,虽没有专用的管脚,但可以通过命令方式进入低功耗
光感,也是通过命令方式进低功耗
超声波,是通过PWM方式控制
激光,也是通过PWM方式控制
前期先实现关闭MP3与雷达,最后实现关闭单片机,关闭雷达整机功耗就可以降到9MA以下,所以系统还是比较省电的。需要一定的算法支持,因为恢复雷达,需要等待一段时间 。先这样了,调通后再做一下总结吧。
已经实现关闭雷达与mp3,关掉后,功耗可以降到9ma左右,比上一代产品降了算是不少了,心理也更有底气了。接下来利用PM来实现单片机的关闭。
要点还是要记录下的:
1、何时进入低功耗状态:
当任务进入到空闲线程,执行 rt_system_power_manager() 最终是调用函数 static void _pm_change_sleep_mode(struct rt_pm *pm, rt_uint8_t mode) 进入低功耗和唤醒,也就是说这个函数就有唤醒的部分代码,具体是何时唤醒?
static void _pm_change_sleep_mode(struct rt_pm *pm, rt_uint8_t mode)
{
rt_tick_t timeout_tick, delta_tick;
rt_base_t level;
int ret = RT_EOK;
if (mode == PM_SLEEP_MODE_NONE)
{
pm->sleep_mode = mode;
pm->ops->sleep(pm, PM_SLEEP_MODE_NONE);
}
else
{
level = rt_pm_enter_critical(mode);//关掉全局中断
/* Notify app will enter sleep mode */
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_ENTER_SLEEP, mode, _pm_notify.data);//调用进入休眠通知
/* Suspend all peripheral device */
ret = _pm_device_suspend(mode);//挂起所有的休眠外设
if (ret != RT_EOK)
{
_pm_device_resume(mode);
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);
rt_pm_exit_critical(level, mode);
return;
}
/* Tickless*/
if (pm->timer_mask & (0x01 << 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)//得大于2个tickless,否则模式切换为空闲
{
mode = PM_SLEEP_MODE_IDLE;
}
else
{
pm->ops->timer_start(pm, timeout_tick);//执行它
}
}
}
/* enter lower power state */
pm->ops->sleep(pm, mode);
/* wake up from lower power state*/
if (pm->timer_mask & (0x01 << 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);
rt_timer_check();
}
}
/* resume all device */
_pm_device_resume(pm->sleep_mode);
if (_pm_notify.notify)
_pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);
rt_pm_exit_critical(level, mode);
}
}
这个函数对于使用PM框架的核心,理解了这个函数就一定会用PM,可以看到唤醒函数是哪个呢?其实是在初始化时定义的。
关于何时唤醒的问题:其实是通过rt_timer_next_timeout_tick();从系统其他定时器中来实现的,其他定时器定了个时间,但由于进入DEEPSLEEP后就没法跑了,所以传递到lptime里面跑。这样间接实现了定时唤醒。并且其他有通过延时挂起的线程,也能唤醒lptime.如下:这样的话,要想实现真正的定时的话,只能是通过阻塞线程来实现,消除掉对时间的定时。这样才能惟一的指向自己所希望的定时时间。
2、休眠模式通过 void stm32_sleep(struct rt_pm *pm, rt_uint8_t mode)实现。
系统在空闲时进入 Stop 模式,以达到更低的降功耗效果。根据手册可知,Stop 2 模式会关闭系统时钟,当前的 OS Tick 基于内核的 Systick 定时器。那么在系统时钟停止后,OS Tick 也会停止,对于某些依赖 OS Tick 的应用,在进入 Stop 2 模式,又被中断唤醒后,就会出现问题,因此需要在系统唤醒后,对 OS Tick 进行补偿。Stop 2 模式下,绝大多数外设都停止工作,仅低功耗定时器 1(LP_TIM1)和RTC,选择 LSI 作为时钟源后,仍然能正常运行,所以可以选择 LP_TIM1 或者RTC 作为 Stop 2 模式的时间补偿定时器。
struct rt_pm_ops
{
void (*sleep)(struct rt_pm *pm, uint8_t mode);
void (*run)(struct rt_pm *pm, uint8_t mode);
void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout);
void (*timer_stop)(struct rt_pm *pm);
rt_tick_t (*timer_get_tick)(struct rt_pm *pm);
};
后面三个timer_start 、timer_stop、timer_get_tick三个接口实现,根据 Tick 配置低功耗定时器超时,唤醒后获取实际休眠时间并转换为OS Tick,告知 PM 组件即可。
休眠模式的时间补偿需要在初始化阶段通过设置 timer_mask 的对应模式的 bit 控制开启。在 int drv_pm_hw_init(void)中timer_mask设置。在这个函数中,最后是通过void rt_system_pm_init(const struct rt_pm_ops *ops, rt_uint8_t timer_mask, void *user_data)这个函数实现最终的初始化。
3、关于stml4的功耗控制:STM32L4系列 系统功耗的主要是三个因素:稳压器(voltage regulator)、CPU 工作频率、芯片自身低功耗的处理。
- 稳压器
L4 使用两个嵌入式线性稳压器为所有数字电路、待机电路以及备份时钟域供电,分别是主稳压器(main regulator,下文简称 MR)和低功耗稳压器(low-power regulator,下文简称 LPR)。稳压器在复位后处于使能状态,根据应用模式,选择不同的稳压器对 Vcore 域供电。其中,MR 的输出电压可以由软件配置为不同的范围(Range 1 和 Rnage 2)。
稳压器 | 应用场合 |
---|---|
MR(Range 1) | Vcore = 1.2V,用于运行模式、睡眠模式和停止模式0,MR 未 Vcore 域提供全功率 |
MR(Range 2) | Vcore = 1.0V,使用的场景同上 |
LPR | 用于低功耗运行模式、低功耗休眠模式、停止模式 1、停止模式2 |
OFF | Standby 和 Shutdown 模式下,MR 和 LPR 都被关闭 |
- CPU 工作频率
通过降低 CPU 的主频达到降低功耗的目的:MR 工作在 Range 1 正常模式时,SYSCLK 最高可以工作在 80M;MR 工作在 Range 2 时,SYSCLK 最高不能超过 26 M;低功耗运行模式和低功耗休眠模式,即 Vcore 域由 LPR 供电,SYSCLK 必须小于 2M。
- 芯片本身的低功耗处理
芯片本身定义了一系列的休眠模式,如 Sleeep、Stop、Standby 和 Shutdown,前面的四种模式功耗逐渐降低,实质是芯片内部通过关闭外设和时钟来实现。
4、理解PM管理思路
任何有高功耗的请求在,那就切换不到更低功耗。
5、PM对具体平台应用思路:
使用PM组件,进入睡眠前,若有控制管脚那么先通过线程关掉所有外设,再阻塞掉所有的线程,然后对管脚进行反初始化,最后打开想要唤醒的中断源。
6、遇到的问题,醒来后与mp3的通信不正常。下面是一些参考资料,具体的设置,再补充
1) 时钟问题:STM32被唤醒以后的时钟自动切换到内部HIS RC振荡器,大家都是知道的,RC振荡器的精度是不高的。而且,睡觉前对于时钟的设置都是恢复到复位状态,只是时钟这个地方复位,其他的没有。这也会带来一个问题,可能你睡觉前使用的是内部时钟,可是睡觉后,时钟却变了,带来的问题就是UART和定时器。或许你想不使用PLL,就是8M,这样醒来后的时钟HIS也是8M,这样虽然在时钟上没有差别了,但是时钟却不稳定了。UART波特率肯定不能太高,否则通信会有问题。
2) 醒来时间:这个问题也是个非常大的问题,datasheet上给出的醒来时间是7us,这个可能真的不假,但是醒来,不能马上干_你的活,为什么。初始化IO,你可能问,我不初始化不行吗,回答应该是否定的。因为,如果你想使用低功耗的话,睡觉前IO口都应该设置为模拟输入,这样才能达到datasheet上的14uA,但是这样也带来一个问题,那就是初始化IO,醒来必须要初始化IO。如果你还想把时钟切换到外部时钟,耗时会更加长,接近200ms,因为STM32会等待外部时钟稳定后才能工作,然后还要在重新初始化所有IO,这个非常的耗时。可能我只需要醒来10ms,但是这些活干完就需要100ms。
3) RTC唤醒:RTC这个也是个问题,为什么?大家需要注意的是RTC只能使用报警才能唤醒MCU,秒中断是不可以唤醒的。并且报警中断必须不停的设置,设置一次只生效一次,中断完了,还需要设置下次中断的时间。并且还有个问题,报警中断必须等待到秒中断到了之后才能设置,也就是正好秒寄存器更新了一次的时候设置,这就带来一个问题,等待秒中断。如果睡前还想再能被报警唤醒的话必须重新设置报警中断,而且设置报警中断的时候需要等到秒中断才能设置新的值。这个等待的时间是不定的。可能会几百个毫秒。说以要空空的耗费几百个毫秒等到秒中断标志来设置报警中断。可能我的MCU只需要执行10ms就需要睡觉了。还是要空空的耗费掉几百个毫秒.