下冰雹 · 2020年09月23日

FreeRTOS系列第12篇---FreeRTOS任务应用函数

任务应用函数是一组辅助类函数,一般用于调试信息输出、获取任务句柄、获取任务状态、操作任务标签值等等。

1.获取任务系统状态
1.1函数描述

UBaseType_t uxTaskGetSystemState(

     TaskStatus_t * constpxTaskStatusArray,
     const UBaseType_tuxArraySize,
     unsigned long * constpulTotalRunTime );
  该函数向TaskStatus_t结构体填充相关信息,系统中每一个任务的信息都可以填充到TaskStatus_t结构体数组中,数组大小由uxArraySize指定。结构体TaskStatus_t定义如下:

typedef struct xTASK_STATUS
{
   /* 任务句柄*/
   TaskHandle_t xHandle;
 
   /* 指针,指向任务名*/
   const signed char *pcTaskName;
 
   /*任务ID,是一个独一无二的数字*/
   UBaseType_t xTaskNumber;
 
   /*填充结构体时,任务当前的状态(运行、就绪、挂起等等)*/
   eTaskState eCurrentState;
 
   /*填充结构体时,任务运行(或继承)的优先级。*/
   UBaseType_t uxCurrentPriority;
 
   /* 当任务因继承而改变优先级时,该变量保存任务最初的优先级。仅当configUSE_MUTEXES定义为1有效。*/
   UBaseType_t uxBasePriority;
 
   /* 分配给任务的总运行时间。仅当宏configGENERATE_RUN_TIME_STATS为1时有效。*/
   unsigned long ulRunTimeCounter;
 
   /* 从任务创建起,堆栈剩余的最小数量,这个值越接近0,堆栈溢出的可能越大。 */
   unsigned short usStackHighWaterMark;
}TaskStatus_t;

注意,这个函数仅用来调试用,调用此函数会挂起所有任务,直到函数最后才恢复挂起的任务,因此任务可能被挂起很长时间。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY必须设置为1,此函数才有效。

1.2参数描述

pxTaskStatusArray:指向TaskStatus_t类型的结构体数组。这个数组至少要包含1个元素。RTOS控制的任务数量可以使用API函数uxTaskGetNumberOfTasks()获取。
uxArraySize:参数pxTaskStatusArray指向的数组大小,也就是该数组的索引数目。
pulTotalRunTime:如果在文件FreeRTOSConfig.h中设置宏configGENERATE_RUN_TIME_STATS为1,则该函数将总运行时间写入*pulTotalRunTime中。pulTotalRunTime可以设置为NULL,表示忽略总运行时间。

1.3返回值

被填充的TaskStatus_t结构体数量。这个值应该等于通过调用API函数uxTaskGetNumberOfTasks()返回的值,但如果传递给uxArraySize参数的值太小,则返回0。

1.4用法举例
/*本例演示如是使用uxTaskGetSystemState()函数来获取运行时间信息,并将其转化为程序员更易识别的字符格式,这些转化后的字符保存到pcWriteBuffer中。*/
void vTaskGetRunTimeStats(signed char *pcWriteBuffer )
{
   TaskStatus_t*pxTaskStatusArray;
   volatileUBaseType_t uxArraySize, x;
   unsignedlong ulTotalRunTime, ulStatsAsPercentage;
 
   /* 防御性代码,确保字符串有合理的结束*/
  *pcWriteBuffer = 0x00;
 
   /* 获取任务总数目*/
  uxArraySize = uxTaskGetNumberOfTasks ();
 
   /*为每个任务的TaskStatus_t结构体分配内存,也可以静态的分配一个足够大的数组 */
  pxTaskStatusArray = pvPortMalloc( uxArraySize * sizeof( TaskStatus_t ));
 
   if(pxTaskStatusArray != NULL )
   {
      /*获取每个任务的状态信息 */
     uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize,&ulTotalRunTime );
 
      /* 百分比计算 */
     ulTotalRunTime /= 100UL;
 
      /* 避免除零错误 */
      if(ulTotalRunTime > 0 )
      {
         /* 将获得的每一个任务状态信息部分的转化为程序员容易识别的字符串格式*/
        for( x = 0; x < uxArraySize; x++ )
         {
           /* 计算任务运行时间与总运行时间的百分比。*/
           ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter /ulTotalRunTime;
 
           if( ulStatsAsPercentage > 0UL )
           {
              sprintf( pcWriteBuffer, "%s\t\t%lu\t\t%lu%%\r\n",
                                pxTaskStatusArray[ x ].pcTaskName,
                                pxTaskStatusArray[ x ].ulRunTimeCounter,
                                ulStatsAsPercentage );
           }
           else
           {
              /* 任务运行时间不足总运行时间的1%*/
              sprintf( pcWriteBuffer, "%s\t\t%lu\t\t<1%%\r\n",
                                pxTaskStatusArray[ x ].pcTaskName,
                                 pxTaskStatusArray[x ].ulRunTimeCounter );
           }
 
           pcWriteBuffer += strlen( ( char * ) pcWriteBuffer );
         }
      }
 
      /* 释放之前申请的内存*/
     vPortFree( pxTaskStatusArray );
   }
}
2.获取当前任务句柄
2.1函数描述

TaskHandle_t xTaskGetCurrentTaskHandle(void );

在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetCurrentTaskHandle必须设置为1,此函数才有效。

2.2返回值

返回当前任务(调用该函数的任务)的句柄。

3.获取空闲任务句柄
3.1函数描述

TaskHandle_t xTaskGetIdleTaskHandle(void );

在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetIdleTaskHandle必须设置为1,此函数才有效。

3.2返回值

返回空闲任务句柄。空闲任务在RTOS调度器启动时自动创建。

4.获取任务堆栈最大使用深度
4.1函数描述

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

任务的堆栈空间会随着任务执行以及中断处理而增长或缩小。该函数可以返回任务启动后的最小剩余堆栈空间。换句话说,可以间接估算出一个任务最多需要多少堆栈空间。在文件FreeRTOSConfig.h中,宏INCLUDE_uxTaskGetStackHighWaterMark 必须设置成1,此函数才有效。

4.2参数描述

xTask:任务句柄。NULL表示查看当前任务的堆栈使用情况。

4.3返回值

返回最小剩余堆栈空间,以字为单位。比如一个32为架构处理器,返回值为1表示有4字节堆栈空间没有使用过。如果返回值为0,则任务很可能已经发生了堆栈溢出。

4.4用法举例
 void vTask1( void * pvParameters )
 {
     UBaseType_tuxHighWaterMark;

     /* 入口处检测一次 */
     uxHighWaterMark =uxTaskGetStackHighWaterMark( NULL );

     for( ;; )
     {
         /* 正常调用函数 */
         vTaskDelay( 1000 );

         /* 测量堆栈使用情况 */
         uxHighWaterMark =uxTaskGetStackHighWaterMark( NULL );
     }
}
5.获取任务状态
5.1函数描述

eTaskState eTaskGetState( TaskHandle_txTask );

返回一个枚举类型的任务状态值。在文件FreeRTOSConfig.h中,宏INCLUDE_eTaskGetState必须设置为1,此函数才有效。

5.2参数描述

xTask:任务句柄

5.3返回值

下表列出返回值和对应的任务状态。
image.png

6.获取任务描述内容
6.1函数描述

char * pcTaskGetTaskName( TaskHandle_txTaskToQuery );

获取任务的描述内容,在文件FreeRTOSConfig.h中,宏INCLUDE_pcTaskGetTaskName必须设置成1,此函数才有效。

6.2参数描述

xTaskToQuery:任务的句柄。NULL表示获取当前任务的描述内容指针。

6.3返回值

一个指针,指向任务描述字符串。

7.获取系统节拍次数
7.1函数描述

volatile TickType_t xTaskGetTickCount(void );

这个函数不能在ISR中调用。在ISR中用xTaskGetTickCountFromISR(),原型为volatileTickType_t xTaskGetTickCountFromISR( void )。

7.2返回值

返回从vTaskStartScheduler函数调用后的系统时钟节拍次数。

8.获取调度器状态
8.1函数描述

BaseType_t xTaskGetSchedulerState( void);

获取调度器当前状态。在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetSchedulerState或configUSE_TIMERS必须定义为1,此函数才有效。

8.2返回值

返回值是以下常量之一(定义在task.h):taskSCHEDULER_NOT_STARTED(未启动)、taskSCHEDULER_RUNNING(正常运行)、taskSCHEDULER_SUSPENDED(挂起)。

9.获取任务总数
9.1函数描述

UBaseType_t uxTaskGetNumberOfTasks(void );

获取RTOS内核当前管理的任务总数。包含所有就绪、阻塞和挂起状态的任务。对于一个删除的任务,如果它的堆栈空间还没有被空闲任务释放掉,则这个被删除的任务也含在计数值中。

9.2返回值

返回RTOS内核当前管理的任务总数。

10.获取所有任务详情
10.1函数描述

void vTaskList( char *pcWriteBuffer );

将每个任务的状态、堆栈使用情况等以字符的形式保存到参数pcWriteBuffer指向的区域。vTaskList()函数调用usTaskGetSystemState()函数,然后将得到的信息格式化为程序员易读的字符形式。输出的内容例子如下图所示,图中State一栏中,B表示阻塞、R表示就绪、D表示删除(等待清除内存)、S表示挂起或阻塞。

image.png

注意,调用这个函数会挂起所有任务,这一过程可能持续较长时间,因此本函数仅在调试时使用。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS必须定义为1,此函数才有效。

10.2参数描述

pcWriteBuffer:任务的信息会写入这个缓冲区,为ASCII表单形式。这个缓冲区要足够大,以容纳生成的报告,每个任务大约需要40个字节。

11.获取任务运行时间
11.1函数描述

void vTaskGetRunTimeStats( char*pcWriteBuffer );

这个函数用于统计每个任务的运行时间。要使用这个函数必须满足一些条件,那就是必须有一个用于时间统计的定时器或计数器,这个定时器或计数器的精度要至少大于10倍的系统节拍周期。这个定时器或计数器的配置以及获取定时时间是由两个宏定义实现的,这两个宏一般在文件FreeRTOSConfig.h中定义。配置定时器或计数器的宏为portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),获取定时时间的宏为portGET_RUN_TIME_COUNTER_VALUE。实现了这两个宏定义后,还必须在文件FreeRTOSConfig.h中将宏configGENERATE_RUN_TIME_STATS和configUSE_STATS_FORMATTING_FUNCTIONS设置为1,此API函数才有效。

这个API函数调用usTaskGetSystemState()函数获取每个任务的状态信息,并把其中的运行时间格式化为程序员易读的字符形式,并将这些信息保存到参数pcWriteBuffer指向的区域。

注意,调用这个函数会挂起所有任务,这一过程可能持续较长时间,因此本函数仅在调试时使用。

11.2参数描述

pcWriteBuffer:任务的运行时间信息会写入这个缓冲区,为ASCII表单形式。这个缓冲区要足够大,以容纳生成的报告,每个任务大约需要40个字节。

11.3用法举例

以lpc17xx系列为控制为例,我们使用定时器0来作为统计基准时钟。

11.3.1使能函数宏

在文件FreeRTOSConfig.h中,设置宏configGENERATE_RUN_TIME_STATS和configUSE_STATS_FORMATTING_FUNCTIONS为1,

11.3.2定时初始化定时器代码
void vConfigureTimerForRunTimeStats( void )
{
    /* 使能定时器0的外设电源,配置外设时钟 */
    PCONP |= 0x02UL;
    PCLKSEL0 = (PCLKSEL0& (~(0x3<<2))) | (0x01 << 2);
 
    /* 复位定时器 0 */
    T0TCR = 0x02;
 
    /* 作为计数器 */
    T0CTCR = 0x00;
 
    /* 预分频,设置合适的分辨率即可 */
    T0PR =  ( configCPU_CLOCK_HZ / 10000UL ) - 1UL;
 
    /* 启动计数器 */
    T0TCR = 0x01;
}
11.3.3定义配置定时器和获取定时时间宏

在文件FreeRTOSConfig.h中,定义下列代码:

extern void vConfigureTimerForRunTimeStats( void );
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() vConfigureTimerForRunTimeStats()
#defineportGET_RUN_TIME_COUNTER_VALUE() T0TC
12.设置任务标签值
12.1函数描述

voidvTaskSetApplicationTaskTag( TaskHandle_t xTask,

            TaskHookFunction_tpxTagValue );
可以给每个任务分配一个标签值。这个值一般用于应用程序,RTOS内核不会使用。在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必须设置为1,此函数才有效。
12.2参数描述

xTask:任务句柄。NULL表示当前任务。
pxTagValue:要分配给任务的标签值。这是一个TaskHookFunction_t类型的函数指针,但也可以给任务标签分配任意的值。

  注:TaskHookFunction_t原型定义:typedef BaseType_t (*TaskHookFunction_t)(void * )
12.3用法举例
/* 在这个例子中,给任务设置一个整形标签值。例子中使用了RTOS跟踪钩子宏。*/
void vATask( void *pvParameters )
{
  /* 为自己的标签分配一个整形值 */
 vTaskSetApplicationTaskTag( NULL, ( void * ) 1 );
 
  for( ;; )
  {
    /* 任务主体代码 */
  }
}
/*****************************************************************************/
 
/*在这个任务中,给任务设置一个函数标签值。首先定义一个回调函数,这个函数必须声明为TaskHookFunction_t类型。 */
static BaseType_t prvExampleTaskHook( void * pvParameter )
{
  /* 这里为用户定义代码 –可能是记录数据、更新任务状态值等。*/
 
  return 0;
}
 
/* 将回调函数设置为任务的标签值。 */
void vAnotherTask( void *pvParameters )
{
  /* 注册回调函数*/
 vTaskSetApplicationTaskTag( NULL, prvExampleTaskHook );
 
  for( ;; )
  {
     /* 任务主体代码 */
  }
}
 
/* 每当任务切换时,会调用xTaskCallApplicationTaskHook 函数(见14.)。 */
#define traceTASK_SWITCHED_OUT() xTaskCallApplicationTaskHook(pxCurrentTCB,0 )

13.获取任务标签值
13.1函数描述

  TaskHookFunction_txTaskGetApplicationTaskTag( TaskHandle_t xTask );

  返回分配给任务的标签值。程序员定义标签值,RTOS内核通常不会访问标签值。

  函数仅对高级用户使用。在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必须设置为1,此函数才有效。
13.2参数描述

xTask:任务句柄。NULL表示当前任务。

13.3返回值

返回指定任务的标签值。

14.执行任务的应用钩子函数
14.1函数描述

BaseType_txTaskCallApplicationTaskHook(

                     TaskHandle_txTask,
                     void*pvParameter );
  可以为每个任务分配一个标签值,当这个值是一个TaskHookFunction_t类型函数指针时,相当于应用程序向任务注册了一个回调函数,而API函数xTaskCallApplicationTaskHook用来调用这个回调函数。

一般这个函数配合RTOS跟踪钩子宏使用,见12.设置任务标签值一节的用法举例。

14.2参数描述

xTask:任务句柄。NULL表示当前任务。
pvParameter:作为参数传递给应用钩子函数

15.设置线程本地存储指针
15.1函数描述

void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet,

                           BaseType_t xIndex,
                           void*pvValue )
  此函数仅用于高级用户。

线程本地存储允许应用程序在任务的控制块中存储一些值,每个任务都有自己独立的储存空间。

比如,许多库函数都包含一个叫做errno的全局变量。某些库函数使用errno返回库函数错误信息,应用程序检查这个全局变量来确定发生了那些错误。在单线程程序中,将errno定义成全局变量是可以的,但是在多线程应用中,每个线程(任务)必须具有自己独有的errno值,否则,一个任务可能会读取到另一个任务的errno值。

FreeRTOS提供了一个灵活的机制,使得应用程序可以使用线程本地存储指针来读写线程本地存储。在文件FreeRTOSConfig.h中,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每个任务线程本地存储指针数组的大小。API函数vTaskSetThreadLocalStoragePointer()用于向指针数组中写入值,API函数pvTaskGetThreadLocalStoragePointer()用于从指针数组中读取值。

15.2参数描述

xTaskToSet:任务句柄。NULL表示当前任务。
xIndex:写入到线程本地存储数组的索引号,线程本笃存储数组的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS设定,该宏在文件FreeRTOSConfig.h中。
pvValue:写入到指定索引地址的数据值

15.3用法举例

参见16.获取线程本地存储指针一节。

16.读取线程本地存储指针
16.1函数描述

void*pvTaskGetThreadLocalStoragePointer(

                     TaskHandle_txTaskToQuery,
                     BaseType_txIndex );
此函数仅用于高级用户。从线程本地存储指针数组中读取值。更详细描述见15.设置线程本地存储指针一节。
16.2参数描写

xTaskToQuery:任务句柄。NULL表示当前任务。
xIndex:写入到线程本地存储数组的索引号,线程本笃存储数组的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS设定,该宏在文件FreeRTOSConfig.h中。

16.3返回值

返回一个指针,这个指针存储在线程本地存储指针数组中,数组索引由参数xIndex指定。

16.4用法举例
16.4.1存储一个整形数

uint32_tulVariable;

/* 向当前任务的线程本地存储数组下标为1的位置写入一个指向32位常量值的指针。*/
vTaskSetThreadLocalStoragePointer(NULL, 1, ( void * ) 0x12345678 );
 
/*向当前任务的线程本地存储数组下标为0的位置写入一个指向32整形值的指针*/
ulVariable= ERROR_CODE;
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) ulVariable );
 
/*从当前任务的线程本地存储数组下标为5的位置读取出一个指针并赋值给32位整形变量。*/
ulVariable= ( uint32_t ) pvTaskGetThreadLocalStoragePointer( NULL, 5 );
16.4.2存储结构提
typedefstruct
{
    uint32_t ulValue1;
    uint32_t ulValue2;
}xExampleStruct;
 
xExampleStruct*pxStruct;
 
/*为结构体分配内存*/
pxStruct= pvPortMalloc( sizeof( xExampleStruct ) );
 
/*为结构体成员赋值*/
pxStruct->ulValue1= 0;
pxStruct->ulValue2= 1;
 
/*向当前任务的线程本地存储数组下标为0的位置写入一个指向结构体变量的指针*/
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) pxStruct );
 
/*从当前任务的线程本地存储数组下标为0的位置读取出一个结构体指针*/
pxStruct= ( xExampleStruct * ) pvTaskGetThreadLocalStoragePointer( NULL, 0 );
17.设置超时状态
17.1函数描述

void vTaskSetTimeOutState( TimeOut_t *const pxTimeOut );

此函数仅用于高级用户,通常与API函数xTaskCheckForTimeOut()共同使用。
任务因为等待某事件而进入阻塞状态,通常情况下任务会设置一个等待超时周期。如果在等待事件超时,任务会退出阻塞状态。想象一个这样的应用,某任务等待一个事件而进入阻塞状态,但是事件迟迟不发生,超时后任务退出阻塞状态继续执行任务。假如任务等待的事件仍然没有发生,则任务又会阻塞在该事件下。只要任务等待的事件一直不发生,这个任务进入阻塞然后超时退出阻塞,再进入阻塞的循环就会一直存在。是不是可以设定一个总超时时间,只要总阻塞时间大于这个总超时时间,则可以结束这个任务或进行相应记录?freeRTOS提供了两个API函数来完成这个功能,这就是vTaskSetTimeOutState()和xTaskCheckForTimeOut()。

vTaskSetTimeOutState()函数用于设置初始条件,之后调用xTaskCheckForTimeOut()函数检查任务总阻塞时间是否超过总超时时间,如果没有超过,则调整剩余的超时时间计数器。

17.2参数描述

pxTimeOut:指向一个结构体的指针,该结构体用来保存确定超时是否发生的必要信息。vTaskSetTimeOutState()函数会设置结构体的成员。

17.3用法举例

参见18.超时检测。

18.超时检测
18.1函数描述

BaseType_t xTaskCheckForTimeOut(TimeOut_t * const pxTimeOut,

                        TickType_t* const pxTicksToWait );
  此函数仅用于高级用户,通常与API函数vTaskSetTimeOutState共同使用。

详细描述见17.设置超时状态

18.2参数描述

pxTimeOut:指向一个结构体的指针。该结构体保存确定超时是否发生的必要信息。使用API函数vTaskSetTimeOutState初始化该结构体。
pxTicksToWait:TickType_t指针,指向的变量保存总超时时间。

18.3返回值

pdTRUE:总超时发生
pdFALSE:总超时未发生

18.4用法举例

/* 函数用于从RX缓冲区中接收uxWantedBytes字节数据,RX缓冲区由UART中断填充。如果RX缓冲区没有足够的数据,则任务进入阻塞状态,直到RX缓冲区有足够数据或者发生超时。如果超时后仍然没有足够的数据,则任务会再次进入阻塞状态,xTaskCheckForTimeOut()函数用于重新计算总超时时间以确保总阻塞状态时间不超过MAX_TIME_TO_WAIT。如果总阻塞状态时间大于了总超时时间,则不管RX缓冲区是否有充足数据,都将这些数据读出来。

*/
size_txUART_Receive( uint8_t *pucBuffer, size_t uxWantedBytes )
{
    size_t uxReceived = 0;
    TickType_t xTicksToWait = MAX_TIME_TO_WAIT;
    TimeOut_t xTimeOut;
 
   /* 初始化结构体变量xTimeOut。*/
   vTaskSetTimeOutState( &xTimeOut );
 
   /* 无限循环,直到缓冲区包含足够的数据或者阻塞超时发生。*/
   while( UART_bytes_in_rx_buffer(pxUARTInstance ) < uxWantedBytes )
   {
      /* RX缓冲区没有足够多的数据,表示任务已经进入过一次阻塞状态。调用API函数xTaskCheckForTimeOut检查总阻塞时间是否超过总超时时间,如果没有,则调整剩余的总超时时间。*/
      if( xTaskCheckForTimeOut( &xTimeOut,&xTicksToWait ) != pdFALSE )
      {
         /* 如果总阻塞时间大于总超时时间,则退出这个循环 */
         break;
      }
 
      /* 在等待了xTicksToWait个系统节拍周期后,向接收中断发出通知,需要更多数据。
*/
      ulTaskNotifyTake( pdTRUE, xTicksToWait );
   }
 
    /*从RX缓冲区读取uxWantedBytes个字节并放到pucBuffer缓冲区。*/
    uxReceived = UART_read_from_receive_buffer(pxUARTInstance,  pucBuffer,  uxWantedBytes );
 
    return uxReceived;
}

相关阅读

FreeRTOS系列第10篇---FreeRTOS任务创建和删除
FreeRTOS系列第11篇---FreeRTOS任务控制

作者:朱工
首发博客:https://blog.csdn.net/zhzht19861011/article/details/50498173
关注FreeRTOS从基础到高级专栏,即时收取FreeRTOS系列文章。
推荐阅读
关注数
3248
内容数
54
介绍FreeRTOS的基本功能,移植与使用。主要介绍FreeRTOS的裁剪、任务、内存管理、队列、信号量、任务通知等基本组成,看完可以会用FreeRTOS,高级篇会深入介绍FreeRTOS的实现细节、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息