本节我们来讲讲FreeRTOS的低功耗模式——tickless的相关知识。
Tickless在许多小型RTOS中都有应用,是一种通用的实现低功耗的方法;需要注意的是,Tickless只是操作系统层面的实现手段,实质上是帮助用户识别可以进入低功耗的时间段,真正进入低功耗模式还需要MCU的硬件支持。
学习本节内容前,最好了解stm32的低功耗相关知识,可以浏览笔者以前的文章:STM32编程HAL库开发的第14篇《电源控制(三种低功耗模式:sleep、stop、standby)》
1)tickless模式的原理
我们知道,FreeRTOS的任务,是以系统滴答时钟节拍来运行的,执行完一段工作后,任务可以调用系统的延时函数(如vTaskDelay)来延时一定的时钟节拍周期,之后再次运行。
假如所有的任务都暂时执行完毕,进入了阻塞态,那么此时就可以进入CPU的低功耗模式来减少能量的消耗;只要在最近的需要运行任务的那个时钟节拍唤醒CPU,就不会对各个任务的执行产生任何影响。
举一个例子便于理解,见下图:
在时间0~2时,任务A、B、C有可能运行,会一直有用户任务占用CPU;
到时刻2时,任务A、B、C都进入阻塞态,空闲任务得到运行;
到时刻4时,又有任务进入到了运行态,那么CPU会退出空闲任务,执行运行态的任务。
在这一段过程中,时刻2~4之间的两个时钟节拍,就可以使MCU进入低功耗模式,只要在时刻4把MCU唤醒,那么用户任务的执行就不受任何影响。
FreeRTOS中的tickless模式的功能,就是在空闲任务执行时(代表没有用户任务在执行),进入MCU的低功耗模式,然后在最近的需要唤醒MCU的那个时钟节拍时,把MCU唤醒,再更新时钟节拍的计数(使得时钟不会因为进入低功耗而漏计节拍数)。
从tickless的原理上来看,进入低功耗模式的时间点很好确定,因为所有的用户任务都不执行时,就会进入空闲任务,所以我们只需要在空闲任务中执行进入MCU低功耗模式的操作就可以了。
退出低功耗模式时,如果是被中断唤醒,那么不需要做特殊的处理,只需要在唤醒后更新时钟节拍数;而如果是任务延时后被唤醒,就有点麻烦了,因为需要计算所有任务的延时,选出离运行时刻最短的那个,在这个时钟节拍来唤醒MCU,并更新时钟节拍数。由于多个任务同时运行,它们的延时、阻塞都不确定,而且是动态变化的,它们之间的延时还有交叉、重叠,所以让MCU休眠多长时间是很难确定的。
而FreeRTOS的tickless模式的强大之处在于它将这个需要唤醒的时长帮我们计算好了,我们甚至不需要知道这个时长是多少,也不需要知道它在哪起的作用,只要开启了tickless模式,它就会在空闲任务中让MCU进入低功耗模式,并且在最近的需要运行任务的那个时钟节拍将MCU唤醒;顺便把更新时钟节拍的工作也干了。
2)FreeRTOS中低功耗模式的使用
使用Tickless模式几个重要的宏定义和函数:
a) 要想使用tickless模式,要先将FreeRTOSConfig.h文件中的宏configUSE_TICKLESS_IDLE定义为1。
b) 另外一个重要的宏,是在FreeRTOS.h文件中定义的configEXPECTED_IDLE_TIME_BEFORE_SLEEP,这个宏定义了一个周期数,只有空闲任务连续执行的时间大于这个周期,才会进入低功耗模式。这个宏定义是不能小于2的,也就是说,只有系统计算出有两个或以上的周期只运行空闲任务时,才会进入低功耗模式。
c) portmacro.h文件中的portSUPPRESS_TICKS_AND_SLEEP()宏,是进入和退出低功耗模式的关键,它会被空闲任务调用,完成进入和退出低功耗的工作,并更新系统时钟节拍计数;这是tickless模式工作的最主要的一个函数。使用STM32的话,FreeRTOS已经帮我们把这个宏实现好了。如果是HAL库生成的,一般在port.c文件中,由vPortSuppressTicksAndSleep这个函数实现,可以看到其中有这么几行:
中间的__wfi();这句,其实就是嵌入的汇编指令WFI,即进入低功耗模式。
如果是其他硬件平台,可能需要自己编写这个宏或函数,并且configUSE_TICKLESS_IDLE宏定义要改为2,即使用自定义的低功耗处理函数。
d) configPRE_SLEEP_PROCESSING()和configPOST_SLEEP_PROCESSING()宏,会在portSUPPRESS_TICKS_AND_SLEEP()实现的宏里面被调用,见上图,它们是需要用户编写的。这两个宏是用在MCU进入低功耗模式前、退出低功耗模式后执行的操作。一般在进入低功耗模式前,可以设置降低时钟主频、改为内部RC时钟、关闭一些无必要的外设等等,以降低硬件功耗;而退出低功耗模式后,需要将这些设置恢复。
默认情况下,这两个函数是空函数,用户可以自行添加需要的操作,如关闭部分外设的时钟,等等:
设置好以上几项,就可以使用低功耗模式了。
3)编程试验
下面我们来编程试验一下tickless低功耗模式的使用。
Cubemx中建立工程的时候,注意使能tickless模式,如下图,选择Built in模式:
注意,这里使能有两个选项,Built in表示的是使能内置的低功耗模式;User defined表示的是使能用户自定义的模式。这里的这个设置是改变宏configUSE_TICKLESS_IDLE的值,Built in是将其设置为1,User defined是将其设置为2。
我们可以在这里修改,也可以在生成keil工程后,直接在源文件FreeRTOSConfig.h中修改configUSE_TICKLESS_IDLE的宏定义为1:
时钟基准要设置为systick:
因为cubemx里生成的低功耗代码,是以systick作为freeRTOS的节拍时钟的,如果不这么设置,自动生成的代码会无法在低功耗模式时关闭时钟节拍。
再确认一下,没有意外的中断会打断低功耗模式:
生成keil代码,在keil工程中编写代码。首先是三个任务的处理函数。
DefaultTask和Task03,都是延时2000ms后打印运行的消息:
Task02,先占用CPU时间1000ms,再延时1000ms运行,期间打印提示信息:
另外,在进入低功耗之前,打印即将睡眠的节拍数:
这个程序运行结果如下:
由后面的时间戳可以观察打印信息执行的大致时刻。
首先Task02占用了1s的CPU时间,然后开始延时;
在延时的1000ms期间,低功耗模式开始运行了,它连续几次进入睡眠状态,共睡眠了233*4+65 = 997个周期;
之后被唤醒,Task03开始运行了;
Task03运行完之后,又进入了低功耗模式,睡眠了3个节拍;
之后Task02完成了一个循环;睡眠时间总共997 + 3 = 1000个周期。
可以观察到,这个程序的输出,前几次都是睡眠233个节拍,这是系统为了防止超过systick计时的最大值自动做的限制。
另外,可以观察到优先级较高的Task03任务,每次运行都相对Task02提早了几个节拍,这可能是因为进、出低功耗模式时,以及打印输出时,都会占用一点时间。
好了,本节的内容就到这里了。
如果觉得有用可以关注作者微信号“小白白学电子”,在公众号可以找到代码和资料下载地址: