FreeRTOS低功耗模式
在系统或电源复位以后,微控制器处于运行状态。当CPU不需继续运行时,可以利用多种低功耗模式来节省功耗,例如等待某个外部事件时,用户需要根据最低电源消耗,最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。
STM32有三种低功耗模式:
1.睡眠模式(Cortex内核停止,所有外设包括Cortex核心的外设,如NVIC,系统嘀嗒定时器Systick等仍在运行)
2.停机模式(所有的时钟都停止)
3.待机模式(1.8V电源关闭)
这三种模式的对比如下
如何有效降低睡眠模式的功耗
设计低功耗主要从以下几个方面着手:
1.关闭可以关闭的外设时钟。
2.降低系统主频。WOM
3.注意I/O的状态,因为睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。
如何有效降低停机模式的功耗
设计低功耗主要从以下几个方面着手
1.注意I/O的状态,因为在停机状态下,所有I/O都被保持在它们运行模式时的状态
2.注意I/O和外设IC的链接。
3.测试低功耗的时候,一定不要链接调试器,更不能边调试边侧电流。
* 关于低功耗的停机模式说明:
(1) 停止模式是在Corte的深睡眠模式基础上结合了外设的时钟控制机制,在停止模式下电压调节器可运行在正常或低功耗模式。此时在1.2V供电区域的的所有时钟都被停止,PLL、HSI和HSE的RC振荡器的功能被禁止,SRAM和寄存器内容被保留下来。
(2) 在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态。
(3) 一定要关闭滴答定时器,实际测试发现滴答定时器中断也能唤醒停机模式。
(4) 当一个中断或唤醒事件导致退出停止模式时, HSI RC振荡器被选为系统时钟。
(5) 退出低功耗的停机模式后,需要重新配置使用HSE。
我们知道一般简单应用中处理器大量的时间都在处理空闲任务,所以我们就可以考虑处理器处理空闲任务的时候进入低功耗模式,当需要处理空闲任务的时候就进入了低功耗模式,当需要处理应用层代码的时候就将处理器从低功耗模式中唤醒。但FreeRTOS的系统时钟是由嘀嗒定时器中断来提供的,系统时钟频率越高,嘀嗒定时器中断频率也就越高。如果嘀嗒定时器中断频率太高的话会导致大量的能量和时间消耗再进出睡眠模式中,这样导致的结果就是低功耗模式的作用被大大的消弱。
为此,FreeRTOS提供了tickless模式,即当处理器进入空闲任务后关闭嘀嗒定时器中断,只有当其他中断发生或其他任务需要处理的时候才会从低功耗模式中唤醒。为此,需要解决两个问题
1.关闭嘀嗒定时器会导致系统节拍停止,系统时钟就会停止
可以记录下系统节拍中断的关闭时间,当系统节拍中断再次开启运行的时候补上这段时间
2.如何保证下一个要运行的任务能被准确的唤醒
在进入低功耗模式之前获取还有多长时间进行下一个任务
Tickless具体实现过程
1.宏configUSE_TICKLESS_IDLE
#define configUSE_TICKLESS_IDLE 1 //启用低功耗模式
2.宏portSUPPRESS_TICKS_AND_SLEEP()
使能Tickless后,符合下面两种情况会调用宏portSUPPRESS_TICKS_AND_SLEEP()来处理低功耗的工作
1.空闲任务是唯一可运行的任务,因为其他所有任务都处于阻塞态或挂起态
2.系统处于低功耗模式的时间至少大于configEXPECTED_IDLE_TIME_BEFORE_SLEEP个时钟节拍,宏configEXPECTED_IDLE_TIME_BEFORE_SLEEP默认在文件FreeRTOS.h中定义为2,重新定义大于2。
这里FreeRTOS已经帮我们实现了STM32的portSUPPRESS_TICKS_AND_SLEEP()函数,宏portSUPPRESS_TICKS_AND_SLEEP在文件portmacro.h中如下定义
#ifndef portSUPPRESS_TICKS_AND_SLEEP extern void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime); #define portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime) vPortSuppressTicksAndSleep(xExpectedIdleTime) #endif
vPortSuppressTicksAndSleep()在port.c中实现如下
#if configUSE_TICKLESS_IDLE == 1 __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL; TickType_t xModifiableIdleTime; /* Make sure the SysTick reload value does not overflow the counter. */ if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) { xExpectedIdleTime = xMaximumPossibleSuppressedTicks; } /* Stop the SysTick momentarily. The time the SysTick is stopped for is accounted for as best it can be, but using the tickless mode will inevitably result in some tiny drift of the time maintained by the kernel with respect to calendar time. */ portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT; /* Calculate the reload value required to wait xExpectedIdleTime tick periods. -1 is used because this code will execute part way through one of the tick periods. */ ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) ); if( ulReloadValue > ulStoppedTimerCompensation ) { ulReloadValue -= ulStoppedTimerCompensation; } /* Enter a critical section but don't use the taskENTER_CRITICAL() method as that will mask interrupts that should exit sleep mode. */ __disable_irq(); __dsb( portSY_FULL_READ_WRITE ); __isb( portSY_FULL_READ_WRITE ); /* If a context switch is pending or a task is waiting for the scheduler to be unsuspended then abandon the low power entry. */ if( eTaskConfirmSleepModeStatus() == eAbortSleep ) { /* Restart from whatever is left in the count register to complete this tick period. */ portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* Reset the reload register to the value required for normal tick periods. */ portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; /* Re-enable interrupts - see comments above __disable_irq() call above. */ __enable_irq(); } else { /* Set the new reload value. */ portNVIC_SYSTICK_LOAD_REG = ulReloadValue; /* Clear the SysTick count flag and set the count value back to zero. */ portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* 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 ); /* Stop SysTick. Again, the time the SysTick is stopped for is accounted for as best it can be, but using the tickless mode will inevitably result in some tiny drift of the time maintained by the kernel with respect to calendar time. */ ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG; portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT ); /* Re-enable interrupts - see comments above __disable_irq() call above. */ __enable_irq(); if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ) { uint32_t ulCalculatedLoadValue; /* The tick interrupt has already executed, and the SysTick count reloaded with ulReloadValue. Reset the portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick period. */ ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG ); /* 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_REG = 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; } else { /* Something other than the tick interrupt ended the sleep. Work out how long the sleep lasted rounded to complete tick periods (not the ulReload value which accounted for part ticks). */ ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG; /* How many complete tick periods passed while the processor was waiting? */ ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick; /* The reload value is set to whatever fraction of a single tick period remains. */ portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements; } /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG again, then set portNVIC_SYSTICK_LOAD_REG back to its standard value. The critical section is used to ensure the tick interrupt can only execute once in the case that the reload register is near zero. */ portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; portENTER_CRITICAL(); { portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; vTaskStepTick( ulCompleteTickPeriods ); portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; } portEXIT_CRITICAL(); } } #endif /* #if configUSE_TICKLESS_IDLE */ /*-----------------------------------------------------------*/ /* * Setup the SysTick timer to generate the tick interrupts at the required * frequency. */ #if configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0 void vPortSetupTimerInterrupt( void ) { /* Calculate the constants required to configure the tick interrupt. */ #if configUSE_TICKLESS_IDLE == 1 { ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ); xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick; ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ ); } #endif /* configUSE_TICKLESS_IDLE */ /* Configure SysTick to interrupt at the requested rate. */ portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL; portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT ); } #endif /* configOVERRIDE_DEFAULT_TICK_CONFIGURATION */ /*-----------------------------------------------------------*/
宏configPRE_SLEEP_PROCESSING()和configPOST_SLEEP_PROCESSING()
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,比如:
1.将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗以后关闭系统时钟。
2.修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换到内部时钟源,比如STM32的内部RC振荡器
3.关闭其他外设时钟,比如IO口的时钟
4.关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过MOS管来控制某个模快电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源
/***************************************************************************************************************/ /* FreeRTOS与低功耗管理相关配置 */ /***************************************************************************************************************/ extern void PreSleepProcessing(uint32_t ulExpectedIdleTime); extern void PostSleepProcessing(uint32_t ulExpectedIdleTime); #define configPRE_SLEEP_PROCESSING PreSleepProcessing //进入低功耗模式前要做的处理 #define configPOST_SLEEP_PROCESSING PostSleepProcessing //退出低功耗模式后要做的处理
宏conifgEXPECTED_IDLE_TIME_BEFORE_SLEEP
处理器工作咋在低功耗模式的时间虽说没有任何限制,但是时间太短也不行,所以需要做个限制,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
此宏会在空闲任务函数prvIdleTask()中使用