提示:好记性不如烂笔头。本博客作为学习笔记,有错误的地方希望指正
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");
}
}