STM32F1+HAL库+FreeTOTS学习20——低功耗Tickless模式
- 1. 低功耗Tickless模式简介
- 2. 低功耗Tickless的相关配置
- 3. 空闲函数对于低功耗Tickless的处理
- 4. FreeRTOS中Tickless的使用方式
上期我们学习了FreeRTOS中的软件定时器,这一期我们开始学习低功耗Tickless模式
1. 低功耗Tickless模式简介
我们首先来看一下STM32上的低功耗模式:
当系统或电源复位之后,微控制器处于运行状态,当CPU不需要运行的时候,我们可以利用很多低功耗模式来节省功耗,然后再等待某一个特定的外部事件,对CPU进行唤醒。STM32中提供了三种不同的低功耗模式:
- 睡眠模式:内核停止运行,此外所有外设(包括芯片的片上外设,NVIC,定时器,ADC等)都处于运行的状态
- 停止模式:内核停止运行,此外说有外设的时钟都已停止,再次唤醒可以继续从原来进入低功耗的位置继续运行。
- 待机模式:再停止模式的基础上,内核核心区域的1.8V供电区域断电,下次唤醒会导致系统复位。
- 此外,运行状态下,也可以通过降低系统时钟频率、关闭APB和AHB总线上未使用的外设时钟来降低功耗。
下面是低功耗模式一览:
FreeRTOS中也有自己低功耗模式,是基于stm32上的睡眠模式实现的,叫做Tickless的低功耗模式,方便带FreeRTOS操作系统的应用开发
- Tickless低功耗模式的本质是通过调用指令 WFI 实现睡眠模式!
- 在FreeRTOS系统的运行过程中,大部分事件都是在运行空闲任务,FreeRTOS里面的Tickless低功耗模式也是在空闲任务中实现的。
- 在进入空闲时间期间,让MCU进入低功耗模式,当准备运行其他任务时,唤醒MCU退出低功耗模式。
- 将滴答定时器的中断周期设置为低功耗运行的时间(通过下次任务切换的时间,计算出低功耗需要运行的时间),通过滴答定时器中断唤醒,退出低功耗模式,补上系统时钟的节拍数
上面这些操作,都是是现在空闲函数中的,关于空闲函数中对于低功耗做了那些处理,我们会在本期内容的第三点中介绍,我们这里先来看一下低功耗Tickless的相关配置:
2. 低功耗Tickless的相关配置
在FreeRTOS中,关于低功耗Tickless的相关配置,有如下几个:
1. configUSE_TICKLESS_IDLE
此宏用于使能低功耗 Tickless 模式,当此宏定义为 1 时,系统会在进入空闲任务期间进入相应的低功耗模式大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的时长。
2. configEXPECTED_IDLE_TIME_BEFORE_SLEEP
此宏用于定义系统进入相应低功耗模式的最短时长,如果系统在进入相应低功耗模式前,计算出系统将进入相应低功耗的时长小于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 定义的最小时长,则系统不进入相应的低功耗模式,要注意的是,此宏的值不能小于 2。
3. configPRE_SLEEP_PROCESSING(x)
此宏用于定义一些需要在系统进入相应低功耗模式前执行的事务,例如可以在进入低功耗
模式前关闭一些 MCU 片上外设的时钟,以达到降低功耗的目的。
4. configPOSR_SLEEP_PROCESSING(x)
此宏用于定义一些需要在系统退出相应低功耗模式后执行的事务,例如开启在系统在进入
相应低功耗模式前关闭的 MCU 片上外设的时钟,以是系统能够正常运行
了解了相关配置,我们来看空闲函数对于低功耗Tickless的处理:
3. 空闲函数对于低功耗Tickless的处理
在空闲任务执行的期间,让MCU 进入相应的低功耗模式,接着在其他任务因被解除阻塞或其他原因,而准备运行的时候,让 MCU 退出相应的低功耗模式,去执行相应的任务,这个就是空闲函数对于低功耗Tickless的处理,下面我们来看具有的代码:
/* 空闲函数 */
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{/* 低功耗Tickless模式无关的代码,这里省略 *//* 此宏用于处理FreeRTOS中的Tickless模式 */#if ( configUSE_TICKLESS_IDLE != 0 ){/* 空闲函数预计执行的时间 */TickType_t xExpectedIdleTime;/* 获取预计执行空闲任务的时间,但本次获取的时间不一定准确,因为可能受到任务调度器的影响*/xExpectedIdleTime = prvGetExpectedIdleTime();/* 如果空闲任务预计执行的时间大于进入低功耗的最低时长要求(默认是2ms),才进入低功耗 */if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ){/* 挂起任务调度器 */vTaskSuspendAll();{/*断言:下次阻塞解除的时间大于当前的tick值*/configASSERT( xNextTaskUnblockTime >= xTickCount );/* 获取准确的低功耗模式执行时间 */xExpectedIdleTime = prvGetExpectedIdleTime();/* 如果不想要进入低功耗模式,可以将宏xExpectedIdleTime 设置为0即可 */configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );/* 如果空闲任务预计执行的时间大于进入低功耗的最低时长要求(默认是2ms),才进入低功耗 */if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ){/* 用于调试,不必理会 */traceLOW_POWER_IDLE_BEGIN();/* 此宏用于MCU进入低功耗模式,传入参数就是低功耗运行的时长*/portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );/* 用于调试,不必理会 */traceLOW_POWER_IDLE_END();}else{mtCOVERAGE_TEST_MARKER();}}/* 恢复任务调度 */( void ) xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TICKLESS_IDLE */}
}
可以看到空闲任务中主要计算了空闲函数预计执行的市场,如果大于进入低功耗模式的最小时长,则调用 portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ),进入低功耗模式。
#ifndef portSUPPRESS_TICKS_AND_SLEEPextern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime );#define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )#endif
可以看到portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime )实际上是一个宏定义,内部调用了一个外部声明的vPortSuppressTicksAndSleep()函数,其具体定义如下:
/* 开启了低功耗才会编译以下代码 */
#if ( configUSE_TICKLESS_IDLE == 1 )/* 用于实现低功耗的代码 */__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ){uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;TickType_t xModifiableIdleTime;/* 如果进入低功耗的时间,超出了滴答定时器的最大计时范围,就把进入低功耗的时间设到最大 */if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ){xExpectedIdleTime = xMaximumPossibleSuppressedTicks;}/* 暂时关闭sysTick中断 */portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;/* 计算 SysTick 需要重载的值,以便在预计的空闲时间内继续计数 */ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );/* 如果计算出的 ulReloadValue 大于 ulStoppedTimerCompensation,则从重载值中减去这个补偿值。这样可以确保在 SysTick 暂停期间所丢失的时间得以调整。 */if( ulReloadValue > ulStoppedTimerCompensation ){ulReloadValue -= ulStoppedTimerCompensation;}/*进入临界区,但不要使用 taskENTER_CRITICAL() 方法,因为这样会屏蔽应当退出睡眠模式的中断。 *//* 关闭所有中断 */__disable_irq();__dsb( portSY_FULL_READ_WRITE );__isb( portSY_FULL_READ_WRITE );/* 判断是否要中止睡眠模式 */if( eTaskConfirmSleepModeStatus() == eAbortSleep ){/*重新加载滴答定时器的值 */portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;/* 启动滴答定时器 */portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;/* 重置加载寄存器*/portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;/* 开启中断 */__enable_irq();}/* 运行进入睡眠模式 */else{/* 设置新的加载值,以便sysTick在下一个周期正常工作 */portNVIC_SYSTICK_LOAD_REG = ulReloadValue;/* 清除滴答定时器计数标志 */portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;/* 重启滴答定时器 */portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;/* 进入低功耗模式 */xModifiableIdleTime = xExpectedIdleTime;/* 进入低功耗之前的一些操作,由用户自己实现 */configPRE_SLEEP_PROCESSING( xModifiableIdleTime );/* 这里开始正式进入低功耗,进入后内核不在运行,系统停止,但是外设还未停止 *//* 如果空闲时间大于0,通过WFI指令进入睡眠模式 */if( xModifiableIdleTime > 0 ){__dsb( portSY_FULL_READ_WRITE );__wfi();__isb( portSY_FULL_READ_WRITE );}/* 到这里就以及退出低功耗模式了,系统开始继续运行 *//* 退出低功耗之后的一些操作,由用户自己实现 */configPOST_SLEEP_PROCESSING( xExpectedIdleTime );/* 开启中断 */__enable_irq();__dsb( portSY_FULL_READ_WRITE );__isb( portSY_FULL_READ_WRITE );/* 关闭所有中断 */__disable_irq();__dsb( portSY_FULL_READ_WRITE );__isb( portSY_FULL_READ_WRITE );/* 停止sysTick时钟,避免时钟咋停止期间发生中断 */portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );/* 、、、往后的内容省略、、、 */}}#endif /* #if configUSE_TICKLESS_IDLE */
可以看到vPortSuppressTicksAndSleep()函数在通过WFI指令进入睡眠模式的同时保证sysTick的正常运行,以及完成了,系统节拍的补充。在这个过程中,FreeRTOS提供了两个函数,供用户在进入低功耗模式之前和退出低功耗模式之后完成自己的一些操作,这两个函数分别是:configPRE_SLEEP_PROCESSING( xModifiableIdleTime ) 和 configPOST_SLEEP_PROCESSING( xExpectedIdleTime ),其具体定义如下:
#ifndef configPRE_SLEEP_PROCESSING#define configPRE_SLEEP_PROCESSING( x )
#endif#ifndef configPOST_SLEEP_PROCESSING#define configPOST_SLEEP_PROCESSING( x )
#endif
可以看到,这里面并没有具体的代码实现,下面我们就来实现以下,介绍一些FreeRTOS中的Tickless模式如何使用。
4. FreeRTOS中Tickless的使用方式
- 开启低功耗模式:configUSE_TICKLESS_IDLE
在FreeRTOSConfig.h中找到宏 “ configUSE_TICKLESS_IDLE ”,并将其配置为1,如下:
#define configUSE_TICKLESS_IDLE 1 /* 1: 使能tickless低功耗模式, 默认: 0 */
- 声明PRE_SLEEP_PROCESSING()函数和POST_SLEEP_PROCESSING()函数
在freertos_demo.h中声明如下:
void PRE_SLEEP_PROCESSING(void);
void POST_SLEEP_PROCESSING(void);
- 实现PRE_SLEEP_PROCESSING()函数和POST_SLEEP_PROCESSING()函数
在freertos_demo.c中实现:
/* 进入低功耗之前需要执行的操作 */
void PRE_SLEEP_PROCESSING(void)
{__HAL_RCC_GPIOA_CLK_DISABLE();
}/* 退出低功耗之后需要执行的操作 */
void POST_SLEEP_PROCESSING(void)
{__HAL_RCC_GPIOA_CLK_ENABLE();
}
- 外部声明PRE_SLEEP_PROCESSING()函数和POST_SLEEP_PROCESSING()函数
在FreeRTOS.h中找到宏 “ configPRE_SLEEP_PROCESSING ” 和 “ configPOST_SLEEP_PROCESSING ”,如下:
#ifndef configPRE_SLEEP_PROCESSING#define configPRE_SLEEP_PROCESSING( x )
#endif#ifndef configPOST_SLEEP_PROCESSING#define configPOST_SLEEP_PROCESSING( x )
#endif
将其替换成如下:
#ifndef configPRE_SLEEP_PROCESSING
extern void PRE_SLEEP_PROCESSING(void);
#define configPRE_SLEEP_PROCESSING( x ) PRE_SLEEP_PROCESSING()
#endif#ifndef configPOST_SLEEP_PROCESSING
extern void POST_SLEEP_PROCESSING(void);
#define configPOST_SLEEP_PROCESSING( x ) POST_SLEEP_PROCESSING()
#endif
完成以上四步,即完成了FreeRTOS中Tickless的使用方式,但由于条件有限,没有办法测量其具体功耗,所以没有实验结果,所以本期内容就到这里创造不易,点个关注再走呗。