从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(五)任务管理    NO.2 常用的任务函数讲解

目录

​一、任务挂起函数:​

​① vTaskSuspend()​

​② vTaskSuspendAll()​

​2、任务恢复函数:​

​① vTaskResume()​

​② xTaskResumeFromISR()​

​③ xTaskResumeAll()​

​3、任务删除函数:​

​① vTaskDelete()​

​4、任务延时函数:​

​① vTaskDelay()​

​② vTaskDelayUntil()​


一、任务挂起函数:

① vTaskSuspend()

挂起指定任务。被挂起的任务绝不会得到CPU的使用权,不管该任务具有什么优先级。

无论任务在挂起时候调用过多少次这个vTaskSuspend()函数,也只需调用一次

参数:任务句柄。

使用方法:

**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t LED_Task_Handle = NULL;/* LED 任务句柄 */
static void KEY_Task(void* parameter)
{
while (1) {
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
/* K1 被按下 */
printf("挂起 LED 任务! \n");
vTaskSuspend(LED_Task_Handle);/* 挂起 LED 任务 */
}
vTaskDelay(20);/* 延时 20 个 tick */
}
}

② vTaskSuspendAll()

挂起所有任务就是挂起任务调度器。调度器被挂起后则不能进行上下文切换

通过uxSchedulerSuspended变量记录挂起多少次。调用了多少次的 vTaskSuspendAll()就要调用多少次xTaskResumeAll()进行恢复。

在调度器挂起期间,被中断恢复的任务,将被挂载到xPendingReadyList待处理就绪列表中。

 

2、任务恢复函数:

① vTaskResume()

       任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。

也只需调用一次

/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t LED_Task_Handle = NULL;/* LED 任务句柄 */
static void KEY_Task(void* parameter)
{
while (1) {
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
/* K2 被按下 */
printf("恢复 LED 任务! \n");
vTaskResume(LED_Task_Handle);/* 恢复 LED 任务! */
}
vTaskDelay(20);/* 延时 20 个 tick */
}
}

② xTaskResumeFromISR()

       xTaskResumeFromISR()专门用在中断服务程序中恢复一个任务

只需调用一次恢复任务函数就可以恢复。

       使用 xTaskResumeFromISR()的时候有几个需要注意的地方:
       1、当函数的返回值为 pdTRUE 时: 恢复运行的任务的优先级等于或高于正在运行的任务 , 表明在中断服务函数退出后必须进行一次上下文切换,使用portYIELD_FROM_ISR()进行上下文切换。当函数的返回值为 pdFALSE 时: 恢复运行的任务的优先级低于当前正在运行的任务,表明在中断服务函数退出后不需要进行上下文切换
       2、xTaskResumeFromISR() 通常被认为是一个危险的函数,因为它的调用并非是固定的,中断可能随时来来临。所以, xTaskResumeFromISR()不能用于任务和中断间的同步,如果中断恰巧在任务被挂起之前到达,这就会导致一次中断丢失(任务还没有挂起,调用 xTaskResumeFromISR()函数是没有意义的,只能等下一次中断)。这种情况下,可以使用信号量或者任务通知来同步就可以避免这种情况。

void vAnExampleISR( void )
{
BaseType_t xYieldRequired;
/* 恢复被挂起的任务 */
xYieldRequired = xTaskResumeFromISR( xHandle );
if ( xYieldRequired == pdTRUE ) {
/* 执行上下文切换, ISR 返回的时候将运行另外一个任务 */
portYIELD_FROM_ISR();
}
}

③ xTaskResumeAll()

       那么调用对应的xTaskResumeAll()会将变量uxSchedulerSuspended减一

       将任何准备好的任务从待处理就绪列表,移动到相应的就绪列表中

       重置下一个任务的解除阻塞时间

       更新时基确保滴答定时器的计数不会滑动。

       调用了多少次的vTaskSuspendAll()就要调用多少次xTaskResumeAll()进行恢复。

void vDemoFunction( void )
vTaskSuspendAll();
/* 处理 xxx 代码 */
vTaskSuspendAll();
/* 处理 xxx 代码 */
vTaskSuspendAll();
/* 处理 xxx 代码 */
xTaskResumeAll();
xTaskResumeAll();
xTaskResumeAll();
}

 

3、任务删除函数:

① vTaskDelete()

       vTaskDelete()用于删除一个任务。当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。

       如果此时删除的任务是任务自身的话,那么删除任务函数不能在任务本身内完成删除操作,因为需要上下文切换到另一个任务。所以需要将任务放在回收列表中(xTasksWaitingTermination),空闲任务会检查结束列表并在空闲任务prvIdleTask()中释放删除任务的控制块和已删除任务的堆栈内存。

       删除任务时,只会自动释放内核本身分配给任务的内存。应用程序(而不是内核)分配给任务的内存或任何其他资源必须是删除任务时由应用程序显式释放

/* 创建一个任务,将创建的任务句柄存储在 DeleteHandle 中*/
TaskHandle_t DeleteHandle;
if (xTaskCreate(DeleteTask,
"DeleteTask",
STACK_SIZE,
NULL,
PRIORITY,
&DeleteHandle) != pdPASS )
{
/* 创建任务失败,因为没有足够的堆内存可分配。 */
}
void DeleteTask( void )
{
/* 用户代码 xxxxx */
/* ............ */
/* 删除任务本身 */
vTaskDelete( NULL );
}
/* 在其他任务删除 DeleteTask 任务 */
vTaskDelete( DeleteHandle );

4、任务延时函数:

① vTaskDelay()

       vTaskDelay()是阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU资源。

       vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。

       vTaskDelay()延时是相对性的延时,它指定的延时时间是从调用 vTaskDelay()结束后开始计算的,经过指定的时间后延时结束。

从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(六)任务管理    NO.2 常用的任务函数讲解_ 从0到1学习FreeRTOS

图来自野火教程,若侵即删。

void vTaskA( void * pvParameters )
{
while (1) {
// ...
// 这里为任务主体代码
// ...
/* 调用相对延时函数,阻塞 1000 个 tick */
vTaskDelay( 1000 );
}
}

② vTaskDelayUntil()

       任务会先调用vTaskDelayUntil()使任务进入阻塞态,等到时间到了就从阻塞中解除,然后执行主体代码,任务主体代码执行完毕。会继续调用vTaskDelayUntil()使任务进入阻塞态,然后就是循环这样子执行。即使任务在执行过程中发生中断,那么也不会影响这个任务的运行周期,仅仅是缩短了阻塞的时间而已,到了要唤醒的时间依旧会将任务唤醒。

从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(六)任务管理    NO.2 常用的任务函数讲解_任务管理_02

从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(六)任务管理    NO.2 常用的任务函数讲解_FreeRTOS 内核应用开发_03

图来自野火教程,若侵即删。

       无论是溢出还是没有溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。也就是说任务执行的时间必须小于任务周期时间xTimeIncrement

 

 

void vTaskA( void * pvParameters )
{
/* 用于保存上次时间。调用后系统自动更新 */
static portTickType PreviousWakeTime;
/* 设置延时时间,将时间转为节拍数 */
const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
/* 获取当前系统时间 */
PreviousWakeTime = xTaskGetTickCount();
while (1)
{
/* 调用绝对延时函数,任务时间间隔为 1000 个 tick */
vTaskDelayUntil( &PreviousWakeTime, TimeIncrement );
// ...
// 这里为任务主体代码
// ...
}
}