目录
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