文章目录
- [1. 任务](#1. 任务)
-
- [1.1 任务调度机制](#1.1 任务调度机制)
- [1.2 任务状态](#1.2 任务状态)
- [1.3 任务优先级](#1.3 任务优先级)
- [1.4 任务实现](#1.4 任务实现)
- [1.5 任务控制块](#1.5 任务控制块)
- [1.6 任务堆栈](#1.6 任务堆栈)
- [1.7 任务创建](#1.7 任务创建)
-
- [1.7.1 参考代码 ------ 动态任务创建与任务删除](#1.7.1 参考代码 —— 动态任务创建与任务删除)
- [1.7.2 参考代码 ------ 静态任务创建与任务删除](#1.7.2 参考代码 —— 静态任务创建与任务删除)
- [1.8 任务控制](#1.8 任务控制)
- [1.10 任务通知](#1.10 任务通知)
- [1.* 【实验】任务壮态查询 API 函数的使用](#1.* 【实验】任务壮态查询 API 函数的使用)
- 1.*【实验】任务运行时间信息统计
关于FreeRTOS 的API函数,在FreeRTOS官网文档中都有详细介绍,下面不一一详细展开;
1. 任务
任务具有以下几个特性:
- 没有任务数量限制
- 支持任务抢占
- 支持优先级管理
- 每个任务都拥有堆栈,导致RAM使用量增大
1.1 任务调度机制
以前在使用 51、AVR、STM32 单片机裸机(未使用系统的程序)的时候一般都是在 main 函数里面用 while(1)做一个大循环来完成所有的功能。有时候也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环 while(1)作为后台程序。这种程序实时性差,前后台系统各个任务都是排队等着轮流执行,不管这个程序现在有多紧急,没轮到你就只能等着。相当于所有任务(应用程序)的优先级都是一样的。
而RTOS的实时性虽然更高,但多任务处理也带来了任务调度的问题。在 RTOS 系统中的任务调度器就是用于解决该问你。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。
1.2 任务状态
FreeRTOS 中的任务永远处于下面几个状态中的某一个:

- 运行态:当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。
- 就绪态:处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起)、随时可以运行的任务, 但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。
- 阻塞态 :如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了延时函数
vTaskDelay()的话,该任务就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临。 - 挂起态 :与阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,相比较于挂起态,进入挂起态的任务是没有超时时间的。任务进入和退出挂起态通过调用函数
vTaskSuspend()和xTaskResume().
1.3 任务优先级
每个任务都可分配到一个从 0~(configMAX_PRIORITIES-1) 的优先级,在FreeRTOS中,优先级数字越大,优先级越高(这一点与UCOS相反)。
c
// 在FreeRTOSConfig.h文件中有对可使用的最大优先级数量的定义
#define configMAX_PRIORITIES (32) //可使用的最大优先级数量为32
注:
- 优先级0留给空闲任务,创建任务时不要使用
- 软件定时器默认优先级为最高(configMAX_PRIORITIES-1),故创建任务时,也不要使用最高优先级
1.4 任务实现
在FreeRTOS中使用函数 xTaskCreate()或 xTaskCreateStatic()来创建任 务,这两个函数的第一个参数 pxTaskCode,就是这个任务的任务函数。
任务函数就是完成本任务工作的功能函数。FreeRTOS 官方给出的任务函数模板如下:
c
void vATaskFunction(void *pvParameters) //【1】
{
for( ; ; ) //【2】
{
// 任务要实现的功能代码 【3】
vTaskDelay(); //【4】
}
/* 不能从任务函数中返回或退出 ,从任务函数中返回或退出的话就会调用configASSERT(),前提是你定义了 configASSERT()。如果一定要从任务函数中退出的话那一定 要调用函数 vTaskDelete(NULL)来删除此任务。*/
vTaskDelete(NULL); //【5】
}
其中:
- 【1】:任务函数的函数名是自定义的,但任务函数的返回类型一定要为
void类型,而且任务的参数也是void指针类型的。 - 【2】:任务的具体执行过程是一个大循环,for(; ; )就代表一个循环,其作用和 while(1)一样。
- 【3】:循环里就是真正的任务功能代码。
- 【4】:FreeRTOS 的延时函数,此处不一定要用延时函数,其他只要能让 FreeRTOS 发生任务切换的 API 函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。但最常用的就是 FreeRTOS 的延时函数。
- 【5】:任务函数一般不允许跳出循环,如果一定要跳出循环,在跳出循环以后一定要调用函数
vTaskDelete(NULL)删除此任务。
以下是一个例程的main.c文件代码,其中的函数start_task()、led0_task()、led1_task()就是实现具体任务的功能函数:
c
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "FreeRTOS.h"
#include "task.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 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
/***************************************************************************
** 函数名称 : main
** 功能描述 : 工程入口函数
** 输入变量 : 无
** 返 回 值 :
0:程序执行正常
1:程序执行异常
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 : 描述任务开始函数
***************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
//创建任务,创建一个开始任务
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(); //开启任务调度
}
/***************************************************************************
** 函数名称 : start_task
** 功能描述 : 开始任务的功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 : 描述工程中的所有任务
***************************************************************************/
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);
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/***************************************************************************
** 函数名称 : led0_task
** 功能描述 : LED0任务功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 :
***************************************************************************/
void led0_task(void *pvParameters)
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
/***************************************************************************
** 函数名称 : led1_task
** 功能描述 : LED1任务功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 :
***************************************************************************/
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
总流程如下图:

1.5 任务控制块
FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结构体来表示描述,这个结构体叫做任务控制块 :TCB_t,在使用函数 xTaskCreate()创建任务时就会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB, 新版本重命名为 TCB_t,此结构体在文件 tasks.c 中有定义如下:
c
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; //任务堆栈栈顶 /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
Li/ stItem_t xStateListItem; //状态列表项 *< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; //事件列表项 *< Used to reference a task from an event list. */
UBaseType_t uxPriority; // 任务优先级 *< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; // 任务堆栈起始地址 /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
Note Newlib support has been included by popular demand, but is not
used by the FreeRTOS maintainers themselves. FreeRTOS is not
responsible for resulting newlib operation. User must be familiar with
newlib and must provide system-wide implementations of the necessary
stubs. Be warned that (at the time of writing) the current newlib design
implements a system-wide malloc() that must be provided with locks. */
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
/* See the comments above the definition of
tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
在结构体中有很多成员变量,每一个成员变量就是任务的每一个属性。
1.6 任务堆栈
FreeRTOS 之所以能正确的恢复一个被挂起的任务就是因为有任务堆栈,任务调度器在进行任务切换时会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中, 等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方继续运行。
创建任务的时候就需要给任务指定堆栈,如果使用函数 xTaskCreate()创建任务(动态方法) ,那么任务堆栈就会由函数 xTaskCreate()自动创建。 如果使用函数 xTaskCreateStatic()创建任务(静态方法)的话就需要自行定义任务堆栈,然后堆栈首地址作为函数的参数 puxStackBuffer 传递给函数。下面是xTaskCreateStatic()的参数定义:
c
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer, // 任务堆栈
StaticTask_t * const pxTaskBuffer )
- 堆栈大小:不管是使用函数
xTaskCreate()还是xTaskCreateStatic()创建任务都需要指定任务堆栈大小。任务堆栈的数据类型为StackType_t,它本质上是uint32_t,在头文件portmacro.h中有定义。
c
#define portSTACK_TYPE uint32_t // 4个字节
如果定义某任务的任务堆栈大小为50,即其任务堆栈大小为 50 * 4 * 8 = 1600位
1.7 任务创建
| 函数 | 描述 |
|---|---|
xTaskCreate() |
使用动态的方法创建一个任务。 |
xTaskCreateStatic() |
使用静态的方法创建一个任务。 |
xTaskCreateRestricted() |
创建一个使用 MPU 进行限制的任务,相关内存使用动态内存分配。 |
vTaskDelete() |
删除一个任务。 |
MPU:内存管理单元
1.7.1 参考代码 ------ 动态任务创建与任务删除

c
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 2 //任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
// while(1)
// {
// RX4_CommandDispose();
// LED_Status();
// LED_Change();
// Button_scan();
// Button_Command_Scan(); // 双启、复位按钮扫描函数
// ReadLEDlightValue();
// foolProofCylinder_Scan();
// }
}
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);
vTaskDelete(StartTask_Handler); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
// LED0=!LED0;
PCA9554_OUT(6,ON);
sprintfU4("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
// vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
sprintfU4("任务1挂起了!\r\n");
vTaskSuspend(Task1Task_Handler);
}
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(6,OFF);
vTaskDelay(1000);
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
// LED1=!LED1;
PCA9554_OUT(8,ON);
sprintfU4("任务2已经执行:%d次\r\n",task2_num);
if(task2_num==10)
{
// vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
sprintfU4("任务1恢复了!\r\n");
vTaskResume(Task1Task_Handler);
}
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,OFF);
vTaskDelay(1000);
}
}
运行结果如下:
任务1已经执行:1次
任务2已经执行:1次
任务1已经执行:2次
任务2已经执行:2次
任务1已经执行:3次
任务2已经执行:3次
任务1已经执行:4次
任务2已经执行:4次
任务1已经执行:5次
任务1挂起了!
任务2已经执行:5次
任务2已经执行:6次
任务2已经执行:7次
任务2已经执行:8次
任务2已经执行:9次
任务2已经执行:10次
任务1恢复了!
任务1已经执行:6次
任务2已经执行:11次
任务1已经执行:7次
任务2已经执行:12次
任务1已经执行:8次
任务2已经执行:13次
任务1已经执行:9次
任务2已经执行:14次
任务1已经执行:10次
任务2已经执行:15次
任务1已经执行:11次
1.7.2 参考代码 ------ 静态任务创建与任务删除
- 使用静态任务创建功能,首先需在
FreeRTOSConfig.h文件中将configSUPPORT_STATIC_ALLOCATION宏定义为1,开启静态任务创建功能
c
#define configSUPPORT_STATIC_ALLOCATION 1
- 在静态任务中,内存的分配需要由用户来分配,空闲任务的内存分配由函数
vApplicationGetIdleTaskMemory()完成,定时器服务任务的内存分配由函数vApplicationGetTimerTaskMemory()完成
c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "FreeRTOS.h"
#include "task.h"
//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
//定时器服务任务堆栈
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
//定时器服务任务控制块
static StaticTask_t TimerTaskTCB;
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
//在静态任务中,内存的分配需要由用户来分配,空闲任务的内存分配由函数`vApplicationGetIdleTaskMemory()`完成,定时器服务任务的内存分配由函数`vApplicationGetTimerTaskMemory()`完成
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器服务任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer:任务控制块内存
//ppxTimerTaskStackBuffer:任务堆栈内存
//pulTimerTaskStackSize:任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
*ppxTimerTaskStackBuffer=TimerTaskStack;
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint32_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )StartTaskStack, //任务堆栈
(StaticTask_t* )&StartTaskTCB); //任务控制块
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint32_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(StackType_t* )Task1TaskStack,
(StaticTask_t* )&Task1TaskTCB);
//创建TASK2任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务,task1和task2运行一次后,删除创建他们的start task
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
LED0 = 1;
printf("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task1Task_Handler);//任务1执行5次删除任务2
printf("任务1删除了任务2!\r\n");
}
vTaskDelay(3000); //延时1s,也就是1000个时钟节拍
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
LED0 = 0;
printf("任务2已经执行:%d次\r\n",task2_num);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
LED0 = 1;
}
}
1.8 任务控制
| 函数 | 描述 |
|---|---|
| vTaskDelay | 相对延时函数,阻塞当前任务指定的 tick 数,延时结束后任务恢复就绪态 |
| vTaskDelayUntil | 绝对延时函数,阻塞任务直到指定的系统 tick 时刻,常用于固定周期执行的任务 |
| xTaskDelayUntil | 带返回值的绝对延时函数,功能同 vTaskDelayUntil,返回操作状态 |
| uxTaskPriorityGet | 获取当前任务(或指定任务)的当前优先级 |
| uxTaskPriorityGetFromISR | 在中断服务函数(ISR)中,获取指定任务的当前优先级 |
| uxTaskBasePriorityGet | 获取指定任务的基础优先级(未被继承的原始优先级) |
| uxTaskBasePriorityGetFromISR | 在中断服务函数中,获取指定任务的基础优先级 |
| vTaskPrioritySet | 设置指定任务的优先级,可动态调整任务调度顺序 |
| vTaskSuspend | 挂起指定任务,被挂起的任务不再被调度执行,直到被恢复 |
| vTaskResume | 恢复被挂起的任务,使其重新进入就绪态 |
| xTaskResumeFromISR | 在中断服务函数中,恢复被挂起的任务,返回是否需要上下文切换 |
| xTaskAbortDelay | 强制唤醒正在延时的任务,使其提前结束阻塞状态 |
- 其他任务实用函数:
| 函数 | 描述 |
|---|---|
uxTaskGetSystemState |
获取系统中所有任务的状态信息,填充到 TaskStatus_t 数组中,可用于任务监控与调试。 |
vTaskGetInfo |
获取指定任务的详细信息,包括任务句柄、名称、状态、优先级、栈剩余空间等。 |
xTaskGetCurrentTaskHandle |
获取当前正在运行任务的句柄。 |
xTaskGetIdleTaskHandle |
获取空闲任务(Idle Task)的句柄。 |
uxTaskGetStackHighWaterMark |
获取指定任务栈的历史最小剩余空间(高水位标记),用于评估栈使用情况,避免栈溢出。 |
eTaskGetState |
获取指定任务的当前状态(如运行、就绪、阻塞、挂起等),返回枚举类型 eTaskState。 |
pcTaskGetName |
获取指定任务的名称字符串。 |
xTaskGetHandle |
根据任务名称查找并返回对应的任务句柄。 |
xTaskGetTickCount |
获取系统当前的滴答计数器值(非中断安全版本)。 |
xTaskGetTickCountFromISR |
在中断服务函数(ISR)中获取系统当前的滴答计数器值(中断安全版本)。 |
xTaskGetSchedulerState |
获取调度器的当前状态(如未启动、运行、挂起)。 |
uxTaskGetNumberOfTasks |
获取系统中当前存在的任务总数(包括就绪、阻塞、挂起等所有状态的任务)。 |
vTaskList |
生成所有任务的状态列表并输出到指定缓冲区,格式为"任务名 状态 优先级 栈剩余 任务编号",用于调试。 |
vTaskListTasks |
与 vTaskList 功能一致,生成任务状态列表,用于调试输出。 |
vTaskStartTrace |
启动任务跟踪功能,开始记录任务切换等事件。 |
ulTaskEndTrace |
停止任务跟踪功能,并返回跟踪记录的字节数。 |
vTaskGetRunTimeStats |
生成任务运行时间统计信息,输出到指定缓冲区,格式为"任务名 运行时间 占比",用于性能分析。 |
vTaskGetRunTimeStatistics |
与 vTaskGetRunTimeStats 功能一致,获取任务运行时间统计数据。 |
vTaskGetIdleRunTimeCounter |
获取空闲任务的运行时间计数器值(旧版接口)。 |
ulTaskGetRunTimeCounter |
获取指定任务的运行时间计数器值。 |
ulTaskGetRunTimePercent |
计算指定任务的运行时间占总运行时间的百分比。 |
ulTaskGetIdleRunTimeCounter |
获取空闲任务的运行时间计数器值(新版接口,返回 uint32_t)。 |
ulTaskGetIdleRunTimePercent |
计算空闲任务的运行时间占总运行时间的百分比。 |
vTaskSetApplicationTaskTag |
为指定任务设置一个应用自定义标签(指针),用于存储额外数据。 |
xTaskGetApplicationTaskTag |
获取指定任务的自定义标签值。 |
xTaskCallApplicationTaskHook |
调用指定任务的应用钩子函数(Hook Function)。 |
pvTaskGetThreadLocalStoragePointer |
获取当前任务的线程本地存储(TLS)指针。 |
vTaskSetThreadLocalStoragePointer |
设置当前任务的线程本地存储(TLS)指针。 |
vTaskSetTimeOutState |
保存当前任务的超时状态,用于后续超时检查。 |
xTaskCheckForTimeOut |
检查任务是否超时,结合 vTaskSetTimeOutState 使用。 |
vTaskResetState |
重置任务的状态信息(部分移植版本支持)。 |
1.10 任务通知
| 函数 | 描述 |
|---|---|
xTaskNotifyGive() / xTaskNotifyGiveIndexed() |
向目标任务发送一个增量型通知 ,将任务通知值加1,若任务因等待通知而阻塞则唤醒;Indexed版本用于多通知组场景。 |
vTaskNotifyGiveFromISR() / vTaskNotifyGiveIndexedFromISR() |
在中断服务函数(ISR)中向目标任务发送增量型通知,功能同 xTaskNotifyGive(),为中断安全版本。 |
ulTaskNotifyTake() / ulTaskNotifyTakeIndexed() |
任务主动获取通知值,可选择读取后清零或减1;若通知值为0则阻塞等待,返回获取到的通知值。 |
xTaskNotify() / xTaskNotifyIndexed() |
向目标任务发送自定义通知,可通过参数指定通知值的操作方式(覆盖、按位或、加1等),并唤醒阻塞任务。 |
xTaskNotifyAndQuery() / xTaskNotifyAndQueryIndexed() |
向目标任务发送自定义通知,并同时获取该任务修改前的通知值,便于调试或状态同步。 |
xTaskNotifyAndQueryFromISR() / xTaskNotifyAndQueryFromISRIndexed() |
在中断服务函数(ISR)中发送自定义通知并查询原通知值,为中断安全版本。 |
xTaskNotifyFromISR() / xTaskNotifyFromISRIndexed() |
在中断服务函数(ISR)中向目标任务发送自定义通知,为中断安全版本。 |
xTaskNotifyWait() / xTaskNotifyWaitIndexed() |
任务阻塞等待通知,可指定超时时间,并在等待前后对通知值进行位掩码操作,灵活处理通知状态。 |
xTaskNotifyStateClear() / xTaskNotifyStateClearIndexed() |
清除目标任务的通知挂起状态,但不修改通知值本身。 |
ulTaskNotifyValueClear() / ulTaskNotifyValueClearIndexed() |
清除目标任务的通知值并返回清除前的值,同时清除通知挂起状态。 |
1.* 【实验】任务壮态查询 API 函数的使用
c
#include "main.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)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
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)
{
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//query任务函数
void query_task(void *pvParameters)
{
u32 TotalRunTime;
UBaseType_t ArraySize,x;
TaskStatus_t *StatusArray;
char TaskInfo[10];
eTaskState TaskState;
TaskHandle_t TaskHandle;
TaskStatus_t TaskStatus;
//第一步:函数uxTaskGetSystemState()的使用
printf("/********第一步:函数uxTaskGetSystemState()的使用**********/\r\n");
ArraySize=uxTaskGetNumberOfTasks(); //获取系统任务数量
StatusArray=pvPortMalloc(ArraySize*sizeof(TaskStatus_t));//申请内存
if(StatusArray!=NULL) //内存申请成功
{
ArraySize=uxTaskGetSystemState((TaskStatus_t* )StatusArray, //任务信息存储数组
(UBaseType_t )ArraySize, //任务信息存储数组大小
(uint32_t* )&TotalRunTime);//保存系统总的运行时间
printf("任务名\t\t优先级\t\t任务编号\t\t\r\n");
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);
}
}
vPortFree(StatusArray); //释放内存
printf("/**************************结束***************************/\r\n");
printf("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1!=0) delay_ms(10); //等待KEY_UP键按下
//第二步:函数vTaskGetInfo()的使用
printf("/************第二步:函数vTaskGetInfo()的使用**************/\r\n");
TaskHandle=xTaskGetHandle("led0_task"); //根据任务名获取任务句柄。
//获取LED0_Task的任务信息
vTaskGetInfo((TaskHandle_t )TaskHandle, //任务句柄
(TaskStatus_t* )&TaskStatus, //任务信息结构体
(BaseType_t )pdTRUE, //允许统计任务堆栈历史最小剩余大小
(eTaskState )eInvalid); //函数自己获取任务运行壮态
//通过串口打印出指定任务的有关信息。
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("按下INPUT2继续!\r\n\r\n\r\n");
while(INPUT2!=0) delay_ms(10); //等待KEY_UP键按下
//第三步:函数eTaskGetState()的使用
printf("/***********第三步:函数eTaskGetState()的使用*************/\r\n");
TaskHandle=xTaskGetHandle("query_task"); //根据任务名获取任务句柄。
TaskState=eTaskGetState(TaskHandle); //获取query_task任务的任务壮态
memset(TaskInfo,0,10);
switch((int)TaskState)
{
case 0:
sprintf(TaskInfo,"Running");
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("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1!=0) delay_ms(10); //等待KEY_UP键按下
//第四步:函数vTaskList()的使用
printf("/*************第三步:函数vTaskList()的使用*************/\r\n");
vTaskList(InfoBuffer); //获取所有任务的信息
printf("%s\r\n",InfoBuffer); //通过串口打印所有任务的信息
printf("/**************************结束**************************/\r\n");
while(1)
{
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
运行结果:

1.*【实验】任务运行时间信息统计
注:函数
vTaskGetRunTimeStats()相对来说会很耗时间,所以不要太过于频繁的调用此函数,测试阶段可以使用此函数来分析任务的运行情况。还有运行时间不是真正的运行时间,真正的时间值要乘以50us .
main.c
c
#include "main.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]; //保存任务运行时间信息
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
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(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
sprintfU4("Task1 run time:%d\r\n@_@",task1_num);
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
PCA9554_OUT(1,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(1,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
sprintfU4("Task2 run time:%d\r\n@_@",task2_num);
}
}
//RunTimeStats任务
void RunTimeStats_task(void *pvParameters)
{
while(1)
{
// sprintfU4("RunTimeStats_task running\r\n");
if(INPUT1 == 0)
{
memset(RunTimeInfo,0,400); //信息缓冲区清零
vTaskGetRunTimeStats(RunTimeInfo); //获取任务运行时间信息
sprintfU4("任务名\t\t运行时间\t运行所占百分比\r\n");
sprintfU4("%s\r\n",RunTimeInfo);
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
timer.c
c
//FreeRTOS时间统计所用的节拍计数器
volatile unsigned long long FreeRTOSRunTimeTicks;
//初始化TIM3使其为FreeRTOS的时间统计提供时基
void ConfigureTimeForRunTimeStats(void)
{
//定时器3初始化,定时器时钟为72M,分频系数为72-1,所以定时器3的频率
//为72M/72=1M,自动重装载为50-1,那么定时器周期就是50us
FreeRTOSRunTimeTicks=0;
TIM3_Int_Init(50-1,72-1); //初始化TIM3
}
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级4级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
FreeRTOSRunTimeTicks++;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
FreeRTOSConfig.h
c
/***************************************************************************************************************/
/* FreeRTOS与运行时间和任务状态收集有关的配置选项 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS 1 //为1时启用运行时间统计功能
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()//定时器3提供时间统计的时基,频率为10K,即周期为100us
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks //获取时间统计时间值
#define configUSE_TRACE_FACILITY 1 //为1启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
//prvWriteNameToBuffer(),vTaskList(),
//vTaskGetRunTimeStats()
运行结果:
