任务状态
在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] ;
//动态创建任务1
xReturn[0] = xTaskCreate(
(TaskFunction_t )task1,//任务入口函数
(const char *)"task1",//任务名字
(uint16_t)512,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[0]//任务控制块
);
//动态创建任务2
xReturn[1] = xTaskCreate(
(TaskFunction_t )task2,//任务入口函数
(const char *)"task2",//任务名字
(uint16_t)512,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[1]//任务控制块
);
//创建成功
if (pdPASS == xReturn[0] == xReturn[1])
//启动任务,开启调度
vTaskStartScheduler();
//创建失败
else
//点亮LED6
changeLedStateByLocation(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)
//恢复任务1
vTaskResume(xHandleTsak[1]);
if(count[0] % 1000000 == 0)
//恢复任务0
vTaskResume(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] ;
//动态创建任务1
xReturn[0] = xTaskCreate(
(TaskFunction_t )task1,//任务入口函数
(const char *)"task1",//任务名字
(uint16_t)512,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[0]//任务控制块
);
//动态创建任务2
xReturn[1] = xTaskCreate(
(TaskFunction_t )task2,//任务入口函数
(const char *)"task2",//任务名字
(uint16_t)512,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[1]//任务控制块
);
//动态创建任务3
xReturn[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
//点亮LED6
changeLedStateByLocation(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的软件定时器及通过示例讲述其用法
最后 ,也欢迎大家留言或私信交流,大家共同进步!😁😁😁