前言
Cortex-M核的MCU一般支持以下三种低功耗方式:
● 睡眠(Sleep)模式
● 停止(Stop)模式
● 待机(Standby)模式
睡眠模式
进入睡眠模式有两种指令:WFI(等待中断)和WFE(等待事件),
WFI进入睡眠模式后,任意中断都可唤醒。
WFE进入睡眠模式后,任意唤醒事件都可唤醒
FreeRTOS 使用 WFI 指令进入实现低功耗控制。
停止模式
在特定的条件下执行WFI(等待中断)或者WFE(等待事件)指令,保留SRAM数据,调压器正常工作或者低功耗,大部分时钟关闭。
由外部中断唤醒
待机模式
相比于停止,待机模式的功耗更低。在停止模式基础上,SRAM数据丢失,调压器也关闭,大部分寄存器内容也丢失
由wakeup引进,复位引脚,看门狗或者RTC退出
由于停止/待机模式下软件的正常运行功能都将无法使用,并且其实现需要特定的硬件设计及系统方案,不在本次总结的讨论范围内。
本次总结仅对FreeRTOS系统功能正常运行情况下的低功耗总结,即睡眠模式。
任务运行状态
FreeRTOS作为多任务实时操作系统,开发过程中研发难免需要对软件任务的状态进行查看,FreeRTOS提供了相关的接口用来查询相关状态,
void vTaskList( char *pcWriteBuffer ); | 获取当前所有任务状态 |
void vTaskGetRunTimeStats( char *pcWriteBuffer ); | 获取当前所有任务占用率 |
size_t xPortGetFreeHeapSize( void ) | 获取当前堆剩余字节数 |
size_t xPortGetMinimumEverFreeHeapSize( void ) | 获取历史堆最小剩余字节数 |
任务状态
void vTaskList( char *pcWriteBuffer )
FreeRTOSConfig.h 中 必须定义才能使用vTaskList
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
任务占用率
void vTaskGetRunTimeStats( char *pcWriteBuffer )
Abs time : 任务占用的tick数
% time : 占用率
FreeRTOSConfig.h 中 必须定义才能使用vTaskList
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
configGENERATE_RUN_TIME_STATS 定义为1 后 还需要定义以下宏
统计定时器配置函数
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
portGET_RUN_TIME_COUNTER_VALUE()/portALT_GET_RUN_TIME_COUNTER_VALUE(Time)
统计定时器值获取函数
方式1:高精度定时器
用户实现自己实现高精度定时器
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() configureTimeStats()//初始化硬件定时器
#define portGET_RUN_TIME_COUNTER_VALUE() getRunTime()//获取定时器的计数值
优点:占用率计算准确
缺点:多使用硬件资源,多了个更高频率的定时器,影响性能
方式2:直接使用系统ticks
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() xTaskGetTickCount() //初始化执行一次
#define portGET_RUN_TIME_COUNTER_VALUE() xTaskGetTickCount()
优点:不占用额外任何资源,不影响性能
缺点:占用率计算精度不高
堆使用状态
size_t xPortGetFreeHeapSize( void ) //当前堆剩余字节数
size_t xPortGetMinimumEverFreeHeapSize( void ) //获取历史堆最小剩余字节数
关系到 FreeRTOSConfig.h 中的 configTOTAL_HEAP_SIZE 大小设置
#define configTOTAL_HEAP_SIZE ((size_t)(64 * 1024))
空闲任务
空闲任务是 FreeRTOS 必不可少的一个任务,因此,空闲任务的优先级肯定是最低的,FreeRTOS 在空闲任务中也会执行一些其他的处理。
vTaskStartScheduler()启动任务调度器的时候FreeRTOS会自动创建空闲任务,如果某个任务要调用函数 vTaskDelete()删除自身,那么这个任务的资源需要在空闲任务中释放掉。因此,空闲任务也需要一定的时间片执行。
对于系统而言,一般软件都不会CPU满额运行,大部分时间都处于空闲状态,若用户需要在空闲状态下做一些特殊处理,如低功耗模式、关闭某些外设、降低系统主频等。
FreeRTOS 提供了相关的功能,即钩子函数,可简单理解为回调函数
常见钩子函数如下
FreeRTOSConfig.h 宏定义 | 钩子函数 | 说明 |
configUSE_IDLE_HOOK | void vApplicationIdleHook(void) | 空闲回调 |
configUSE_TICK_HOOK | void vApplicationTickHook(void); | tick自加回调 |
configCHECK_FOR_STACK_OVERFLOW | void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName) | 任务栈溢出回调 |
configUSE_MALLOC_FAILED_HOOK | void vApplicationMallocFailedHook(void) | 内存申请失败回调 |
configUSE_DAEMON_TASK_STARTUP_HOOK | void vApplicationDaemonTaskStartupHook( void ); | 定时器任务启动回调 |
定义举例
void vApplicationIdleHook(void)
{
/* Enter sleep-mode */
Cy_SysPm_Sleep(CY_SYSPM_WAIT_FOR_INTERRUPT);
}
void vApplicationStackOverflowHook(TaskHandle_t *pxTask,
signed char *pcTaskName)
{
/* Remove warning for unused parameters */
(void)pxTask;
(void)pcTaskName;
/* Print the error message with task name if debug is enabled in
uart_debug.h file */
DebugPrintf("Error! : RTOS - stack overflow in %s \r\n", pcTaskName);
/* Halt the CPU */
CY_ASSERT(0);
}
void vApplicationMallocFailedHook(void)
{
/* Print the error message if debug is enabled in uart_debug.h file */
DebugPrintf("Error! : RTOS - Memory allocation failed \r\n");
/* Halt the CPU */
CY_ASSERT(0);
}
Idle低功耗
最简单的低功耗方式即:在空闲任务钩子函数中将处理器设置为低功耗模式
void vApplicationIdleHook(void)
{
/* Enter sleep-mode */
Cy_SysPm_Sleep(CY_SYSPM_WAIT_FOR_INTERRUPT);
}
几乎所有支持 RTOS 系统的MCU都可以使用这种方法实现低功耗,该方式有以下特点:
大多数场景下,系统时钟是由滴答定时器中断来提供的,系统时钟频率越高,那么滴答定时器中断频率也就越高,但中断MCU从睡眠模式中唤醒, 使得MCU周期性的进入和退出睡眠模式。因此,如果滴答定时器中断频率太高的话会导致大量的能量和时间消耗在进出睡眠模式中,这样导致的结果就是低功耗模式的作用被大大的削弱。
Tickless 低功耗
针对通用低功耗的问题,FreeRTOS提供了另一种低功耗方式,该方式不同MCU的支持可能存在不同,即为 Tickless 模式:
当处理器进入空闲任务周期以后就关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒。
由此需要解决两个问题:
问题1:关闭系统节拍中断会导致系统节拍计数器停止,系统tick就会停止
FreeRTOS解决方法:
FreeRTOS 可通过另一定时器记录下系统节拍中断的关闭时间,当恢复的时候补上这段时间,如果是专用的低功耗处理器,该定时器一般都是用专用的低功耗定时器。
问题2:如何保证下一个要运行的任务能被准确的唤醒。即使处理器进入了低功耗模式,但是中断和应用层任务也要保证及时的响应和处理。 中断不用说,本身就可以唤醒。但是应用层任务就不行了,它无法将处理器从低功耗模式唤醒,无法唤醒就无法运行。
FreeRTOS解决方法:
计算在进入低功耗模式之前能够获取到还有多长时间切换到下一任务,借助新增的定时器,将其中断周期修改为低功耗运行时间,其中断到来后即可唤醒处理器。
以apollo为例,其官方支持Tickless模式并且提供相应文档说明,但cypress不支持
#define configOVERRIDE_DEFAULT_TICK_CONFIGURATION 1 // Enable non-SysTick based Tick
#define configUSE_TICKLESS_IDLE 2 // Ambiq specific implementation for Tickless
#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))
extern uint32_t am_freertos_sleep(uint32_t);
extern void am_freertos_wakeup(uint32_t);
#define configPRE_SLEEP_PROCESSING( time ) \
do { \
(time) = am_freertos_sleep(time); \
} while (0);
#define configPOST_SLEEP_PROCESSING(time) am_freertos_wakeup(time)
#endif
/*-----------------------------------------------------------*/
#ifndef AM_PART_APOLLO
#define AM_FREERTOS_USE_STIMER_FOR_TICK
#endif
#ifdef AM_FREERTOS_USE_STIMER_FOR_TICK
#ifdef APOLLO4_FPGA
#define configSTIMER_CLOCK_HZ 1500000
#define configSTIMER_CLOCK AM_HAL_STIMER_HFRC_6MHZ
#else
#define configSTIMER_CLOCK_HZ 32768
#define configSTIMER_CLOCK AM_HAL_STIMER_XTAL_32KHZ
#endif
#else // Use CTimer
#define configCTIMER_NUM 3
#define configCTIMER_CLOCK_HZ 32768
#define configCTIMER_CLOCK AM_HAL_CTIMER_XT_32_768KHZ
#endif
两种模式对比分析
图中有三个任务,它们分别为一个空闲任务(Idle),两个用户任务(Task1 和 Task2), 其中空闲任务一共有运行了三次,分别为(1) 、(2) 、(3),其中 T1 到 T12 是 12 个时刻,下面我们分别从这两种低功耗的实现方法去分析一下整个过程。
1、Idle低功耗模式
如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒,以 (1)为例,再 T2 时刻处理器从低功耗模式中唤醒,但是接下来由于没有就绪的其他任务所以处理器又再一次进入低功耗模式。T2、T3 和 T4 这三个时刻都一样,反复的进入低功耗、退出低功耗,最理想的情况应该是从 T1 时刻就进入低功耗,然后在 T5 时刻退出。
在(2)中空闲任务只工作了两个时钟节拍,但是也执行了低功耗模式的进入和退出,显然这个意义不大,因为进出低功耗也是需要时间的。
(3)中空闲任务在 T12 时刻被某个外部中断唤醒,中断的具体处理过程在任务 2(使用信号量实现中断与任务之间的同步)。
2 、 Tickless 低功耗模式
在(1)中的 T1 时刻处理器进入低功耗模式,在 T5 时刻退出低功耗模式。相比通用低功耗模式少了 3 次进出低功耗模式的操作。
在(2)中由于空闲任务只运行了两个时钟节拍, 所以就没必要进入低功耗模式。说明在 Tickless 模式中只有空闲任务要运行时间的超过某个最小阈值的时候才会进入低功耗模式,此阈值通过 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 来设置,上一章已经讲过了。
(3)中的情况和通用低功耗模式一样。
总结
1、可以看出相对与通用低功耗模式, Tickless 模式更加合理有效,所以如果有低功耗设计需求的话大家尽量使用 Tickless 模式。
2、几乎所有支持FreeRTOS的MCU都支持Idle低功耗模式,但不一定支持Tickless 模式,Tickless 模式的支持需要依赖于MCU厂家对该功能的支持,具体实现方式一般在对应的FreeRTOS的port.c文件中。
参考资料:
FreeRTOS的低功耗Tickless模式与空闲函数_freertos 空闲任务进入低功耗模式-CSDN博客
FreeRTOS_Reference_Manual_V10.0.0.pdf
FreeRTOS™ - FreeRTOS™