顶尖的赣州网站建设,东城网站开发,请打开app,wordpress 底部修改插件文章目录 一、空闲任务详解1. 空闲任务简介2. 空闲任务的创建3. 空闲任务函数 二、空闲任务钩子函数详解1. 钩子函数2. 空闲任务钩子函数 三、空闲任务钩子函数实验 一、空闲任务详解
1. 空闲任务简介
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务#xff0c;这… 文章目录 一、空闲任务详解1. 空闲任务简介2. 空闲任务的创建3. 空闲任务函数 二、空闲任务钩子函数详解1. 钩子函数2. 空闲任务钩子函数 三、空闲任务钩子函数实验 一、空闲任务详解
1. 空闲任务简介
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务这样就可以确保至少有一任务可以运行。但是这个空闲任务使用最低优先级如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有另外一个重要的职责如果某个任务要调用函数 vTaskDelete()删除自身那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉如果删除的是别的任务那么相应的内存就会被直接释放掉不需要在空闲任务中释放。因此一定要给空闲任务执行的机会除此以外空闲任务就没有什么特别重要的功能了所以可以根据实际情况减少空闲任务使用 CPU 的时间(比如当 CPU 运行空闲任务的时候使处理器进入低功耗模式)。
用户可以创建与空闲任务优先级相同的应用任务当宏 configIDLE_SHOULD_YIELD 为 1的话应用任务就可以使用空闲任务的时间片也就是说空闲任务会让出时间片给同优先级的应用任务。这种方法在 介绍configIDLE_SHOULD_YIELD 的时候就讲过了这种机制要求FreeRTOS 使用抢占式内核。
2. 空闲任务的创建
当调用函数 vTaskStartScheduler()启动任务调度器的时候此函数就会自动创建空闲任务代码如下
void vTaskStartScheduler( void )
{BaseType_t xReturn;//创建空闲任务使用最低优先级#if( configSUPPORT_STATIC_ALLOCATION 1 ) (1){StaticTask_t *pxIdleTaskTCBBuffer NULL;StackType_t *pxIdleTaskStackBuffer NULL;uint32_t ulIdleTaskStackSize;vApplicationGetIdleTaskMemory( pxIdleTaskTCBBuffer, pxIdleTaskStackBuffer, \ulIdleTaskStackSize );xIdleTaskHandle xTaskCreateStatic( prvIdleTask,IDLE,ulIdleTaskStackSize,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer ); if( xIdleTaskHandle ! NULL ){xReturn pdPASS;}else{xReturn pdFAIL;}}#else (2){xReturn xTaskCreate( prvIdleTask,IDLE, configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),xIdleTaskHandle );}#endif /* configSUPPORT_STATIC_ALLOCATION *//*********************************************************************//**************************省略其他代码*******************************//*********************************************************************/
}(1)、使用静态方法创建空闲任务。 (2)、使用动态方法创建空闲任务空闲任务的任务函数为 prvIdleTask()任务堆栈大小为configMINIMAL_STACK_SIZE任务堆栈大小可以在 FreeRTOSConfig.h 中修改。任务优先级为tskIDLE_PRIORITY宏 tskIDLE_PRIORITY 为 0说明空闲任务优先级最低用户不能随意修改空闲任务的优先级
3. 空闲任务函数
空闲任务的任务函数为 prvIdleTask()但是实际上是找不到这个函数的因为它是通过宏定义来实现的在文件 portmacro.h 中有如下宏定义
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )其中 portTASK_FUNCTION()在文件 tasks.c 中有定义它就是空闲任务的任务函数源码如下
static portTASK_FUNCTION( prvIdleTask, pvParameters ) (1)
{( void ) pvParameters; //防止报错//本函数为 FreeRTOS 的空闲任务任务函数当任务调度器启动以后空闲任务会自动//创建for( ;; ){//检查是否有任务要删除自己如果有的话就释放这些任务的任务控制块 TCB 和//任务堆栈的内存prvCheckTasksWaitingTermination(); (2)#if ( configUSE_PREEMPTION 0 ){//如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他//任务有效如果有使用抢占式内核的话就不需要这一步因为只要有任//何任务有效(就绪)之后都会自动的抢夺 CPU 使用权taskYIELD();}#endif /* configUSE_PREEMPTION */#if ( ( configUSE_PREEMPTION 1 ) ( configIDLE_SHOULD_YIELD 1 ) ) (3){//如果使用抢占式内核并且使能时间片调度的话当有任务和空闲任务共享//一个优先级的时候并且此任务处于就绪态的话空闲任务就应该放弃本时//间片将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级//下的就绪列表中有多个用户任务的话就执行这些任务。if( listCURRENT_LIST_LENGTH( \ (4)( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) ( UBaseType_t ) 1 ){taskYIELD();}else{mtCOVERAGE_TEST_MARKER();}}#endif #if ( configUSE_IDLE_HOOK 1){extern void vApplicationIdleHook( void );//执行用户定义的空闲任务钩子函数注意钩子函数里面不能使用任何//可以引起阻塞空闲任务的 API 函数。vApplicationIdleHook(); (5)}#endif /* configUSE_IDLE_HOOK *///如果使能了 Tickless 模式的话就执行相关的处理代码#if ( configUSE_TICKLESS_IDLE ! 0 ) (6){TickType_t xExpectedIdleTime;xExpectedIdleTime prvGetExpectedIdleTime(); (7)if( xExpectedIdleTime configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) (8){vTaskSuspendAll(); (9){//调度器已经被挂起重新采集一次时间值这次的时间值可以//使用configASSERT( xNextTaskUnblockTime xTickCount );xExpectedIdleTime prvGetExpectedIdleTime(); (10)if( xExpectedIdleTime \configEXPECTED_IDLE_TIME_BEFORE_SLEEP ){traceLOW_POWER_IDLE_BEGIN();portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); (11)traceLOW_POWER_IDLE_END();}else{mtCOVERAGE_TEST_MARKER();}}( void ) xTaskResumeAll(); (12)}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TICKLESS_IDLE */}
}
(1)、将此行展开就是 static void prvIdleTask(void *pvParameters)创建空闲任务的时候任务函数名就是 prvIdleTask()。
(2)、调用函数 prvCheckTasksWaitingTermination()检查是否有需要释放内存的被删除任务当 有 任 务 调 用 函 数 vTaskDelete() 删 除 自 身 的 话 此 任 务 就 会 添 加 到 列 表xTasksWaitingTermination 中 。 函 数 prvCheckTasksWaitingTermination() 会 检 查 列 表xTasksWaitingTermination 是否为空如果不为空的话就依次将列表中所有任务对应的内存释放掉(任务控制块 TCB 和任务堆栈的内存)。
(3)、使用抢占式内核并且 configIDLE_SHOULD_YIELD 为 1说明空闲任务需要让出时间片给同优先级的其他就绪任务。
(4)、检查优先级为 tskIDLE_PRIORITY(空闲任务优先级)的就绪任务列表是否为空如果不为空的话就调用函数 taskYIELD()进行一次任务切换。
(5)、如果使能了空闲任务钩子函数的话就执行这个钩子函数空闲任务钩子函数的函数名为 vApplicationIdleHook()这个函数需要用户自行编写在编写这个这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数。
(6)、configUSE_TICKLESS_IDLE 不为 0说明使能了 FreeRTOS 的低功耗 Tickless 模式。
(7)、调用函数 prvGetExpectedIdleTime()获取处理器进入低功耗模式的时长此值保存在变量 xExpectedIdleTime 中单位为时钟节拍数。
(8)、xExpectedIdleTime 值要大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才有效。
(9)、处理 Tickless 模式挂起任务调度器其实就是起到临界段代码保护功能
(10)、重新获取一次时间值这次的时间值是直接用于portSUPPRESS_TICKS_AND_SLEEP()的。
(11)、调用 portSUPPRESS_TICKS_AND_SLEEP()进入低功耗 Tickless 模式。
(12)、恢复任务调度器。
二、空闲任务钩子函数详解
1. 钩子函数
FreeRTOS 中有多个钩子函数钩子函数类似回调函数当某个功能(函数)执行的时候就会调用钩子函数至于钩子函数的具体内容那就由用户来编写。如果不需要使用钩子函数的话就什么也不用管钩子函数是一个可选功能可以通过宏定义来选择使用哪个钩子函数可选的钩子函数如下表所示 钩子函数的使用方法基本相同用户使能相应的钩子函数然后自行根据实际需求编写钩子函数的内容下一节我们会以空闲任务钩子函数为例讲解如何使用钩子函数。
2. 空闲任务钩子函数
在每个空闲任务运行周期都会调用空闲任务钩子函数如果想在空闲任务优先级下处理某个任务有两种选择 ● 在空闲任务钩子函数中处理任务。 不管什么时候都要保证系统中至少有一个任务可以运行因此绝对不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的 API 函数比如 vTaskDelay()或者其他带有阻塞时间的信号量或队列操作函数。
● 创建一个与空闲任务优先级相同的任务。 创建一个任务是最好的解决方法但是这种方法会消耗更多的 RAM。 要使用空闲任务钩子函数首先要在 FreeRTOSConfig.h 中将宏 configUSE_IDLE_HOOK 改为 1然后编写空闲任务钩子函数 vApplicationIdleHook()。通常在空闲任务钩子函数中将处理器设置为低功耗模式来节省电能为了与 FreeRTOS 自带的 Tickless 模式做区分这里我暂且将这种低功耗的实现方法称之为通用低功耗模式(因为几乎所有的 RTOS 系统都可以使用这种方法实现低功耗)。这种通用低功耗模式和 FreeRTOS 自带的 Tickless 模式的区别我们通过下图来对比分析一下。 图中有三个任务它们分别为一个空闲任务(Idle)两个用户任务(Task1 和 Task2),其中空闲任务一共有运行了三次分别为(1)、(2)、(3)其中 T1 到 T12 是 12 个时刻下面我们分别从这两种低功耗的实现方法去分析一下整个过程。
1通用低功耗模式 如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒以(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)中的情况和通用低功耗模式一样。 可以看出相对与通用低功耗模式FreeRTOS 自带的 Tickless 模式更加合理有效所以如果有低功耗设计需求的话大家尽量使用 FreeRTOS 再带的 Tickless 模式。当然了如果对于功耗要求不严格的话通用低功耗模式也可以使用下一节将通过一个实验讲解如何在空闲任务钩子函数中实现低功耗。
三、空闲任务钩子函数实验
1、实验目的 学习如何在 FreeRTOS 空闲任务钩子函数中实现低功耗。
2、实验设计 FreeRTOS 低功耗 Tickless 模式关闭 Tickless 模式在空闲任务钩子函数中使用 WFI 指令是处理器进入睡眠模式。
3、实验程序与分析 ● 相关宏设置
#define configUSE_TICKLESS_IDLE 0 //关闭低功耗 tickless 模式
#define configUSE_IDLE_HOOK 1 //使能空闲任务钩子函数● 空闲任务钩子函数
//进入低功耗模式前需要处理的事情
void BeforeEnterSleep(void)
{//关闭某些低功耗模式下不使用的外设时钟此处只是演示性代码RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,DISABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,DISABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,DISABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,DISABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,DISABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,DISABLE);
}//退出低功耗模式以后需要处理的事情
void AfterExitSleep(void)
{//退出低功耗模式以后打开那些被关闭的外设时钟此处只是演示性代码RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
}//空闲任务钩子函数
void vApplicationIdleHook(void)
{__disable_irq();__dsb(portSY_FULL_READ_WRITE );__isb(portSY_FULL_READ_WRITE );BeforeEnterSleep(); //进入睡眠模式之前需要处理的事情__wfi(); //进入睡眠模式AfterExitSleep(); //退出睡眠模式之后需要处理的事情__dsb(portSY_FULL_READ_WRITE );__isb(portSY_FULL_READ_WRITE );__enable_irq();
}空闲任务钩子函数主要目的就是调用 WFI 指令使 STM32F103 进入睡眠模式在进入和退出低功耗模式的时候也可以做一些其他处理比如关闭外设时钟等等用法和 FreeRTOS 的Tickless 模式类似。
● 其他任务函数和设置 其他有关设置和任务函数的内容同“FreeRTOS 实验 18-1 FreeRTOS 低功耗 Tickless 模式实验”一样这里就不列出来了。