一般前后台程序就是查询,Delay,中断.一直在一个主频下,或者轮询的延迟时候休眠一下,等待标志等等.当然,在RTOS中可能没那么简单,任务多,不知道哪个完成哪个怎么的,要做到低功耗,就得靠RTOS有一定的能力.
实现OS的低功耗,一种是靠IDLE任务,一种是用户适当停止当前任务.而这个适当,没那么简单.
Tickless Idle 的设计思想在于尽可能得在 MCU 空闲时使其进入低功耗模式,这是FreeRTOS的低功耗使用方法,他只关断CPU状态.
合理的进入低功耗模式,更不能是进入低功耗几个周期又出来,这样很没意义,FreeRTOS是基于SysTick来运作的,当然你也可以是其他时钟,只是配置起来麻烦了一些, 任务调度器可以预期到下一个周期性任务的触发时间,但是突发任务却无法预测,要用户有自己的中断来触发唤醒,调整SysTick中断触发时间,可以少进入几次中断,少唤醒几次,也就是Tick less,从而更长的时间停留在低功耗模式中,这时候SysTick的RELOAD是变化的.
MCU可能被两种不同的情况唤醒,动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 MCU 处于低功耗模式下的时间,这样在MCU唤醒后对系统时间进行软件补偿,不至于系统的一些时间周期错误.比如vTaskDelay可能因此误以为足够延迟等等.
进入prvIdleTask,也就是IDLE任务后就立马有一个判断.
具体先使用下面代码计算用户所需空闲等待时间.
xExpectedIdleTime = prvGetExpectedIdleTime();
然后判断延迟的时间是否值得进入低功耗:
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
如果合理,挂起调度器,因为要马上进入低功耗了.
vTaskSuspendAll();
再判断一次,免得因为进入过程插入的时间周期导致进入空闲时候不合理.
xExpectedIdleTime = prvGetExpectedIdleTime();
如果依然要进入低功耗,就进入吧.否则直接恢复调度器,如果真的进入低功耗,就要等唤醒才能执行下一句,也是恢复调度器.
xTaskResumeAll();
首先进入低功耗的函数是portSUPPRESS_TICKS_AND_SLEEP,这其实是一个define.
extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ); #define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )
对应函数默认是weak的,也就是我们自己可以写一个函数来代替他.
首先有这么一个判断.最大的Idle时间不能超过系统最大Idle数值.否则还是多Tick几次.
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) { xExpectedIdleTime = xMaximumPossibleSuppressedTicks; }
然后去掉SysTick的使能.
portNVIC_SYSTICK_CTRL &= ~portNVIC_SYSTICK_ENABLE;
计算一下需要等多久才唤醒.
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) ); if( ulReloadValue > ulStoppedTimerCompensation ) { ulReloadValue -= ulStoppedTimerCompensation; }
然后判断是否可以休眠.
if( eTaskConfirmSleepModeStatus() == eAbortSleep )
如果不可以就重新开始计时.
/* Restart from whatever is left in the count register to complete this tick period. */ portNVIC_SYSTICK_LOAD = portNVIC_SYSTICK_CURRENT_VALUE; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL |= portNVIC_SYSTICK_ENABLE; /* Reset the reload register to the value required for normal tick periods. */ portNVIC_SYSTICK_LOAD = ulTimerCountsForOneTick - 1UL; /* Re-enable interrupts - see comments above __disable_irq() call above. */ __enable_irq();
可以低功耗的话设置新的重载,进入低功耗.
/* Set the new reload value. */ portNVIC_SYSTICK_LOAD = ulReloadValue; /* Clear the SysTick count flag and set the count value back to zero. */ portNVIC_SYSTICK_CURRENT_VALUE = 0UL; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL |= portNVIC_SYSTICK_ENABLE;
进入休眠的地方有两个钩子,一个进入前钩子,一个退出后钩子,可以使用关闭一些外设等等.
configPRE_SLEEP_PROCESSING( &xModifiableIdleTime ); if( xModifiableIdleTime > 0 ) { __dsb( portSY_FULL_READ_WRITE ); __wfi(); __isb( portSY_FULL_READ_WRITE ); } configPOST_SLEEP_PROCESSING( &xExpectedIdleTime );
进入后立马判断是不是因为够钟才清醒的.
if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG ) != 0 )
如果不是的话,就是意外突发事件.如果是正常,重新补偿时间就够了.
uint32_t ulCalculatedLoadValue; /* The tick interrupt has already executed, and the SysTick count reloaded with ulReloadValue. Reset the portNVIC_SYSTICK_LOAD with whatever remains of this tick period. */ ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE ); /* Don't allow a tiny value, or values that have somehow underflowed because the post sleep hook did something that took too long. */ if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) ) { ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ); } portNVIC_SYSTICK_LOAD = ulCalculatedLoadValue; /* The tick interrupt handler will already have pended the tick processing in the kernel. As the pending tick will be processed as soon as this function exits, the tick value maintained by the tick is stepped forward by one less than the time spent waiting. */ ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
完全退出休眠,重启SysTick到正常状态.
portNVIC_SYSTICK_CURRENT_VALUE = 0UL; portENTER_CRITICAL(); { portNVIC_SYSTICK_CTRL |= portNVIC_SYSTICK_ENABLE; vTaskStepTick( ulCompleteTickPeriods ); portNVIC_SYSTICK_LOAD = ulTimerCountsForOneTick - 1UL; } portEXIT_CRITICAL();
这样,通过RTOS不断进入WFI,WFE状态,可以在IDLE时候节省冷源.