目录
3、vTaskDelay(TickType_t xTicksToDelay)将一个任务挂起指定时间
4、vTaskSuspend() / vTaskResume() – 挂起和恢复任务
5、vTaskPrioritySet() – 动态修改任务优先级
一. 什么是任务(Task)?
在 FreeRTOS 中,任务(Task)是一个独立执行的程序单元,通常表现为一个具有无限循环的函数。每个任务都有独立的任务栈、任务控制块(TCB)、优先级和状态信息。
任务的基本状态包括:
-
就绪态(Ready):任务已准备好运行,等待调度器分配 CPU。
-
运行态(Running):任务正在占用 CPU 执行。
-
阻塞态(Blocked):任务在等待某个事件(如延时、队列、信号量等),暂不参与调度。
-
挂起态(Suspended):任务被挂起,必须手动恢复,调度器不会考虑它。
-
删除态(Deleted):任务被删除,系统可回收其资源(可选状态,某些资料也称其为“未创建态”)。
二.FreeRTOS任务常见函数
1.xTaskCreate() 创建新任务
(1)函数原型
创建freeRTOS任务函数:
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 任务栈大小,单位是字节
void *pvParameters, // 传递给任务的参数
UBaseType_t uxPriority, // 任务优先级(数值越大,优先级越高)
TaskHandle_t *pxCreatedTask // 返回的任务句柄(可为 NULL)
);
xTaskCreate()函数定义在头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
(2)返回值
返回值是任务创建是否成功:
-
pdPASS(值为 1):任务创建成功;
-
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(值为 0):任务创建失败(可能是因为堆不够用了);
(3)各个参数详细解释
① pxTaskCode:你定义的任务函数,具体的任务函数格式如下:
void vTaskFunction(void *pvParameters)(这个格式必须固定);
详细任务函数是这样写的:
void vTaskFunction(void *pvParameters)
{
while (1) {
// 任务循环执行的内容
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时 1 秒
}
}
//注意:这个任务函数不能返回,要用 while(1) 保持循环;
//所有任务都是并发调度的,所以必须显式释放 CPU 控制权,如使用 vTaskDelay()等;
② pcName:任务的名字,需要是一个字符串,用于标识任务;
③usStackDepth:分配的栈大小(stack size),决定了任务运行时的“工作空间”大小。单位是字节!
如果你创建了一个任务:xTaskCreate(vTask1, "MyTask", 2048, NULL, 1, NULL);代表分配了2048 B的栈空间给这个任务;
那我们在创建任务的时候,该给这个栈分配多大的空间呢?大致分为3个区间:
-
简单任务(只做打印、延时)→ 1024 ~ 2048 通常够用;
-
稍复杂的任务(包含字符串处理、调用库函数)→ 2048 ~ 4096;
-
非常复杂的任务(涉及协议栈、大数组、递归)→ 4096 以上;
当你每创建一个任务,FreeRTOS 就会为它在内存中分配一段独立的栈空间。所以,这里每个 FreeRTOS 任务都有自己的独立栈空间,任务之间栈空间互不干扰,保证了任务上下文切换的可靠性。
④pvParameters:传递给任务的参数,可以是结构体或基本类型的指针;这个参数是传给任务函数的输入参数,你可以把它看作是任务函数的“启动传参”。
void Task1(void *pvParameters)
{
char *message = (char *)pvParameters;//强转;
while(1) {
printf("任务收到消息: %s\n", message);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
char *msg = "我是传给任务的参数";
xTaskCreate(Task1, "MyTask", 2048, msg, 1, NULL);
}
⑤ uxPriority:用来指定任务的“优先级”的,也就是任务的重要程度,数值越大,表示优先级越高,能更早被调度运行,它的值大小通常从 0 ~ configMAX_PRIORITIES - 1 ;
⑥ pxCreatedTask:返回的任务句柄,如果你想后续操作这个任务(比如挂起它、删除它、修改优先级等),你就需要获取这个任务的“身份ID”—这个ID就是任务句柄。pxCreatedTask 是一个指针,FreeRTOS 会把新创建的任务句柄写进它指向的位置,如果你不需要以后操作这个任务,可以传 NULL;(第4个参数是属于输入参数,而第6个这个是输出) ;
(4)创建任务的代码示例
示例 1:创建两个任务
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// 任务1函数
void task1(void *pvParameters)
{
while (1) {
printf("Task 1 is running...\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒--100个tick
}
}
// 任务2函数
void task2(void *pvParameters)
{
while (1) {
printf("Task 2 is running...\n");
vTaskDelay(pdMS_TO_TICKS(1500)); // 延时1.5秒
}
}
void app_main(void)
{
// 创建任务1
xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
// 创建任务2
xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
}
示例 2:你有两个任务:
-
Task A(数据采集任务):一直循环采集传感器数据;
-
Task B(控制任务):定时挂起或恢复 Task A,比如在某些条件下暂停采集(例如用户按了某个按钮或温度过高)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "TASK_CTRL";
TaskHandle_t sensorTaskHandle = NULL; // 用于存储 sensor task 的任务句柄
void sensorTask(void *pvParameters)
{
while (1)
{
ESP_LOGI(TAG, "采集传感器数据...");
vTaskDelay(pdMS_TO_TICKS(1000)); // 每1秒采集一次
}
}
void controlTask(void *pvParameters)
{
while (1)
{
ESP_LOGI(TAG, "控制任务:挂起采集任务");
vTaskSuspend(sensorTaskHandle); // 挂起传感器采集任务
vTaskDelay(pdMS_TO_TICKS(5000)); // 停5秒
ESP_LOGI(TAG, "控制任务:恢复采集任务");
vTaskResume(sensorTaskHandle); // 恢复采集任务
vTaskDelay(pdMS_TO_TICKS(10000)); // 10秒后再次挂起
}
}
void app_main(void)
{
// 创建采集任务,并返回任务句柄
xTaskCreate(sensorTask, "SensorTask", 2048, NULL, 5, &sensorTaskHandle);
// 创建控制任务(不需要句柄)
xTaskCreate(controlTask, "ControlTask", 2048, NULL, 6, NULL);
}
2、vTaskDelete() 删除一个任务
(1)函数原型
void vTaskDelete(TaskHandle_t xTaskToDelete);//无返回值
参数xTaskToDelete指的是要删除的任务的句柄(TaskHandle)。如果这个参数是 NULL,表示删除当前正在运行的任务。
任务一旦被删除,其占用的栈空间和任务控制块(TCB)会被系统回收。
在删除任务后句柄不会自动置为 NULL,你可以手动置为 NULL 防止误用:
vTaskDelete(TaskAHandle);
TaskAHandle = NULL;
删除指定任务是送句柄:vTaskDelete(xHandle); 如果是删除当前任务,可以写vTaskDelete(NULL); 。
(2)什么场景会用到删除任务?
①任务完成其工作后自动释放资源
-
有些任务是“临时”性质的,比如一次性执行某个操作(文件写入、数据上传、传感器采样处理等),完成后不再需要。
-
这种任务执行完毕后调用 vTaskDelete(NULL) 自杀,节省系统资源。
-
示例:传感器采样任务,采样并上传完数据后,自动删除自己。
②根据系统运行状态动态管理任务
-
某些任务只在特定条件下需要存在,比如功能开关、工作模式切换等。
-
关闭某模块时,删除相关任务,释放CPU和内存,节省功耗。
-
示例:蓝牙任务只在蓝牙打开时创建,关闭时删除。
③ 错误恢复和重启任务
-
如果某个任务出现异常或卡死,系统可能选择删除并重新创建任务来“重启”它。
-
示例:通信任务检测到长时间无响应,删除并重建任务恢复状态。
④ 内存管理需要
-
任务创建时会分配堆栈内存,频繁创建的任务如果不删除会导致内存泄漏。
-
删除任务可以及时释放堆栈和任务控制块(TCB)的内存。
⑤ 动态任务管理系统
-
有些系统设计成动态创建和删除任务,根据用户操作或运行需求灵活调度。
-
例如多用户设备,为每个用户创建任务,用户退出时删除任务。
(3)代码示例
/*任务:
*1、创建两个任务
*2、任务 A 每秒打印一次信息
*3、任务 B 启动后等待 5 秒,然后删除任务 A
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TaskHandle_t TaskAHandle = NULL;
void TaskA(void *pvParameters)
{
while (1)
{
printf("Task A is running...\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟 1 秒
}
}
void TaskB(void *pvParameters)
{
printf("Task B is running... Will delete Task A in 5 seconds.\n");
vTaskDelay(pdMS_TO_TICKS(5000)); // 延迟 5 秒
printf("Task B deleting Task A now.\n");
vTaskDelete(TaskAHandle); // 删除任务 A
TaskAHandle = NULL;//防止误用;
printf("Task B finished its job. Suspending itself.\n");
vTaskSuspend(NULL); // 挂起自己,不再执行
}
void app_main(void)
{
xTaskCreate(TaskA, "TaskA", 2048, NULL, 2, &TaskAHandle);
xTaskCreate(TaskB, "TaskB", 2048, NULL, 2, NULL);
}
3、vTaskDelay(TickType_t xTicksToDelay)将一个任务挂起指定时间
(1)函数原型
void vTaskDelay(const TickType_t xTicksToDelay);//无返回值
(2)使用场景
-
周期性任务;
-
延时启动、稳定时序;
(3)代码示例
void sensorTask(void *pvParam)
{
while (1)
{
printf("读取传感器数据\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
(4)注意事项
-
延时单位是 tick,建议配合 pdMS_TO_TICKS(ms) 使用;
-
延时过程中,任务进入 Blocked 状态,允许其他任务运行;
-
它并不精确,是相对于进入时刻“起跳”的。
4、vTaskSuspend() / vTaskResume() – 挂起和恢复任务
(1)函数原型
void vTaskSuspend(TaskHandle_t xTaskToSuspend);//无返回值
void vTaskResume(TaskHandle_t xTaskToResume);//无返回值
(2)使用场景
-
暂停后台任务;
-
控制任务在特定事件前后运行;
(3)代码示例
TaskHandle_t ledTaskHandle = NULL;
void ledTask(void *pvParam)
{
while (1)
{
printf("LED任务执行中\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main()
{
xTaskCreate(ledTask, "LED Task", 2048, NULL, 3, &ledTaskHandle);
vTaskDelay(pdMS_TO_TICKS(5000));
vTaskSuspend(ledTaskHandle); // 暂停任务
vTaskDelay(pdMS_TO_TICKS(5000));
vTaskResume(ledTaskHandle); // 恢复任务
}
(4)注意事项
-
xTaskToSuspend/Resume 不能为 NULL;
-
挂起状态下任务不会被调度;
-
推荐使用任务句柄来管理。
5、vTaskPrioritySet() – 动态修改任务优先级
(1)函数原型
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);//无返回值
(2)使用场景
-
实时响应需求高时临时提升优先级;
-
多任务之间协调顺序。
(3)代码示例
vTaskPrioritySet(myTaskHandle, 8); // 提高任务优先级,最新的优先级是8;
三、FreeRTOS 任务间间是如何通讯?
FreeRTOS提供了以下几类通讯机制:队列、信号量、互斥量、事件组、软件定时器、任务通知;
1.队列(Queue)
队列是FreeRTOS中最常见的任务通讯机制之一,主要用于任务之间或中断和任务之间传递数据,它本质上是一个先进先出的数据结构(FIFO)。
详细介绍请跳转笔记《FreeRTOS基础学习(三)消息队列》
2.信号量(Semaphore)
信号量是一种二进制或计数型“标志量”,本身不传数据,只传“状态”或“通知”。用于协调多个任务或中断对共享资源的访问,防止资源冲突。它就像一个“通行证”,拿到信号量 = 拿到通行证,可以进入访问资源;如果没拿到,那就等待通行证释放。FreeRTOS提供了3种主要类型的信号量:二值信号量、计数信号量、互斥信号量。
详细介绍请跳转笔记《FreeRTOS基础学习(四)信号量》
3.软件定时器(Timer)
软件定时器是一个定时工具,时间到时自动触发回调函数。用于延迟执行某些逻辑,比如周期性检查、重试操作、状态超时处理等。它不依赖于任务,后台由 FreeRTOS 的定时器服务任务统一调度。
详细介绍请跳转笔记《FreeRTOS基础学习(五)软件定时器》
4.事件组(Event Group)
事件组是 是一个 32 位的标志位(每个位都可代表一个事件),用来管理和同步多个事件标志(每个位代表一个事件)。每个事件位是 uint32_t 类型的一个位(共32位),可以用来表示不同事件的发生状态。
详细介绍请跳转笔记《FreeRTOS基础学习(六)事件组》