ESP32下FreeRTOS实时操作系统使用
文章目录
- ESP32下FreeRTOS实时操作系统使用
- 一、概述
- 二、为什么要使用实时操作系统RTOS?
- 三、FreeRTOS任务
- 3.1 什么是 FreeRTOS 任务?
- 3.2 FreeRTOS 任务的特点
- 3.3 FreeRTOS 任务的生命周期
- 3.4 FreeRTOS 任务的状态
- 3.5 FreeRTOS 任务与多线程的区别
- 3.6 常用 FreeRTOS 任务函数
- 3.6.1 任务创建函数`xTaskCreate`()原型和头文件:
- 3.6.2 任务创建函数`xTaskCreatePinnedToCore`()原型和头文件:(仅在 ESP-IDF 中可用)
- 3.6.3 任务延时函数`vTaskDelay`()原型和头文件:
- 3.6.4 任务删除函数`vTaskDelete`()原型和头文件:
- 3.6.5 暂停任务函数`vTaskSuspend`()原型和头文件:
- 3.6.6 恢复挂起任务函数`vTaskResume`()原型和头文件:
- 3.6.7 返回指定任务优先级函数`uxTaskPriorityGet`()原型和头文件:
- 3.6.8 设置任务新优先级函数`vTaskPrioritySet`()原型和头文件:
- 3.6.9 获取当前正在运行任务的句柄函数`xTaskGetCurrentTaskHandle`()原型和头文件:
- 3.7 任务创建实例
- 3.7.1 示例说明:
- 3.7.2 完整代码:
- 3.7.3 代码解析:
- 3.7.4 运行效果:
- 四、FreeRTOS队列
- 4.1 FreeRTOS 队列的特点
- 4.2 常用 FreeRTOS 队列函数
- 4.2.1 创建队列函数`xQueueCreate`()原型和头文件:
- 4.2.2 发送数据到队列函数`xQueueSend`()原型和头文件:
- 4.2.3 发送数据到队列(中断安全版本)函数`xQueueSendFromISR`()原型和头文件:
- 4.2.4 从队列接收数据函数`xQueueReceive`()原型和头文件:
- 4.2.5 从队列接收数据(中断安全版本)函数`xQueueReceiveFromISR`()原型和头文件:
- 4.2.6 获取队列中元素个数函数`uxQueueMessagesWaiting`()原型和头文件:
- 4.2.7 获取队列中剩余空间函数`uxQueueSpacesAvailable`()原型和头文件:
- 4.3 FreeRTOS队列示例
- 代码说明
- 运行结果
- 五、FreeRTOS信号量(Semaphore)
- 5.1 信号量的基本概念
- 5.2 二值信号量(Binary Semaphore)
- 5.3. 计数信号量(Counting Semaphore)
- 5.4 信号量创建函数
- 5.4.1 创建二值信号量函数`xSemaphoreCreateBinary`()原型和头文件:
- 5.4.2 创建计数信号量函数`xSemaphoreCreateCounting`()原型和头文件:
- 5.5 信号量获取函数
- 5.5.1 获取二值信号量函数`xSemaphoreTake`()原型和头文件:
- 5.5.2 获取计数信号量函数`xSemaphoreTake`()原型和头文件:
- 5.6 信号量释放函数
- 5.6.1 释放二值信号量函数`xSemaphoreGive`()原型和头文件:
- 5.6.2 释放计数信号量函数`xSemaphoreGive`()原型和头文件:
- 5.7 信号量删除函数
- 5.7.1 删除信号量函数`vSemaphoreDelete`()原型和头文件:
- 5.8 二值信号量使用示例
- 5.9 计数信号量使用示例
- 六、FreeRTOS互斥锁
- 6.1 互斥锁的特点
- 6.2 FreeRTOS 互斥锁的基本函数
- 6.2.1 创建互斥锁函数`xSemaphoreCreateMutex`()原型和头文件:
- 6.2.2 获取互斥锁函数`xSemaphoreTake`()原型和头文件:
- 6.2.3 释放互斥锁函数`xSemaphoreGive`()原型和头文件:
- 6.2.4 中断安全的获取互斥锁函数`xSemaphoreTakeFromISR`()原型和头文件:
- 6.2.5 中断安全的释放互斥锁函数`xSemaphoreGiveFromISR`()原型和头文件:
- 6.3 互斥锁使用示例
- 6.4 总结
- 七、FreeRTOS事件组
- 7.1 事件组的特性
- 7.2 FreeRTOS事件组常用函数
- 7.2.1 创建事件组函数`xEventGroupCreate`()原型和头文件:
- 7.2.2 等待事件组的标志位函数`xEventGroupWaitBits`()原型和头文件:
- 7.2.3 设置事件组中的标志位函数`xEventGroupSetBits`()原型和头文件:
- 7.2.4 清除事件组中的标志位函数**`xEventGroupClearBits`**()原型和头文件:
- 7.2.5 在中断服务例程中清除事件组中的标志位函数`xEventGroupClearBitsFromISR`()原型和头文件:
- 7.2.6 获取当前事件组中的标志位函数`xEventGroupGetBits`()原型和头文件:
- 7.3 事件组使用示例
- 八、FreeRTOS直达任务通知
- 8.1 主要特点
- 8.2 工作原理
- 8.3 FreeRTOS直达任务通知常用函数
- 8.3.1 通知目标任务函数`xTaskNotifyGive`()原型和头文件:
- 8.3.2 设置任务通知函数`xTaskNotify`()原型和头文件:
- 8.3.3 等待通知函数`xTaskNotifyWait`()原型和头文件:
- 8.3.4 获取通知并清除函数`ulTaskNotifyTake`()原型和头文件:
- 8.3.5 中断安全的通知函数`xTaskNotifyFromISR`()原型和头文件:
- 8.4 直达任务通知示例
- 8.4.1 直达任务通知案例一(不指定通知值):
- 8.4.2 直达任务通知案例二(指定通知值):
一、概述
我们的ESP32是基于ESP-IDF来开发的,而ESP-IDF是基于FreeRTOS的框架,里面用到的组件,包括应用程序都是基于FreeRTOS来开发的,因此我们必须掌握FreeRTOS的使用,这里我们我深究FreeRTOS的原理,只关注FreeRTOS API接口的使用,在前面我们学习了Linux系统高级编程,如:文件、进程、进程间通信、线程、多线程、网络等,我们学习过更高级的Linux操作系统,那么使用这个FreeRTOS实时操作系统应该会很简单。
二、为什么要使用实时操作系统RTOS?
在低端设备中程序基本可以分为:裸机和RTOS,针对简单的程序,我们使用裸机程序完全可以满足,一旦功能复杂,程序模块众多的时候,裸机程序很难满足我们的需求,因此我们需要使用操作系统。下面我们举个例子:
-
裸机程序:
while(1){func1();func2();func3(); }
-
RTOS程序:
while(1){func1(); }while(1){func2(); }while(1){func3(); }
对于裸机程序我们需要把所有的功能模块写在一个while
大循环里,可以想象其中任何一个功能模块发生阻塞或者延时等待时,其他的功能模块都要等待。而对于RTOS程序可以创建多个任务处理不同的功能模块,当模块A需要阻塞时,它可以放弃自己的执行时间片,把时间片让给其他任务,这样整个程序可以高效的运行。
三、FreeRTOS任务
3.1 什么是 FreeRTOS 任务?
在 FreeRTOS 中,任务(Task)是一个独立运行的程序代码块,它是实时操作系统的基本执行单元。FreeRTOS 的多任务机制允许多个任务“并行运行”(实际上是通过快速切换任务实现的,称为任务调度)。每个任务都有自己的运行逻辑和执行周期,FreeRTOS 会在后台管理这些任务的执行。
3.2 FreeRTOS 任务的特点
- 并发性:
- FreeRTOS 会在多个任务之间切换,使得它们看起来像是同时运行(多任务)。但在单核 MCU 上,任务的切换是由 FreeRTOS 调度器(Scheduler)以时间片轮转或优先级方式实现的。
- 独立性:
- 每个任务都是独立的,具有自己的上下文(寄存器值、堆栈等)。
- 优先级:
- 每个任务可以设置一个优先级(通常用整数表示),优先级越高,任务获得 CPU 时间的可能性越大。
- 状态管理:
- 任务可以处于不同的状态,如 运行(Running)、就绪(Ready)、阻塞(Blocked) 和 挂起(Suspended)。
- 堆栈:
- 每个任务都需要单独的堆栈来存储其运行时的上下文信息。
3.3 FreeRTOS 任务的生命周期
- 创建任务:
- 使用
xTaskCreate
或xTaskCreatePinnedToCore
创建一个任务,并指定任务函数、名称、堆栈大小、优先级等。
- 使用
- 任务调度:
- FreeRTOS 调度器启动后,任务会根据优先级和状态被安排执行。
- 任务状态切换:
- FreeRTOS 会根据任务的运行情况、事件触发、延时(
vTaskDelay
)等条件切换任务的状态。
- FreeRTOS 会根据任务的运行情况、事件触发、延时(
- 删除任务:
- 使用
vTaskDelete
删除不再需要的任务,并释放其分配的资源。
- 使用
3.4 FreeRTOS 任务的状态
一个 FreeRTOS 任务可以处于以下几种状态:
- Running(运行中):
- 当前任务正在 CPU 上执行。
- Ready(就绪):
- 任务准备好运行,等待 CPU 分配时间片。
- Blocked(阻塞):
- 任务正在等待某个事件(如延时、信号量、队列消息等)发生。
- Suspended(挂起):
- 任务被暂停,直到另一个任务显式唤醒它。
3.5 FreeRTOS 任务与多线程的区别
FreeRTOS 的任务与传统的操作系统中的线程有一些相似之处,但也有以下区别:
- FreeRTOS 更轻量,适用于资源受限的嵌入式系统。
- 任务的调度是实时性的,优先保证高优先级任务的运行。
- 没有标准的进程/线程隔离机制,任务之间共享整个内存空间。
3.6 常用 FreeRTOS 任务函数
3.6.1 任务创建函数xTaskCreate
()原型和头文件:
原型:
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, // 任务函数指针const char * const pcName, // 任务名称configSTACK_DEPTH_TYPE usStackDepth, // 堆栈大小void *pvParameters, // 任务参数UBaseType_t uxPriority, // 任务优先级TaskHandle_t *pxCreatedTask // 任务句柄
);
参数:
pvTaskCode
:指向任务函数的指针,该函数的原型为void TaskFunction(void *pvParameters)
。pcName
:任务的名称(字符串),主要用于调试,可设置为 NULL。usStackDepth
:任务堆栈的大小(以字为单位,通常是 4 字节一个字)。pvParameters
:传递给任务的参数,可为 NULL。uxPriority
:任务的优先级,数值越大优先级越高。pxCreatedTask
:返回任务的句柄,可用于后续操作(如删除任务),可设置为 NULL。
返回值:
- 如果创建成功,返回
pdPASS
;否则返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.2 任务创建函数xTaskCreatePinnedToCore
()原型和头文件:(仅在 ESP-IDF 中可用)
原型:
BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode,const char * const pcName,const uint32_t usStackDepth,void *pvParameters,UBaseType_t uxPriority,TaskHandle_t *pxCreatedTask,const BaseType_t xCoreID
);
参数:
-
pvTaskCode
:指向任务函数的指针,该函数的原型为void TaskFunction(void *pvParameters)
。 -
pcName
:任务的名称(字符串),主要用于调试,可设置为 NULL。 -
usStackDepth
:任务堆栈的大小(以字为单位,通常是 4 字节一个字)。 -
pvParameters
:传递给任务的参数,可为 NULL。 -
uxPriority
:任务的优先级,数值越大优先级越高。 -
pxCreatedTask
:返回任务的句柄,可用于后续操作(如删除任务),可设置为 NULL。 -
xCoreID
:指定运行任务的 CPU 核心,0
或1
(在双核芯片上),可以用tskNO_AFFINITY
表示任何核心。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.3 任务延时函数vTaskDelay
()原型和头文件:
原型:
void vTaskDelay(const TickType_t xTicksToDelay);
参数:
xTicksToDelay
:延时的时钟节拍数,使用宏pdMS_TO_TICKS(ms)
将毫秒转为节拍数。
功能:
- 将当前任务置为阻塞状态,指定时间到达后进入就绪状态。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.4 任务删除函数vTaskDelete
()原型和头文件:
原型:
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数:
xTaskToDelete
:需要删除的任务句柄,传递NULL
表示删除调用该函数的任务本身。
功能:
- 删除指定任务并释放其资源。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.5 暂停任务函数vTaskSuspend
()原型和头文件:
原型:
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
参数:
xTaskToSuspend
:需要挂起的任务句柄,传递NULL
表示挂起调用此函数的任务。
功能:
- 暂停任务的执行,任务进入挂起状态。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.6 恢复挂起任务函数vTaskResume
()原型和头文件:
原型:
void vTaskResume(TaskHandle_t xTaskToResume);
参数:
xTaskToResume
:需要恢复的任务句柄。
功能:
- 将挂起的任务恢复到就绪状态。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.7 返回指定任务优先级函数uxTaskPriorityGet
()原型和头文件:
原型:
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);
参数:
xTask
:需要获取优先级的任务句柄,传递NULL
表示获取调用该函数的任务的优先级。
返回值:
- 返回指定任务的优先级。
头文件:
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
3.6.8 设置任务新优先级函数vTaskPrioritySet
()原型和头文件:
原型:
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);
参数:
xTask
:需要更改优先级的任务句柄,传递NULL
表示更改调用该函数的任务的优先级。uxNewPriority
:新优先级。
功能:
- 设置任务的新优先级。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
3.6.9 获取当前正在运行任务的句柄函数xTaskGetCurrentTaskHandle
()原型和头文件:
原型:
TaskHandle_t xTaskGetCurrentTaskHandle(void);
功能:
- 获取当前正在运行任务的句柄。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
3.7 任务创建实例
以下是一个使用 FreeRTOS 创建任务的示例程序,该程序适用于 ESP32(使用 ESP-IDF 开发框架)。
3.7.1 示例说明:
- 创建两个任务:
- Task1:每秒打印 “Task 1 Running”
- Task2:每两秒打印 “Task 2 Running”
- 使用
vTaskDelay()
进行任务间的延时 - 在
app_main()
中创建任务
3.7.2 完整代码:
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "esp_log.h" // 包含日志头文件// 定义任务句柄(可选,用于后续控制任务)
TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;// 任务1:每秒打印一次
void Task1(void *pvParameters) {while (1) {ESP_LOGI("Task1", "Task 1 Running");vTaskDelay(pdMS_TO_TICKS(1000)); // 1000ms (1秒)}
}// 任务2:每两秒打印一次
void Task2(void *pvParameters) {while (1) {ESP_LOGI("Task2", "Task 2 Running"); vTaskDelay(pdMS_TO_TICKS(2000)); // 2000ms (2秒)}
}// FreeRTOS 主程序
void app_main(void) {ESP_LOGI("Main", "Starting FreeRTOS Tasks");// 创建任务1xTaskCreate(Task1, // 任务函数"Task1", // 任务名称2048, // 任务堆栈大小(单位:字)NULL, // 任务参数1, // 任务优先级&Task1Handle // 任务句柄);// 创建任务2xTaskCreate(Task2, // 任务函数"Task2", // 任务名称2048, // 任务堆栈大小NULL, // 任务参数2, // 任务优先级(比Task1高)&Task2Handle // 任务句柄);
}
3.7.3 代码解析:
- 头文件:
#include "freertos/FreeRTOS.h"
→ FreeRTOS 核心库#include "freertos/task.h"
→ 任务管理库#include "esp_log.h"
→ ESP-IDF 日志库(仅适用于 ESP32)#include <stdio.h>
→ 标准输入输出库(通用)
- 任务
Task1
和Task2
:ESP_LOGI("Task1", "Task 1 Running");
→ 通过ESP_LOGI
记录日志vTaskDelay(pdMS_TO_TICKS(1000));
→ 任务休眠 1 秒
- 创建任务
xTaskCreate()
:- 参数:
- 任务函数(
Task1
或Task2
) - 任务名称(字符串)
- 堆栈大小(
2048
,通常足够) - 任务参数(这里传
NULL
) - 优先级(
1
和2
,优先级2
比1
高) - 任务句柄(可用于管理任务)
- 任务函数(
- 参数:
- ESP-IDF 任务调度:
app_main()
是 ESP32 的入口函数,相当于main()
。xTaskCreate()
创建任务后,FreeRTOS 任务调度开始运行。
3.7.4 运行效果:
当程序运行时,串口日志输出如下:
I (1000) Task1: Task 1 Running
I (2000) Task2: Task 2 Running
I (2000) Task1: Task 1 Running
I (4000) Task2: Task 2 Running
I (4000) Task1: Task 1 Running
...
说明:
Task1
每 1 秒打印一次Task2
每 2 秒打印一次
通过任务的使用,FreeRTOS 可以在单片机中实现并发处理和实时响应。它非常适合需要同时管理多个任务(如传感器读取、通信协议处理、UI 更新等)的嵌入式项目。
四、FreeRTOS队列
FreeRTOS 队列(Queue) 是一种 任务间通信机制,允许任务之间或者任务与中断服务例程(ISR)之间安全地交换数据。队列可以存储不同数据类型,并支持先进先出(FIFO)的存取方式。
4.1 FreeRTOS 队列的特点
✅ 任务间通信:用于不同任务之间传递数据。
✅ 中断安全:可以从中断服务例程(ISR)中发送或接收数据(需使用特定 API)。
✅ 同步机制:队列可以用作同步工具,任务可以阻塞等待数据。
✅ 数据缓存:允许多个数据项排队等待接收。
4.2 常用 FreeRTOS 队列函数
4.2.1 创建队列函数xQueueCreate
()原型和头文件:
原型:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
✅ 功能:创建一个新的队列,并返回该队列的句柄。
🔹 参数说明:
uxQueueLength
:队列最多可容纳的元素个数。uxItemSize
:队列中每个元素的大小(字节)。
🔹 返回值:
- 成功:返回队列句柄(
QueueHandle_t
)。 - 失败:返回
NULL
(可能是内存不足)。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
🔹 示例:
QueueHandle_t myQueue = xQueueCreate(5, sizeof(int)); // 创建存储 5 个整数的队列
4.2.2 发送数据到队列函数xQueueSend
()原型和头文件:
原型:
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
✅ 功能:将数据放入队列(如果队列已满,可选择阻塞等待)。
🔹 参数说明:
xQueue
:目标队列的句柄。pvItemToQueue
:指向要发送数据的指针。xTicksToWait
:如果队列已满,阻塞的时间(单位:tick,portMAX_DELAY
表示无限等待)。
🔹 返回值:
pdPASS
(成功)。errQUEUE_FULL
(队列已满,超时未能放入数据)。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
🔹 示例:
int value = 100;
xQueueSend(myQueue, &value, portMAX_DELAY);
4.2.3 发送数据到队列(中断安全版本)函数xQueueSendFromISR
()原型和头文件:
在 FreeRTOS 中,普通的 xQueueSend()
不能在中断服务程序(ISR) 中使用。要在 ISR(中断服务程序)中安全地向队列发送数据,需要使用中断安全版本 API。
原型:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken
);
✅ 功能:
🔹 在 ISR(中断服务程序)中安全地将数据发送到队列。
🔹 如果队列已满,数据不会被添加,并立即返回。
🔹 参数说明:
xQueue
:目标队列的句柄(必须已创建)。pvItemToQueue
:指向要发送数据的指针(数据的大小由xQueueCreate()
设定)。pxHigherPriorityTaskWoken
:指向一个变量,如果任务切换应该发生,该变量会被设置为pdTRUE
。
🔹 返回值:
pdPASS
数据成功放入队列。errQUEUE_FULL
(队列已满,超时未能放入数据)。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
4.2.4 从队列接收数据函数xQueueReceive
()原型和头文件:
原型:
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
✅ 功能:从队列接收数据(如果队列为空,可选择阻塞等待)。
🔹 参数说明:
xQueue
:目标队列的句柄。pvBuffer
:用于存储接收数据的指针。xTicksToWait
:如果队列为空,阻塞的时间(单位:tick,portMAX_DELAY
表示无限等待)。
🔹 返回值:
pdPASS
(成功接收数据)。pdFAIL
(队列为空,超时未能接收数据)。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
🔹 示例:
int receivedValue;
if (xQueueReceive(myQueue, &receivedValue, portMAX_DELAY) == pdPASS) {printf("Received: %d\n", receivedValue);
}
4.2.5 从队列接收数据(中断安全版本)函数xQueueReceiveFromISR
()原型和头文件:
原型:
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken
);
✅ 功能:
在中断服务例程(ISR)中,从队列中接收数据。与普通的 xQueueReceive()
不同,xQueueReceiveFromISR()
适用于 ISR 中的队列数据接收,并且不会导致阻塞。
🔹 参数说明:
xQueue
:目标队列的句柄,必须是已经创建的队列。pvBuffer
:用于存储接收数据的缓冲区。接收到的数据会存储到此指针指向的内存中。pxHigherPriorityTaskWoken
:这是一个输出参数。如果接收数据成功并且需要执行任务切换,它会被设置为pdTRUE
。
🔹 返回值:
pdPASS
数据成功从队列中接收并返回。pdFAIL
(队列为空,超时未能接收数据)。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
4.2.6 获取队列中元素个数函数uxQueueMessagesWaiting
()原型和头文件:
原型:
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);
✅ 功能:返回当前队列中存储的元素个数。
🔹 参数说明:
xQueue
:目标队列的句柄。
🔹 返回值:
- 当前队列中的元素个数。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
🔹 示例:
UBaseType_t itemCount = uxQueueMessagesWaiting(myQueue);
printf("Queue has %d items\n", itemCount);
4.2.7 获取队列中剩余空间函数uxQueueSpacesAvailable
()原型和头文件:
原型:
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);
✅ 功能:返回队列中剩余的可用空间个数。
🔹 参数说明:
xQueue
:目标队列的句柄。
🔹 返回值:
- 队列中可存储的剩余元素个数。
🔹 头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
🔹 示例:
UBaseType_t freeSpace = uxQueueSpacesAvailable(myQueue);
printf("Queue free space: %d\n", freeSpace);
4.3 FreeRTOS队列示例
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "freertos/queue.h" // 包含队列管理头文件
#include "esp_log.h" // 包含日志头文件QueueHandle_t queueHandle = NULL; // 创建队列句柄struct QueueData{ // 队列数据结构int value;
};/* 任务A 从队列中读取数据 */
void TaskA(void *pvParameters)
{struct QueueData data;// 从队列中读取数据并打印while(1){//BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);if(xQueueReceive(queueHandle, &data, 100) == pdTRUE){ // 读取队列数据,超时时间为100msESP_LOGI("Task1", "从队列中读取数据:%d", data.value);}else{ESP_LOGI("Task1", "队列为空");}}
}/* 任务B 向队列中写入数据 */
void TaskB(void *pvParameters)
{struct QueueData data = {0};// 向队列中每隔1秒写入数据while(1){// BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);xQueueSend(queueHandle, &data, 100); // 向队列中写入数据,超时时间为100msvTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒data.value++;}
}void app_main(void)
{//QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);queueHandle = xQueueCreate(10, sizeof(struct QueueData)); // 创建队列,队列长度为10,队列元素大小为struct QueueData的大小if(queueHandle == NULL){printf("创建队列失败\r\n");return ;}//BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(&TaskA, "TaskA", 2048, NULL, 3, NULL, 1); // 创建任务A,任务栈大小为2048,优先级为3,运行在core1上xTaskCreatePinnedToCore(&TaskB, "TaskB", 2048, NULL, 3, NULL, 1); // 创建任务B,任务栈大小为2048,优先级为3,运行在core1上
}
这个代码实现了两个任务:TaskA
和 TaskB
,它们通过 FreeRTOS 的队列进行通信。接下来我会详细解释每个部分。
代码说明
- 包含头文件
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
这些头文件是用来支持 FreeRTOS 操作和 ESP32 的日志功能。分别用于标准输入输出、FreeRTOS 的任务和队列操作,以及 ESP32 上的日志输出。
- 队列数据结构
struct QueueData{int value;
};
这是一个简单的结构体,代表队列中传递的数据。队列中每次传递的是 QueueData
结构体类型的数据。在本例中,队列只保存一个整数值 value
。
- 任务A:从队列读取数据
void TaskA(void *pvParameters)
{struct QueueData data;while(1){if(xQueueReceive(queueHandle, &data, 100) == pdTRUE){ESP_LOGI("Task1", "从队列中读取数据:%d", data.value);} else {ESP_LOGI("Task1", "队列为空");}}
}
TaskA
是一个读取任务。它每次从队列中接收一个数据项,超时时间为100ms。如果队列为空,则输出日志 队列为空
;否则,它会输出接收到的数据值。
- 任务B:向队列写入数据
void TaskB(void *pvParameters)
{struct QueueData data = {0};while(1){xQueueSend(queueHandle, &data, 100); // 向队列中写入数据,超时时间为100msvTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒data.value++; // 每次递增值}
}
TaskB
是一个写入任务。它每隔1秒向队列中写入一个 QueueData
类型的数据项,并将其中的 value
递增。在每次写入之后,任务会延时1秒。
- 主函数(app_main)
void app_main(void)
{queueHandle = xQueueCreate(10, sizeof(struct QueueData)); // 创建队列if(queueHandle == NULL){printf("创建队列失败\r\n");return;}xTaskCreatePinnedToCore(&TaskA, "TaskA", 2048, NULL, 3, NULL, 1); // 创建任务A,任务栈大小为2048,优先级为3,运行在core1上xTaskCreatePinnedToCore(&TaskB, "TaskB", 2048, NULL, 3, NULL, 1); // 创建任务B,任务栈大小为2048,优先级为3,运行在core1上
}
- 创建一个队列,队列最多可以容纳10个
QueueData
类型的数据项。 - 创建并启动两个任务
TaskA
和TaskB
。两个任务都在 core1 上运行。
运行结果
这段代码通过使用 FreeRTOS 的队列完成了任务间的通信,TaskB
向队列写入数据,TaskA
从队列读取数据并打印。
五、FreeRTOS信号量(Semaphore)
信号量(Semaphore) 是一种用于多任务同步和互斥的机制,广泛应用于嵌入式系统和实时操作系统中。FreeRTOS 提供了两种类型的信号量:
- 二值信号量(Binary Semaphore)
- 计数信号量(Counting Semaphore)
5.1 信号量的基本概念
信号量的核心思想是用于协调多个任务的访问顺序,通常用于以下目的:
- 任务同步:确保多个任务在特定条件下同步执行。
- 互斥访问:防止多个任务同时访问共享资源(如全局变量、外设等)。
5.2 二值信号量(Binary Semaphore)
二值信号量只有两个状态:信号量被占用(0) 或 信号量空闲(1)。因此,它常常用作 互斥锁,用来保护共享资源。
工作原理:
- 任务通过获取信号量来表示占用资源。
- 任务通过释放信号量来表示释放资源。
常见用法:
- 用于任务同步。例如,任务 A 执行完成后,通过释放二值信号量来通知任务 B 开始执行。
- 用于互斥访问临界区,以确保资源的独占访问。
5.3. 计数信号量(Counting Semaphore)
计数信号量可以具有任意整数值,通常用来表示资源的可用数量。例如,表示某种资源池中剩余资源的数量。
工作原理:
- 每次任务获取信号量时,计数器减 1。
- 每次任务释放信号量时,计数器加 1。
常见用法:
- 用于管理一个有限数量的资源池,例如,有限数量的缓冲区、硬件外设等。
- 任务通过计数信号量控制对资源的访问,确保资源池中的资源被合理分配。
5.4 信号量创建函数
5.4.1 创建二值信号量函数xSemaphoreCreateBinary
()原型和头文件:
原型:
SemaphoreHandle_t xSemaphoreCreateBinary(void);
功能:
创建一个二值信号量,通常用于互斥访问。
参数:
- 无参数。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
- 如果成功,返回一个有效的信号量句柄(
SemaphoreHandle_t
)。 - 如果失败,返回
NULL
。
示例:
SemaphoreHandle_t binarySemaphore = xSemaphoreCreateBinary();
5.4.2 创建计数信号量函数xSemaphoreCreateCounting
()原型和头文件:
原型:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
功能:
创建一个计数信号量,通常用于管理多个资源。
参数:
uxMaxCount
:信号量的最大计数值,表示可用资源的最大数量。uxInitialCount
:信号量的初始计数值,表示当前可用资源的数量。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
- 如果成功,返回一个有效的信号量句柄(
SemaphoreHandle_t
)。 - 如果失败,返回
NULL
。
示例:
SemaphoreHandle_t countingSemaphore = xSemaphoreCreateCounting(5, 3);
5.5 信号量获取函数
5.5.1 获取二值信号量函数xSemaphoreTake
()原型和头文件:
原型:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
功能:
从信号量获取(阻塞当前任务),直到信号量可用或超时。
参数:
xSemaphore
:信号量句柄。xTicksToWait
:等待信号量的最大时间(单位:ticks)。如果设置为0
,表示不阻塞;如果设置为portMAX_DELAY
,表示无限等待。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
pdTRUE
:成功获取信号量。pdFALSE
:未能获取信号量,通常是超时或队列已满。
示例:
if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {// 成功获取信号量,进行操作
} else {// 超时或其他错误
}
5.5.2 获取计数信号量函数xSemaphoreTake
()原型和头文件:
原型:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
功能:
从计数信号量获取,计数会减少,直到信号量值变为0或超时。
参数:
xSemaphore
:信号量句柄。xTicksToWait
:等待信号量的最大时间(单位:ticks)。如果设置为0
,表示不阻塞;如果设置为portMAX_DELAY
,表示无限等待。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
pdTRUE
:成功获取信号量。pdFALSE
:未能获取信号量,通常是超时或计数为0。
示例:
if (xSemaphoreTake(countingSemaphore, portMAX_DELAY) == pdTRUE) {// 成功获取信号量,进行操作
}
5.6 信号量释放函数
5.6.1 释放二值信号量函数xSemaphoreGive
()原型和头文件:
原型:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
功能:
释放一个二值信号量,使其他任务可以获取它。
参数:
xSemaphore
:信号量句柄。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
pdTRUE
:成功释放信号量。pdFALSE
:信号量释放失败(通常在非阻塞获取的情况下)。
示例:
xSemaphoreGive(binarySemaphore); // 释放信号量
5.6.2 释放计数信号量函数xSemaphoreGive
()原型和头文件:
原型:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
功能:
释放计数信号量,将计数器加 1,表示有一个资源变得可用。
参数:
xSemaphore
:信号量句柄。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
pdTRUE
:成功释放信号量。pdFALSE
:信号量释放失败。
示例:
xSemaphoreGive(countingSemaphore); // 释放信号量
5.7 信号量删除函数
5.7.1 删除信号量函数vSemaphoreDelete
()原型和头文件:
原型:
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
功能:
删除信号量,并释放其占用的资源。
参数:
xSemaphore
:要删除的信号量句柄。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
返回值:
- 无返回值。
示例:
vSemaphoreDelete(binarySemaphore); // 删除信号量
5.8 二值信号量使用示例
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "freertos/semphr.h" // 包含信号量管理头文件
#include "esp_log.h" // 包含日志头文件SemaphoreHandle_t xSemaphore = NULL; // 信号量句柄void TaskA(void *pvParameters)
{while(1){// BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE){ // 尝试获取信号量,如果成功,则执行下面语句ESP_LOGI("TEST", "TaskA get semaphore");}}
}void TaskB(void *pvParameters)
{while(1){// BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);if(xSemaphoreGive(xSemaphore) == pdTRUE){ // 尝试释放信号量,如果成功,则执行下面语句ESP_LOGI("TEST", "TaskB release semaphore");vTaskDelay(pdMS_TO_TICKS(1000));}}
}void app_main(void)
{// SemaphoreHandle_t xSemaphoreCreateBinary(void); xSemaphore = xSemaphoreCreateBinary(); // 创建一个二值信号量if(xSemaphore == NULL){ESP_LOGI("TEST", "Semaphore create failed");return;}// BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, NULL, 1); // 创建任务A,优先级为3,运行在core1上xTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, NULL, 1); // 创建任务B,优先级为3,运行在core1上
}
在这个例子中,TaskA
会等待信号量,TaskB
会定期释放信号量。TaskA
和 TaskB
使用 xSemaphoreTake
和 xSemaphoreGive
实现信号量的获取和释放,从而实现任务间的同步。
5.9 计数信号量使用示例
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "freertos/semphr.h" // 包含信号量管理头文件
#include "esp_log.h" // 包含日志头文件SemaphoreHandle_t xSemaphore = NULL; // 信号量句柄/* 任务A 尝试获取信号量资源,延时1秒释放信号量资源 */
void TaskA(void *pvParameters)
{while(1){// BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE){ // 尝试获取信号量资源,等待时间为portMAX_DELAYESP_LOGI("TaskA", "获取到信号量资源");vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒xSemaphoreGive(xSemaphore); // 释放信号量资源ESP_LOGI("TaskA", "释放信号量资源");}}
}/* 任务B 每隔1.5秒释放一个信号量资源 */
void TaskB(void *pvParameters)
{while(1){ESP_LOGI("TaskB", "释放一个信号量");// BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);xSemaphoreGive(xSemaphore); // 释放一个信号量资源vTaskDelay(pdMS_TO_TICKS(1500)); // 延时1.5秒}
}/* 任务C 尝试获取信号量资源,延时0.5秒释放信号量资源 */
void TaskC(void *pvParameters)
{while(1){// BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE){ // 尝试获取信号量资源,等待时间为portMAX_DELAYESP_LOGI("TaskC", "获取到信号量资源");vTaskDelay(pdMS_TO_TICKS(500)); // 延时0.5秒xSemaphoreGive(xSemaphore); // 释放信号量资源ESP_LOGI("TaskC", "释放信号量资源");}}
}void app_main(void)
{// SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);xSemaphore = xSemaphoreCreateCounting(3, 3); // 创建信号量,最大计数为3,初始计数为3if(xSemaphore == NULL){ESP_LOGI("main", "Semaphore create failed!");return;}// BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 2, NULL, 1); // 创建任务A,优先级为2,运行在core1上xTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 1, NULL, 1); // 创建任务B,优先级为1,运行在core1上xTaskCreatePinnedToCore(TaskC, "TaskC", 2048, NULL, 2, NULL, 1); // 创建任务C,优先级为2,运行在core1上
}
程序运行后,每个任务都依赖于计数信号量来管理共享资源:
- 任务 A 和 任务 C 都会尝试获取资源,但最多只能同时有 3 个任务获得资源。如果资源池中有空闲资源,它们会成功获取并执行。
- 任务 B 会定期释放资源,允许其他任务使用。
六、FreeRTOS互斥锁
FreeRTOS 互斥锁(Mutex,Mutual Exclusion)是用于解决多任务并发访问共享资源时可能出现的竞争条件问题的工具。它是一种特殊类型的信号量,主要用于保护共享资源,确保在某一时刻只有一个任务可以访问该资源,其他任务必须等待直到该资源被释放。
6.1 互斥锁的特点
- 独占访问:互斥锁的一个关键特点是“独占性”,即当一个任务获得了互斥锁后,其他任务只能等待该锁被释放,才能继续操作共享资源。
- 优先级继承:FreeRTOS 的互斥锁实现了优先级继承机制,即当一个低优先级的任务持有互斥锁时,如果一个高优先级任务请求该锁,低优先级任务的优先级将被提升到高优先级任务的级别,避免了优先级反转问题。高优先级任务完成后,低优先级任务会恢复其原始优先级。
- 适用于临界区保护:互斥锁主要用于保护访问共享资源的临界区,确保在同一时刻只有一个任务能执行这些操作,避免资源冲突。
- 不同于信号量:虽然互斥锁和信号量都可以用来同步任务,但它们有不同的应用场景。互斥锁是专门用于互斥访问共享资源的,而信号量更灵活,可以用来实现任务间的通知、资源计数等。
6.2 FreeRTOS 互斥锁的基本函数
在 FreeRTOS 中,互斥锁的操作类似于信号量的操作,提供了创建、获取、释放等功能。
6.2.1 创建互斥锁函数xSemaphoreCreateMutex
()原型和头文件:
函数原型:
SemaphoreHandle_t xSemaphoreCreateMutex(void);
参数:
- 无
返回值:
- 返回一个
SemaphoreHandle_t
类型的互斥锁句柄。如果创建失败,则返回NULL
。
头文件:
#include "freertos/semphr.h"
说明:
- 用于创建一个互斥锁,返回的句柄用于获取和释放互斥锁。
- 互斥锁的初始状态是未被占用。
6.2.2 获取互斥锁函数xSemaphoreTake
()原型和头文件:
函数原型:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);
参数:
xMutex
:要获取的互斥锁的句柄(SemaphoreHandle_t
类型)。xTicksToWait
:等待时间,单位为 FreeRTOS 时钟滴答数。如果设置为portMAX_DELAY
,则任务将永远等待,直到获取到锁。如果指定了一个具体的时间值,则任务会在等待超时后返回。
返回值:
pdTRUE
:成功获取锁。pdFALSE
:未成功获取锁。
头文件:
#include "freertos/semphr.h"
说明:
- 该函数使任务等待互斥锁的获取。如果当前有其他任务持有该锁,任务将被阻塞直到获得锁。
xTicksToWait
参数可以控制任务等待锁的最长时间。
6.2.3 释放互斥锁函数xSemaphoreGive
()原型和头文件:
函数原型:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex);
参数:
xMutex
:要释放的互斥锁的句柄(SemaphoreHandle_t
类型)。
返回值:
pdTRUE
:成功释放锁。pdFALSE
:未成功释放锁。
头文件:
#include "freertos/semphr.h"
说明:
- 该函数用于释放任务持有的互斥锁,允许其他任务获取该锁。
- 只有在任务已经获取到锁的情况下,才可以释放锁。
6.2.4 中断安全的获取互斥锁函数xSemaphoreTakeFromISR
()原型和头文件:
函数原型:
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xMutex, BaseType_t *pxHigherPriorityTaskWoken);
参数:
xMutex
:要获取的互斥锁的句柄(SemaphoreHandle_t
类型)。pxHigherPriorityTaskWoken
:指向BaseType_t
类型的指针。如果获取锁时发生了任务切换(例如高优先级任务被唤醒),则该参数设置为pdTRUE
,否则为pdFALSE
。
返回值:
pdTRUE
:成功获取锁。pdFALSE
:未成功获取锁。
头文件:
#include "freertos/semphr.h"
说明:
- 该函数用于在中断上下文中获取互斥锁,必须用于中断服务例程(ISR)中。任务执行时需要使用
xSemaphoreTake
,而在 ISR 中则应该使用此函数。 pxHigherPriorityTaskWoken
用于指示是否需要在获取锁后进行任务切换。
6.2.5 中断安全的释放互斥锁函数xSemaphoreGiveFromISR
()原型和头文件:
函数原型:
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xMutex, BaseType_t *pxHigherPriorityTaskWoken);
参数:
xMutex
:要释放的互斥锁的句柄(SemaphoreHandle_t
类型)。pxHigherPriorityTaskWoken
:指向BaseType_t
类型的指针。如果释放锁时发生了任务切换(例如高优先级任务被唤醒),则该参数设置为pdTRUE
,否则为pdFALSE
。
返回值:
pdTRUE
:成功释放锁。pdFALSE
:未成功释放锁。
头文件:
#include "freertos/semphr.h"
说明:
- 该函数用于在中断上下文中释放互斥锁,适用于中断服务例程(ISR)中的任务。用于释放互斥锁并确保任务调度的正确性。
6.3 互斥锁使用示例
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "freertos/semphr.h" // 包含信号量管理头文件
#include "esp_log.h" // 包含日志头文件SemaphoreHandle_t xMutex; // 创建互斥锁句柄void TaskA(void *pvParameters)
{while(1){// BaseType_t xSemaphoreTake(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE){ // 尝试获取互斥锁ESP_LOGI("TaskA", "TsakA 获取互斥锁成功");vTaskDelay(pdMS_TO_TICKS(1000)); // 休眠1秒// BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex);xSemaphoreGive(xMutex); // 释放互斥锁ESP_LOGI("TaskA", "TsakA 释放互斥锁成功");}}
}void TaskB(void *pvParameters)
{while(1){// BaseType_t xSemaphoreTake(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE){ // 尝试获取互斥锁ESP_LOGI("TaskB", "TsakB 获取互斥锁成功");vTaskDelay(pdMS_TO_TICKS(1000)); // 休眠1秒xSemaphoreGive(xMutex); // 释放互斥锁ESP_LOGI("TaskB", "TsakB 释放互斥锁成功");}}
}void app_main(void)
{// SemaphoreHandle_t xSemaphoreCreateMutex(void);xMutex = xSemaphoreCreateMutex(); // 创建互斥锁if(xMutex == NULL){ESP_LOGE("main", "create mutex failed");return;}// BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, NULL, 0); // 创建任务AxTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, NULL, 1); // 创建任务B
}
代码解释:
xSemaphoreTake(xMutex, portMAX_DELAY)
:任务尝试获取互斥锁,如果当前没有其他任务持有锁,任务将成功获取并继续执行。如果锁已经被其他任务占用,当前任务将被阻塞,直到锁被释放。xSemaphoreGive(xMutex)
:任务执行完临界区操作后,释放锁,允许其他任务获取该锁。- 通过互斥锁,任务1和任务2互斥地访问共享资源(这里使用延时模拟临界区操作)。
6.4 总结
- FreeRTOS 的互斥锁(Mutex)是用来同步多个任务对共享资源的访问的工具。
- 它确保在任何时刻只有一个任务可以访问临界区。
- 互斥锁是信号量的特殊版本,具有优先级继承机制,可以有效避免优先级反转问题。
- 使用时需要根据任务的需求进行合理的创建、获取、释放。
七、FreeRTOS事件组
FreeRTOS事件组(Event Groups)是一种用于任务之间同步的机制,允许多个任务通过等待和设置标志位来实现通信和同步。它是FreeRTOS中的一个重要特性,可以使任务在多个事件之间同步,适用于高效地管理多个事件的状态。
7.1 事件组的特性
- 事件标志:事件组包含多个位标志,每个标志位可以是独立的,可以表示某个事件的发生与否。每个事件组可以有多个位,每个位可以代表一个事件。
- 任务等待和设置标志:任务可以等待一个或多个事件标志的设置,并且可以设置一个或多个事件标志。任务通过设置标志来通知其他任务某些条件已满足。
- 并发任务的同步:多个任务可以使用事件组来同步和交换数据。一个任务可以设置事件组中的一个或多个标志,其他任务可以等待这些标志被设置或清除。
- 位操作:事件组是通过位操作来管理标志位的,可以进行位与(AND)、位或(OR)等操作。
7.2 FreeRTOS事件组常用函数
7.2.1 创建事件组函数xEventGroupCreate
()原型和头文件:
函数原型:
EventGroupHandle_t xEventGroupCreate(void);
功能:创建一个事件组,事件组是一个位域,每个位表示一个事件标志。
参数:
- 无参数。
返回值:
- 返回创建的事件组句柄,失败时返回
NULL
。
头文件:
#include "freertos/event_groups.h"
7.2.2 等待事件组的标志位函数xEventGroupWaitBits
()原型和头文件:
函数原型:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,EventBits_t uxBitsToWaitFor,BaseType_t xClearOnExit,BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
功能:等待指定的事件组标志位,直到标志位被设置,或者等待超时。
参数:
xEventGroup
:事件组的句柄。uxBitsToWaitFor
:需要等待的标志位的掩码。xClearOnExit
:指定在返回时是否清除标志位,pdTRUE
:清除,pdFALSE
:不清除。xWaitForAllBits
:是否等待所有位都被设置,pdTRUE
:所有位必须设置,pdFALSE
:任何位设置就返回。xTicksToWait
:等待的时间,以滴答计时。如果设置为portMAX_DELAY
,任务将一直等待直到满足条件。
返回值:
- 返回设置的事件组标志位。
头文件:
#include "freertos/event_groups.h"
7.2.3 设置事件组中的标志位函数xEventGroupSetBits
()原型和头文件:
函数原型:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet );
功能:设置事件组中的一个或多个标志位。
参数:
xEventGroup
:事件组的句柄。uxBitsToSet
:要设置的标志位掩码。
返回值:
- 返回当前事件组中的所有标志位的掩码。
头文件:
#include "freertos/event_groups.h"
7.2.4 清除事件组中的标志位函数**xEventGroupClearBits
**()原型和头文件:
函数原型:
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear );
功能:清除事件组中的一个或多个标志位。
参数:
xEventGroup
:事件组的句柄。uxBitsToClear
:要清除的标志位掩码。
返回值:
- 返回当前事件组中的所有标志位的掩码。
头文件:
#include "freertos/event_groups.h"
7.2.5 在中断服务例程中清除事件组中的标志位函数xEventGroupClearBitsFromISR
()原型和头文件:
函数原型:
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear, BaseType_t *pxHigherPriorityTaskWoken );
功能:在中断服务例程中清除事件组中的标志位。
参数:
xEventGroup
:事件组的句柄。uxBitsToClear
:要清除的标志位掩码。pxHigherPriorityTaskWoken
:指向一个BaseType_t
类型的指针,用于标识是否有任务被唤醒。
返回值:
- 返回
pdTRUE
或pdFALSE
,指示是否有任务被唤醒。
头文件:
#include "freertos/event_groups.h"
7.2.6 获取当前事件组中的标志位函数xEventGroupGetBits
()原型和头文件:
函数原型:
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );
功能:获取当前事件组中的标志位。
参数:
xEventGroup
:事件组的句柄。
返回值:
- 返回当前事件组中的标志位掩码。
头文件:
#include "freertos/event_groups.h"
7.3 事件组使用示例
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "freertos/event_groups.h" // 包含事件组管理头文件
#include "esp_log.h" // 包含日志头文件#define NUM0_BIT BIT0 // 定义事件组中位0的位置
#define NUM1_BIT BIT1 // 定义事件组中位1的位置static EventGroupHandle_t xEventGroup; // 事件组句柄/* 任务A 设置事件组位 */
void TaskA(void *pvParameters)
{// 定时设置不同的事件组位while(1){// EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet );xEventGroupSetBits(xEventGroup, NUM0_BIT); // 设置事件组位0vTaskDelay(pdMS_TO_TICKS(1000));xEventGroupSetBits(xEventGroup, NUM1_BIT); // 设置事件组位1vTaskDelay(pdMS_TO_TICKS(1000));}
}/* 任务B 等待事件组位 */
void TaskB(void *pvParameters)
{EventBits_t eventBits; // 事件位变量// 等待事件位0和位1同时发生while(1){// EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, EventBits_t uxBitsToWaitFor, BaseType_t xClearOnExit, BaseType_t xWaitForAllBits, TickType_t xTicksToWait );eventBits = xEventGroupWaitBits(xEventGroup, NUM0_BIT|NUM1_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000)); // 等待事件组位0和位1同时发生,超时时间5000msif(eventBits & NUM0_BIT){ // 事件组位0发生ESP_LOGI("TaskB", "获取到事件组位0"); }if(eventBits & NUM1_BIT){ // 事件组位1发生ESP_LOGI("TaskB", "获取到事件组位1"); }}
}void app_main(void)
{// EventGroupHandle_t xEventGroupCreate(void);xEventGroup = xEventGroupCreate(); // 创建事件组if(xEventGroup == NULL){ESP_LOGI("main", "EventGroupCreate failed!");return;}// BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, NULL, 1); // 创建任务AxTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, NULL, 1); // 创建任务B
}
代码解释:
- 任务A 每隔1秒分别设置
NUM0_BIT
和NUM1_BIT
。 - 任务B 每次从事件组中等待
NUM0_BIT
和NUM1_BIT
同时设置。等待5秒超时,如果在此时间内成功获取到这两个事件,它会打印日志。 - 事件组 用来同步任务A和任务B,通过设置和等待事件标志位实现任务间的协调。
在程序运行时,任务A将每秒设置 NUM0_BIT
和 NUM1_BIT
,而任务B会等待这两个标志位同时被设置并打印相关信息。如果任务B成功接收到信号,它会输出日志。
八、FreeRTOS直达任务通知
FreeRTOS直达任务通知(Direct-to-Task Notifications)是一种高效的任务间通信机制,它允许一个任务通过发送“通知”直接通知另一个任务。这种通知机制是专为任务间快速信号传递设计的,比传统的信号量、队列等通信方式更加轻量级、快速和高效。
8.1 主要特点
- 任务直接通知任务:
- 任务可以通过直接给另一个任务发送通知,而不需要使用队列、信号量或消息传递等传统的同步和通信机制。
- 高效性:
- 直达任务通知的设计目标是减少任务间同步的开销,因此它比队列和信号量机制更加高效。它占用的内存更少,并且通知操作通常只需少量时钟周期。
- 轻量级的通知机制:
- 这是一种非常轻量级的机制,通常用于实现任务之间的简单通知或事件触发,而不需要复杂的数据交换。
- 任务直接通知:
- 与队列不同,通知通常不携带大数据。它可以通过发送一个整数值或标志来传递信息。这个整数值可以表示一个事件、状态或者一个任务的标识。
- 可以用作信号量替代:
- 任务通知可以用来替代传统的二值信号量,但它仅支持任务之间的同步,不适用于中断到任务的同步。
8.2 工作原理
- 每个任务都有一个与之关联的“通知值”(或叫做“通知变量”),这个通知值可以包含多个标志位。
- 任务通过调用
xTaskNotifyGive()
发送通知,而接收任务则可以通过xTaskNotifyWait()
等函数来接收通知。 - 通知值一般是一个32位的整数,这些值可以被分解成多个标志位。任务可以通过设置、清除或查询这些标志位来进行同步或传递简单信息。
8.3 FreeRTOS直达任务通知常用函数
8.3.1 通知目标任务函数xTaskNotifyGive
()原型和头文件:
用于向目标任务发送一个通知,通常用于给目标任务一个信号,表示某个事件发生了,任务可以做出响应。
原型:
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
参数说明:
xTaskToNotify
:需要接收通知的任务句柄。
返回值:
pdTRUE
:通知成功发送。pdFALSE
:通知发送失败。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
示例:
xTaskNotifyGive(xTaskHandle); // 通知任务xTaskHandle,触发目标任务执行
8.3.2 设置任务通知函数xTaskNotify
()原型和头文件:
该函数不仅允许发送通知,还能指定通知值,通知值可以用于传递状态信息。
原型:
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction);
参数说明:
-
xTaskToNotify
:需要接收通知的任务句柄。 -
ulValue
:发送的通知值,通常用于传递额外的信息。它是一个32位的整数。 -
eAction
:通知操作类型,指定如何处理通知值。它有三个选项:
eSetBits
:将指定的通知值与任务的现有通知值进行按位“或”运算。eIncrement
:将通知值递增1。eSetValueWithOverwrite
:设置通知值为ulValue
,并丢弃先前的通知值。
返回值:
pdTRUE
:通知成功。pdFALSE
:通知失败。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
示例:
xTaskNotify(xTaskHandle, 0x01, eSetBits); // 向任务xTaskHandle发送通知,设置第0位为1
8.3.3 等待通知函数xTaskNotifyWait
()原型和头文件:
此函数使得任务处于阻塞状态,直到接收到通知或超时。它可以在任务中等待指定的通知位,并在接收到通知后进行处理。
原型:
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToWaitFor, uint32_t *pulNotificationValue, TickType_t xTicksToWait);
参数说明:
ulBitsToClearOnEntry
:在进入等待状态时清除的通知位。使用此参数可以清除已接收到的通知位。ulBitsToWaitFor
:等待的通知位。如果多个通知位被设置,任务将继续等待直到所有指定的通知位都被设置。pulNotificationValue
:一个指向uint32_t
类型的指针,用于存储任务的通知值。xTicksToWait
:任务等待通知的最大时间(以系统时钟节拍为单位)。如果设置为portMAX_DELAY
,则表示永远等待。
返回值:
pdTRUE
:成功接收到通知。pdFALSE
:超时,没有接收到通知。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
示例:
uint32_t notificationValue;
xTaskNotifyWait(0, NUM0_BIT, ¬ificationValue, pdMS_TO_TICKS(1000)); // 等待通知位NUM0_BIT,超时10
8.3.4 获取通知并清除函数ulTaskNotifyTake
()原型和头文件:
此函数类似于xTaskNotifyWait()
,但它会在接收到通知后清除通知值。
原型:
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
参数说明:
xClearCountOnExit
:如果为pdTRUE
,则在任务退出时清除通知值。xTicksToWait
:任务等待通知的最大时间,单位为系统时钟节拍。
返回值:
- 返回任务通知的值。如果任务在指定时间内接收到通知,它将返回通知的值。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
示例:
uint32_t notification = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)); // 等待通知,清除通知值,
8.3.5 中断安全的通知函数xTaskNotifyFromISR
()原型和头文件:
这是中断上下文中使用的版本,允许从ISR(中断服务程序)中发送通知。
原型:
BaseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken);
参数说明:
xTaskToNotify
:需要接收通知的任务句柄。ulValue
:通知值,用于传递信息。eAction
:通知操作类型,具体说明参见xTaskNotify()
中的eAction
参数。pxHigherPriorityTaskWoken
:在ISR中,若任务被唤醒,设置此指针为pdTRUE
。
返回值:
pdTRUE
:通知成功。pdFALSE
:通知失败。
头文件:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(xTaskHandle, 0x01, eSetBits, &xHigherPriorityTaskWoken); // 从ISR通知任务
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 如果任务被唤醒,则进行上下文切换
这些函数构成了FreeRTOS中的直达任务通知机制,能够高效地进行任务间同步。通过这些通知,任务可以非常快速且轻量地与其他任务进行通信。这个机制对于任务之间的信号传递、事件通知以及同步操作非常适用,特别是在嵌入式系统中需要高效执行时。
8.4 直达任务通知示例
8.4.1 直达任务通知案例一(不指定通知值):
#include <stdio.h> // 包含输入输出函数头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理函数头文件
#include "esp_log.h" // 包含日志打印函数头文件static TaskHandle_t xTaskAHandle = NULL; // 创建任务A句柄
static TaskHandle_t xTaskBHandle = NULL; // 创建任务B句柄/* 任务A 设置通知并通知任务B */
void TaskA(void *pvParameters)
{while(1){ESP_LOGI("TaskA", "TaskA开始运行");// BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);xTaskNotifyGive(xTaskBHandle); // 通知任务BESP_LOGI("TaskA", "TaskA向TaskB发送通知");vTaskDelay(pdMS_TO_TICKS(1000));}
}/* 任务B 接收通知并处理 */
void TaskB(void *pvParameters)
{while(1){// uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 接收通知ESP_LOGI("TaskB", "TaskB接收到TaskA的通知");vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void)
{// BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, &xTaskAHandle, 1); // 创建任务A,名称为TaskA,栈深度为2048,优先级为3,创建句柄存入xTaskAHandle,运行在core1上xTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, &xTaskBHandle, 1); // 创建任务B,名称为TaskB,栈深度为2048,优先级为3,创建句柄存入xTaskBHandle,运行在core1上
}
程序解释:
- 任务A:该任务会每隔1秒发送一次通知给任务B。它使用
xTaskNotifyGive()
函数来触发任务B的执行。 - 任务B:任务B会等待任务A发送的通知,接收到通知后执行任务操作。
ulTaskNotifyTake()
函数用于等待通知,且在接收到通知后进行操作。
关键函数:
xTaskNotifyGive(TaskHandle_t xTaskToNotify)
:此函数用于给指定任务发送通知信号。ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait)
:此函数用于等待接收通知信号。如果任务B收到了通知,则执行相关操作。
输出:
当运行此程序时,你会看到以下日志输出:
8.4.2 直达任务通知案例二(指定通知值):
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "esp_log.h" // 包含日志头文件static TaskHandle_t TaskAHandle = NULL; // 创建任务A的句柄
static TaskHandle_t TaskBHandle = NULL; // 创建任务B的句柄/* 任务A 发送指定的通知值给任务B */
void TaskA(void *pvParameters)
{uint32_t value = 100;while(1){ESP_LOGI("TaskA", "TaskA开始运行");// BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction);xTaskNotify(TaskBHandle, value, eSetValueWithoutOverwrite); // 发送通知给任务B,通知值为value,不覆盖原有通知值vTaskDelay(pdMS_TO_TICKS(1000));}
}/* 任务B 接收并检查指定的通知值 */
void TaskB(void *pvParameters)
{uint32_t value;while(1){// uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);value = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); // 接收通知值,不清除通知,等待时间为portMAX_DELAYESP_LOGI("TaskB", "TaskB接收到通知值为%ld", value); if(value == 100){ // 检查通知值是否正确ESP_LOGI("TaskB", "TaskB接收到正确的通知值, 执行任务B的操作");}else{ESP_LOGI("TaskB", "TaskB接收到错误的通知值, 忽略");}vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void)
{// BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, &TaskAHandle, 1); // 创建任务A,名称为TaskA,栈深度为2048,优先级为3,创建句柄存入TaskAHandle,运行在core1上xTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, &TaskBHandle, 1); // 创建任务B,名称为TaskB,栈深度为2048,优先级为3,创建句柄存入TaskBHandle,运行在core1上
}
这段代码实现了在 FreeRTOS 上使用任务通知机制进行任务间的通信。任务A定期向任务B发送通知,任务B接收到通知后会检查通知值并执行相应的操作。以下是对代码的详细介绍:
代码分析:
- 头文件包含:
#include <stdio.h> // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h" // 包含FreeRTOS头文件
#include "freertos/task.h" // 包含任务管理头文件
#include "esp_log.h" // 包含日志头文件
stdio.h
:标准输入输出头文件,用于打印调试信息。freertos/FreeRTOS.h
:包含FreeRTOS核心功能的头文件,提供内核的支持。freertos/task.h
:包含任务管理的头文件,定义了任务创建、调度、删除等API。esp_log.h
:用于日志打印,便于调试和查看任务执行情况。
- 任务句柄声明:
static TaskHandle_t TaskAHandle = NULL; // 创建任务A的句柄
static TaskHandle_t TaskBHandle = NULL; // 创建任务B的句柄
TaskAHandle
和TaskBHandle
用于存储创建任务A和任务B的句柄。这些句柄可以用来管理任务,比如删除任务、查看任务状态等。
- 任务A (
TaskA
):
void TaskA(void *pvParameters)
{uint32_t value = 100;while(1){ESP_LOGI("TaskA", "TaskA开始运行");// BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction);xTaskNotify(TaskBHandle, value, eSetValueWithoutOverwrite); // 发送通知给任务B,通知值为value,不覆盖原有通知值vTaskDelay(pdMS_TO_TICKS(1000));}
}
-
功能
:任务A会周期性地发送通知给任务B。
value
被设置为100
,它是将要发送给任务B的通知值。- 使用
xTaskNotify
发送通知给任务B,通知的值为100
,eSetValueWithoutOverwrite
表示如果任务B之前有通知值,它不会被覆盖(而是添加到现有的值上)。 - 每次发送通知后,任务A会通过
vTaskDelay(pdMS_TO_TICKS(1000))
延时 1 秒。
- 任务B (
TaskB
):
void TaskB(void *pvParameters)
{uint32_t value;while(1){// uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);value = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); // 接收通知值,不清除通知,等待时间为portMAX_DELAYESP_LOGI("TaskB", "TaskB接收到通知值为%ld", value); if(value == 100){ // 检查通知值是否正确ESP_LOGI("TaskB", "TaskB接收到正确的通知值, 执行任务B的操作");}else{ESP_LOGI("TaskB", "TaskB接收到错误的通知值, 忽略");}vTaskDelay(pdMS_TO_TICKS(1000));}
}
-
功能
:任务B会等待任务A发送的通知,并检查通知值。
ulTaskNotifyTake(pdFALSE, portMAX_DELAY)
:任务B调用此函数等待任务A的通知。pdFALSE
表示接收到通知后不会清除通知,portMAX_DELAY
表示任务B会无限期阻塞,直到收到通知。- 一旦接收到通知,任务B会打印接收到的通知值,并判断它是否为预期的
100
。如果是100
,执行任务B的操作;如果不是,忽略并继续等待通知。 - 每次执行完操作后,任务B会延时 1 秒。
app_main
函数:
void app_main(void)
{// BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID);xTaskCreatePinnedToCore(TaskA, "TaskA", 2048, NULL, 3, &TaskAHandle, 1); // 创建任务A,名称为TaskA,栈深度为2048,优先级为3,创建句柄存入TaskAHandle,运行在core1上xTaskCreatePinnedToCore(TaskB, "TaskB", 2048, NULL, 3, &TaskBHandle, 1); // 创建任务B,名称为TaskB,栈深度为2048,优先级为3,创建句柄存入TaskBHandle,运行在core1上
}
-
xTaskCreatePinnedToCore
:用于创建任务并将其固定到指定的CPU核心(在这个例子中是核心1)。
- 任务A 和 任务B 都使用了
xTaskCreatePinnedToCore
来创建,并将它们绑定到核心1。 - 每个任务的栈深度为2048字节,优先级为3,创建时指定任务句柄。
- 任务A 和 任务B 都使用了
代码的整体流程:
- 任务A:每隔 1 秒向任务B发送通知值
100
,通知值不会覆盖原有的值。 - 任务B:持续等待任务A的通知,接收到通知后会检查其值是否为
100
。如果是,它将执行相应的操作,否则忽略该通知。
输出:
当运行此程序时,你会看到以下日志输出: