文章目录
- 1.队列操作
- 2.信号量
- 3.事件组
- 4.直达任务通知
-
-
- 直达任务通知的定义与核心特性
-
- [1. 任务通知相关函数](#1. 任务通知相关函数)
- [2. `eAction`参数的操作说明](#2.
eAction参数的操作说明)
-
- 5.原生和esp-idf区别(freertos)
1.队列操作
任务间的同步
同步是不同任务之间或任务与外部事件之间的协同工作方式,确保多个并发任务按预期顺序 / 时机执行;涉及线程 / 任务的通信与协调机制,目的是避免数据竞争、解决竞态条件,保障系统正确运行`
互斥
互斥是指某一资源同时仅允许一个访问者访问,具有唯一性和排他性。

c
QueueHandle_t xQueueCreate( //创建一个队列,成功返回队列句柄
UBaseType_t uxQueueLength, //队列容量
UBaseType_t uxItemSize //每个队列项所占内存的大小(单位是字节)
);
c
BaseType_t xQueueSend( //向队列头部发送一个消息
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //要发送的消息指针
TickType_t xTicksToWait ); //等待时间
c
BaseType_t xQueueSendToBack( //向队列尾部发送一个消息
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //要发送的消息指针
TickType_t xTicksToWait ); //等待时间
c
//这是 FreeRTOS 中用于队列消息接收的 API 函数,
//核心作用是从指定队列中取出一条消息,
//并将消息内容拷贝到pvBuffer指向的缓冲区中。
BaseType_t xQueueReceive( //从队列接收一条消息
QueueHandle_t xQueue, //队列句柄
void * pvBuffer, //指向接收消息缓冲区的指针
TickType_t xTicksToWait ); //等待时间
c
BaseType_t xQueueSendFromISR( //xQueueSend的中断版本
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //发送的消息指针
BaseType_t * pxHigherPriorityTaskWoken );
//如果发送到队列导致任务解除阻塞,且解除阻塞的任务的优先级高于当前运行的任务
//,则设置为pdTRUE
参数pxHigherPriorityTaskWoken的作用:若消息发送后,解除了更高优先级任务的阻塞,则该参数会被置为pdTRUE。
此时需在中断退出前调用上下文切换函数(如portYIELD_FROM_ISR()),以确保高优先级任务及时运行。
c
//队列操作函数
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"
QueueHandle_t queue_handle = NULL;
typedef struct
{
/* data */
int value;
}queue_data_t;
void taskA(void* param)
{
//从消息队列中接收数据,并打印
queue_data_t data;
while(1)
{
if(xQueueReceive(queue_handle,&data,portMAX_DELAY) == pdTRUE)
{
ESP_LOGI("task","value = %d",data.value);
}
}
}
void taskB(void* param)
{
queue_data_t data;
memset(&data,0,sizeof(queue_data_t));
//向消息队列中发送数据
while(1)
{
xQueueSend(queue_handle,&data,300);
vTaskDelay(pdMS_TO_TICKS(1000));
data.value++;
}
}
void app_main(void)
{
queue_handle = xQueueCreate(10,sizeof(queue_data_t));
xTaskCreate(taskA,"taskA",2048,NULL,10,NULL);
xTaskCreate(taskB,"taskB",2048,NULL,10,NULL);
}

2.信号量

c
//创建二值信号量,成功则返回信号量句柄(二值信号量最大只有1个)
SemaphoreHandle_t xSemaphoreCreateBinary( void );
//创建计数信号量,成功则返回信号量句柄
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, //最大信号量数
UBaseType_t uxInitialCount); //初始信号量数
这是 FreeRTOS 中两种信号量的创建函数:
- 二值信号量(xSemaphoreCreateBinary):
仅支持 0 和 1 两种状态,常用于互斥访问或同步单次事件(如中断通知任务)。创建后初始值默认为 0(需通过xSemaphoreGive释放后变为 1)。 - 计数信号量(xSemaphoreCreateCounting):
支持uxMaxCount以内的数值,常用于资源计数(如有限数量的外设)。
uxInitialCount为初始可用的信号量数量。
c
//获取一个信号量,如果获得信号量,则返回pdTRUE
xSemaphoreTake(
SemaphoreHandle_t xSemaphore, //信号量句柄
TickType_t xTicksToWait); //等待时间
//释放一个信号量
xSemaphoreGive(
SemaphoreHandle_t xSemaphore); //信号量句柄
//删除信号量
void vSemaphoreDelete(
SemaphoreHandle_t xSemaphore);
函数功能解读
这是FreeRTOS中信号量的核心操作函数:
-
xSemaphoreTake(获取信号量)- 尝试获取信号量:若信号量可用(值>0),则信号量值减1,返回
pdTRUE; - 若信号量不可用,任务会阻塞
xTicksToWait个系统节拍,超时则返回pdFALSE。 - 用于申请资源 或等待同步事件。
- 尝试获取信号量:若信号量可用(值>0),则信号量值减1,返回
-
xSemaphoreGive(释放信号量)- 释放信号量,使信号量值加1(二值信号量最多到1,计数信号量不超过最大限制);
- 若有任务因等待该信号量而阻塞,会唤醒优先级最高的任务。
- 用于释放资源 或触发同步事件。
-
vSemaphoreDelete(删除信号量)- 销毁指定的信号量,释放其占用的内存;
- 需确保后续不再使用该信号量句柄。
c
//创建一个互斥锁
SemaphoreHandle_t xSemaphoreCreateMutex( void )
-
互斥锁的创建函数
xSemaphoreCreateMutex是FreeRTOS中创建互斥锁的API,调用后返回互斥锁的句柄(创建失败则返回NULL)。 -
互斥锁的特性
互斥锁与二值信号量功能相似(均用于资源互斥访问),但核心区别是实现了优先级继承机制:
- 当低优先级任务持有互斥锁时,若高优先级任务请求该锁,低优先级任务会临时继承高优先级任务的优先级,避免"优先级反转"问题(即高优先级任务因等待低优先级任务持有的资源而长时间阻塞)。
c
//二值信号量代码实现
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"
SemaphoreHandle_t bin_sem;
void taskA(void* param)
{
//释放信号量
while(1)
{
xSemaphoreGive(bin_sem);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskB(void* param)
{
//等待信号量成功后,打印提示
while(1)
{
if(pdTRUE == xSemaphoreTake(bin_sem,portMAX_DELAY))
{
ESP_LOGI("bin","task");
}
}
}
void app_main(void)
{
bin_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}

3.事件组
- 事件位:用于指示事件是否发生,事件位通常称为事件标志。
- 事件组:是一组事件位,事件组中的事件位通过位编号来引用。

c
//创建一个事件组,返回事件组句柄,失败返回NULL
EventGroupHandle_t xEventGroupCreate( void );
c
//等待事件组中某个标志位,用返回值以确定哪些位已完成设置
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup, //事件组句柄
const EventBits_t uxBitsToWaitFor, //哪些位需要等待
const BaseType_t xClearOnExit, //是否自动清除标志位
const BaseType_t xWaitForAllBits, //是否等待的标志位都成功了才返回
TickType_t xTicksToWait //最大阻塞时间
);
这是FreeRTOS中事件组的核心等待函数,用于任务等待事件组中指定事件位的触发,参数含义如下:
xEventGroup:目标事件组的句柄(需提前通过xEventGroupCreate创建)。uxBitsToWaitFor:需要等待的事件位(用位掩码表示,例如0x01表示等待第0位,0x03表示等待第0、1位)。xClearOnExit:是否在函数返回前自动清除已满足的事件位:pdTRUE:自动清除等待的事件位;pdFALSE:事件位保持当前状态。
xWaitForAllBits:等待条件的类型:pdTRUE:等待所有指定事件位都被置位才返回;pdFALSE:等待任意一个指定事件位被置位就返回。
xTicksToWait:最大阻塞时间(单位为系统节拍Tick),超时则返回当前已置位的事件位。
c
//设置标志位
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup, //事件组句柄
const EventBits_t uxBitsToSet //设置哪个位
);
//清除标志位
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup, //事件组句柄
const EventBits_t uxBitsToClear //清除的标志位
);
功能与参数解读
这是FreeRTOS中事件组的置位/清除操作函数,用于控制事件组中事件位的状态:
-
xEventGroupSetBits(设置标志位)- 作用:将事件组中指定的事件位置为"1"(触发事件);
- 参数:
xEventGroup:目标事件组的句柄;uxBitsToSet:要置位的事件位(位掩码,如0x02表示置位第1位);
- 返回值:返回操作后事件组的当前位状态。
- 场景:任务完成某个操作后,置位对应的事件位,通知等待该事件的任务。
-
xEventGroupClearBits(清除标志位)- 作用:将事件组中指定的事件位置为"0"(重置事件);
- 参数:
xEventGroup:目标事件组的句柄;uxBitsToClear:要清除的事件位(位掩码,如0x03表示清除第0、1位);
- 返回值:返回操作前事件组的位状态(因操作是原子性的,可用于判断清除前的状态)。
- 场景:等待事件的任务处理完成后,手动清除已触发的事件位,以便后续再次等待。
c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"
#include "freertos/event_groups.h"
#define NUM1_BIT BIT0
#define NUM2_BIT BIT1
static EventGroupHandle_t test_event;
void taskA(void* param)
{
//定时设置不同的事件位
while(1)
{
xEventGroupSetBits(test_event,NUM1_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
xEventGroupSetBits(test_event,NUM2_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskB(void * param)
{
//等待事件位
EventBits_t ev;
while(1)
{
ev = xEventGroupWaitBits(test_event,NUM1_BIT|NUM2_BIT,pdTRUE,pdFALSE,pdMS_TO_TICKS(5000));
if(ev & NUM1_BIT)
{
ESP_LOGI("ev","get BIT0 event");
}
if(ev & NUM2_BIT)
{
ESP_LOGI("ev","get BIT1 event");
}
}
}
void app_main(void)
{
test_event = xEventGroupCreate();
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}
4.直达任务通知
直达任务通知的定义与核心特性
-
基础属性 :
每个RTOS任务都包含一个任务通知数组,每条通知有"挂起/非挂起"的状态,同时附带32位通知值。
-
本质特点 :
是直接发送给任务的事件,无需队列、事件组、信号量等中间对象中转;向任务发送直达任务通知时,会将目标任务的对应通知置为"挂起"状态(此"挂起"仅针对通知,并非挂起任务本身)。
1. 任务通知相关函数
c
//用于将通知直接发送到RTOS任务并可能取消该任务的阻塞状态
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify, //要通知的任务句柄
uint32_t ulValue, //携带的通知值
eNotifyAction eAction //执行的操作
);
//等待接收任务通知
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry, //进入函数清除的通知值位
uint32_t ulBitsToClearOnExit, //退出函数清除的通知值位
uint32_t *pulNotificationValue, //通知值
TickType_t xTicksToWait //等待时长
);
2. eAction参数的操作说明
| eAction设置 | 已执行的操作 |
|---|---|
| eNoAction | 目标任务接收事件,但其通知值未更新。这种情况下,不使用ulValue。 |
| eSetBits | 目标任务的通知值 使用ulValue 按位或运算 |
| eIncrement | 目标任务的通知值自增1(类似信号量的give操作) |
| eSetValueWithOverwrite | 目标任务的通知值 无条件设置为ulValue。 |
| eSetValueWithoutOrwrite | 如果目标任务没有挂起的通知,则其通知值 将设置为ulValue。 如果目标任务已经有挂起的通知,则不会更新其通知值。 |
5.原生和esp-idf区别(freertos)
ESP-IDF中FreeRTOS的特殊适配与建议
一、ESP-IDF对FreeRTOS的修改点
- 优先级与多核:优先级机制在多核场景不适用(多任务可同时运行)。
- 自动创建的系统任务 :包含空闲、定时器、
app_main、IPC、ESP定时器等任务。 - 内存堆管理:不使用原生FreeRTOS堆管理,实现了自定义堆。
- 任务创建 :需用
xTaskCreatePinnedToCore()(指定运行核心)。 - 任务删除:避免删除另一个核心上的任务。
- 临界区同步:用自旋锁保证同步。
- 浮点运算任务:必须指定运行核心,不能由系统自动安排。
二、开发建议
- 任务核心指定 :应用开发创建任务时需指定内核,不建议用
tskNO_AFFINITY(由系统自动分配)。 - 核心分工:无线网络相关任务(如WiFi、蓝牙)固定到CPU0(PRO_CPU);应用其余任务固定到CPU1(APP_CPU)。