ESP32 FreeRTOS-任务控制(5)

提示:好记性不如烂笔头。本博客作为学习笔记,有错误的地方希望指正

ESP32-FreeRTOS序列:

ESP32 FreeRTOS-任务的创建与删除 (1)
ESP32 FreeRTOS-任务输入参数(2)
ESP32 FreeRTOS-任务优先级(3)
ESP32 FreeRTOS-调试任务实用工具(4)
ESP32 FreeRTOS-任务控制(5)

前言:

  参考资料:FreeRTOS API参考
  第一篇我们主要讲述任务控制的API,这里主要有任务延时、获取任务优先级设置任务优先级、任务挂起、任务恢复等等。
  对于了解任务挂起和任务恢复,我们先要了解FreeRTOS的任务状态模型。
任务状态:
一个任务可以存在于以下状态之一。

  • 运行态:
      当一个任务实际执行时,它被说成是处于运行状态。它目前正在使用处理器。如果运行RTOS的处理器只有一个核心,那么在任何时候只能有一个任务处于运行状态。
  • 就绪态:
      准备好的任务是那些能够执行的任务(它们不处于阻塞或暂停状态),但目前没有执行,因为一个同等或更高优先级的不同任务已经处于运行状态。
  • 阻塞态:
      如果一个任务目前正在等待一个时间性的或外部的事件,那么它就被称为处于阻塞状态。例如,如果一个任务调用vTaskDelay(),它将阻塞(被置于阻塞状态),直到延迟期结束–一个时间性事件。任务也可以阻塞以等待队列、信号、事件组、通知或信号事件。处于阻塞状态的任务通常有一个 "超时 "期,超时后任务将被超时,并被解除阻塞,即使该任务所等待的事件没有发生。
      处于阻塞状态的任务不使用任何处理时间,不能被选择进入运行状态。
  • 挂起态:
      和处于阻塞状态的任务一样,暂停状态的任务不能被选择进入运行状态,但暂停状态的任务没有超时。相反,任务只有在分别通过vTaskSuspend()和xTaskResume()API调用明确命令进入或退出暂停状态时,才会进入或退出暂停状态。
    在这里插入图片描述
      不同的状态可以通过上述这张图片操作达到不同的状态。

一、任务延时vTaskDelay()

API原型:

void vTaskDelay( const TickType_t xTicksToDelay );

  INCLUDE_vTaskDelay必须被定义为1才能使用这个函数。更多信息请参见RTOS配置文档。
  将一个任务延迟给定的tick数。任务被阻止的实际时间取决于tick rate。常数portTICK_PERIOD_MS可以用来从tick rate计算实时时间–分辨率为一个tick period。
  vTaskDelay()指定了任务希望解除封锁的时间,相对于调用vTaskDelay()的时间。因此,vTaskDelay()并不提供控制周期性任务频率的好方法,因为代码中的路径以及其他任务和中断活动会影响vTaskDelay()被调用的频率,从而影响任务下次执行的时间。参见vTaskDelayUntil(),它是一个旨在促进固定频率执行的替代性API函数。它是通过指定一个绝对时间(而不是相对时间)来实现的,在这个时间里,调用的任务应该解除封锁。
参数:

  • xTicksToDelay: 调用任务应阻塞的时间,以tick为单位。

使用示例:

void vTaskFunction( void * pvParameters )
 {
    
    
 /* Block for 500ms. */
 const TickType_t xDelay = 500 / portTICK_PERIOD_MS;

     for( ;; )
     {
    
    
         /* Simply toggle the LED every 500ms, blocking between each toggle. */
         vToggleLED();
         vTaskDelay( xDelay );
     }
}

二、任务延时vTaskDelayUntil()

API原型:

void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,const TickType_t xTimeIncrement );

  INCLUDE_vTaskDelayUntil必须被定义为1,这个函数才可用。更多信息请参见RTOS配置文档。
  将一个任务延迟到一个指定的时间。这个函数可以被周期性任务使用,以确保恒定的执行频率。这个函数在一个重要方面与vTaskDelay()不同:vTaskDelay()指定一个任务希望解锁的时间,相对于调用vTaskDelay()的时间,而vTaskDelayUntil()则指定一个任务希望解锁的绝对时间。
  vTaskDelay()将导致任务从调用vTaskDelay()时起封锁指定的点数。因此,很难使用vTaskDelay()本身来产生一个固定的执行频率,因为在调用vTaskDelay()后,任务解除阻塞和该任务下次调用vTaskDelay()之间的时间可能不固定[任务在调用之间可能采取不同的代码路径,或者每次执行时可能被中断或抢占的次数不同]。
  vTaskDelay()指定了相对于函数被调用时的唤醒时间,而vTaskDelayUntil()则指定了它希望解锁的绝对(精确)时间。
  应该注意的是,如果vTaskDelayUntil()被用来指定一个已经过去的唤醒时间,它将立即返回(没有阻塞)。因此,使用vTaskDelayUntil()定期执行的任务将不得不重新计算它所需的唤醒时间,如果定期执行因任何原因而停止(例如,任务被暂时置于暂停状态)导致任务错过一次或多次定期执行。这可以通过检查作为pxPreviousWakeTime参数的参考变量和当前的tick计数来检测。然而,在大多数使用情况下,这并不是必须的。
  常数 portTICK_PERIOD_MS 可以用来从 tick rate 计算实时时间 - 分辨率为一个 tick period。
  当RTOS调度器被调用vTaskSuspendAll()暂停时,不能调用这个函数。
参数:

  • pxPreviousWakeTime: 指向一个变量,用于保存任务最后一次解除封锁的时间。该变量必须在第一次使用前用当前时间进行初始化(见下面的例子)。在这之后,该变量会在vTaskDelayUntil()中自动更新。
  • xTimeIncrement: 循环时间段。任务将在时间(*pxPreviousWakeTime + xTimeIncrement)上解除封锁。以相同的xTimeIncrement参数值调用vTaskDelayUntil将导致任务以固定的间隔期执行。

使用示例:

// Perform an action every 10 ticks.
 void vTaskFunction( void * pvParameters )
 {
    
    
 TickType_t xLastWakeTime;
 const TickType_t xFrequency = 10;

     // Initialise the xLastWakeTime variable with the current time.
     xLastWakeTime = xTaskGetTickCount();

     for( ;; )
     {
    
    
         // Wait for the next cycle.
         vTaskDelayUntil( &xLastWakeTime, xFrequency );

         // Perform action here.
     }
 }

三、任务延时xTaskDelayUntil()

API原型:

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

  INCLUDE_xTaskDelayUntil必须被定义为1,这个函数才可用。更多信息请参见RTOS配置文档。
  将一个任务延迟到一个指定的时间。这个函数可以被周期性任务使用,以确保恒定的执行频率。
  这个函数在一个重要方面与vTaskDelay()不同:vTaskDelay()将导致一个任务从vTaskDelay()被调用时起,阻塞指定的点数,而xTaskDelayUntil()将导致一个任务从pxPreviousWakeTime参数中指定的时间起,阻塞指定的点数。使用vTaskDelay()本身很难产生一个固定的执行频率,因为从任务开始执行到该任务调用vTaskDelay()之间的时间可能并不固定[任务在调用之间可能采取不同的代码路径,或者每次执行时可能被打断或被抢占的次数不同]。
  vTaskDelay()指定了相对于函数被调用时的唤醒时间,而xTaskDelayUntil()则指定了它希望解除封锁的绝对(精确)时间。
  宏pdMS_TO_TICKS()可以用来计算从一个以毫秒为单位的时间开始的刻度数,其分辨率为一个刻度周期。
参数:

  • pxPreviousWakeTime: 指针指向一个变量,用于保存任务最后一次解锁的时间。该变量必须在第一次使用前用当前时间进行初始化(见下面的例子)。之后,该变量会在xTaskDelayUntil()中自动更新。
  • xTimeIncrement: 循环时间段。任务将在时间(*pxPreviousWakeTime + xTimeIncrement)上解除封锁。以相同的xTimeIncrement参数值调用xTaskDelayUntil将导致任务以固定的间隔期执行。

返回:
  一个可以用来检查任务是否真的被延迟的值:如果任务被延迟,则pdTRUE,否则pdFALSE。如果下一个预期唤醒时间是在过去,那么任务将不会被延迟。
使用示例:

// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
    
    
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
BaseType_t xWasDelayed;

    // Initialise the xLastWakeTime variable with the current time.
    xLastWakeTime = xTaskGetTickCount ();
    for( ;; )
    {
    
    
        // Wait for the next cycle.
        xWasDelayed = xTaskDelayUntil( &xLastWakeTime, xFrequency );

        // Perform action here. xWasDelayed value can be used to determine
        // whether a deadline was missed if the code here took too long.
    }
}

四、获取任务优先级uxTaskPriorityGet()

API原型:

UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );

  INCLUDE_uxTaskPriorityGet必须被定义为1才能使用这个函数。更多信息请参见RTOS配置文档。获取任何任务的优先级。
参数:

  • xTask 要查询的任务的句柄。传递一个NULL句柄会导致返回调用任务的优先级。

返回:
  任务的优先级。
使用示例:

 void vAFunction( void )
 {
    
    
 TaskHandle_t xHandle;

     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // ...

     // Use the handle to obtain the priority of the created task.
     // It was created with tskIDLE_PRIORITY, but may have changed
     // it itself.
     if( uxTaskPriorityGet( xHandle ) != tskIDLE_PRIORITY )
     {
    
    
         // The task has changed its priority.
     }

     // ...

     // Is our priority higher than the created task?
     if( uxTaskPriorityGet( xHandle ) < uxTaskPriorityGet( NULL ) )
     {
    
    
         // Our priority (obtained using NULL handle) is higher.
     }
 }

五、任务优先级设置vTaskPrioritySet()

API原型:

void vTaskPrioritySet( TaskHandle_t xTask,UBaseType_t uxNewPriority );

  INCLUDE_vTaskPrioritySet必须被定义为1才能使用这个功能。更多信息请参见RTOS配置文档。
  设置任何任务的优先级。
  如果被设置的优先级高于当前执行的任务,在函数返回之前会发生上下文切换。
参数:

  • xTask: 其优先级被设置的任务的句柄。一个NULL的句柄会设置调用任务的优先级。
  • uxNewPriority: 将被设置的任务的优先级。优先级被断言为小于configMAX_PRIORITIES。如果configASSERT是未定义的,那么优先级就会默默地以(configMAX_PRIORITIES - 1)为上限。

使用示例:

void vAFunction( void )
 {
    
    
 TaskHandle_t xHandle;
     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
     // ...
     // Use the handle to raise the priority of the created task.
     vTaskPrioritySet( xHandle, tskIDLE_PRIORITY + 1 )
     // ...
     // Use a NULL handle to raise our priority to the same value.
     vTaskPrioritySet( NULL, tskIDLE_PRIORITY + 1 );
 }

六、任务挂起vTaskSuspend()

API原型:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

  INCLUDE_vTaskSuspend必须被定义为1,这个函数才可用。更多信息请参见RTOS配置文档。
  暂停任何任务。当暂停一个任务时,无论它的优先级是多少,都不会得到任何微控制器的处理时间。
  对vTaskSuspend的调用是不累积的–也就是说,在同一个任务上调用vTaskSuspend()两次,仍然只需要调用vTaskResume()来准备被暂停的任务。
参数:

  • xTaskToSuspend: 被暂停的任务的句柄。传递一个NULL句柄将导致调用任务被暂停。

使用示例:

void vAFunction( void )
 {
    
    
 TaskHandle_t xHandle;

     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // ...

     // Use the handle to suspend the created task.
     vTaskSuspend( xHandle );

     // ...

     // The created task will not run during this period, unless
     // another task calls vTaskResume( xHandle ).

     //...

     // Suspend ourselves.
     vTaskSuspend( NULL );

     // We cannot get here unless another task calls vTaskResume
     // with our handle as the parameter.
 }

七、任务恢复vTaskResume()

API原型:

void vTaskResume( TaskHandle_t xTaskToResume );

  INCLUDE_vTaskSuspend必须被定义为1,这个函数才可用。更多信息请参见RTOS配置文档。
  恢复一个暂停的任务。
  通过一次或多次调用vTaskSuspend()而暂停的任务,将通过一次调用vTaskResume()而再次运行。
参数:

  • xTaskToResume: 正在准备的任务的指针。

使用示例:

void vAFunction( void )
 {
    
    
 TaskHandle_t xHandle;

     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // ...

     // Use the handle to suspend the created task.
     vTaskSuspend( xHandle );

     // ...

     // The created task will not run during this period, unless
     // another task calls vTaskResume( xHandle ).

     //...

     // Resume the suspended task ourselves.
     vTaskResume( xHandle );

     // The created task will once again get microcontroller processing
     // time in accordance with its priority within the system.
 }

八、中断重任务恢复xTaskResumeFromISR()

API原型:

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );

  INCLUDE_vTaskSuspend和INCLUDE_xTaskResumeFromISR必须被定义为1才能使用这个函数。更多信息请参见RTOS配置文档。
  一个恢复被暂停的任务的函数,可以从ISR中调用。
  通过多次调用vTaskSuspend()而暂停的任务将通过一次调用xTaskResumeFromISR()而再次运行。
  xTaskResumeFromISR()通常被认为是一个危险的函数,因为它的动作没有被锁定。由于这个原因,如果中断有可能在任务暂停之前到达,从而导致中断丢失,那么绝对不应该用它来使任务与中断同步。使用信号,或者最好是直接向任务发出通知,可以避免这种情况的发生。我们提供了一个使用直接到任务通知的工作实例。
参数:

  • xTaskToResume: 正在准备的任务的指针。

返回:
  pdTRUE如果恢复任务应该导致上下文切换,否则pdFALSE。这被ISR用来确定在ISR之后是否可能需要进行上下文切换。
使用示例:

void vAFunction( void )
 {
    
    
     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // ... Rest of code.
 }

 void vTaskCode( void *pvParameters )
 {
    
    
     // The task being suspended and resumed.
     for( ;; )
     {
    
    
         // ... Perform some function here.

         // The task suspends itself.
         vTaskSuspend( NULL );

         // The task is now suspended, so will not reach here until the ISR resumes it.
     }
 }
 void vAnExampleISR( void )
 {
    
    
     BaseType_t xYieldRequired;

     // Resume the suspended task.
     xYieldRequired = xTaskResumeFromISR( xHandle );

     // We should switch context so the ISR returns to a different task.
     // NOTE:  How this is done depends on the port you are using.  Check
     // the documentation and examples for your port.
     portYIELD_FROM_ISR( xYieldRequired );
 }

九、任务中止延时xTaskAbortDelay()

API原型:

BaseType_t xTaskAbortDelay( TaskHandle_t xTask );

  强制任务离开阻塞状态,并进入就绪状态,即使任务在阻塞状态下等待的事件没有发生,并且任何指定的超时也没有过期。
  INCLUDE_xTaskAbortDelay必须被定义为1才能使用这个功能。更多信息请参见RTOS配置文档。
参数:

  • xTask: 将被强制脱离阻塞状态的任务的句柄。为了获得一个任务的句柄,使用xTaskCreate()创建任务并利用pxCreatedTask参数,或者使用xTaskCreateStatic()创建任务并存储返回值,或者在调用xTaskGetHandle()时使用任务的名字。

返回:
  如果xTask所引用的任务没有处于阻塞状态,那么将返回pdFAIL。否则返回pdPASS。

三、综合示例

  其中任务的优先级前面我们已经介绍过了,这里主要是实验性的测试,值的注意的是,任务优先级的数值越大,即任务优先级越高。强制取消延时我们在主任务中控制APP_task_Control任务,然后控制之后两个任务实现同步,延时时间一致都是500m。另外任务挂起函数如果输入的参数是NULL的话,这样就是挂起本函数,不是空的话就按照指定的任务句柄挂起函数。

/**
 * @file 5_TaskControl.c
 * @author WSP
 * @brief 
 * @version 0.1
 * @date 2022-10-09
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include "System_Include.h"

const static char * TAG = "Task_Parameter";
//  APP_task 任务的一些变量
TaskHandle_t APP_task_Control_handle = NULL;

// 根据宏定义来修改测试代码中不同部分
#define User_vTaskDelay    1

/**
 * @brief APP_task
 * @param arg 任务传入的参数
 * @return NULL
 */
void APP_task_Control(void * arg)
{
    
    

    #ifdef User_xTaskAbortDelay
        TickType_t xLastWakeTime, xNowWakeTime;
    #endif
    while (1){
    
    
        #ifdef User_xTaskAbortDelay
            xLastWakeTime = xTaskGetTickCount();
        #endif
        vTaskDelay(2000/portTICK_PERIOD_MS);
        ESP_LOGI(TAG,"APP_task_Control");
        #ifdef User_xTaskAbortDelay
            xNowWakeTime = xTaskGetTickCount();
            ESP_LOGI(TAG,"xNowWakeTime - xLastWakeTime = %d",xNowWakeTime - xLastWakeTime); // 打印执行时间 正常不取消延时的话是200 取消之后就是50
        #endif
    }
}
/**
 * @brief   创建函数初始化
 * @param   NULL
 * @return  NULL
 */
void Task_Control_Init(void)
{
    
    
    // 创建任务一
    xTaskCreate(APP_task_Control,               // 创建任务
                "APP_task_Control",             // 创建任务名
                2048,                           // 任务堆栈
                NULL,
                3,                              // 默认3   
                &APP_task_Control_handle);      // 任务句柄 

    vTaskDelay(3000/portTICK_PERIOD_MS);        // 延时等待

#if User_vTaskDelayUntil
    TickType_t vLastWakeTime;
    const TickType_t vFrequency = 2000;
    vLastWakeTime = xTaskGetTickCount();
#elif User_xTaskDelayUntil
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = 100;
    xLastWakeTime = xTaskGetTickCount();
#endif 
    while (1){
    
    
        #if User_vTaskDelay
            vTaskDelay(1000/portTICK_PERIOD_MS);  
        #elif User_vTaskDelayUntil
            vTaskDelayUntil(&vLastWakeTime,vFrequency);  
        #elif User_xTaskDelayUntil
            ESP_LOGI(TAG,"Whether the task is delayed! %s ",xTaskDelayUntil(&xLastWakeTime,xFrequency) == pdTRUE ? "YES" : "NO");
        #elif User_uxTaskPriorityGet
            vTaskDelay(1000/portTICK_PERIOD_MS);  
            ESP_LOGI(TAG,"Task Priorty :%d",uxTaskPriorityGet(APP_task_Control_handle));
        #elif User_vTaskPrioritySet
            vTaskDelay(1000/portTICK_PERIOD_MS);  
            ESP_LOGI(TAG,"Task Priorty :%d",uxTaskPriorityGet(APP_task_Control_handle));    // 先获取任务优先级
            vTaskPrioritySet(APP_task_Control_handle,1);                                    // 设置任务优先级
            ESP_LOGI(TAG,"Task Priorty :%d",uxTaskPriorityGet(APP_task_Control_handle));    // 再次获取任务优先级
        #elif User_vTaskSuspend
            vTaskDelay(1000/portTICK_PERIOD_MS); 
            vTaskSuspend(APP_task_Control_handle);      // 先将任务挂起
        #elif User_vTaskResume
            vTaskSuspend(APP_task_Control_handle);      // 先将任务挂起
            ESP_LOGI(TAG,"APP_task_Control suspend");  
            vTaskDelay(3000/portTICK_PERIOD_MS); 
            vTaskResume(APP_task_Control_handle);       // 将任务恢复
            ESP_LOGI(TAG,"APP_task_Control resume");  
            vTaskDelay(1000/portTICK_PERIOD_MS); 
        #elif User_xTaskAbortDelay
            xTaskAbortDelay(APP_task_Control_handle);   // 强制给任务APP_task_Control取消延时 直接和 当前主任务一样
            vTaskDelay(500/portTICK_PERIOD_MS); 
        #endif
            ESP_LOGI(TAG,"Main Task Run");  
    }
}

猜你喜欢

转载自blog.csdn.net/believe666/article/details/127205502