很多应用场合对于功耗的要求很严格,比如长期无人照看的数据采集仪器,可穿戴设备等。其实很多 MCU 都有相应的低功耗模式,以此来降低设备运行时的功耗,进行裸机开发的时候就可以使用这些低功耗模式。但是现在我们要使用操作系统,因此操作系统对于低功耗的支持也显得尤为重要,这样硬件与软件相结合,可以进一步降低系统的功耗。这样开发也会方便很多,毕竟系统已经原生支持低功耗了,我们只需要按照系统的要求来做编写相应的应用层代码即可。FreeRTOS 提供了一个叫做 Tickless 的低功耗模式。
1、STM32F1 低功耗模式
STM32 本身就支持低功耗模式,共有三种低功耗模式:
● 睡眠(Sleep)模式。
● 停止(Stop)模式。
● 待机(Standby)模式。
这三种模式对比如表所示:
这三种低功耗模式对应三种不同的功耗水平,根据实际的应用环境选择相对应的低功耗模式。接下来我们就详细的看一下这三者有何区别。
1)、睡眠(Sleep)模式
● 进入睡眠模式
进入睡眠模式有两种指令:WFI(等待中断)和WFE(等待事件)。根据Cortex-M 内核的SCR(系统控制)寄存器可以选择使用立即休眠还是退出时休眠,当 SCR 寄存器的 SLEEPONEXIT(bit1)位为 0 的时候使用立即休眠,当为 1 的时候使用退出时休眠。关于立即休眠和退出时休眠的详细内容请参考《权威指南》“第 9 章 低功耗和系统控制特性”章节。
CMSIS(Cortex 微控制器软件接口标准)提供了两个函数来操作指令 WFI 和 WFE,我们可以 直接使用这两个函数:__WFI 和__WFE。FreeRTOS 系统会使用 WFI 指令进入休眠模式。
● 退出休眠模式
如果使用 WFI 指令进入休眠模式的话那么任意一个中断都会将 MCU 从休眠模式中唤醒,如果使用 WFE 指令进入休眠模式的话那么当有事件发生的话就会退出休眠模式,比如配置一个 EXIT 线作为事件。
当 STM32F103 处于休眠模式的时候 Cortex-M3 内核停止运行,但是其他外设运行正常,比如 NVIC、SRAM 等。休眠模式的功耗比其他两个高,但是休眠模式没有唤醒延时,应用程序可以立即运行。
2)、停止(Stop)模式
停止模式基于 Cortex-M3 的深度休眠模式与外设时钟门控,在此模式下 1.2V 域的所有时钟都会停止,PLL、HSI 和 HSE RC 振荡器会被禁止,但是内部 SRAM 的数据会被保留。调压器可以工作在正常模式,也可配置为低功耗模式。如果有必要的话可以通过将 PWR_CR 寄存器的FPDS 位置 1 来使 Flash 在停止模式的时候进入掉电状态,当 Flash 处于掉电状态的时候 MCU从停止模式唤醒以后需要更多的启动延时。停止模式的进入和退出如表所示:
3)、待机(Standby)模式
相比于前面两种低功耗模式,待机模式的功耗最低。待机模式是基于 Cortex-M3 的深度睡眠模式的,其中调压器被禁止。1.2V 域断电,PLL、HSI 振荡器和 HSE 振荡器也被关闭。除了备份区域和待机电路相关的寄存器外,SRAM 和其他寄存器的内容都将丢失。待机模式的进入和退出如表所示:
退出待机模式的话会导致 STM32F1 重启,所以待机模式的唤醒延时也是最大的。实际应用中要根据使用环境和要求选择合适的待机模式。关于 STM32 低功耗模式的详细介绍和使用请参考 ST 官方的参考手册。
2、Tickless 模式详解
1)、如何降低功耗?
一般的简单应用中处理器大量的时间都在处理空闲任务,所以我们就可以考虑当处理器处理空闲任务的时候就进入低功耗模式,当需要处理应用层代码的时候就将处理器从低功耗模式唤醒。FreeRTOS 就是通过在处理器处理空闲任务的时候将处理器设置为低功耗模式来降低能耗。一般会在空闲任务的钩子函数中执行低功耗相关处理,比如设置处理器进入低功耗模式、关闭其他外设时钟、降低系统主频等等。
我们知道 FreeRTOS 的系统时钟是由滴答定时器中断来提供的,系统时钟频率越高,那么滴答定时器中断频率也就越高。以前讲过,中断是可以将 STM32F103 从睡眠模式中唤醒,周期性的滴答定时器中断就会导致 STM32F103 周期性的进入和退出睡眠模式。因此,如果滴答定时器中断频率太高的话会导致大量的能量和时间消耗在进出睡眠模式中,这样导致的结果就是低功耗模式的作用被大大的削弱。
为此,FreeRTOS 特地提供了一个解决方法——Tickless 模式,当处理器进入空闲任务周期以后就关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒。为此我们将面临两个问题:
问题一:关闭系统节拍中断会导致系统节拍计数器停止,系统时钟就会停止。
FreeRTOS 的系统时钟是依赖于系统节拍中断(滴答定时器中断)的,如果关闭了系统节拍中断的话就会导致系统时钟停止运行,这是绝对不允许的!该如何解决这个问题呢?我们可以记录下系统节拍中断的关闭时间,当系统节拍中断再次开启运行的时候补上这段时间就行了。这时候我们就需要另外一个定时器来记录这段该补上的时间,如果使用专用的低功耗处理器的话基本上都会有一个低功耗定时器,比如 STM32L4 系列(L 系列是 ST 的低功耗处理器)就有一个叫做 LPTIM(低功耗定时器)的定时器。STM32F103 没有这种定时器那么就接着使用滴答定时器来完成这个功能,具体实现方法后面会讲解。
问题二:如何保证下一个要运行的任务能被准确的唤醒?
即使处理器进入了低功耗模式,但是我的中断和应用层任务也要保证及时的响应和处理。中断自然不用说,本身就可以将处理器从低功耗模式中唤醒。但是应用层任务就不行了,它无法将处理器从低功耗模式唤醒,无法唤醒就无法运行!这个问题看来很棘手,既然应用层任务无法将处理器从低功耗模式唤醒,那么我们就借助其他的力量来完成这个功能。如果处理器在进入低功耗模式之前能够获取到还有多长时间运行下一个任务那么问题就迎刃而解了,我们只需要开一个定时器,定时器的定时周期设置为这个时间值就行了,定时时间到了以后产生定时中断,处理器不就从低功耗模式唤醒了。这里似乎又引出了一个新的问题,那就是如何知道还有多长时间执行下一个任务?这个时间也就是低功耗模式的执行时间,值得庆辛的是 FreeRTOS已经帮我们完成了这个工作。
2、Tickless 具体实现
1)、宏 configUSE_TICKLESS_IDLE
要想使用 Tickless 模式,首先必须将 FreeRTOSConfig.h 中的宏 configUSE_TICKLESS_IDLE设置为 1,代码如下:
#define configUSE_TICKLESS_IDLE 1 //1 启用低功耗 tickless 模式
2)、宏 portSUPPRESS_TICKS_AND_SLEEP()
使能 Tickless 模式以后当下面两种情况都出现的时候 FreeRTOS 内核就会调用宏portSUPPRESS_TICKS_AND_SLEEP()来处理低功耗相关的工作。
● 空闲任务是唯一可运行的任务,因为其他所有的任务都处于阻塞态或者挂起态。
● 系统处于低功耗模式的时间至少大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP个时钟节拍,宏 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 默认在文件 FreeRTOS.h 中定义为 2,我们可以在 FreeRTOSConfig.h 中重新定义,此宏必须大于 2!
portSUPPRESS_TICKS_AND_SLEEP()有个参数,此参数用来指定还有多长时间将有任务进入就绪态,其实就是处理器进入低功耗模式的时长(单位为时钟节拍数),因为一旦有其他任务 进 入 就 绪 态 处 理 器 就 必 须 退 出 低 功 耗 模 式 去 处 理 这 个 任 务 。portSUPPRESS_TICKS_AND_SLEEP()应该是由用户根据自己所选择的平台来编写的,此宏会被空闲任务调用来完成具体的低功耗工作。但是!如果使用 STM32 的话编写这个宏的工作就不用我们来完成了,因为 FreeRTOS 已经帮我们做好了,有没有瞬间觉得好幸福啊。当然了你也可以自己去重新编写,不使用 FreeRTOS 提供的 ,如果自己编写的话需要先将configUSE_TICKLESS_IDLE 设置为 2。宏 portSUPPRESS_TICKS_AND_SLEEP 在文件 portmacro.h 中定义。
3)、宏 configPRE_SLEEP_PROCESSING ()和 configPOST_SLEEP_PROCESSING()
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源。
有关产品低功耗设计的方法还有很多,大家可以上网查找一下,上面列举出的这几点在处理器进入低功耗模式之前就要完成处理。FreeRTOS 为我们提供了一个宏来完成这些操作,它就是 configPRE_SLEEP_PROCESSING(),这个宏的具体实现内容需要用户去编写。如果在进入低功耗模式之前我们降低了处理器频率、关闭了某些外设时钟等的话,那在退出低功耗模式以后就 需 要 恢 复 处 理 器 频 率 、 重 新 打 开 外 设 时 钟 等 , 这 个 操 作 在 宏configPOST_SLEEP_PROCESSING()中完成,同样的这个宏的具体内容也需要用户去编写。这两个宏会被函数 vPortSuppressTicksAndSleep()调用,我们可以在 FreeRTOSConfig.h 定义这两个宏,如下:
/********************************************************************************/
/* FreeRTOS 与低功耗管理相关配置 */
/********************************************************************************/
extern void PreSleepProcessing(uint32_t ulExpectedIdleTime);
extern void PostSleepProcessing(uint32_t ulExpectedIdleTime);
//进入低功耗模式前要做的处理
#define configPRE_SLEEP_PROCESSING PreSleepProcessing
//退出低功耗模式后要做的处理
#define configPOST_SLEEP_PROCESSING PostSleepProcessing
函数 PreSleepProcessing()和 PostSleepProcessing()可以在任意一个 C 文件中编写,本章对应的例程是在 main.c 文件中,函数的具体内容在下一节详解。
4)、宏 configEXPECTED_IDLE_TIME_BEFORE_SLEEP
处理器工作在低功耗模式的时间虽说没有任何限制,1 个时钟节拍也行,滴答定时器所能计时的最大值也行。但是时间太短的话意义也不大啊,就 1 个时钟节拍,我这刚进去就得出来!所 以 我 们 必 须 对 工 作 在 低 功 耗 模 式 的 时 间 做 个 限 制 , 不 能 太 短 了 , 宏configEXPECTED_IDLE_TIME_BEFORE_SLEEP 就是用来完成这个功能的。此宏默认在文件FreeRTOS 中有定义,如下:
#ifndef configEXPECTED_IDLE_TIME_BEFORE_SLEEP
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
#endif
#if configEXPECTED_IDLE_TIME_BEFORE_SLEEP < 2
#error configEXPECTED_IDLE_TIME_BEFORE_SLEEP must not be less than 2
#endif
默认情况下 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 为 2 个时钟节拍,并且最小不能小于 2 个时钟节拍。如果要修改这个值的话可以在文件 FreeRTOSConfi.h 中对其重新定义。此宏会在空闲任务函数 prvIdleTask()中使用。