FreeRTOS_其他任务API函数

news2024/9/24 17:13:29

目录

1. 任务相关 API 函数预览

2. 任务相关 API 函数详解

2.1 函数 uxTaskPriorityGet()

2.2 函数 vTaskPrioritySet()

2.3 函数 uxTaskGetSystemState()

2.4 函数 vTaskGetInfo()

2.5 函数 xTaskGetApplicationTaskTag()

2.6 函数 xTaskGetCurrentTaskHandle()

2.7 函数 xTaskGetHandle()

2.8 函数 xTaskGetIdleTaskHandle()

2.9 函数 uxTaskGetStackHighWaterMark()

2.10 函数 eTaskGetState()

2.11 函数 pcTaskGetName()

2.12 函数 xTaskGetTickCount()

2.13 函数 xTaskGetTickCountFromISR()

2.14 函数 xTaskGetSchedulerState()

2.15 函数 uxTaskGetNumberOfTasks()

2.16 函数 vTaskList()

2.17 函数 vTaskGetRunTimeStats()

2.18 函数 vTaskSetApplicationTaskTag()

2.19 函数 SetThreadLocalStoragePointer()

2.20 函数 GetThreadLocalStoragePointer()

3. 任务状态查询 API 实验

3.1 实验程序

3.2 程序运行结果分析

4. 任务运行时间信息统计实验

4.1 相关宏设置

4.2 实验程序

4.2.1 main.c

4.2.2 Timer.c

4.2.3 Timer.


        在前面我们已经学习了与任务相关的 API 函数,但是实际上真正设计到的 API 函数只有那么几个。但是 FreeRTOS 还有很多和任务相关的辅助 API 函数。在这里,我们学习一下和任务相关的辅助 API 函数。

1. 任务相关 API 函数预览

        uxTaskPriorityGet()                                                                         查询某个任务的优先级

        vTaskPrioritySet()                                                                           改变某个任务的任务优先级

        uxTaskGetSystemState()                                                                获取系统中的任务状态

        vTaskGetInfo()                                                                                获取某个任务的任务信息

        xTaskGetApplicationTaskTag()                                                       获取某个任务的标签(Tag)值

        xTaskGetCurrentTaskHandle()                                                       获取当前正在运行的任务的任务句柄

        xTaskGetHandle()                                                                           根据任务名字查找某个任务的句柄

        xTaskGetIdleTaskHandle()                                                              获取空闲任务的任务句柄

        uxTaskGetStackHighWaterMark()                                                   获取任务的堆栈的历史剩余最小值,FreeRTOS 中叫做 “高水位线”

        eTaskGetState()                                                                              获取某个任务的状态,这个状态是 eTaskState 类型

        pcTaskGetName()                                                                           获取某个任务的任务名字

        xTaskGetTickCount()                                                                       获取系统时间计数器值

        xTaskGetTickCountFromISR()                                                         在中断服务函数中获取时间计数器值

        xTaskGetSchedulerState()                                                               获取任务调度器的状态,开启或未开启

        uxTaskGetNumberOfTasks()                                                            获取当前系统中存在的任务数量

        vTaskList()                                                                                        以一种表格的形式输出当前系统中所有任务的详细信息

        vTaskGetRunTimeStats()                                                                  获取每个任务的运行时间

        vTaskSetApplicationTaskTag()                                                          设置任务标签(Tag)值

        SetThreadLocalStoragePointer()                                                      设置线程本地存储指针

        GetThreadLocalStoragePointer()                                                      获取线程本地存储指针

2. 任务相关 API 函数详解

2.1 函数 uxTaskPriorityGet()

        此函数用来设置指定任务的优先级,要使用此函数的话宏 INCLUDE_uxTaskPriorityGet 应设置为 1 ,函数原型如下:

UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask)

参数:

xTask:                        要查找任务的任务句柄

返回值:                       获取到的对应任务的任务优先级

TaskHandle_t xTaskHandle;
UBaseType_t uxPriority;

//获取当前任务的任务优先级
xTaskHandle = xTaskGetCurrentTaskHandle();

//获取当前任务的任务优先级
uxPriority = uxTaskPriorityGet(xTaskHandle);

需要注意的是:任务的任务优先级值越高,任务获得 CPU 执行时间的优先级也越高。FreeRTOS 任务调度器根据任务的优先级来决定任务的执行顺序。

void vTaskPrioritySet(TaskHandle_t xTask,
                      UBaseType_t uxNewPriority)

2.2 函数 vTaskPrioritySet()

        此函数用于改变某一个任务的任务优先级,要使用此函数的话宏 INCLUDE_vTaskPrioritySet 应该定义为 1,函数原型如下:

void vTaskPrioritySet( TaskHandle_t xTask, 
                       UBaseType_t uxNewPriority )

参数:

xTask:                                                要查找的任务的任务句柄

uxNewPriority:                                   任务要使用的新的优先级,可以是 0~configMAX_PRIORITIES - 1

返回值:                                               无

2.3 函数 uxTaskGetSystemState()

        此函数用于获取系统中所有任务的任务状态每个任务的状态信息保存在一个 TaskStatus_t 类型的结构体里面,这个结构体里面包含了任务的任务句柄、任务名字、堆栈、优先级等信息,要使用此函数的话宏 configUSE_TRACE_FACILITY 应该定义为 1,函数原型如下:

UBaseType_t uxTaskGetSystemState(TaskStatus_t* const  pxTaskStatusArray,
                                 const UBaseType_t    uxArraySize,
                                 uint32_t* const      pulTotalRunTime)

参数:

pxTaskStatusArray:        指向 TaskStatus_t 结构体类型的数组首地址,每个任务至少需要一个 TaskStatus_t 结构体,任务的数量可以使用函数 uxTaskGetNumberOfTasks() 来获取。结构体 TaskStatus_t 在文件 task.h 中有如下定义:

typedef struct xTASK_STATUS 
{ 
    TaskHandle_t xHandle; //任务句柄 
    const char * pcTaskName; //任务名字
    UBaseType_t xTaskNumber; //任务编号 
    eTaskState eCurrentState; //当前任务壮态,eTaskState 是一个枚举类型 
    UBaseType_t uxCurrentPriority; //任务当前的优先级 
    UBaseType_t uxBasePriority; //任务基础优先级 
    uint32_t ulRunTimeCounter;//任务运行的总时间 
    StackType_t * pxStackBase; //堆栈基地址 
    uint16_t usStackHighWaterMark; //从任务创建以来任务堆栈剩余的最小大小,此 
    //值如果太小的话说明堆栈有溢出的风险。 
} TaskStatus_t; 

uxArraySize:                        保存任务状态数组的数组的大小

pulTotalRunTime:                如果 configGENERATE_RUN_TIME_STATS 为 1 的话此参数用来保存系统总的运行时间

返回值:

统计到的任务状态的个数,也就是填写到数组 pxTaskStatusArray 中的个数,此值应该等于函数 uxTaskGetNumberOfTasks() 的返回值。如果参数 uxArraySize 太小的话返回值可能为 0.

2.4 函数 vTaskGetInfo()

        此函数是用来获取某个任务的任务信息。但是获取的是指定的单个任务的任务状态,任务的状态信息填充到参数 pxTaskStatus 中,这个参数也是 TaskStatus_t 类型的。要使用此函数的话宏 configUSE_TRACE_FACILITY 要定义为 1,函数原型如下:

void vTaskGetInfo( TaskHandle_t xTask, 
                   TaskStatus_t * pxTaskStatus, 
                   BaseType_t xGetFreeStackSpace, 
                   eTaskState eState ) 

参数:

xTask:                                        要查找的任务的任务句柄

pxTaskStatus:                            指向类型为 TaskStatus_t 的结构体变量

xGetFreeStackSpace:               在结构体 TaskStatus_t 中有个字段 usStackHighWaterMark 来保存自任务运行以来任务堆栈剩余的历史最小大小,这个值越小说明越接近堆栈溢出,但是计算这个值需要花费一点时间,所以我们可以通过将 xGetFreeStackSpace 设置为 pdFALSE 来跳过这个步骤,当设置为 pdTRUE 的时候就会检查堆栈的历史剩余最小值。

eState:                                        结构体 TaskStatus_t 中有个字段 eCurrentState 用来保存任务运行状态,这个字段是 eTaskState 类型的,这个是枚举类型,在 task.h 中如下定义:

typedef enum 
{ 
    eRunning = 0, //运行壮态 
    eReady, //就绪态 
    eBlocked, //阻塞态 
    eSuspended, //挂起态
    eDeleted, //任务被删除 
    eInvalid //无效 
} eTaskState;

        获取任务状态信息会耗费不少时间,所以为了加快函数 vTaskGetInfo() 的执行速度结构体 TaskStatus_t 中的字段 eCurrentState 就可以由用户直接赋值,参数 eState 就是要赋的值。如果不在乎这点时间,那么可以将 eState 设置为 eInvalid ,这样任务的状态信息就由函数 vTaskGetInfo() 去想办法获取。

返回值:                                 无

2.5 函数 xTaskGetApplicationTaskTag()

        此函数用于获取任务的 Tag(标签)值,任务控制块中有个成员变量 pxTaskTag 来保存任务的标签值。标签的功能由用户自行决定,此函数就是用来获取这个标签值的,FreeRTOS 系统内核是不会使用到这个标签的。要使用此函数的话宏 configUSE_APPLICATION_TASK_TAG 必须为 1,函数原型如下:

TaskHookFunction_t xTaskGetApplicationTaskTag(TaskHandle_t     xTask)

参数:

xTask:                                        要获取标签值的任务对应的任务句柄,如果为 NULL 的话就获取当前正在运行的任务标签值

返回值:                                       任务的标签值

2.6 函数 xTaskGetCurrentTaskHandle()

        此函数用来获取当前任务的任务句柄,其实获取到的就是任务控制块,任务句柄其实就是任务控制。如果要使用此函数的话宏 INCLUDE_xTaskGetCurrentTaskHandle 应该为 1,函数原型如下:

TaskHandle_t xTaskGetCurrentTaskHandle(void)

参数:                                无

返回值:                             当前任务的任务句柄

2.7 函数 xTaskGetHandle()

        此函数根据任务名字获取任务的任务句柄,在使用函数 xTaskCreate() 或 xTaskCreateStatic() 创建任务的时候都会给任务分配一个任务名,函数 xTaskGetHandle() 就是使用这个任务名字来查询对应的任务句柄的。要使用此函数的话宏 INCLUDE_xTaskGetHandle 应该设置为 1,此函数原型如下:

TaskHandle_t xTaskGetHandle(const char*    pcNameToQuery)

参数:

pcNameToQuery:                        任务名,C语言字符串

返回值:

NULL:                                          没有任务名 pcNameToQuery 所对应的任务

其他值:                                        任务名 pcNameToQuery 所对应的任务句柄

2.8 函数 xTaskGetIdleTaskHandle()

        此函数用来返回空闲任务的任务句柄,要使用此函数的话宏 INCLUDE_xTaskGetIdleTaskHandle 必须为 1 ,函数原型如下:

TaskHandle_t xTaskGetIdleTaskHandle(void)

参数:                无

返回值:            空闲任务的任务句柄

2.9 函数 uxTaskGetStackHighWaterMark()

        每个任务都有自己的堆栈,堆栈的总大小在创建任务的时候就确定了,此函数用于检查任务从创建好到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大!FreeRTOS 把这个历史剩余最小值叫做 “高水位线”。此函数相对来说会多耗费一点时间,所以在代码调试阶段可以使用,产品发布的时候最好不要使用。要使用此函数的话宏 INCLUDE_uxTaskGetStackHighWaterMark 必须为 1,此函数原型如下:

UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t  xTask)

参数:

xTask:                        要查询的任务的任务句柄,当这个参数为 NULL 的话说明查询自身任务(即调用函数 uxTaskGetStackHighWaterMark() 的任务)的 “高水位线”

返回值:                       任务堆栈的 “高水位线” 值,也就是堆栈的历史剩余最小值。

2.10 函数 eTaskGetState()

        此函数用于查询某个任务的运行状态,比如:运行态、阻塞态、挂起态、就绪态等,返回值是个枚举类型。要使用此函数的话宏 INCLUDE_eTaskGetState 必须为 1,函数原型如下:

eTaskState  eTaskGetState(TaskHandle_t   xTask)

参数:

xTask:                                要查询的任务的任务句柄

返回值:                              返回值为 eTaskState 类型,这是个枚举类型,在文件 task.h 中有定义

2.11 函数 pcTaskGetName()

        此函数用以根据某个任务的任务句柄来查询这个任务对应的任务名,函数原型如下:

char *pcTaskGetName(TaskHandle_t    xTaskToQuery)

参数:

xTaskToQuery:                        要查询的任务的任务句柄,此参数为 NULL 的话表示查询自身任务(调用函数 pcTaskGetName())的任务名字

返回值:                                    返回任务所对应的任务名

2.12 函数 xTaskGetTickCount()

        此函数用以查询任务调度器从启动到现在时间计数器 xTickCount 的值xTickCount 是系统的时钟节拍值,并不是真实的时间值。每个滴答定时器中断 xTickCount 就会加 1 ,一秒钟滴答定时器中断多少次取决于宏 configTICK_RATE_HZ 。理论上 xTickCount 存在溢出的问题,但是这个溢出对于 FreeRTOS 的内核没有影响,但是如果用户的应用程序有使用到的话就要考虑溢出了。什么时候溢出取决于宏 configUSE_16_BIT_TICKS,当此宏为 1 的时候 xTickCount 就是个 16 位的变量,当为 0 的时候就是个 32 位的变量。函数原型如下:

TickType_t      xTaskGetTickCount(void)

参数:                        无

返回值:                     时间计时器 xTickCount 的值

2.13 函数 xTaskGetTickCountFromISR()

        此函数是 xTaskGetTickCount() 的中断级版本,用于在中断服务函数中获取时间计数器 xTickCount 的值,函数原型如下:

TickType_t   xTaskGetTickCountFromISR(void)

参数:                                 无

返回值:                             时间计数器 xTickCount 的值

2.14 函数 xTaskGetSchedulerState()

        此函数用于获取 FreeRTOS 的任务调度器运行情况:运行?关闭?还是挂起!要使用此函数的话宏 INCLUDE_xTaskGetSchedulerState 必须为 1,此函数原型如下:

BaseType_t    xTaskGetSchedulerState(void)

参数:                                无

返回值:                            

taskSCHEDULER_NOT_STARTED:                        调度器未启动,调度器的启动是通过函数 vTaskStartScheduler() 来完成,所以在函数 vTaskStartScheduler() 未调用之前调用函数 xTaskGetSchedulerState() 的话就会返回此值。

taskSCHEDULER_RUNNING:                                   调度器正在运行

taskSCHEDULER_SUSPENDED:                           调度器挂起

2.15 函数 uxTaskGetNumberOfTasks()

        此函数用于查询系统当前存在的任务数量,函数原型如下:

UBaseType_t    uxTaskGetNumberOfTasks(void)

参数:                                无

返回值:                            当前系统中存在的任务数量,此值 = 挂起态的任务 + 阻塞态的任务 + 就绪态的任务 + 空闲任务 + 运行态的任务。

2.16 函数 vTaskList()

        此函数会创建一个表格来描述每个任务的详细信息

表中的信息如下:

Name:创建任务的时候给任务分配的名字。

State:任务的状态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态。

Priority:任务优先级。

Stack:任务堆栈的 “高水位线” ,就是堆栈历史最小剩余大小。

Num:任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。

函数原型如下:

void vTaskList(char *pcWriteBuffer)

参数:                                

pcWriteBuffer:                        保存任务状态信息表的存储区。存储区要足够大来保存任务状态信息表。

返回值:                                    无

2.17 函数 vTaskGetRunTimeStats()

        FreeRTOS 可以通过相关的配置来统计任务的运行时间信息,任务的运行时间信息提供了每个任务获取到 CPU 使用权总的时间。函数 vTaskGetRunTimeState() 会将统计到的信息填充到一个表里面,表里面提供了每个任务的运行时间和其所占总时间的百分比

        函数 vTaskGetRunTimeStats() 是一个非常实用的函数,要使用此函数的话宏 configGENERATE_RUN_TIME_STATS 和 configUSE_STATS_FORMATTING_FUNCTIONS 必须都为 1。如果宏 configGENERATE_RUN_TIME_STATS 为 1 的话还需要实现一个几个宏定义:

        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏用来初始化一个外设来提供时间统计功能所需的时基,一般是定时器/计数器。这个时基的分辨率一定要比 FreeRTOS 的系统时钟高,一般这个时基的时钟精度比系统时钟的高 10~20 倍就可以了。

        portGET_RUN_TIME_COUNTER_VALUE() 或者 portALT_GET_RUN_TIME_COUNTER_VALUE(Time),这两个宏实现其中一个就行了,这两个宏用于提供当前的时基的时间值。

        函数原型如下:

void vTaskGetRunTimeStats(char *pcWriteBuffer)

参数:

pcWriteBuffer:                        保存任务时间信息的存储区。存储区要足够大来保存任务时间信息。

返回值:                                     无

2.18 函数 vTaskSetApplicationTaskTag()

        此函数是为高级用户准备的,此函数用于设置某个任务的标签值,这个标签值的具体函数和用法由用户自行决定,FreeRTOS 内核不会使用这个标签值,如果要使用的话宏 configUSE_APPLICATION_TASK_TAG 必须为 1 ,函数原型如下:

void vTaskSetApplicationTaskTag(TaskHandle_t        xTask,
                                TaskHookFunction_t  pxHookFunction)

参数:

xTask:                                  要设置标签值的任务,此值为 NULL 的话表示设置自身任务的标签值

pxHookFunction:                 要设置的标签值,这是一个 TaskHookFunction_t 类型的函数指针,但是可以设置为其他值

返回值:                                 无

2.19 函数 SetThreadLocalStoragePointer()

        此函数用于设置线程本地存储指针的值,每个任务都有它自己的指针数组来作为线程本地存储,使用这些线程本地存储可以用来在任务控制块中存储一些应用信息,这些信息只属于任务自己。线程本地存储指针数组的大小由宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 来决定的。如果要使用此函数的话宏 configNUM_THREAD_LOCAL_STORAGE_ POINTER 不能为 0。宏的具体值是本地存储指针数组的大小,函数原型如下:

void vTaskSetThreadLocalStoragePointer(TaskHandle_t    xTaskToSet,
                                       BaseType_t      xIndex,
                                       void*           pvValue)

参数:

xTaskToSet:                                         要设置线程本地存储指针的任务的任务句柄,如果是 NULL 的话表示设置任 务自身的线程本地存储指针。

xIndex:                                                 要设置的线程本地存储指针数组的索引。

pvValue:                                                 要存储的值。

2.20 函数 GetThreadLocalStoragePointer()

        此函数用于获取线程本地存储指针的值,如果要使用此函数的话宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 不能为 0 ,函数原型如下:

void *pvTaskGetThreadLocalStoragePointer(TaskHandle_t  xTaskToQuery,
                                         BaseType_t    xIndex)

参数:

xTaskToSet:                要获取的线程本地存储指针的任务句柄,如果是 NULL 的话表示获取任务自身的线程本地存储指针。

xIndex:                        要获取的线程本地存储指针数组的索引。

返回值:                        获取到的线程本地存储指针的值。

3. 任务状态查询 API 实验

        本实验主要来学习函数 uxTaskGetSystemState()、vTaskGetInfo()、eTaskGetState() 和 vTaskList() 的使用方法

实验设计:

本实验设计了三个任务:start_task、led0_task 和 query_task,这三个任务的任务功能如下:

start_task:用来创建其他两个任务

led0_task:控制 LED0 灯闪烁,提示系统正在运行

query_task:任务状态和信息查询任务,在此任务中学习使用与任务的状态信息查询有关的 API 函数

同时,实验需要按键 KEY_UP,通过按键 KEY_UP 控制程序的运行步骤。

3.1 实验程序

以下实验程序进行了详细的解释,如有任何问题,欢迎留言!共同进步!!!

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"

//任务优先级
#define START_TASK_PRIO     1
//任务堆栈大小
#define START_STK_SIZE      128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LED0_TASK_PRIO     2
//任务堆栈大小
#define LED0_STK_SIZE      128
//任务句柄
TaskHandle_t Led0Task_Handler;
//任务函数
void led0_task(void *pvParameters);

//任务优先级
#define QUERY_TASK_PRIO     3
//任务堆栈大小
#define QUERY_STK_SIZE      256
//任务句柄
TaskHandle_t QueryTask_Handler;
//任务函数
void query_task(void *pvParameters);

char InfoBuffer[1000];      //保存信息的数组

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);     //设置系统中断优先级分组4
    delay_init(168);
    uart_init(115200);
    LED_Init();
    LCD_Init();
    KEY_Init();
    
    POINT_COLOR=RED;
    LCD_ShowString(30,30,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,50,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,70,200,16,16,"Task Info Query");
    LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,110,200,16,16,"2023/07/02");
    
    //创建开始任务
    xTaskCreate((TaskFunction_t)start_task,             //任务函数
                (const char*   )"start_task",           //任务名称
                (uint16_t      )START_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )START_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&StartTask_Handler);    //任务句柄
    vTaskStartScheduler();      //开启任务调度
}

//开始任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();   //进入临界区
    //创建LED0任务
    xTaskCreate((TaskFunction_t)led0_task,             //任务函数
                (const char*   )"led0_task",           //任务名称
                (uint16_t      )LED0_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )LED0_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&Led0Task_Handler);    //任务句柄
    //创建QUERY任务
    xTaskCreate((TaskFunction_t)query_task,             //任务函数
                (const char*   )"query_task",           //任务名称
                (uint16_t      )QUERY_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )QUERY_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&QueryTask_Handler);    //任务句柄
    vTaskDelete(StartTask_Handler);
    taskEXIT_CRITICAL();    //离开临界区
}

//LED0任务函数
void led0_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500);  //延时500ms,也就是500个时钟节拍
    }
}

//QUERY任务函数
void query_task(void *pvParameters)
{
    u32 TotalRunTime;  //定义变量记录任务总的运行时间
    UBaseType_t ArraySize,x;
    TaskStatus_t *StatusArray; //设置结构体变量
    
    //第一步:函数 uxTaskGetSystemState() 的使用
    
    //此函数用于获取系统中所有任务的任务状态,每个任务的状态信息保存在一个 TaskStatus_t 类型的结构体里面
    //这个结构体里面包含了任务句柄、任务名字、堆栈、优先级等信息,要使用此函数需要将宏 configUSE_TRACE_FACILITY 定义为1
    //UBaseType_t uxTaskGetSystemState(TaskStatus_t* const  pxTaskStatusArray,
                                     //const UBaseType_t    uxArraySize,
                                     //uint32_t* const      pulTotalRunTime)
    //第一个参数:指向 TaskStatus_t 结构体类型的数组首地址,任务的数量可以使用函数 uxTaskGetNumberOfTasks() 来获取
    //第二个参数:保存任务状态数组的数组大小
    //第三个参数:保存系统总的运行时间
    //返回值:统计到的任务状态的个数,也就是填写到数组 pxTaskStatusArray 中的个数,此值应该等于函数 uxTaskGetNumberOfTasks() 的返回值
    //typedef struct xTASK_STATUS 
    //{ 
    //    TaskHandle_t xHandle; //任务句柄 
    //    const char * pcTaskName; //任务名字
    //    UBaseType_t xTaskNumber; //任务编号 
    //    eTaskState eCurrentState; //当前任务壮态,eTaskState 是一个枚举类型 
    //    UBaseType_t uxCurrentPriority; //任务当前的优先级 
    //    UBaseType_t uxBasePriority; //任务基础优先级 
    //    uint32_t ulRunTimeCounter;//任务运行的总时间 
    //    StackType_t * pxStackBase; //堆栈基地址 
    //    uint16_t usStackHighWaterMark; //从任务创建以来任务堆栈剩余的最小大小,此 
    //    //值如果太小的话说明堆栈有溢出的风险。 
    //} TaskStatus_t; 
    printf("/********第一步:函数uxTaskGetSystemState()的使用**********/\r\n");
    ArraySize=uxTaskGetNumberOfTasks();   //获取系统任务数量
    StatusArray=pvPortMalloc(ArraySize*sizeof(TaskStatus_t)); //动态申请内存
    //申请内存的大小等于TaskStatus_t结构体的大小乘以获取到的任务数量
    //每一个任务都需要结构体TaskStatus_t来保存任务的状态信息,所以申请的内存应该能存放所有任务的状态信息
    //pvPortMalloc函数的返回值为 指向分配内存块的指针,当然如果动态内存开辟失败,返回NULL
    if(StatusArray!=NULL) //StatusArray本身是一个指向分配内存块的指针,如果不为空,则表示动态开辟内存成功
    {
        ArraySize=uxTaskGetSystemState((TaskStatus_t* 	)StatusArray, 	//任务信息存储数组
									   (UBaseType_t		)ArraySize, 	//任务信息存储数组大小
								       (uint32_t*		)&TotalRunTime);//保存系统总的运行时间
        //函数uxTaskGetSystemState的返回值就是统计到的任务状态的个数
        printf("TaskName\t\tPriority\t\tTaskNumber\t\t\r\n");
        //其中TaskNum是任务名字;Priority是任务优先级;TaskNumber是任务编号;可看上面注释掉的结构体
        for(x=0;x<ArraySize;x++)
        {
            //通过串口打印出获取到的系统任务的有关信息,比如任务名称、任务优先级和任务编号。
            printf("%s\t\t%d\t\t\t%d\t\t\t\r\n",
                    StatusArray[x].pcTaskName,
                    (int)StatusArray[x].uxCurrentPriority,
                    (int)StatusArray[x].xTaskNumber);  // \t是转义字符
        }
    }
    vPortFree(StatusArray); //把动态开辟的内存释放掉,以防止出现内存泄露
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=4)
        delay_ms(10);  //等待 KEY_UP 按键按下
    
    //第二步:函数 vTaskGetInfo() 的使用
    TaskHandle_t TaskHandle;
    TaskStatus_t TaskStatus;
    
    //此函数是用来获取某个任务的任务信息,但是获取的是指定的单个任务的任务状态,任务的状态信息填写到 pxTaskStatus 结构体中
    //void vTaskGetInfo( TaskHandle_t xTask, 
                       //TaskStatus_t * pxTaskStatus, 
                       //BaseType_t xGetFreeStackSpace, 
                       //eTaskState eState ) 
    //第一个参数:要查找任务的任务句柄
    //第二个参数:指向类型为 TaskStatus_t 的结构体变量
    //第三个参数:将 xGetFreeStackSpace 设置为 pdFALSE 来跳过计算任务堆栈剩余的历史最小大小的(usStackHighWaterMark)步骤
    //第四个参数:结构体 TaskStatus_t 中有个字段 eCurrentState 用来保存任务运行状态,这个字段是 eTaskState 类型的,是枚举类型
    //typedef enum 
    //{ 
    //    eRunning = 0, //运行壮态 
    //    eReady, //就绪态 
    //    eBlocked, //阻塞态 
    //    eSuspended, //挂起态
    //    eDeleted, //任务被删除 
    //    eInvalid //无效 
    //} eTaskState;
    
    printf("/************第二步:函数vTaskGetInfo()的使用**************/\r\n");
    TaskHandle=xTaskGetHandle("led0_task"); //根据任务名获取任务句柄  函数的返回值为任务名所对应的任务句柄
    //获取LED0_Task的任务信息
	vTaskGetInfo((TaskHandle_t	)TaskHandle, 		//任务句柄
				 (TaskStatus_t*	)&TaskStatus, 		//任务信息结构体
				 (BaseType_t	)pdTRUE,			//允许统计任务堆栈历史最小剩余大小
			     (eTaskState	)eInvalid);			//函数自己获取任务运行壮态
    //通过串口打印出指定任务的有关信息
    //typedef struct xTASK_STATUS 
    //{ 
    //    TaskHandle_t xHandle; //任务句柄 
    //    const char * pcTaskName; //任务名字
    //    UBaseType_t xTaskNumber; //任务编号 
    //    eTaskState eCurrentState; //当前任务壮态,eTaskState 是一个枚举类型 
    //    UBaseType_t uxCurrentPriority; //任务当前的优先级 
    //    UBaseType_t uxBasePriority; //任务基础优先级 
    //    uint32_t ulRunTimeCounter;//任务运行的总时间 
    //    StackType_t * pxStackBase; //堆栈基地址 
    //    uint16_t usStackHighWaterMark; //从任务创建以来任务堆栈剩余的最小大小,此 
    //    //值如果太小的话说明堆栈有溢出的风险。 
    //} TaskStatus_t; 
    printf("任务名:                %s\r\n",TaskStatus.pcTaskName);
    printf("任务编号:              %d\r\n",(int)TaskStatus.xTaskNumber);
    printf("任务状态:              %d\r\n",TaskStatus.eCurrentState);
    printf("任务当前优先级:        %d\r\n",(int)TaskStatus.uxCurrentPriority);
    printf("任务基优先级:          %d\r\n",(int)TaskStatus.uxBasePriority);
    printf("任务堆栈基地址:        %#x\r\n",(int)TaskStatus.pxStackBase);
    printf("任务堆栈历史剩余最小值:%d\r\n",TaskStatus.usStackHighWaterMark);
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY_UP按键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=4)
        delay_ms(10);
    
    //第三步:函数eTaskGetState()的使用
    
    //此函数用于查询某个任务的运行状态,比如说:运行态、阻塞态、挂起态、就绪态等,返回值是个枚举类型
    //参数:xTask   要查询的任务的任务句柄
    //返回值:      返回值为 eTaskState 类型,这是个枚举类型
    //typedef enum 
    //{ 
    //    eRunning = 0, //运行壮态 
    //    eReady, //就绪态 
    //    eBlocked, //阻塞态 
    //    eSuspended, //挂起态
    //    eDeleted, //任务被删除 
    //    eInvalid //无效 
    //} eTaskState;
    eTaskState TaskState; //设置结构体变量
    char TaskInfo[10]; //设置数组,长度为10
    printf("/***********第三步:函数eTaskGetState()的使用*************/\r\n");
    TaskHandle=xTaskGetHandle("query_task"); //根据任务名获取任务任务句柄 返回值为任务名所对应的任务句柄
    //使用xTaskGetHandle需要将宏INCLUDE_xTaskGetHandle设置为1
    TaskState=eTaskGetState(TaskHandle);   获取query_task任务的任务状态
    memset(TaskInfo,0,10);
    //memset是一个标准的C库函数,用于设置内存块的值,函数原型为 void *memset(void *s,int c,size_t n);
    //第一个参数:指向要设置值的内存块的指针   第二个参数:要设置的值,通常是一个无符号字符或整数形式的字节
    //第三个参数:要设置的字节数   (返回值是指向 s 的指针,也就是指向存储数组首元素的指针)
    //所以 memset(TaskInfo,0,10); 的作用就是将TaskInfo数组初始化
    switch((int)TaskState)//这里注意:之所以switch的参数是(int)TaskState,是因为这个结构体是枚举类型的,
    {//在C语言的语法中,枚举类型的成员变量默认是从0开始的,每个成员变量对应于一个数字,打印结果为0  1  2 ;默认枚举常量是有值存在的;
        case 0:
            sprintf(TaskInfo,"Running");//sprintf的用法是将字符串 Running 打印存储到数组 TaskInfo 中
            break;
        case 1:
            sprintf(TaskInfo,"Ready");
            break;
        case 2:
            sprintf(TaskInfo,"Suspend");
            break;
        case 3:
            sprintf(TaskInfo,"Delete");
            break;
        case 4:
            sprintf(TaskInfo,"Invalid");
            break;
    }
    printf("任务状态值:%d,对应的状态为:%s\r\n",TaskState,TaskInfo);
    printf("/**************************结束**************************/\r\n");
	printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=4)
        delay_ms(10);
    
    //第四步:函数 vTaskList() 的使用
    
    //此函数会创建一个表格来描述每个任务的详细信息
    //表中信息如下:
    //Name:创建任务的时候给任务分配的名字。
    //State:任务的状态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态。
    //Priority:任务优先级。
    //Stack:任务堆栈的 “高水位线” ,就是堆栈历史最小剩余大小。
    //Num:任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。
    //参数:pcWriteBuffer:保存任务状态信息表的存储区。存储区要足够大来保存任务状态信息表
    //无返回值。
    printf("/*************第四步:函数vTaskList()的使用*************/\r\n");
    vTaskList(InfoBuffer);    //创建一个表格来获取所有任务的信息,信息存储在 InfoBuffer 中
    //configUSE_TRACE_FACILITY 设置为 1,启用任务跟踪功能
    //configUSE_STATS_FORMATTING_FUNCTIONS 启用更高级的任务跟踪和状态输出函数。
    //configUSE_APPLICATION_TASK_TAG 设置为 1,启用任务标签功能,可以为任务指定标签,从而在任务状态输出中显示任务标签。
    //configUSE_TASK_LIST 设置为 1,启用 vTaskList 函数。
    //总之:要使用vTaskList需要将上述四个宏其中之一设置为 1,根据实际情况选择合适的宏设置
    printf("%s\r\n",InfoBuffer);    //通过串口打印所有任务的信息
    printf("/**************************结束**************************/\r\n");
    while(1)
    {
        LED1=!LED1;
        vTaskDelay(1000);     //延时1s,也就是1000个时钟节拍
    }
}


3.2 程序运行结果分析

第一步:

        第一步是学习 uxTaskGetSystemState() 函数的使用,通过此函数获取到系统中当前所有任务的信息,并且通过串口输出其中的一部分信息,这里我们输出任务名、任务的优先级和任务的编号。

        可以看出空闲任务的任务优先级是最低的,也就是 0 ;定时器服务的优先级是最高的,也就是 31;而且任务的优先级和任务的编号是不同的,优先级是用户自行设定的,而编号是根据任务 创建的先后顺序来自动分配的。

第二步:

        第二步是通过函数 vTaskGetInfo() 来获取任务名为 "led0_task" 的任务信息,并通过串口输出。

        通过 vTaskGetInfo() 函数可以看出任务名为 led0_task 的任务编号为 4,任务状态为2,任务状态是由枚举常量定义的,所以初始化的时候默认就是一个常量,任务当前的优先级为 2 ,任务基优先级为 2,任务堆栈基地址为 0x200001328,任务堆栈历史剩余最小值为 107;

第三步:

        第三步通过函数 eTaskGetState() 来获取任务 query_task 的运行状态,串口助手显示。

        可以看出任务状态值为 0,任务状态值是通过枚举结构体来定义的,每个枚举常量在初始化的时候就对应一个常量。

第四步:

        第四步通过函数 vTaskList() 获取系统中当前所有任务的详细信息,并且将这些信息按表格的形式组织在一起存放在用户提供的缓冲区中,例程中将这些信息放到了缓冲区 InfoBuffer 中。最后通过串口输出缓冲区 InfoBuffer 中的信息。

4. 任务运行时间信息统计实验

        FreeRTOS 可以通过函数 vTaskGetRunTimeState() 来统计每个任务使用 CPU 的时间,以及所使用的时间占总时间的比例。在调试代码的时候我们可以根据这个时间使用值来分析哪个任务的 CPU 占用率高,然后合理的分配或优化任务。本实验我们来学习 FreeRTOS 的任务运行时间状态统计功能。

        FreeRTOS 可以通过相关的配置来统计任务的运行时间信息,任务的运行时间信息提供了每个任务获取到 CPU 使用权总的时间。函数 vTaskGetRunTimeState() 会将统计到的信息填充到一个表里面,表里面提供了每个任务的运行时间和其所占总时间的百分比

        函数 vTaskGetRunTimeStats() 是一个非常实用的函数,要使用此函数的话宏 configGENERATE_RUN_TIME_STATS 和 configUSE_STATS_FORMATTING_FUNCTIONS 必须都为 1。如果宏 configGENERATE_RUN_TIME_STATS 为 1 的话还需要实现一个几个宏定义:

        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏用来初始化一个外设来提供时间统计功能所需的时基,一般是定时器/计数器。这个时基的分辨率一定要比 FreeRTOS 的系统时钟高,一般这个时基的时钟精度比系统时钟的高 10~20 倍就可以了。

        portGET_RUN_TIME_COUNTER_VALUE() 或者 portALT_GET_RUN_TIME_COUNTER_VALUE(Time),这两个宏实现其中一个就行了,这两个宏用于提供当前的时基的时间值。

4.1 相关宏设置

        根据上面的学习,要调用函数 vTaskGetRunTimeState() 宏 configGENERATE_RUN_TIME_STATS 必须为 1,还需要再定义其他两个宏:

portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():配置一个高精度定时器/计数器提供时基。

portGET_RUN_TIME_COUNTER_VALUE():读取时基的时间值。

//这三个宏在 FreeRTOSConfig.h 中定义,如下

#define configGENERATE_RUN_TIME_STATS    1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    ConfigureTimeForRunTimeStats()
#define portGET_RUN_TIME_COUNTER_VALUE()            FreeRTOSRunTimeTicks

        其中函数 ConfigureTimeForRunTimeStats() 变量 FreeRTOSRunTimeTicks 在 Timer.c 里面定义,如下:

//FreeRTOS 时间统计所用的节拍计数器 
volatile unsigned long long FreeRTOSRunTimeTicks; 
//unsigned long long表示64位
//volatile unsigned long long修饰变量FreeRTOSRunTimeTicks
//此时会告诉编译器每次访问 FreeRTOSRunTimeTicks 变量时都应该从内存中读取最新的值。(也就是说这个值随时都可能被修改)
//这样,即使 FreeRTOSRunTimeTicks 变量在其他地方被更改,我们仍可以在循环中打印出最新的值


//初始化 TIM3 使其为 FreeRTOS 的时间统计提供时基 
void ConfigureTimeForRunTimeStats(void) 
{ 
    //定时器 3 初始化,定时器时钟为 84M,分频系数为 84-1,所以定时器 3 的频率 
    //为 84M/84=1M,自动重装载为 50-1,那么定时器周期就是 50us 
    FreeRTOSRunTimeTicks=0; 
    TIM3_Int_Init(50-1,84-1); //初始化 TIM3 
}

        函数 ConfigureTimeForRunTimeStats() 其实就是初始化定时器,因为时间统计功能需要用户提供一个高精度的时钟,这里使用定时器 3 在介绍函数 vTaskGetRunTimeStats() 时说过,进行时间统计时的这个时钟的精度要比 FreeRTOS 的系统时钟高,大约10~20倍即可FreeRTOS 系统时钟我们配置的是 1000HZ,周期 1ms,这里我们给定时器 3 的中断频率配置为 20KHZ,周期为 50us,刚好是系统时钟频率的 20 倍。

这里解释一下,为什么 FreeRTOS 系统我们配置的是 1000HZ ,周期就是 1ms?又为什么我们给定时器 3 的中断频率配置为 20KHZ,周期就是 50 us?

中断周期 = (1 / 时钟节拍频率) * 1000 ms

首先,上述的公式的知道,给定频率,怎么计算中断的周期。

FreeRTOS:时钟节拍频率 1000HZ,1/1000=0.001;0.001*1000=1ms;

Timer 3:时钟节拍频率为 20000HZ,1/20000=0.00005;0.00005*1000=0.05ms=50us;1ms=1000us

//定时器 3 初始化函数如下
//通用定时器 3 中断初始化 
//arr:自动重装值。 
//psc:时钟预分频数 
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us. 
//Ft=定时器工作频率,单位:Mhz 
//这里使用的是定时器 3! 
void TIM3_Int_Init(u16 arr,u16 psc) 
{ 
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; 
    NVIC_InitTypeDef NVIC_InitStructure; 
 
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能 TIM3 时钟 
 
    TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值 
    TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频 
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 
    TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
 
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //初始化 TIM3 
 
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器 3 更新中断 
    TIM_Cmd(TIM3,ENABLE); //使能定时器 3 
    NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器 3 中断                              
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级 1 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级 0 
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; 
    NVIC_Init(&NVIC_InitStructure); 
} 
 
//定时器 3 中断服务函数 
void TIM3_IRQHandler(void) 
{ 
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断 
    { 
        FreeRTOSRunTimeTicks++; //运行时间统计时基计数器加一 
    } 
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位 
}

其中,FreeRTOSRunTimeTicks 就是一个全局变量,用来为时间统计功能提供时间,每次进入中断服务函数,该变量++;

4.2 实验程序

实验目的:

        学习使用 FreeRTOS 运行时间状态统计函数 vTaskGetRunTimeStats() 的使用。

实验设计:

        本实验设置四个任务:start_task、task1_task、task2_task 和 RunTimeStats_task,这四个任务的任务功能如下:

        start_task:用来创建其他 3 个任务。

        task1_task:应用任务 1 ,控制 LED0 灯闪烁,并且刷新 LCD 屏幕上指定区域的颜色。

        task2_task:应用任务 2 ,控制 LED1 灯闪烁,并且刷新 LCD 屏幕上指定区域的颜色。

        RunTimeStats_task:获取按键值,当 KEY_UP 按键按下以后就调用函数 vTaskGetRunTimeStats() 获取任务的运行时间信息,并且将其通过串口输出到串口调试助手上。

        实验中需要一个按键 KEY_UP,用来获取系统中任务运行时间信息。

4.2.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "Timer.h"

//任务优先级
#define START_TASK_PRIO     1
//任务堆栈大小
#define START_STK_SIZE      128 
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO     2
//任务堆栈大小
#define TASK1_STK_SIZE      128 
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define TASK2_TASK_PRIO     3
//任务堆栈大小
#define TASK2_STK_SIZE      128 
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);

//任务优先级
#define RUNTIMESTATS_TASK_PRIO     4
//任务堆栈大小
#define RUNTIMESTATS_STK_SIZE      128 
//任务句柄
TaskHandle_t RunTimeStats_Handler;
//任务函数
void RunTimeStats_task(void *pvParameters);

char RunTimeInfo[400];              //定义一个全局变量保存的数组,用来保存任务运行时间等信息

//LCD刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE,  BRED,
                       GRED,  GBLUE, RED,   MAGENTA,
                       GREEN, CYAN,  YELLOW,BROWN,
                       BRRED, GRAY };

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    delay_init(168);
    uart_init(115200);
    LED_Init();
    LCD_Init();
    KEY_Init();
    
    POINT_COLOR=RED;
    LCD_ShowString(30,30,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,50,200,16,16,"FreeRTOS Examplement");
    LCD_ShowString(30,70,200,16,16,"Get Run Time Stats");
    LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,110,200,16,16,"2023/07/03");
    
    //创建开始任务
    xTaskCreate((TaskFunction_t)start_task,             //任务函数
                (const char*   )"start_task",           //任务名称
                (uint16_t      )START_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )START_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&StartTask_Handler);    //任务句柄
    vTaskStartScheduler();     //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();    //进入临界区
    //创建Task1任务
    xTaskCreate((TaskFunction_t)task1_task,             //任务函数
                (const char*   )"task1_task",           //任务名称
                (uint16_t      )TASK1_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )TASK1_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&Task1Task_Handler);    //任务句柄
    //创建Task2任务
    xTaskCreate((TaskFunction_t)task2_task,             //任务函数
                (const char*   )"task2_task",           //任务名称
                (uint16_t      )TASK2_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )TASK2_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&Task2Task_Handler);    //任务句柄
    //创建RunTimeStats任务
    xTaskCreate((TaskFunction_t)RunTimeStats_task,             //任务函数
                (const char*   )"RunTimeStats_task",           //任务名称
                (uint16_t      )RUNTIMESTATS_STK_SIZE,         //任务堆栈大小
                (void*         )NULL,                   //传递给任务函数的参数
                (UBaseType_t   )RUNTIMESTATS_TASK_PRIO,        //任务优先级
                (TaskHandle_t* )&RunTimeStats_Handler);    //任务句柄
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();     //离开临界区
}

//任务1任务函数
void task1_task(void *pvParameters)
{
    u8 task1_num=0;
    
    POINT_COLOR=BLACK;
    
    LCD_DrawRectangle(5,110,115,314);   //画一个矩形  参数分别为(x1,y1),(x2,y2),分别为矩形的对角坐标
    LCD_DrawLine(5,130,115,130);    //画线   参数分别为(x1,y1),(x2,y2),分别为线的起点坐标和终点坐标
    POINT_COLOR=BLUE;
    LCD_ShowString(6,111,110,16,16,"Task1 Run:000"); //对应参数分别为起点坐标,(6,111)正好对应所画矩形的下一行,区域大小110*16
    while(1)
    {
        task1_num++; //任务 1 执行一次task1_num++,加到255的时候就会清零,因为task1_num变量定义就是unsigned char类型的,2^8-1
        LED0=!LED0;
        LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]);  //填充区域  留心这种区域的填充方法 task1_num%14
        LCD_ShowxNum(86,111,task1_num,3,16,0x80);  //显示任务运行次数 
        //LCD_ShowString(6,111,110,16,16,"Task1 Run:000"); 任务显示是要显示在 Task1 Run 之后的,LCD_ShowString 的起始坐标为(6,111)
        //Task1 Run:总共10个字符 ,8*10=80字节,80+6=86,对应于LCD_ShowxNum 坐标(86,111)
        vTaskDelay(1000);       //延时1s,也就是1000个时钟节拍
    }
}

//任务2任务函数
void task2_task(void *pvParameters)
{
    u8 task2_num=0;
    
    POINT_COLOR=BLACK;
    
    LCD_DrawRectangle(125,110,234,314);  //画一个矩形
    LCD_DrawLine(125,130,234,130);  //画线
    POINT_COLOR=BLUE;
    LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
    while(1)
    {
        task2_num++; //任务 2 执行一次task2_num++,加到255的时候就会清零,因为task2_num变量定义就是unsigned char类型的,2^8-1
        LED1=!LED1;
        LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务运行次数
        LCD_Fill(126,131,233,313,lcd_discolor[task2_num%14]); //填充区域
        vTaskDelay(1000);   //延时1s,也就是1000个时钟节拍
    }
}

//RunTimeStats任务
void RunTimeStats_task(void *pvParameters)
{
    u8 key=0;
    while(1)
    {
        key=KEY_Scan(0);
        if(key==4) //KEY_UP按键按下
        {
            memset(RunTimeInfo,0,400);   //信息缓存区清零
            //memset为C语言的内存设置函数,表示将数组RunTimeInfo的400长的字节设置为0,变相的表示将信息缓存区初始化清零
            vTaskGetRunTimeStats(RunTimeInfo); //调用函数获取任务运行时间,函数的参数是一个指向字符缓冲区的指针,任务的运行时间状态信息将被写入到该缓冲区中
            //宏 configGENERATE_RUN_TIME_STATS 必须为 1
            //宏 configUSE_TRACE_FACILITY 必须为 1 
            printf("任务名\t\t\t运行时间\t运行所占百分比\r\n");
            printf("%s\r\n",RunTimeInfo);
        }
        vTaskDelay(10);         //延时10ms,也就是1000个时钟节拍
    }
}


4.2.2 Timer.c

#include "sys.h"
#include "LED.h"
#include "Timer.h"
#include "usart.h"

//FreeRTOS 时间统计所用的节拍计数器
volatile unsigned long long FreeRTOSRunTimeTicks;

//初始化TIM3使其为FreeRTOS的时间统计提供时基
void ConfigureTimeForRunTimeStats(void)
{
    FreeRTOSRunTimeTicks=0;
    TIM3_Int_Init(50-1,84-1); //定时器3初始化
    //定时器3初始化,定时器时钟为84M,分频系数为84-1,所以定时器3的频率为84M/84=1M
    //自动重装载值为50-1,那么定时器的周期就是50us
}

//通用定时器 3 设置
//AutomaticArray:自动重装载值
//PresclarSendCount:时钟预分频数
//定时器溢出时间计算:Tout=((AutomaticArray+1)*(PresclarSendCount+1))/Ft us
//Ft:定时器工作频率,单位 MHz
void TIM3_Int_Init(u16 AutomaticArray,u16 PresclarSendCount)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  //使能TIM3时钟
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseInitStructure.TIM_Period=AutomaticArray;
    TIM_TimeBaseInitStructure.TIM_Prescaler=PresclarSendCount;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
    
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);  //允许定时器3更新中断
    TIM_Cmd(TIM3,ENABLE);  //使能定时器3
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00;   //子优先级
    NVIC_Init(&NVIC_InitStructure);
}

//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
    {
        FreeRTOSRunTimeTicks++;
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}


4.2.3 Timer.

#ifndef _TIMER__H_
#define _TIMER__H_
#include "sys.h"

extern volatile unsigned long long FreeRTOSRunTimeTicks;

void ConfigureTimeForRunTimeStats(void);
void TIM3_Int_Init(u16 AutomaticArray,u16 PresclarSendCount);
void TIM3_IRQHandler(void);

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/713130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI 对“校园WiFi免认证的”的认知

校园WiFi免认证 很抱歉&#xff0c;我不能提供关于绕过校园WiFi认证的信息。绕过校园WiFi认证是违反校园网络使用规定的行为&#xff0c;可能会导致违纪处分或其他法律后果。建议你遵守校园网络使用规定&#xff0c;合法合规地使用校园WiFi。如果你有任何关于校园WiFi认证的问…

SpringBoot2+Vue2实战(九)集成Echarts

Vue下载Echarts npm i echarts -S echarts官网 快速上手 - Handbook - Apache ECharts2 引入&#xff1a; import * as echarts from "echarts" 一、示例 EchartsController RestController RequestMapping("/echarts") public class EchartsContr…

异常处理~

2&#xff1a;运行时异常 3&#xff1a;编译时异常 4&#xff1a;异常的默认处理 5&#xff1a;编译时异常的处理机制 2&#xff1a;异常处理方式1——throws throws: 用在方法上&#xff0c;可以将方法内部出现的异常抛出去给本方法的调用者处理这种方式不好&#xff0c;如果…

在IDEA中通过maven打包war包

在pom文件中加入这一句 <packaging>war</packaging> 然后打包就好了 war包文件目录

途乐证券|A股半年行情收官 北向净买入超1800亿元

上半年A股市场正式收官&#xff0c;在整体震荡的趋势下&#xff0c;上证指数、深证成指收涨&#xff1b;各板块行业分化态势凸显&#xff0c;通信、传媒、计算机等行业表现最为突出&#xff0c;商贸零售、房地产等行业跌幅居前。业内人士表示&#xff0c;上半年市场行情受产业趋…

企业所得税高怎么办?合理节税有哪些方式

企业所得税高怎么办&#xff1f;合理节税有哪些方式 《税筹顾问》专注于园区招商、企业税务筹划&#xff0c;合理合规助力企业节税&#xff01; 如今越来越多的企业深耕于创新与发展&#xff0c;这也是一种迎合市场的需求迫不得以需要进行的转变&#xff0c;很多企业所得税高的…

不看后悔,appium自动化环境完美搭建

桌面版appium提供可视化操作appium主要功能的使用方式&#xff0c;对于初学者非常适用。 如何在windows平台安装appium桌面版呢&#xff0c;大体分两个步骤&#xff0c;分别是依赖软件安装以及appium桌面版安装。以下是对这两个步骤的拆解文字加图片描述。 01、依赖软件安装 …

Google在AI领域的潜力被严重低估了

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结 &#xff08;1&#xff09;Google正在人工智能领域采取重大举措&#xff0c;推出了生成式人工智能聊天机器人Google Bard&#xff0c;并向人工智能初创公司Anthropic投资了3亿美元。 &#xff08;2&#xff09;Goo…

Dockerfile使用指南

Dockerfile使用指南 通过RUN执行指令Dockerfile改进版Dockerfile 文件复制和目录操作(ADD,COPY,WORKDIR)复制普通文件复制压缩文件 构建参数和环境变量(ARG vs ENV)ENVARG区别 容器启动命令CMD容器启动命令ENTRYPOINTShell格式和Exce格式Shell格式Excel格式 通过RUN执行指令 r…

No2.精选前端面试题,享受每天的挑战和学习

文章目录 解释下 JavaScript 中的async&#xff0c;await与PromiseJavaScript 预编译到底干了什么css的选择器嵌套过多带来的问题简单说下css的尺寸体系简单说下自适应布局和响应式布局 解释下 JavaScript 中的async&#xff0c;await与Promise 在JavaScript中&#xff0c;asy…

Ubuntu 编译 OpenCV SDK for Android + Linux

概述 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉库&#xff0c;它提供了很多函数&#xff0c;这些函数非常高效地实现了计算机视觉算法&#xff08;最基本的滤波到高级的物体检测皆有涵盖&#xff09;。   OpenCV 的应用领域…

[2023-07-03]2023博客之星候选--码龄赛道--15年以上

https://bbs.csdn.net/topics/616395535https://bbs.csdn.net/topics/616395535 用户名总原力值当月获得原力值2023年获得原力值2023年高质量博文数75阿酷tony:[博客] [成就]3999345028 博客之星 2023 《码龄赛道 15年以上》第 75 名 啊&#xff0c;75名啊&#xff01;你叫…

WINDBG 查崩溃

前言&#xff1a;windbg大家都很熟悉&#xff0c;它是做windows系统客户端测试的QA人员很应该掌握的定位程序崩溃原因的工具&#xff0c; 网上也有很多资料&#xff0c;但是真正适合QA阅读和实用的资料不多&#xff0c;我把我认为最重要最应该掌握的结合以前的使用经验分享一下…

SPI机制

SPI机制是Service Provider Interface&#xff0c;是服务提供发现机制&#xff0c;用来启用框架扩展和替换组件。比如java.sql.Driver接口&#xff0c;其他不同厂商可以针对同一接口做出不同的实现&#xff0c;MySQL和PostgreSQL都有不同的实现提供给用户&#xff0c;而Java的S…

学生台灯护眼好还是防近视好?专家推荐的学生台灯分享

其实学生台灯如果护眼效果好也是能在一定程度上防近视的&#xff0c;这两种是由联系的并不冲突。我们要知道造成近视的主要原因的是什么&#xff1f;是不是每天长时间高强度的学习&#xff1f;是不是长时间玩电子产品没有合理休息&#xff1f;这些都导致一个结果&#xff0c;那…

为什么使用消息队列?消息队列有什么优点和缺点?常用的MQ 都有什么区别,以及适合哪些场景?

问题&#xff1a; 为什么使用消息队列&#xff1f;消息队列有什么优点和缺点&#xff1f;Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别&#xff0c;以及适合哪些场景&#xff1f; 为什么使用消息队列&#xff1f; 其实就是问问你消息队列都有哪些使用场景&#xff0c;…

计算机专业大学如何自学?常用网站和工具

耗时5小时&#xff0c;第一个B站视频&#xff0c;满足分享欲 计算机专业现状 or 困境&#xff1f;如何自学&#xff1f;常用网站科普&#xff01;_哔哩哔哩_bilibili &#x1f446;发了个视频&#xff0c;结合文章中的链接&#xff0c;保存到自己浏览器收藏夹里就完了 目录 …

亚马逊买家账号防关联怎么弄?

如果你希望你的亚马逊买家账号不被关联到其他账号&#xff0c;你可以采取以下措施来增强账号的安全性和保护个人信息&#xff1a; 1、强密码&#xff1a;使用强密码来保护你的亚马逊账号。确保密码包含字母、数字和特殊字符&#xff0c;并且长度足够长。避免使用与其他网站或账…

Linux day2

在 Linux 系统中&#xff0c;你看到一个文件的属主和属组都是 root&#xff0c;它的权限是 -rwxrwxr--&#xff0c;那么下面的描述中哪个是不正确的&#xff08;&#xff09; 官方解析&#xff1a; 文件类型和文件权限由10个字符组成&#xff1a; 第 1 位表示文件的类型&#x…

黄牛泛滥如何影响亚洲的演唱会和现场娱乐行业

随着新冠大流行的结束&#xff0c;中国的各类线下娱乐活动尤其是演唱会等又逐渐红火了起来&#xff0c;但蓬勃发展的现场娱乐活动却饱受日益严重的黄牛问题的困扰。黄牛是指个人通过使用自动化机器人批量购买现场活动门票&#xff0c;然后以高昂的价格转售的行为&#xff08;黄…