FreeRTOS-Tickless模式
很多场合中对功耗要求很严格,并且很多MCU都带有低功耗模式,合理地使用这些低功耗模式可以降低系统的功耗。同时,FreeRTOS也提供了Tickless功耗模式,这样软件和硬件相结合,可以更进一步降低系统功耗,并且配置也很简单。
STM32低功耗模式
STM32本身支持低功耗模式,它有休眠、停止、待机三种低功耗模式,相比来说,待机模式功耗最低,休眠模式功耗最高,但是相应地,睡眠模式唤醒延迟最低。下表给出三种模式的对比。
模式
开启方式
唤醒方式
对1.2v域时钟影响
对VDD域时钟影响
调压器
休眠
WFI或WFE
WFI时,任意中断唤醒;WFE时,事件唤醒
CPU时钟关闭,其他时钟无影响
无影响
开启
停止
PDDS和LPDS位+SLEEPDEEP位+WFI位或WFE位
任意EXTI线
所有1.2v域时钟都关闭
HSI和HSE振荡器关闭
开启或处于低功耗模式
待机
PDDS为+SLEEPDEEP位+WFI位或WFE
WKUP引脚上升沿、RTC相关事件或中断
所有1.2v域时钟关闭
HSI和HSE振荡器关闭
关闭
睡眠模式
进入睡眠模式有两种指令:WFI(等待中断)和WFE(等待事件)。CMSIS中提供了两个函数来操作指令WFI和WFE,我们可以直接使用这两个函数,分别是__WFI和__WFE,FreeRTOS系统使用的是WFE进入休眠模式。
如果使用WFI进入休眠模式,那么休眠期间 任意一个中断都会将MCU从休眠状态只用唤醒;使用WFE进入休眠模式,那么当有事件发生的话就会退出休眠模式。STM32进入休眠模式的时候,CM3内核停止运行,但是其他外设都正常。
Tickless模式
在前面时间信息统计那一章节中,我们曾经统计了各个任务运行占用的时间,结果发现,处理器大部分时间都在处理空闲任务,而空闲任务一般都是不重要的任务,所以这期间可以进入低功耗模式。FreeRTOS就是通过这样来降低功耗的,一般会在空闲任务的相关钩子函数中进行一些相关低功耗处理,比如关闭外设时钟、降低系统主频等等。但是这里有个问题就是,既然FreeRTOS使用的是WFI,如果不关闭Systick只要有中断发生就会退出低功耗模式,这样对降低功耗极其不利。如果关闭SyStick那么会导致系统时钟停止运行,系统时钟都停止运行了,FreeRTOS系统就没办法正常工作了。所以,为了解决这个问题,我们还需要另一个定时器,该定时器只需要将系统节拍停止这段时间补偿上去就可以了,如果使用的MCU自带低功耗定时器,那么可以使用这个低功耗定时器来完成这项任务。不幸的是,STM32F1没有这个定时器,那是不是意味着就不能使用Tickless模式了?照样可以使用,只是我们会继续使用滴答定时器来完成这个工作,不过此时的滴答定时器工作模式有所变化。这时候当进入低功耗模式前,将会计算下一个任务的执行时间,根据这个计算时间,滴答定时器只需要在该时间点将系统唤醒,而不需要每个节拍都唤醒,然后再将这段时间补偿到系统实拍中就可以解决了。那么如何计算下一个任务的唤醒时间呢?值得庆幸的是,FreeRTOS已经帮我们提前做好了。我们只需要完成相关配置即可。下面讲述配置过程。
Tickless模式配置
首先开启低功耗模式,也就是使能宏:configUSE_TICKLESS_IDLE 为1。
开启低功耗模式以后,当满足空闲任务是唯一可运行的任务并且系统处于低功耗模式的时间大于configEXPECTED_IDLE_TIME_BEFORE_SLEEP个时钟节拍系统才会进入低功耗模式,为什么要有这个条件呢?其实容易想到,如果低功耗模式时间过短,刚进入低功耗模式就推出了,那就完全没有必要进入了。这个最小节拍可以有用户自己定义。系统默认定义如下
# 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
从上面可以看出,低功耗持续时间最小是2个节拍。
满足上述条件后,系统会调用vPortSuppressTicksAndSleep()这个函数,该函数会使系统进入睡眠模式,并进行任务补偿,需要注意的是,在进入低功耗模式前和退出低功耗模式后,系统都会调用两个函数来做一些低功耗的处理,这两个函数分别是configPRE_SLEEP_PROCESSING()和configPOST_SLEEP_PROCESSING(),这两个函数需要用户自己定义,其完成的功能可以是使能或除能相应外设,降低或回复系统时钟频率等。这里我们定义如下:
FreeRTOSConfig.h文件
extern void PreSleepProcessing ( u32 xExpectedIdleTime) ;
extern void PostSleepProcessing ( u32 xExpectedIdleTime) ;
# define configPRE_SLEEP_PROCESSING PreSleepProcessing
# define configPOST_SLEEP_PROCESSING PostSleepProcessing
void PreSleepProcessing ( TickType_t xExpectedIdleTime)
{
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOA, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOC, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOD, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOE, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOF, DISABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOG, DISABLE) ;
}
void PostSleepProcessing ( TickType_t xExpectedIdleTime)
{
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOA, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOC, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOD, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOE, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOF, ENABLE) ;
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOG, ENABLE) ;
}
低功耗相关的配置就讲述完了,这一章只是讲了如何使用和配置Tickless模式,一些深入的内容这里没有分析。