一 任务状态
-
运行状态(Running):
-
任务当前正在处理器上运行。在单核系统中,同一时间只有一个任务处于运行状态。
-
-
就绪状态(Ready):
-
任务已经准备好运行,但由于优先级较低或其它任务正在运行,暂时没有被调度执行。一旦调度器选择该任务,它将进入运行状态。
-
-
阻塞状态(Blocked):
-
任务正在等待某个事件(如信号量、队列、延时等)发生。在事件发生之前,任务不会进入就绪状态。任务可以通过调用
vTaskDelay()
、xQueueReceive()
等函数进入阻塞状态。
-
-
挂起状态(Suspended):
-
任务被显式挂起,调度器不会选择该任务运行。任务可以通过调用
vTaskSuspend()
进入挂起状态,并通过vTaskResume()
或xTaskResumeFromISR()
恢复为就绪状态。
-
-
删除状态(Deleted):
-
任务已被删除,但其资源(如堆栈)可能还未被释放。任务可以通过调用
vTaskDelete()
进入删除状态。
-
二. 任务创建
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
函数原型
BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pxTaskCode, // 任务函数指针const char * const pcName, // 任务名称(字符串)const uint32_t usStackDepth, // 任务堆栈大小(以字为单位)void * const pvParameters, // 传递给任务函数的参数UBaseType_t uxPriority, // 任务优先级,数值越大,优先级越高。TaskHandle_t * const pxCreatedTask, // 任务句柄(用于引用任务)const BaseType_t xCoreID // 任务绑定的核心编号(0 或 1)
);
返回值
-
pdPASS
:任务创建成功。 -
pdFAIL
:任务创建失败
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, // 任务函数指针const char * const pcName, // 任务名称(字符串)const uint32_t usStackDepth, // 任务堆栈大小(以字为单位)void * const pvParameters, // 传递给任务函数的参数UBaseType_t uxPriority, // 任务优先级TaskHandle_t * const pxCreatedTask // 任务句柄(用于引用任务)
);
返回值
-
pdPASS
:任务创建成功。 -
pdFAIL
:任务创建失败
xTaskCreate
和 xTaskCreatePinnedToCore
的区别
特性 | xTaskCreate | xTaskCreatePinnedToCore |
---|---|---|
适用平台 | 所有 FreeRTOS 平台 | 仅 ESP-IDF(ESP32) |
核心绑定 | 任务可以在任意核心上运行 | 可以绑定到指定核心(0 或 1) |
多核支持 | 依赖调度器分配核心 | 显式指定核心 |
灵活性 | 更通用,适合单核和多核系统 | 更适合需要核心绑定的场景 |
函数来源 | FreeRTOS 标准 API | ESP-IDF 扩展 API |
声明功能函数
void taskA(void *param)
{while(1){//每隔500ms打印ESP_LOGI(TAG,"this is taskA");vTaskDelay(pdMS_TO_TICKS(500));}
}
运行任务
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreate(taskA, "taskA", 2048, NULL, 1, Null);
三.任务间同步
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
1. 信号量(Semaphore)
信号量用于任务间的同步或资源管理。FreeRTOS 提供了两种信号量:
-
二进制信号量(Binary Semaphore):
-
只能取值 0 或 1,用于任务间的简单同步。
-
xSemaphoreCreateBinary()//创建二进制信号量
SemaphoreHandle_t xSemaphore = NULL;void vTask1(void *pvParameters) {while (1) {// 任务 1 执行一些操作printf("Task 1 is running\n");xSemaphoreGive(xSemaphore); // 释放信号量vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒}
}void vTask2(void *pvParameters) {BaseType_t ret = 0;while(1){//无限等待二进制信号量,直到获取成功才返回ret = xSemaphoreTake(s_testBinSem,portMAX_DELAY);if(ret == pdTRUE){ESP_LOGI(TAG,"take binary semaphore");}}
}void app_main() {xSemaphore = xSemaphoreCreateBinary(); // 创建二进制信号量xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, NULL);xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, NULL);
}
-
计数信号量(Counting Semaphore):
-
可以取值大于 1,用于管理多个资源。
-
xSemaphoreCreateCounting(2,2);//输入最大值,及初始值
SemaphoreHandle_t xCountingSemaphore;void vTaskWorker(void *pvParameters) {while (1) {// 获取信号量(限制同时运行的任务数量)xSemaphoreTake(xCountingSemaphore, portMAX_DELAY);printf("Task %d is running\n", (int)pvParameters);vTaskDelay(pdMS_TO_TICKS(2000)); // 模拟任务运行xSemaphoreGive(xCountingSemaphore); // 释放信号量printf("Task %d finished\n", (int)pvParameters);vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒}
}void app_main() {// 创建计数信号量,最大值为 2,初始值为 2xCountingSemaphore = xSemaphoreCreateCounting(2, 2);if (xCountingSemaphore != NULL) {// 创建 4 个任务for (int i = 1; i <= 4; i++) {xTaskCreate(vTaskWorker, "Worker", 2048, (void *)i, 1, NULL);}} else {printf("Failed to create counting semaphore\n");}
}
同一时间只有两个任务在运行
2. 互斥量(Mutex)
互斥量用于保护共享资源,确保同一时间只有一个任务可以访问资源。互斥量具有优先级继承机制,可以防止优先级反转问题。
- xSemaphoreCreateMutex()//创建互斥量
SemaphoreHandle_t xMutex = NULL;
int sharedResource = 0;void vTask1(void *pvParameters) {while (1) {xSemaphoreTake(xMutex, portMAX_DELAY); // 获取互斥量sharedResource++; // 修改共享资源printf("Task 1: sharedResource = %d\n", sharedResource);xSemaphoreGive(xMutex); // 释放互斥量vTaskDelay(pdMS_TO_TICKS(1000));}
}void vTask2(void *pvParameters) {while (1) {xSemaphoreTake(xMutex, portMAX_DELAY); // 获取互斥量sharedResource--; // 修改共享资源printf("Task 2: sharedResource = %d\n", sharedResource);xSemaphoreGive(xMutex); // 释放互斥量vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main() {xMutex = xSemaphoreCreateMutex(); // 创建互斥量xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, NULL);xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, NULL);
}
3. 事件组(Event Group)
事件组用于任务间的复杂同步,允许任务等待多个事件的发生。
任务需要等待多个事件(如传感器数据就绪、网络连接成功等)同时发生。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"// 定义事件位
#define BIT_0 (1 << 0) // 事件位 0
#define BIT_1 (1 << 1) // 事件位 1
#define BIT_2 (1 << 2) // 事件位 2EventGroupHandle_t xEventGroup;
static TaskHandle_t task1_handle = NULL; // 任务1句柄
static TaskHandle_t task2_handle = NULL; // 任务2句柄
void vTask1(void *pvParameters) {while (1) {printf("Task 1 is running\n");xEventGroupSetBits(xEventGroup, BIT_0); // 设置事件位 0vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒}
}void vTask2(void *pvParameters) {while (1) {printf("Task 2 is running\n");xEventGroupSetBits(xEventGroup, BIT_1); // 设置事件位 1vTaskDelay(pdMS_TO_TICKS(2000)); // 延时 2 秒}
}void vTask3(void *pvParameters) {while (1) {// 等待事件位 0 和 1 都被设置EventBits_t uxBits = xEventGroupWaitBits(xEventGroup, // 事件组句柄BIT_0 | BIT_1, // 等待事件位 0 和 1pdTRUE, // 在退出时清除事件位pdTRUE, // 等待所有事件位portMAX_DELAY // 无限等待);if ((uxBits & (BIT_0 | BIT_1))) {printf("Task 3: Both BIT_0 and BIT_1 are set\n");vTaskDelete(task1_handle); // 删除任务vTaskDelete(task2_handle); // 删除任务}}
}void app_main() {// 创建事件组xEventGroup = xEventGroupCreate();if (xEventGroup != NULL) {// 创建任务xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, &task1_handle);xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, &task2_handle);xTaskCreate(vTask3, "Task 3", 2048, NULL, 1, NULL);} else {printf("Failed to create event group\n");}
}
4. 队列(Queue)
队列用于任务间的数据传递,支持 FIFO(先进先出)或 LIFO(后进先出)模式。
//队列句柄
static QueueHandle_t s_testQueue;
//定义一个队列数据内容结构体
typedef struct
{int num; //里面只有一个num成员,用来记录一下数据
}queue_packet;/** 队列任务A,用于定时向队列发送queue_packet数据* @param 无* @return 无
*/
void queue_taskA(void *param)
{int test_cnt = 0;while(1){queue_packet packet;packet.num = test_cnt++;//发送queue_packet数据xQueueSend(s_testQueue,&packet,pdMS_TO_TICKS(200));ESP_LOGI(TAG,"taskA send packet,num:%d",packet.num);//延时1000msvTaskDelay(pdMS_TO_TICKS(1000));}
}/** 队列任务B,用于从队列接收数据* @param 无* @return 无
*/
void queue_taskB(void *param)
{while(1){queue_packet packet;BaseType_t ret = xQueueReceive(s_testQueue,&packet,pdMS_TO_TICKS(200));if(ret == pdTRUE){//如果收到数据就打印出来ESP_LOGI(TAG,"taskB receive packet,num:%d",packet.num);}}
}/** 初始化队列例程* @param 无* @return 无
*/
void rtos_queue_sample(void)
{//初始化一个队列,队列单元内容是queue_packet结构体,最大长度是5s_testQueue = xQueueCreate(5,sizeof(queue_packet));//队列任务A,定时向队列发送数据xTaskCreatePinnedToCore(queue_taskA,"queue_taskA",2048,NULL,3,NULL,1);//队列任务B,从队列中接收数据xTaskCreatePinnedToCore(queue_taskB,"queue_taskB",2048,NULL,3,NULL,1);
}
5.任务通知(Task Notification)
//要使用任务通知,需要记录任务句柄
static TaskHandle_t s_notifyTaskAHandle;
static TaskHandle_t s_notifyTaskBHandle;/** 任务通知A,用于定时向任务通知B直接传输数据* @param 无* @return 无
*/
void notify_taskA(void* param)
{uint32_t rec_val = 0;while(1){if (xTaskNotifyWait(0x00, ULONG_MAX, &rec_val, pdMS_TO_TICKS(1000)) == pdTRUE){ESP_LOGI(TAG,"receive notify value:%lu",rec_val);}}
}/** 任务通知B,实时接收任务通知A的数据* @param 无* @return 无
*/
void notify_taskB(void* param)
{int notify_val = 0;while(1){xTaskNotify(s_notifyTaskAHandle, notify_val, eSetValueWithOverwrite);notify_val++;vTaskDelay(pdMS_TO_TICKS(1000));}
}/** 任务通知例程初始化* @param 无* @return 无
*/
void rtos_notify_sample(void)
{xTaskCreatePinnedToCore(notify_taskA,"notify_taskA",2048,NULL,3,&s_notifyTaskAHandle,1);xTaskCreatePinnedToCore(notify_taskB,"notify_taskB",2048,NULL,3,&s_notifyTaskBHandle,1);
}
注意:
1. 核心分配
- 任务绑定到核心:FreeRTOS 默认将任务分配到任意核心,通过
xTaskCreatePinnedToCore()
将任务绑定到特定的核心。避免核心之间的资源争用,提高并发处理的效率。 - 注意点:避免将所有任务都绑定到同一个核心,尽量平衡两个核心的负载。ESP32 的双核模式可以提高并行处理能力。通常,负责处理无线网络的任务(WIFI或者蓝牙)固定到CPU0上,而处理应用程序其余部分的任务将被固定到CPU1
2. 任务优先级
- 优先级设置:FreeRTOS 使用优先级来决定任务调度的顺序,任务优先级范围是 0 到 configMAX_PRIORITIES - 1。优先级高的任务会优先执行。
- 合理设置优先级:避免使用过高或过低的优先级。过高的优先级会导致低优先级任务长时间无法执行,造成系统不响应。过低的优先级可能导致高优先级任务得不到及时调度。
- 注意实时性:对于实时性要求高的任务,应该分配较高的优先级,避免将所有任务都设置为高优先级。
3. 中断管理
- 中断处理与任务:ESP32 中的中断处理函数(ISR)需要注意与 FreeRTOS 任务的协作。中断处理函数不能执行耗时的操作,应尽量简短。
- 禁止在 ISR 中使用 FreeRTOS API:在中断服务程序(ISR)中不能直接调用大部分 FreeRTOS API(如
xSemaphoreTake()
),可能会导致任务调度或等待,产生死锁或阻塞。 - 使用 ISR 安全的 API:FreeRTOS 提供了针对 ISR 的 API,比如
xSemaphoreGiveFromISR()
、xQueueSendFromISR()
等,它们能够安全地在中断上下文中被调用。
4. 延时管理
vTaskDelay
会让当前任务进入阻塞状态,直到延时的时间到达为止。它延时的单位是 系统时钟滴答(tick),每个滴答时长通过 configTICK_RATE_HZ
配置
- 延时指定的时间(以 tick 为单位),然后将任务挂起(阻塞)。
- 这个函数是基于 任务执行时间 的,也就是说任务延迟的时间是从它调用
vTaskDelay()
的那一刻开始算起的。
vTaskDelayUntil()
的作用是让任务按照固定的周期进行延时。它可以确保任务在每次延时之后,按照指定的周期来执行。适合那些需要定期执行的任务,如定时器任务。
- 根据传入的参数延迟任务的执行,使得任务的执行时间能够周期性地固定在某个时间点上。
- 它是基于 绝对时间 的,任务的延时是从某个固定的时间点开始算起的,而不是从任务调用函数的时间开始计算。