以下转载自安富莱电子: http://forum.armfly.com/forum.php
本章节为大家讲解 FreeRTOS 本身支持的低功耗模式 tickless 实现方法,tickless 低功耗机制是当前
小型 RTOS 所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III(类似方法)都有这种机制。
tickless 低功耗模式介绍
tickless 低功耗机制是当前小型 RTOS 所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III (类
似方法)都有这种机制。
FreeRTOS 的低功耗也是采用的这种方式,那么 tickless 又是怎样一种模式呢?仅从字母上看 tick 是
滴答时钟的意思,less 是 tick 的后缀,表示较少的,这里的含义可以表示为无滴答时钟。 整体看这个字母
就是表示滴答时钟节拍停止运行的情况。
反映在 FreeRTOS 上,tickless 又是怎样一种情况呢?我们都知道,当用户任务都被挂起或者阻塞时,
最低优先级的空闲任务会得到执行。 那么 STM32 支持的睡眠模式,停机模式就可以放在空闲任务里面实
现。 为了实现低功耗最优设计,我们还不能直接把睡眠或者停机模式直接放在空闲任务就可以了。 进入空
闲任务后,首先要计算可以执行低功耗的最大时间,也就是求出下一个要执行的高优先级任务还剩多少时
间。 然后就是把低功耗的唤醒时间设置为这个求出的时间,到时间后系统会从低功耗模式被唤醒,继续执
行多任务。这个就是所谓的 tickless 模式。 从上面的讲解中可以看出,实现 tickless 模式最麻烦是低功耗
可以执行的时间如何获取。 关于这个问题,FreeRTOS 已经为我们做好了。
FreeRTOS 的低功耗模式介绍
对于 Cortex-M3 和 M4 内核来说,FreeRTOS 已经提供了 tickless 低功耗代码的实现,通过调用指
令 WFI 实现睡眠模式,具体代码的实现就在 port.c 文件中,用户只需在 FreeRTOSConfig.h 文件中配置
宏定义 configUSE_TICKLESS_IDLE 为 1 即可。 如果配置此参数为 2,那么用户可以自定义 tickless 低功
耗模式的实现。 当用户将宏定义 configUSE_TICKLESS_IDLE 配置为 1 且系统运行满足以下两个条件时,
系统内核会自动的调用低功耗宏定义函数 portSUPPRESS_TICKS_AND_SLEEP():
-------------------------------
当前空闲任务正在运行,所有其它的任务处在挂起状态或者阻塞状态。
根据用户配置 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的大小,只有当系统可运行于低功耗模
式的时钟节拍数大于等于这个参数时,系统才可以进入到低功耗模式。 此参数默认已经在 FreeRTOS.h
文件进行定义了,下面是具体的定义内容,当然,用户也可以在 FreeRTOSConfig.h 文件中重新定义:
默认定义的大小是 2 个系统时钟节拍,且用户自定义的话,不可以小于 2 个系统时钟节拍。
---------------------------------
函数 portSUPPRESS_TICKS_AND_SLEEP 是 FreeRTOS 实现 tickles 模式的关键,此函数被空闲任务
调用,其定义是在 portmacro.h 文件中:
FreeRTOS 在线电子手册低功耗的说明 http://www.freertos.org/low-power-tickless-rtos.html。
FreeRTOS 的低功耗模式配置
关于 FreeRTOS 低功耗方面的配置主要涉及到以下几个问题。
- 滴答定时器频率与系统主频的关系
对于 Cortex-M3 和 M4 内核的微控制器来说,实时操作系统一般都是采用滴答定时器做系统时钟,
FreeRTOS 也不例外。 SysTick 滴答定时器是一个 24bit 的递减计数器,有两种时钟源可选择,一个是系
统主频,另一个是系统主频的八分频,默认的 port.c 移植文件中是用的系统主频。 这里我们就根据这两种
时钟源来说一说配置上的不同。
SysTick 滴答定时器时钟源选择系统主频
如果滴答定时器选择系统主频的话,那么需要配置 configSYSTICK_CLOCK_HZ 等于
configCPU_CLOCK_HZ,这种关系已经在 port.c 文件中进行默认配置了:其中系统主频 configCPU_CLOCK_HZ 是在 FreeRTOSConfig.h 文件中进行定义的。
SysTick 滴答定时器时钟源选择系统主频的八分频
这种情况的话,需要用户在 FreeRTOSConfig.h 文件中专门配置 configSYSTICK_CLOCK_HZ 为实际
的频率,即系统主频的八分频大小。 - 系统时钟节拍不使用滴答定时器
这种情况我们这里不做讨论,用户看 FreeRTOS 官网此处的说明即可:
http://www.freertos.org/low-power-ARM-cortex-rtos.html - 如何使用微控制器其它低功耗模式
前面我们说了,对 Cortex-M3 和 M4 内核来说,FreeRTOS 自带的低功耗模式是通过指令 WFI 让系
统进入睡眠模式,如果想让系统进入停机模式,又该怎么修改呢?FreeRTOS 为我们提供了两个函数:
configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
configPOST_SLEEP_PROCESSING( xExpectedIdleTime )
这两个函数的定义是在 FreeRTOS.h 文件中定义的,什么都没有执行:
如果需要实际执行代码需要用户在 FreeRTOSConfig.h 文件中重新进行宏定义,将其映射到一个实际的函
数中。 另外,这两个函数是在 port.C 文件中被函数 vPortSuppressTicksAndSleep 调用,具体位置如下:这两个函数位于指令 wfi 的前面和后面,用户想实现其它低功耗方式的关键就在这两个函数里面:
configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
执行低功耗模式前,用户可以在这个函数里面关闭外设时钟来进一步降低系统功耗。 设置其它低功耗
方式也是在这个函数里面,用户只需设置参数 xExpectedIdleTime=0 即可屏蔽掉默认的 wfi 指令执
行方式,因为退出这个函数后会通过 if 语句检测此参数是否大于 0,即上面的代码所示。 因此,如果
用 户 想 实 现 其 它 低 功 耗 模 式 还 是 比 较 方 便 的 , 配 置 好 其 它 低 功 耗 模 式 后 , 设 置 参 数
xExpectedIdleTime = 0 即可,但切不可将此参数随意设置为 0 以外的其它数值。
configPOST_SLEEP_PROCESSING ( xExpectedIdleTime )
退出低功耗模式后,此函数会得到调用,之前在 configPRE_SLEEP_PROCESSING 里面关闭的外设时
钟,可以在此函数里面重新打开,让系统恢复到正常运行状态。 - FreeRTOS 实现 tickless 模式的框架
对 Cortex-M3 和 M4 内核的微控制器来说,FreeRTOS 已经提供了 tickless 低功耗模式的代码,对
于没有支持的微控制器,用户可以在 FreeRTOSConfig.h 文件中配置 portSUPPRESS_TICKS_AND_SLEEP
宏定义,来映射实际执行函数。
如果用户不想使用 FreeRTOS 提供的的 tickless 也可以自定义,方法也是在 FreeRTOSConfig.h 文件
中配置 portSUPPRESS_TICKS_AND_SLEEP 宏定义,来映射实际执行函数。
下面是 FreeRTOS 实现低功耗 tickless 模式的代码框架,方便用户对 tickles 模式有一个认识,同时
也方便 FreeRTOS 没有支持的微控制器,用户可以参考实现。 当然,不局限于这种方法,用户有更好的方
法,也可以的。 其中函数 vTaskStepTick 和 eTaskConfirmSleepModeStatus 是 FreeRTOS 提供的,其
余的函数是需要用户实现的。
显然用户自己配置要麻烦得多,好在FreeRTOS为M3,M4内核的做好了低功耗之睡眠模式,使得我们使用STM32 F1,F4系列的时候可以很简单,只需要在配置文件config.h中加上一个宏定义:#define configUSE_TICKLESS_IDLE 1
那我们要实现停机模式呢?那就只有自己动手了。
在介绍实现停机模式之前,有必要提醒一下:在低功耗的时候,要根据情况配置
回到正题,如何添加用户函数实现停机模式:
* 说 明 : 本实验主要学习FreeRTOS的低功耗(tickless之停机模式) * 实验目的: * 1. 学习FreeRTOS的低功耗(tickless之停机模式) * 2. FreeRTOS自带的tickless 模式使用比较简单,只需用户使能宏配置: * #define configUSE_TICKLESS_IDLE 1 * 3. 为了打印系统信息,前面的所有试验中始化一个定时器中断,精度高于滴答定时器中断, * 每50us进一次,本例子关闭了任务执行情况打印功能,因为高频率的定时器中断影响低 * 功耗tickless模式效率。 * 4. 本实验在FreeRTOS自带的tickless睡眠模式的基础上实现停机模式,实现方法是重新 * 在文件FreeRTOSConfig.h定义如下函数两个函数 * #define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x) * #define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x) * 在文件port.c里面函数vPortSuppressTicksAndSleep调用了上面这两个函数: * --------------------------------------------------------------------- * configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); * if( xModifiableIdleTime > 0 ) * { * __dsb( portSY_FULL_READ_WRITE ); * __wfi(); * __isb( portSY_FULL_READ_WRITE ); * } * configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); * ----------------------------------------------------------------------- * 函数OS_PreSleepProcessing和OS_PostSleepProcessing是需要用户提供的,用户可以在 * 这两个函数里面实现进入停机模式和从停机模式恢复到正常的运行状态。另外,通过在函数 * OS_PreSleepProcessing里设置其形参变量vParameters为0就可以屏蔽原有的休眠模式。
/* ********************************************************************************************************* * 函 数 名: OS_PreSleepProcessing * 功能说明: 下面的函数在文件FreeRTOSConfig.h文件里面进行了宏定义: * #define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x) * #define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x) * 在文件port.c里面函数vPortSuppressTicksAndSleep调用了上面这两个函数: * --------------------------------------------------------------------- * configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); * if( xModifiableIdleTime > 0 ) * { * __dsb( portSY_FULL_READ_WRITE ); * __wfi(); * __isb( portSY_FULL_READ_WRITE ); * } * configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); * ----------------------------------------------------------------------- * 通过这两个函数可以实现在调用__WFI或者__WFE指令前后执行进一步的低功耗操作,主要有以下三种: * 1. 降低系统主频。 * 2. 关闭外设时钟。 * 3. IO引脚要做处理,防止拉电流和灌电流增加功耗。 * 如果此IO口带上拉,请设置为高电平输出或者高阻态输入; * 如果此IO口带下拉,请设置为低电平输出或者高阻态输入; * 下面的函数未做关闭外设时钟的处理。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void OS_PreSleepProcessing(uint32_t vParameters) { (void)vParameters; /* 用户可以考虑在此处加入关闭外设时钟来进一步降低功耗 */ vParameters = 0; PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE); } void OS_PostSleepProcessing(uint32_t vParameters) { /* 如果前面关闭了外设时钟,需要在这里恢复 */ /* 1、当一个中断或唤醒事件导致退出停止模式时,HSI RC振荡器被选为系统时钟。 2、退出低功耗的停机模式后,需要重新配置使用HSE。 */ RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET){} RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){} RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08){} }