【FreeRTOS】详细讲解FreeRTOS中任务管理并通过示例讲述其用法
创始人
2024-05-20 15:07:10

任务状态

  在FreeRTOS中一个任务经创建后会有多个状态,通常可分为以下几种状态:

  • 就绪态:新创建的任务一般处于就绪态。处于就绪态的任务表明其已经存在于就绪列表中,其已经具备所有的任务执行需要条件,只等待调度器调度运行
  • 运行态:运行态,表明该任务正在占用处理器执行任务。调度器永远都只会从就绪列表中选取优先级最高任务运行
  • 挂起态:处于挂起态的任务一般都是长时间不允许运行的任务,此时CUP不会处理该任务的任何信息;
  • 阻塞态:任务处于阻塞态,说明该任务不在就绪列表中,其正在等待某个时序或者是某个外部中断。通常任务挂起任务延时任务等待信号量都属于阻塞。

  既然如此,各个状态间转换关系又是怎么的呢?

  • 创建任务—>就绪态1:任务经过创建函数(xTaskCreate/xTaskCreateStatic)后可以直接进入就绪态
  • 就绪态—>运行态2:任务发生切换时,调度器总从就绪列表中选取优先级最高的任务进入运行态开始运行
  • 运行态—>就绪态3:当有更高优先级任务被创建或恢复后,调度器就会将新任务变成运行态,而原来的任务就会变成阻塞状态,直到新任务执行完毕后原任务才会继续运行;看到这大家有没有觉得这玩意有点像中断呢?🤣🤣🤣
  • 运行态—>阻塞态4:当正在执行的任务发生阻塞(挂起、延时、读取信号量等)时,该任务就会变成阻塞态,并且任务还会从就绪列表中删除,最后调度器会从就绪列表中执行当前优先级最高的任务
  • 阻塞态—>就绪态5:当阻塞任务恢复(任务恢复、延时超时、读取信号量超时等)后,该任务会变成就绪态,并且重新加入到就绪列表,此时调度器仍然执行就绪列表中优先级最高的任务
  • 就绪态—>挂起态6:无论任务处于哪个状态一旦调用API(vTaskSuspend)任务都会切换到挂起状态,挂起任务不会获得CPU使用权,更不会参与调度器的调度 ,挂起后调度器仍然调度就绪列表中优先级最高的任务运行
  • 阻塞态—>挂起态7:与6相同;
  • 运行态—>挂起态8:与6相同;
  • 挂起态—>就绪态9:处于挂起态的任务经任务恢复vTaskResume)后,任务会切换到就绪态,此时调度器仍然调度就序列表中优先级最高的任务运行

任务函数

启动任务调度器
函数原型

void vTaskStartScheduler(void);

函数参数

函数说明
  函数vTaskStartScheduler是FreeRTOS中一个非常重要的函数,无论是什么样的FreeRTOS程序都需要使用vTaskStartScheduler函数启动调度器,该函数就像一个总开关,只有将其打开才能够使得FreeRTOS正常工作。

静态任务创建
函数原型

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,const char * const pcName, const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer );

参数解析

  • TaskFunction_t pxTaskCode:设置创建任务所需执行的任务函数;
  • const char * const pcName:设置任务名字;
  • const uint32_t ulStackDepth:设置任务栈长度;
  • void * const pvParameters:传递给任务的参数;
  • UBaseType_t uxPriority:设置任务优先级;
  • StackType_t * const puxStackBuffer:设置任务堆栈;
  • StaticTask_t * const pxTaskBuffer:任务控制块;

函数说明
  静态创建任务函数xTaskCreateStatic,含有七个参数、一个返回值(表示任务是否创建成功)。需要注意的是使用其创建任务时需要传入任务栈

动态任务创建
函数原型

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask);

参数解析

  • TaskFunction_t pxTaskCode:设置创建任务所需执行的任务函数;
  • const char * const pcName:设置任务名字;
  • const configSTACK_DEPTH_TYPE usStackDepth:设置任务栈大小;
  • void * const pvParameters:传递任务函数参数;
  • UBaseType_t uxPriority:设置任务优先级;
  • TaskHandle_t * const pxCreatedTask:保存任务控制块;

函数说明
  动态创建任务函数,共含有六个参数、一个返回值(返回值也表示任务是否创建成功),与静态创建任务不同之处是该函数不用传入任务栈,其任务栈使用动态方式创建。

单任务挂起
函数原型

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数解析

  • TaskHandle_t xTaskToSuspend:任务的控制权柄;

函数说明
  通过该函数可以使得某任务进入挂起状态,等待下次任务恢复才有可能继续运行。

多任务挂起
函数原型

void vTaskSuspendAll( void );

参数解析

函数说明
  通过函数vTaskSuspendAll能够使得所有的任务都进入挂起状态,看起来将所有任务都挂起了,实际上仅仅锁住调度器也就是挂起任务调度器)。使用该函数虽然锁住了调度器,但系统中断依旧可以正常使用

单任务恢复
函数原型

void vTaskResume( TaskHandle_t xTaskToResume );

参数解析

  • TaskHandle_t xTaskToSuspend:任务的控制权柄;

函数说明
  通过函数vTaskResume可以使由挂起函数vTaskSuspend挂起的任务重新恢复。
  无论同一任务在挂起时候调用过多少次vTaskSuspend()函数,也只需调用一次vTaskResume()函数即可将任务恢复运行;当然,无论调用多少次vTaskResume()函数,也只有在任务是挂起态的时候才进行恢复

多任务恢复
函数原型

BaseType_t xTaskResumeAll( void );

参数解析

  • 返回值:类型为BaseType_t,用于表示恢复任务是否成功;

函数说明
  通过函数xTaskResumeAll可以使得所有被挂起的任务都恢复。
  调度器恢复可以调用xTaskResumeAll()函数,调用多少次的 vTaskSuspendAll()就要调用多少次xTaskResumeAll()进行恢复

在中断中恢复任务
函数原型

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );

参数解析

  • TaskHandle_t xTaskToResume:任务控制权柄;
  • 返回值:类型为BaseType_t,用于表示任务恢复是否成功;

函数说明
  函数xTaskResumeFromISR是在中断中恢复被挂起任务。
  使用该函数时必须将INCLUDE_vTaskSuspend INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。但任务还没有处于挂起态的时候,调用xTaskResumeFromISR()函数是没有任何意义的

任务删除
函数原型

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数解析

  • TaskHandle_t xTaskToDelete:任务的控制句柄;

函数说明
  使用函数vTaskDelete()前需要将INCLUDE_vTaskDelete定义为 1;被删除任务可以从所有就绪、阻塞、挂起和事件列表中删除
  通过函数vTaskDelete()可以删除某个任务;当删除自身时,传入参数NULL,但自己的内存并没有得到释放

任务相对延时
函数原型

void vTaskDelay( const TickType_t xTicksToDelay );

参数解析

  • const TickType_t xTicksToDelay:延时时间,单位为系统节拍时间 (tick);

函数说明
  使用vTaskDelay()函数前,必须把INCLUDE_vTaskDelay 定义为 1。
  函数vTaskDelay()相对地阻塞延时,调用该函数后,任务将进入阻塞状态,并且让出 CPU 资源。延时时长由形参 xTicksToDelay 决定,单位为系统节拍周期, 比如系统的时钟节拍周期为 1ms,那么调用vTaskDelay(10)的延时时间则为10ms,那么经过从调用vTaskDelay()相对的10ms后任务会解除阻塞,因此,函数vTaskDelay()不适用于周期性执行任务的场合,并且其他任务与中断也会影响该函数正常工作

任务绝对延时
函数原型

BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );

参数解析

  • TickType_t *pxPreviousWakeTime:指针,指向一个变量,该变量保存任务最后一次解除阻塞的的时刻。第一次使用时,该变量必须初始化为当前时间,之后这个变量会在vTaskDelayUntil()函数内自动更新。
  • const TickType_t xTimeIncrement:周 期 循 环 时 间 。 当 时 间 等 于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数 xTimeIncrement 的值,调用该函数的任务会按照固定频率执行。
  • 返回值:数据类型为BaseType_t,用于检验任务是否实际延迟的值。

函数说明
  在实际使用过程中,一般使用vTaskDelayUntil函数,但是它的实现本质上还是依靠函数xTaskDelayUntil实现。函数重声明如下:

/** vTaskDelayUntil() is the older version of xTaskDelayUntil() and does not* return a value.*/
#define vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement )                   \do {                                                                        \( void ) xTaskDelayUntil( ( pxPreviousWakeTime ), ( xTimeIncrement ) ); \} while( 0 )

  使用函数vTaskDelayUntil前,必须把INCLUDE_vTaskDelayUntil 定义为 1。
  函数vTaskDelayUntil()延时是绝对性的。
  功能上看,函数vTaskDelayUntil() 与函数vTaskDelay () 都是用来实现任务的周期性延时。但vTaskDelay ()的延时是相对的,是不确定的,它的延时是
vTaskDelay ()调用完毕后开始计算**的。并且 vTaskDelay ()延时时间到了之后,如果有高优先级的任务或者中断正在执行,被延时阻塞的任务并不会马上解除阻塞,所有每次执行任务的周期并不完全确定。vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务。当(*pxPreviousWakeTime +xTimeIncrement)时间到达后,vTaskDelayUntil()函数立刻返回,如果此任务是最高优先级的,则任务会立马解除阻塞


示例

示例1
  利用FreeRTOS的空闲任务计时实现LED1、LED2灯闪烁。

  • 步骤一:创建两个FreeRTOS的任务,其优先级都设置为1;
  • 步骤二:完成两个FreeRTOS任务的函数体,每次执行一次任务后就使用函数vTaskSuspend将任务挂起;
  • 步骤三:完成空闲任务函数函数体,每执行一个函数就将计数值加 1 。当空闲任务执行次数到达指定次数时,就使用函数vTaskResume恢复函数。
void task1(void);
void task2(void);unsigned int count[2] = {0,0};/*****************************************
* 函数功能:freertos工作函数
* 函数参数:无
* 函数返回值:无
*****************************************/
void freertosWork(void)
{/*创建任务两个LED灯闪烁任务*///存储创建任务的返回值BaseType_t xReturn[2] ;//动态创建任务1xReturn[0] = xTaskCreate((TaskFunction_t )task1,//任务入口函数(const char *)"task1",//任务名字(uint16_t)512,//任务栈大小(void*)NULL,//任务入口参数1,//任务优先级  优先级越高,任务优先选越高&xHandleTsak[0]//任务控制块);//动态创建任务2xReturn[1] = xTaskCreate((TaskFunction_t )task2,//任务入口函数(const char *)"task2",//任务名字(uint16_t)512,//任务栈大小(void*)NULL,//任务入口参数1,//任务优先级  优先级越高,任务优先选越高&xHandleTsak[1]//任务控制块);					//创建成功 if (pdPASS == xReturn[0] == xReturn[1])//启动任务,开启调度 vTaskStartScheduler(); //创建失败else//点亮LED6changeLedStateByLocation(LED6,ON);return 0;
}/*****************************************
* 函数功能:freertos的任务1
* 函数参数:无
* 函数返回值:无
*****************************************/
void task1(void)
{while(1){//LED1反转状态rollbackLedByLocation(LED1);//挂起任务vTaskSuspend(xHandleTsak[0]);}
}/*****************************************
* 函数功能:freertos的任务2
* 函数参数:无
* 函数返回值:无
*****************************************/
void task2(void)
{while(1){//LED2状态反转rollbackLedByLocation(LED2);//挂起任务vTaskSuspend(xHandleTsak[1]);}
}/*****************************************
* 函数功能:freertos的空闲任务
* 函数参数:无
* 函数返回值:无
*****************************************/
void vApplicationIdleHook (void)
{++count[0];		if(count[0] % 900000 == 0)//恢复任务1vTaskResume(xHandleTsak[1]);if(count[0] % 1000000 == 0)//恢复任务0vTaskResume(xHandleTsak[0]);if(count[0] % 500000 == 0)rollbackLedByLocation(LED8);
}

结果
  其结果为LED1、LED2根据空闲任务执行次数来切换LED1与LED2的状态;

示例2
  利用FreeRTOS的空闲任务计时实现LED1、LED2、LED3轮换灯闪烁。

  • 步骤一:创建三个FreeRTOS的任务,其优先级都设置为1;
  • 完成三个FreeRTOS任务的函数体,每次执行一次任务后就使用函数vTaskDelayUntil将任务周期性阻塞,切换各个任务;
void task1(void);
void task2(void);
void task3(void);unsigned int count[2] = {0,0};/*****************************************
* 函数功能:freertos工作函数
* 函数参数:无
* 函数返回值:无
*****************************************/
void freertosWork(void)
{/*创建任务两个LED灯闪烁任务*///存储创建任务的返回值BaseType_t xReturn[2] ;//动态创建任务1xReturn[0] = xTaskCreate((TaskFunction_t )task1,//任务入口函数(const char *)"task1",//任务名字(uint16_t)512,//任务栈大小(void*)NULL,//任务入口参数1,//任务优先级  优先级越高,任务优先选越高&xHandleTsak[0]//任务控制块);//动态创建任务2xReturn[1] = xTaskCreate((TaskFunction_t )task2,//任务入口函数(const char *)"task2",//任务名字(uint16_t)512,//任务栈大小(void*)NULL,//任务入口参数1,//任务优先级  优先级越高,任务优先选越高&xHandleTsak[1]//任务控制块);					//动态创建任务3xReturn[2] = xTaskCreate((TaskFunction_t )task3,//任务入口函数(const char *)"task3",//任务名字(uint16_t)512,//任务栈大小(void*)NULL,//任务入口参数1,//任务优先级  优先级越高,任务优先选越高&xHandleTsak[2]//任务控制块);		//创建成功 if (pdPASS == xReturn[0] == xReturn[1] == xReturn[2])//启动任务,开启调度 vTaskStartScheduler(); //创建失败else//点亮LED6changeLedStateByLocation(LED6,ON);return 0;
}/*****************************************
* 函数功能:freertos的任务1
* 函数参数:无
* 函数返回值:无
*****************************************/
void task1(void)
{//保存上次任务执行的时间,调用后会再次刷新时间static portTickType PreviousWakeTime;const volatile TickType_t xDelay900ms = pdMS_TO_TICKS( 900UL );//获取当前时间PreviousWakeTime = xTaskGetTickCount();while(1){//LED1反转状态rollbackLedByLocation(LED1);vTaskDelayUntil( &PreviousWakeTime,xDelay900ms );}
}/*****************************************
* 函数功能:freertos的任务2
* 函数参数:无
* 函数返回值:无
*****************************************/
void task2(void)
{//保存上次任务执行的时间,调用后会再次刷新时间static portTickType PreviousWakeTime;const volatile TickType_t xDelay1400ms = pdMS_TO_TICKS( 1400UL );//获取当前时间PreviousWakeTime = xTaskGetTickCount();while(1){//LED2状态反转rollbackLedByLocation(LED2);	vTaskDelayUntil( &PreviousWakeTime,xDelay1400ms );}
}/*****************************************
* 函数功能:freertos的任务3
* 函数参数:无
* 函数返回值:无
*****************************************/
void task3(void)
{//保存上次任务执行的时间,调用后会再次刷新时间static portTickType PreviousWakeTime;const volatile TickType_t xDelay2000ms = pdMS_TO_TICKS( 2000UL );//获取当前时间PreviousWakeTime = xTaskGetTickCount();while(1){//LED2状态反转rollbackLedByLocation(LED3);	vTaskDelayUntil( &PreviousWakeTime,xDelay2000ms );}
}

结果
  FreeRTOS创建的三个任务,分别以900ms、1400ms、2000ms的时间间隔轮流执行,视觉上就是LED1、LED2、LED3轮流闪烁。


小编这里还有一篇关于定时器的文章,也欢迎各位点击观看😉😉😉【FreeRTOS】详细讲解FreeRTOS的软件定时器及通过示例讲述其用法

最后 ,也欢迎大家留言或私信交流,大家共同进步!😁😁😁

相关内容

热门资讯

世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...