参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、FreeRTOS任务相关API函数介绍
1、uxTaskPriorityGet函数
(1)此函数用于获取指定任务的任务优先级,使用该函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_uxTaskPriorityGet置1。
(2)函数接口定义:
UBaseType_t uxTaskPriorityGet //返回任务优先级数值
(
const TaskHandle_t xTask //要查找的任务句柄,NULL代表任务自身
)
2、vTaskPrioritySet函数
(1)此函数用于改变某个任务的任务优先级,使用该函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskPrioritySet为1。
(2)函数接口定义:
void vTaskPrioritySet
(
TaskHandle_t xTask , //要更改优先级的任务的任务句柄
UBaseType_t uxNewPriority //需要设置的任务优先级
)
3、uxTaskGetNumberOfTasks函数
(1)此函数用于获取系统中任务的任务数量。
(2)函数接口定义:
UBaseType_t uxTaskGetNumberOfTasks //返回系统中任务的数量
(
void
)
4、uxTaskGetSystemState函数
(1)此函数用于获取系统中所有任务的任务状态信息,使用该函数前需在FreeRTOSConfig.h文件中将宏configUSE_TRACE_FACILITY置1。
(2)函数接口定义:
UBaseType_t uxTaskGetSystemState //返回获取信息的任务数量
(
//指向TaskStatus_t结构体数组首地址,用于接收所有任务的任务状态信息
TaskStatus_t * const pxTaskStatusArray,
//接收信息的数组的大小
const UBaseType_t uxArraySize,
//系统总运行时间,为NULL则省略总运行时间值
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime
)
(3)TaskStatus_t结构体定义:
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; //任务句柄
const char * pcTaskName; //任务名
UBaseType_t xTaskNumber; //任务编号
eTaskState e CurrentState; //任务状态
UBaseType_t uxCurrentPriority; //任务优先级
UBaseType_t uxBasePriority; //任务原始优先级
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; //任务运行时间
StackType_t * pxStackBase; //任务栈基地址
configSTACK_DEPTH_TYPE usStackHighWaterMark; //任务栈历史剩余最小值
} TaskStatus_t;
5、vTaskGetInfo函数
(1)此函数用于获取指定的单个任务的状态信息,使用该函数前需在FreeRTOSConfig.h文件中将宏configUSE_TRACE_FACILITY置1。
(2)函数接口定义:
void vTaskGetInfo
(
TaskHandle_t xTask, //指定获取信息的任务的句柄
TaskStatus_t * pxTaskStatus, //接收任务信息的变量
BaseType_t xGetFreeStackSpace, //任务栈历史剩余最小值,当为“pdFALSE” 则跳过这个步骤,当为“pdTRUE”则检查历史剩余最小堆栈
eTaskState eState //任务状态,可直接赋值(不是改变任务状态,而是跳过获取步骤,直接把eState当作结果值),如想获取代入“eInvalid”
)
(3)eTaskState枚举定义:
typedef enum
{
eRunning = 0, /* 运行态 */
eReady /* 就绪态 */
eBlocked, /* 阻塞态 */
eSuspended, /* 挂起态 */
eDeleted, /* 任务被删除 */
eInvalid /* 无效 */
} eTaskState;
6、xTaskGetCurrentTaskHandle函数
(1)此函数用于获取当前任务的任务句柄,使用该函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_xTaskGetCurrentTaskHandle置1。
(2)函数接口定义:
TaskHandle_t xTaskGetCurrentTaskHandle //返回当前任务的任务句柄
(
void
)
7、xTaskGetHandle函数
(1)此函数用于通过任务名获取任务句柄,使用该函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_xTaskGetHandle置1。
(2)函数接口定义:
TaskHandle_t xTaskGetHandle //返回任务名对应的任务句柄
(
const char * pcNameToQuery //任务名
)
8、uxTaskGetStackHighWaterMark函数
(1)此函数用于获取指定任务的任务栈历史最小剩余堆栈,使用该函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_uxTaskGetStackHighWaterMark置1。
(2)函数接口定义:
UBaseType_t uxTaskGetStackHighWaterMark //返回任务栈的历史剩余最小值
(
TaskHandle_t xTask //任务句柄
)
9、eTaskGetState函数
(1)此函数用于查询某个任务的运行状态,使用此函数前需在FreeRTOSConfig.h文件中将宏INCLUDE_eTaskGetState置1。
(2)函数接口定义:
eTaskState eTaskGetState //返回任务状态
(
TaskHandle_t xTask //待获取状态任务的任务句柄
)
10、vTaskList函数
(1)此函数用于以“表格”的形式获取系统中任务的信息,使用此函数前需在FreeRTOSConfig.h文件中将宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS置1。
(2)函数接口定义:
void vTaskList
(
char * pcWriteBuffer //接收任务信息的缓存指针
)
(3)“表格”的属性如下所示:
①Name:创建任务的时候给任务分配的名字。
②State:任务的壮态信息,B是阻塞态,R是就绪态,S是挂起态,D是删除态。
③Priority:任务优先级。
④Stack:任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。
⑤Num:任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。
11、vTaskGetRunTimeStats函数
(1)此函数用于统计任务的运行时间信息,该函数耗时较长,一般仅在调试阶段使用,使用此函数前需在FreeRTOSConfig.h文件中将宏configGENERATE_RUN_TIME_STATS、configUSE_STATS_FORMATTING_FUNCTIONS置1,并且还需要实现如下两个宏定义。
①portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用于初始化用于配置任务运行时间统计的时基定时器,这个时基定时器的计时精度需高于系统时钟节拍精度的10至100倍。
②portGET_RUN_TIME_COUNTER_VALUE():用于获取该功能时基硬件定时器计数的计数值。
(2)函数接口定义:
void vTaskGetRunTimeStats
(
char * pcWriteBuffer //接收任务运行时间信息的缓存指针
)
(3)任务运行时间信息属性:
①Task:任务名称。
②Abs Time:任务实际运行的总时间(绝对时间)。
③% Time:占总处理时间的百分比(空闲任务的占比时间越大,说明CPU压力越小)。
二、任务状态查询API函数实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计4个任务——start_task、task1、task2、task3:
[1]start_task:用于创建其它三个任务,然后删除自身。
[2]task1:实现LED1状态反转。
[3]task2:实现LED2状态反转。
[4]task3:用于展示任务状态信息查询相关API函数的使用。
②预期实验现象:
[1]程序下载到板子上后,两个LED灯闪烁。
[2]串口助手中有相关信息输出。
2、实验步骤
(1)将“列表项的插入和删除实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)在FreeRTOSConfig.h文件中将相关宏全部置1,具体见本章的函数介绍部分。
(3)移除FreeRTOS_experiment.c文件中的原内容,替换为如下内容。
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"
#include "Serial.h"
#include "stdlib.h"
//宏定义
#define START_TASK_STACK_SIZE 128 //start_task任务的堆栈大小
#define START_TASK_PRIO 1 //start_task任务的优先级
#define TASK1_STACK_SIZE 128 //task1任务的堆栈大小
#define TASK1_PRIO 2 //task1任务的优先级
#define TASK2_STACK_SIZE 128 //task2任务的堆栈大小
#define TASK2_PRIO 3 //task2任务的优先级
#define TASK3_STACK_SIZE 128 //task3任务的堆栈大小
#define TASK3_PRIO 4 //task3任务的优先级
//任务函数声明
void start_task(void);
void task1(void);
void task2(void);
void task3(void);
//任务句柄
TaskHandle_t start_task_handler; //start_task任务的句柄
TaskHandle_t task1_handler; //task3任务的句柄
TaskHandle_t task2_handler; //task3任务的句柄
TaskHandle_t task3_handler; //task3任务的句柄
void FreeRTOS_Test(void)
{
//创建任务start_task
xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针
"start_task", //任务名字
START_TASK_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
START_TASK_PRIO, //任务优先级
(TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块
);
//开启任务调度器
vTaskStartScheduler();
}
void start_task(void)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务task1
xTaskCreate((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
(TaskHandle_t *) &task1_handler //任务句柄,就是任务的任务控制块
);
//创建任务task2
xTaskCreate((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
(TaskHandle_t *) &task2_handler //任务句柄,就是任务的任务控制块
);
//创建任务task3
xTaskCreate((TaskFunction_t)task3, //指向任务函数的指针
"task3", //任务名字
TASK3_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK3_PRIO, //任务优先级
(TaskHandle_t *) &task3_handler //任务句柄,就是任务的任务控制块
);
vTaskDelete(NULL); //删除任务自身
taskEXIT_CRITICAL(); //退出临界区
}
void task1(void)
{
while(1)
{
LED1_Turn(); //LED1状态翻转
vTaskDelay(500); //延时(自我阻塞)500ms
}
}
void task2(void)
{
while(1)
{
LED2_Turn(); //LED2状态翻转
vTaskDelay(500); //延时(自我阻塞)500ms
}
}
char task_buff[500];
void task3(void)
{
UBaseType_t priority_num = 0;
UBaseType_t task_num = 0;
UBaseType_t task_num2 = 0;
TaskStatus_t * status_array = NULL;
TaskStatus_t * status_array2 = NULL;
TaskHandle_t task_handle = 0;
//UBaseType_t task_stack_min = 0;
eTaskState state = eRunning;
uint8_t i = 0;
vTaskPrioritySet(task2_handler,4);
priority_num = uxTaskPriorityGet(NULL);
Serial_Printf("task2任务优先级为%ld\r\n",priority_num);
task_num = uxTaskGetNumberOfTasks();
Serial_Printf("任务数量:%ld\r\n",task_num);
status_array = malloc((sizeof(TaskStatus_t) * task_num));
task_num2 = uxTaskGetSystemState(status_array,task_num,NULL);
Serial_Printf("任务名\t\t任务优先级\t任务编号\r\n");
for(i = 0; i < task_num2; i++)
{
Serial_Printf("%s\t\t%ld\t%ld\r\n",status_array[i].pcTaskName,
status_array[i].uxCurrentPriority,status_array[i].xTaskNumber);
}
status_array2 = malloc(sizeof(TaskStatus_t));
vTaskGetInfo( task2_handler,status_array2,pdTRUE,eInvalid);
Serial_Printf("任务名:%s\r\n",status_array2->pcTaskName);
Serial_Printf("任务优先级:%ld\r\n",status_array2->uxCurrentPriority);
Serial_Printf("任务编号:%ld\r\n",status_array2->xTaskNumber);
Serial_Printf("任务状态:%d\r\n",status_array2->eCurrentState);
task_handle = xTaskGetHandle("task1");
Serial_Printf("任务句柄:%#x\r\n",(int)task_handle);
Serial_Printf("task1的任务句柄:%#x\r\n",(int)task1_handler);
state = eTaskGetState(task2_handler);
Serial_Printf("当前task2的任务状态为:%d\r\n",state);
vTaskList(task_buff);
Serial_SendString(task_buff);
while(1)
{
//task_stack_min = uxTaskGetStackHighWaterMark(task2_handler);
//Serial_Printf("task2历史剩余最小堆栈为%ld\r\n",task_stack_min);
vTaskDelay(1000);
}
}
(4)程序完善好后点击“编译”,然后将程序下载到开发板上,打开串口助手,观察串口中的内容。
3、程序执行流程
(1)main函数全流程:
①初始化OLED模块、按键模块、LED模块(还有串口模块,下图未示出)。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。
②task3的任务优先级最高,优先执行task3,task3将向串口打印一大串信息,如下所示,具体可根据代码一一对照,包括task1和task2的执行情况,这里也不再赘述。
三、任务时间统计API函数实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计4个任务——start_task、task1、task2、task3:
[1]start_task:用于创建其它三个任务,然后删除自身。
[2]task1:实现LED1状态反转。
[3]task2:实现LED2状态反转。
[4]task3:用于展示任务运行时间统计相关API函数的使用。
②预期实验现象:
[1]程序下载到板子上后,两个LED灯闪烁。
[2]按下按键,串口助手中有任务执行时间的相关信息输出。
2、实验步骤
(1)将“任务状态查询API函数实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)将“FreeRTOS中断管理实验”工程文件夹中的Timer.c和Timer.h文件拷贝至本实验工程中,如下图所示。
(3)在FreeRTOSConfig.h文件中将宏configGENERATE_RUN_TIME_STATS、configUSE_STATS_FORMATTING_FUNCTIONS置1,并且还需要实现如下两个宏定义。
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
(4)在Timer.c文件中定义ConfigureTimeForRunTimeStats函数,用于初始化用于配置任务运行时间统计的时基定时器,这个时基定时器的计时精度需高于系统时钟节拍精度的10至100倍,在本实验中系统节拍为1ms,则这个时基定时器的计时精度取10us(本实验使用TIM3,其时钟频率为90MHz,则预分频系数可取90,重装载值可取10)。(函数要在头文件中声明,这里不再赘述)
void Timer3_Init(void)
{
/*开启时钟,配置时钟源*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 90 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清除定时器更新标志位
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //开启TIM3的更新中断
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //选择配置NVIC的TIM3线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6; //指定NVIC线路的抢占优先级为6
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //指定NVIC线路的响应优先级为0
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}
uint32_t FreeRTOSRunTimeTicks;
void ConfigureTimeForRunTimeStats(void)
{
Timer3_Init();
FreeRTOSRunTimeTicks = 0;
}
(5)更改task3任务函数的实现。
void task3(void)
{
uint8_t key = 0;
while(1)
{
key = Key_GetNum();
if(key == 1) //按下一次按键,打印一次信息
{
vTaskGetRunTimeStats(task_buff);
Serial_SendString(task_buff);
Serial_SendString("\r\n------------------------\r\n");
}
vTaskDelay(10);
}
}
(6)在main.c文件中实现TIM3定时器中断的服务函数。
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
FreeRTOSRunTimeTicks++; //计数值自增
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
(7)程序完善好后点击“编译”,然后将程序下载到开发板上,打开串口助手分析信息。
3、程序执行流程
(1)main函数全流程:
①初始化OLED模块、按键模块、LED模块(还有串口模块,下图未示出)。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。
②task3的任务优先级最高,优先执行task3,每次在task3中按下按键,单片机将向串口打印一大串信息,如下所示,具体可根据代码一一对照,包括task1和task2的执行情况,这里也不再赘述。