1. FreeRTOS任务相关API函数介绍
函数 | 描述 |
---|---|
uxTaskPriorityGet() | 获取任务优先级 |
vTaskPrioritySet() | 设置任务优先级 |
uxTaskGetNumberOfTasks() | 获取系统中任务的数量 |
uxTaskGetSystemState() | 获取所有任务状态信息 |
vTaskGetInfo() | 获取指定单个的任务信息 |
xTaskGetCurrentTaskHandle() | 获取当前任务的任务句柄 |
xTaskGetHandle() | 根据任务名获取该任务的任务句柄 |
uxTaskGetStackHighWaterMark() | 获取任务的任务栈历史剩余最小值 |
eTaskGetState() | 获取任务状态 |
vTaskList() | 以“表格”形式获取所有任务的信息 |
vTaskGetRunTimeStats() | 获取任务的运行时间 |
学习资料可参考手册《FreeRTOS开发指南》第11章 —— “FreeRTOS其他任务API函数”
1.1 函数介绍
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
此函数用于获取指定任务的任务优先级,使用该函数需将宏 INCLUDE_uxTaskPriorityGet 置 1
形参 | 描述 |
---|---|
xTask | 要查找的任务句柄,NULL代表任务自身 |
返回值 | 描述 |
---|---|
整数 | 任务优先级数值 |
void vTaskPrioritySet( TaskHandle_t xTask , UBaseType_t uxNewPriority )
此函数用于改变某个任务的任务优先级,使用该函数需将宏 INCLUDE_vTaskPrioritySet 为 1
形参 | 描述 |
---|---|
xTask | 任务句柄,NULL代表任务自身 |
uxNewPriority | 需要设置的任务优先级 |
UBaseType_t uxTaskGetNumberOfTasks( void )
此函数用于获取系统中任务的任务数量
返回值 | 描述 |
---|---|
整型 | 系统中任务的数量 |
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime )
此函数用于获取系统中所有任务的任务状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY 置 1
形参 | 描述 |
---|---|
xTaskStatusArray | 指向 TaskStatus_t 结构体数组首地址 |
uxArraySize | 接收信息的数组大小 |
pulTotalRunTime | 系统总运行时间,为 NULL 则省略总运行时间值 |
返回值 | 描述 |
---|---|
整型 | 获取信息的任务数量 |
TaskStatus_t 任务状态信息结构体介绍:
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime )
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;
为什么有原始优先级?因为互斥信号量这些是有一个优先级继承的问题,优先级是会改变的,所以这里有个变量就是保存原始的一个优先级,就这么一个作用。
void vTaskGetInfo( TaskHandle_t xTask,
TaskStatus_t * pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState )
此函数用于获取指定的单个任务的状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY 置 1
形参 | 描述 |
---|---|
xTask | 指定获取信息的任务的句柄 |
pxTaskStatus | 接收任务信息的变量 |
xGetFreeStackSpace | 任务栈历史剩余最小值,当为“pdFALSE” 则跳过这个步骤,当为“pdTRUE”则检查历史剩余最小堆栈 |
eState | 任务状态,可直接赋值,如想获取代入“eInvalid” |
eTaskState 结构体:
typedef enum
{
eRunning = 0, /* 运行态 */
eReady /* 就绪态 */
eBlocked, /* 阻塞态 */
eSuspended, /* 挂起态 */
eDeleted, /* 任务被删除 */
eInvalid /* 无效 */
} eTaskState;
因为通过这个函数来获取任务状态的时候,它是要费时间的,所以我们一般可以直接赋值。比如说 task2 正在运行,那就给它一个运行态,eRunning 写进来;如果说无所谓,你浪费点时间就浪费点时间,那么就带入这个 eInvalid 无效态,把这个参数给些进来,那么此时呢它就会帮我们获取它此时的这个任务的任务状态。
TaskHandle_t xTaskGetCurrentTaskHandle( void )
此函数用于获取当前任务的任务句柄, 使用该函数需将宏 INCLUDE_xTaskGetCurrentTaskHandle 置 1
返回值 | 描述 |
---|---|
TaskHandle_t | 当前任务的任务句柄 |
TaskHandle_t xTaskGetHandle(const char * pcNameToQuery);
此函数用于通过任务名获取任务句柄 , 使用该函数需将宏 INCLUDE_xTaskGetHandle 置 1
形参 | 描述 |
---|---|
pcNameToQuery | 任务名 |
返回值 | 描述 |
---|---|
TaskHandle | 任务句柄 |
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
此函数用于获取指定任务的任务栈历史最小剩余堆栈;那这个跟设置堆栈大小还是非常有密切联系的。如果这个值非常小,就代表堆栈可能有溢出风险,所以这个历史最小值尽量大一点,可以通过这个历史最小值来设置任务堆栈的大小。(正常大概大一倍左右)
比如说剩余的历史最小堆栈值很小,就代表要溢出了,此时可以加大一点任务堆栈;如果剩余很大,代表很多都没用到,所以可以适当减小一点。
使用该函数需将宏 INCLUDE_uxTaskGetStackHighWaterMark 置 1
形参 | 描述 |
---|---|
xTask | 任务句柄 |
返回值 | 描述 |
---|---|
UBaseType_t | 任务栈的历史剩余最小值 |
eTaskState eTaskGetState(TaskHandle_t xTask)
此函数用于查询某个任务的运行状态(就绪态、阻塞态、挂起态等等),使用此函数需将宏 INCLUDE_eTaskGetState 置1
形参 | 描述 |
---|---|
xTask | 待获取状态任务的任务句柄 |
返回值 | 描述 |
---|---|
eTaskState | 任务状态 |
typedef enum
{
eRunning = 0, /* 运行态 */
eReady /* 就绪态 */
eBlocked, /* 阻塞态 */
eSuspended, /* 挂起态 */
eDeleted, /* 任务被删除 */
eInvalid /* 无效 */
} eTaskState;
void vTaskList(char * pcWriteBuffer)
此函数用于以“表格”的形式获取系统中任务的信息 ;
使用此函数需将宏 configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS 置 1
形参 | 描述 |
---|---|
pcWriteBuffer | 接收任务信息的缓存指针 |
Name : 创建任务的时候给任务分配的名字。
State : 任务的壮态信息, B 是阻塞态, R 是就绪态, S 是挂起态, D 是删除态,X 代表运行态。
Priority : 任务优先级。
Stack : 任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。
Num : 任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。
- 表格如下所示:
2. 任务状态查询API函数实验
实验目的:学习 FreeRTOS 任务状态与信息的查询API函数
实验设计:将设计三个任务:start_task、task1、task2
三个任务的功能如下:
start_task:用来创建task1和task2任务
task1:LED0每500ms闪烁一次,提示程序正在运行
task2:用于展示任务状态信息查询相关 API 函数的使用
2.1 任务函数实现
task2
char task_buff[500];
/* 任务二,实现任务状态查询API函数使用 */
void task2( void * pvParameters )
{
UBaseType_t priority_num = 0;
UBaseType_t task_num = 0;
UBaseType_t task_num2 = 0;
TaskStatus_t * status_array = 0;
TaskStatus_t * status_array2 = 0;
TaskHandle_t task_handle = 0;
UBaseType_t task_stack_min = 0;
eTaskState state = 0;
uint8_t i = 0;
vTaskPrioritySet( task2_handler,4 ); /* 设置任务优先级 */
priority_num = uxTaskPriorityGet( NULL ); /* 获取任务优先级 */
printf("task2任务优先级为%ld\r\n",priority_num);
task_num = uxTaskGetNumberOfTasks(); /* 获取系统中任务的数量 */
printf("任务数量:%ld\r\n",task_num);
status_array = mymalloc(SRAMIN,(sizeof(TaskStatus_t) * task_num)); /* 申请位置:SRAM */
task_num2 = uxTaskGetSystemState( status_array,task_num,NULL); /* 获取所有任务状态信息 */
printf("任务名\t\t任务优先级\t任务编号\r\n");
for(i = 0; i < task_num2; i++)
{
printf("%s\t\t%ld\t%ld\r\n",
status_array[i].pcTaskName,
status_array[i].uxCurrentPriority,
status_array[i].xTaskNumber);
}
status_array2 = mymalloc(SRAMIN,sizeof(TaskStatus_t));
vTaskGetInfo( task2_handler,status_array2,pdTRUE,eInvalid); /* 获取指定单个的任务信息 */
printf("任务名:%s\r\n",status_array2->pcTaskName);
printf("任务优先级:%ld\r\n",status_array2->uxCurrentPriority);
printf("任务编号:%ld\r\n",status_array2->xTaskNumber);
printf("任务状态:%d\r\n",status_array2->eCurrentState);
task_handle = xTaskGetHandle( "task1" ); /* 根据任务名获取该任务的任务句柄 */
printf("任务句柄:%#x\r\n",(int)task_handle);
printf("task1的任务句柄:%#x\r\n",(int)task1_handler);
state = eTaskGetState( task2_handler ); /* 获取任务状态 */
printf("当前task2的任务状态为:%d\r\n",state);
vTaskList( task_buff ); /* 以“表格”形式获取所有任务的信息 */
printf("%s\r\n",task_buff);
while(1)
{
// task_stack_min = uxTaskGetStackHighWaterMark( task2_handler );
// printf("task2历史剩余最小堆栈为%ld\r\n",task_stack_min);
vTaskDelay(1000);
}
}
Tmr_Svc(软件定时器)是阻塞态,start_task(开始任务)是删除态,task2 是运行态,task1 和 IDLE(空闲任务)是就绪态。
3. 时间统计API函数介绍
void vTaskGetRunTimeStats( char * pcWriteBuffer )
此函数用于统计任务的运行时间信息,使用此函数需将宏 configGENERATE_RUN_TIME_STAT、configUSE_STATS_FORMATTING_FUNCTIONS 置1
形参 | 描述 |
---|---|
pcWriteBuffer | 接收任务运行时间信息的缓存指针 |
Task:任务名称 Abs
Time:任务实际运行的总时间(绝对时间)。单位不是秒,而是看定时器的中断频率,然后再乘以这个数值才是真正的时间。
%Time:占总处理时间的百分比
- 表格如下所示:
你的空闲时间越长,代表你的应用任务压力就越小,因为我们说过空闲任务它是 cpu 空闲的时候才会去执行的,如果 cpu 有很多空闲时间,就代表 cpu 压力不大;如果 cpu 这个空闲任务时间占比很小,就代表有些任务可能占用太大的时间。像这里,GenQ 也占了 24%,那我们看到这个占这么大,那我们是不是可以把它拆分一下,拆分小一点,这个就是在调试的时候用的比较多,在正式的产品,比如我们调试好了,我们就把这些功能给删掉就行了(需要)。
所以这个功能主要是在用于调试阶段,我们调试产品的时候,用了 FreeRTOS 的时候,我们这时候可以用这个函数来统计一下每个任务它们各自的一个时间占比。它就这么一个作用。
3.1 时间统计API函数使用流程
- 将宏 configGENERATE_RUN_TIME_STATS 置1
- 将宏 configUSE_STATS_FORMATTING_FUNCTIONS 置1
- 当将此宏 configGENERATE_RUN_TIME_STAT 置1之后,还需要实现2个宏定义:
- portCONFIGURE_TIMER_FOR_RUNTIME_STATE() :用于初始化用于配置任务运行时间统计的时基定时器;(开启任务调度器函数中调用,自己实现)
- portGET_RUN_TIME_COUNTER_VALUE():用于获取该功能时基硬件定时器计数的计数值,用来返回当前“时间”。
滴答定时器是来给系统提供时钟节拍的,那这个时基定时器是用来统计任务运行时间的,这两个是有区别的。
- 注意:这个时基定时器的计时精度(分辨率)需高于系统时钟节拍精度的10至100倍!时基越快,统计的数据就越准确。那系统时钟节拍是 1ms 中断一次,那么高它 10~100 倍,也就是说是 0.1ms 到 0.01ms,也就是 10μs 到 100μs 这么一个范围,这个时基定时器的频率就在这么一个范围。那这个呢是在官网上都是可以找到它这些描述的。
- 所以我们一会用 100 倍来统计,也就我们现在的系统时钟节拍是 1ms,那 100 倍就 0.01 ms,也就是 10μs,我们一会把时基定时器配置成 10μs 中断一次。
4. 任务时间统计API函数实验
实验目的:学习 FreeRTOS 任务运行时间统计相关 API 函数的使用
实验设计:将设计三个任务:start_task、task1、task2
三个任务的功能如下:
start_task:用来创建task1和task2任务
task1:LED0每500ms闪烁一次,提示程序正在运行
task2:用于展示任务运行时间统计相关API函数的使用
4.1 宏置 1
- 将宏 configGENERATE_RUN_TIME_STATS 置1
- 将宏 configUSE_STATS_FORMATTING_FUNCTIONS 置1
4.2 宏定义
btim.h
void ConfigureTimeForRunTimeStats(void); /* 时基定时器初始化函数声明 */
btim.c
时基定时器初始化:
TIM_HandleTypeDef g_timx_handle; /* 定时器参数句柄 */
/**
* @brief 基本定时器TIMX定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为45M, 所以定时器时钟 = 90Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr : 自动重装值。
* @param psc : 时钟预分频数
* @retval 无
*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
g_timx_handle.Instance = BTIM_TIMX_INT; /* 定时器x */
g_timx_handle.Init.Prescaler = psc; /* 分频 */
g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handle.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handle);
HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器更新中断 */
}
uint32_t FreeRTOSRunTimeTicks; /* 定义计数值 */
/* 时基定时器的初始化 */
void ConfigureTimeForRunTimeStats(void)
{
btim_timx_int_init(10-1, 90-1); /* 100倍的系统时钟节拍 */
FreeRTOSRunTimeTicks = 0; /* 初始化计数值,把它给清零 */
}
我们现在要设置 10μs,大家可以算一下,我们的基本定时器用了 TIM6 ,它的时钟是 90M(我们这里是以 429 为例,429 主频是 180M,那它是 TIM6,是它的一半,也就是 90M),所以我们这个分频值可以设置 90,90M 后面有 6 个 0,除以 90,等于 1M,1M 是不是 1μs,那我们现在要 10μs,就把这个重装载值设置成 10 就可以了,那这样我们就设置好了。
然后就在中断里面 FreeRTOSRunTimeTicks 要进行自加了,我们每进来一次中断服务函数,我去进行一次加一。
/**
* @brief 定时器底层驱动,开启时钟,设置中断优先级。此函数会被HAL_TIM_Base_Init()函数调用
* @param 无
* @retval 无
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 6, 0); /* 抢占6,子优先级0 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */
}
}
/**
* @brief 基本定时器TIMX中断服务函数
* @param 无
* @retval 无
*/
void BTIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handle); /* 定时器回调函数 */
}
/**
* @brief 回调函数,定时器中断服务函数调用
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
FreeRTOSRunTimeTicks++; /* 计数值自加 */
}
}
我们这里初始化它是 10μs 中断一次,也就是说 10μs 会进来一次中断服务函数,把 FreeRTOSRunTimeTicks 进行一次加一。那这个就是计数值。
4.3 任务函数实现
task2
char task_buff[500];
/* 任务二,实现任务运行时间统计API函数的使用 */
void task2( void * pvParameters )
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
vTaskGetRunTimeStats(task_buff);
printf("%s\r\n",task_buff);
}
vTaskDelay(10);
}
}
那这样其实就可以了。
task1 它运行时间是非常非常短的,而 task2 因为调用了 vTaskGetRunTimeStats 这个函数,所以它所用的时间就比较长了,相对于 task1 来说,所以我们在调试阶段才会去用到这个函数去检查一下我们的任务时间的一个情况。在一般的一个正式产品的话,我们就会把它给删掉了,就不会用到它了,因为它占据的时间是比较长的。