FreeRTOS低功耗模式
FreeRTOS支持低功耗模式 tickless,tickless 低功耗机制时当前小型RTOS所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III(类似方法)都有这种机制。
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文件进行定义了,下面是具体的定义内容,默认定义的大小是 2 个系统时钟节拍,且用户自定义的话,不可以小于 2 个系统时钟节拍。
#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
portSUPPRESS_TICKS_AND_SLEEP()函数是FreeRTOS实现tickless模式的关键,此函数被空闲任务调用,其定义是在portmacro.h文件中:
/* Tickless idle/low power functionality. */
#ifndef portSUPPRESS_TICKS_AND_SLEEP
extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime );
#define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )
#endif
其中函数vPortSuppressTicksAndSleep()是实际的低功耗执行代码,在port.c文件中定义,参数xExpectedIdleTime就是系统可以处于低功耗模式的系统时钟节拍数。
FreeRTOS低功耗模式配置
关于FreeRTOS低功耗方面的配置:
- 如何使用MCU其他低功耗模式
对Cortex-M3和M4内核,FreeRTOS自带的低功耗模式是通过指令WFI让系统进入睡眠模式,如果想让系统进入停机模式,又该怎么修改呢?FreeRTOS 为我们提供了两个函数:
① configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
② configPOST_SLEEP_PROCESSING( xExpectedIdleTime )
这两个函数的定义实在FreeRTOS.h文件中定义的,什么都没有执行:
#ifndef configPRE_SLEEP_PROCESSING
#define configPRE_SLEEP_PROCESSING( x )
#endif
#ifndef configPOST_SLEEP_PROCESSING
#define configPOST_SLEEP_PROCESSING( x )
#endif
如果需要实际执行代码需要用户在 FreeRTOSConfig.h 文件中重新进行宏定义,将其映射到一个实际的函数中。 另外,这两个函数是在 port.C 文件中被函数vPortSuppressTicksAndSleep 调用,具体位置如下:
/* Sleep until something happens. configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again. However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( &xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
__dsb( portSY_FULL_READ_WRITE );
__wfi();
__isb( portSY_FULL_READ_WRITE );
}
configPOST_SLEEP_PROCESSING( &xExpectedIdleTime );
这两个函数位于指令 wfi 的前面和后面,用户想实现其它低功耗方式的关键就在这两个函数里面:
① configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
执行低功耗模式前,用户可以在这个函数里面关闭外设时钟来进一步降低系统功耗。 设置其它低功耗方式也是在这个函数里面,用户只需设置参数 xExpectedIdleTime=0 即可屏蔽掉默认的 wfi 指令执行方式,因为退出这个函数后会通过 if 语句检测此参数是否大于 0,即上面的代码所示。因此,如果用户想实现其它低功耗 模式还是比较方便的,配置好其它低功耗模式后,设置参数xExpectedIdleTime = 0 即可,但切不可将此参数随意设置为 0 以外的其它数值。
② configPOST_SLEEP_PROCESSING ( xExpectedIdleTime )
退出低功耗模式后,此函数会得到调用,之前在configPRE_SLEEP_PROCESSING 里面关闭的外设时钟,可以在此函数里面重新打开,让系统恢复到正常运行状态。
- 如何添加用户函数实现停机模式
在FreeRTOSConfig.h文件中,进行了宏定义:
#define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x)
#define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x)
用户需要提供OS_PreSleepProcessing(x)和OS_PostSleepProcessing(x)函数,可在里面实现进入停机模式和从停机模式恢复正常的运行状态。另外,通过在函数OS_PreSleepProcessing(x)里设置其形参变量 vParameters = 0就可以屏蔽原有的休眠模式。
FreeRTOS低功耗实现步骤
- 在文件FreeRTOSConfig.h配置宏configUSE_TICKLESS_IDLE
#define configUSE_TICKLESS_IDLE 1
- 在FreeRTOS自带的tickless睡眠模式的基础上实现停机模式
在文件FreeRTOSConfig.h定义如下两个函数
#define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x)
#define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x)
3.实现函数OS_PreSleepProcessing(x)和OS_PostSleepProcessing(x)
通过这两个函数可以实现在调用_WFI或者_WFE指令前后执行进一步低功耗操作,主要有一下三种:
① 降低系统主频。
② 关闭外设时钟。
③ 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){}
}
以上转载至https://www.cnblogs.com/yangguang-it/p/7232448.html
函数OS_PreSleepProcessing(x)和OS_PostSleepProcessing(x)实现例子
#define LOW_POWER_FEATURE 2 //1=sleep, 2=stop, 3=standby
/*
true = Can go into sleep
false = Can't go into sleep
*/
volatile bool gIfCanIntoSleep = false;
void OS_PreSleepProcessing(void* vParameters)
{
if (gIfCanIntoSleep == false) //
{
return;
}
vParameters = (void*)0;
#if (LOW_POWER_FEATURE == 1) //sleep mode, See 'HAL_PWR_EnterSLEEPMode'
/* Clear SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
#elif (LOW_POWER_FEATURE == 2)//stop mode, See 'HAL_PWR_EnterSTOPMode'
/* Clear PDDS bit in PWR register to specify entering in STOP mode when CPU enter in Deepsleep */
CLEAR_BIT(PWR->CR, PWR_CR_PDDS);
/* Select the voltage regulator mode by setting LPDS bit in PWR register according to Regulator parameter value */
MODIFY_REG(PWR->CR, PWR_CR_LPDS, PWR_LOWPOWERREGULATOR_ON);
//MODIFY_REG(PWR->CR, PWR_CR_LPDS, PWR_MAINREGULATOR_ON);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
#elif (LOW_POWER_FEATURE == 3)//standby mode
/* For Naiad, we should not use standby mode, beaucase we use IO interrupt. */
HAL_PWR_EnterSTANDBYMode();
#endif
}
void OS_PostSleepProcessing(void* vParameters)
{
naiad_close_led_dat();
#if (LOW_POWER_FEATURE == 1) //sleep mode, See 'HAL_PWR_EnterSLEEPMode'
//Do nothing.
#elif (LOW_POWER_FEATURE == 2)//stop mode, See 'HAL_PWR_EnterSTOPMode'
/* Reset SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
SystemClock_Config();
#endif
}