ESP32 的 ESP-IDF 框架,通过FreeRTOS实现任务同步和通信的方法-信号量(Semaphore)、互斥锁(Mutex) 、事件组(Event Group)、队列(Queue)

在 ESP32 的 ESP-IDF 框架中,任务同步可以通过 FreeRTOS 提供的多种机制实现,包括信号量(Semaphore)互斥锁(Mutex)队列(Queue)事件组(Event Group)线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。

1. 信号量(Semaphore)

信号量用于任务间的资源计数或事件通知。分为 二进制信号量(Binary Semaphore)计数信号量(Counting Semaphore)

适用场景
  • 二进制信号量:单次事件触发(如中断通知任务)。
  • 计数信号量:资源池管理(如限制同时访问某资源的任务数)。

适用场景:任务 A 完成某个事件后,通知任务 B 继续执行(事件触发型同步)。

核心逻辑:信号量初始为 “空”(不可用),任务 B 等待信号量(阻塞);任务 A 完成事件后释放信号量(变为 “满”),任务 B 被唤醒。

API 函数
// 创建二进制信号量
SemaphoreHandle_t xSemaphoreCreateBinary();
// 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
// 获取信号量(阻塞)
xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
// 释放信号量
xSemaphoreGive(SemaphoreHandle_t xSemaphore);
测试代码(二进制信号量)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semstream.h"
#include "freertos/semphr.h"

SemaphoreHandle_t bin_sem;

void task_sender(void *pvParam) 
{
    while (1) 
    {
        printf("Sender: 发送信号\n");
        xSemaphoreGive(bin_sem); // 释放信号量
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

void task_receiver(void *pvParam) 
{
    while (1) 
    {
        if (xSemaphoreTake(bin_sem, pdMS_TO_TICKS(5000)) == pdTRUE) 
        {
            printf("Receiver: 收到信号\n");
        } 
        else 
        {
            printf("Receiver: 超时未收到信号\n");
        }
    }
}

void app_main() 
{
    bin_sem = xSemaphoreCreateBinary();
    xTaskCreate(task_sender, "sender", 2048, NULL, 5, NULL);
    xTaskCreate(task_receiver, "receiver", 2048, NULL, 5, NULL);
}

2. 互斥锁(Mutex)

互斥锁用于保护共享资源,防止多任务同时访问导致数据竞争。

适用场景

-保护全局变量、硬件外设(如 SPI、I2C)等共享资源,确保同一时间仅一个任务操作资源。

核心逻辑:互斥量初始为 “未锁定”,任务访问资源前需获取互斥量(锁定),完成后释放(解锁);未获取到互斥量的任务会阻塞。

API 函数
// 创建互斥锁
SemaphoreHandle_t xSemaphoreCreateMutex();
// 获取锁
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// 释放锁
xSemaphoreGive(xSemaphore);
测试代码(保护共享变量)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

SemaphoreHandle_t mutex;
int shared_counter = 0;

void task_increment(void *pvParam) 
{
    while (1) 
    {
        xSemaphoreTake(mutex, portMAX_DELAY); // 获取锁
        shared_counter++;
        printf("Task %d: Counter = %d\n", (int)pvParam, shared_counter);
        xSemaphoreGive(mutex); // 释放锁
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

void app_main() 
{
    mutex = xSemaphoreCreateMutex();
    xTaskCreate(task_increment, "task1", 2048, (void*)1, 5, NULL);
    xTaskCreate(task_increment, "task2", 2048, (void*)2, 5, NULL);
}

3. 队列(Queue)

队列用于任务间的数据传输,支持 FIFO 或 LIFO 模式。

适用场景

任务间传递结构化数据(如传感器数据、控制命令),即任务间 传递数据 并隐含同步(发送方发送数据后,接收方被唤醒处理)。

核心逻辑:队列是线程安全的 FIFO 缓冲区;发送方将数据入队(若队列满则阻塞),接收方从队列取数据(若队列空则阻塞)。

API 函数
// 创建队列
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
// 发送数据
xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
// 接收数据
xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
测试代码(传递整数)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

QueueHandle_t data_queue;

void task_producer(void *pvParam) 
{
    int value = 0;
    while (1) 
    {
        printf("Producing: %d\n", value);
        xQueueSend(data_queue, &value, portMAX_DELAY); // 发送数据
        value++;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void task_consumer(void *pvParam) 
{
    int received;
    while (1) 
    {
        if(xQueueReceive(data_queue, &received, pdMS_TO_TICKS(2000)) )
        {
            printf("Consumed: %d\n", received);
        } 
        else 
        {
            printf("队列超时\n");
        }
    }
}

void app_main() 
{
    data_queue = xQueueCreate(5, sizeof(int)); // 队列长度5,存储整数
    xTaskCreate(task_producer, "producer", 2048, NULL, 5, NULL);
    xTaskCreate(task_consumer, "consumer", 2048, NULL, 5, NULL);
}

4. 事件组(Event Group)

事件组通过位掩码实现多任务间的事件同步,允许任务等待多个事件中的任意一个或全部。

适用场景

多任务协同等待多个条件触发(如网络连接成功 + 传感器就绪)。如任务 B 需要等待 多个独立事件 全部 / 部分完成(如等待传感器数据、网络连接、用户输入)。

核心逻辑:事件组用一个 32 位变量(事件位)表示多个事件状态;任务 A/B/C 分别设置对应事件位,任务 D 等待指定事件位组合(全部置位 / 任意一个置位)。

API 函数
// 创建事件组
EventGroupHandle_t xEventGroupCreate();
// 设置事件位
xEventGroupSetBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet);
// 等待事件位
EventBits_t xEventGroupWaitBits(
    EventGroupHandle_t xEventGroup,
    EventBits_t uxBitsToWaitFor,
    BaseType_t xClearOnExit, // pdTRUE: 等待后清除位
    BaseType_t xWaitForAllBits, // pdTRUE: 等待所有位
    TickType_t xTicksToWait
);
测试代码(等待多个事件)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

EventGroupHandle_t event_group;
#define BIT_NET_CONNECTED (1 << 0)
#define BIT_SENSOR_READY  (1 << 1)

void task_network(void *pvParam) 
{
    vTaskDelay(2000 / portTICK_PERIOD_MS);
    printf("网络连接成功\n");
    xEventGroupSetBits(event_group, BIT_NET_CONNECTED);
    vTaskDelete(NULL);
}

void task_sensor(void *pvParam) 
{
    vTaskDelay(3000 / portTICK_PERIOD_MS);
    printf("传感器初始化完成\n");
    xEventGroupSetBits(event_group, BIT_SENSOR_READY);
    vTaskDelete(NULL);
}

void task_main(void *pvParam) 
{
    EventBits_t bits = xEventGroupWaitBits(
        event_group,
        BIT_NET_CONNECTED | BIT_SENSOR_READY,
        pdTRUE, // 清除事件位
        pdTRUE, // 等待所有位
        portMAX_DELAY
    );
    printf("所有条件就绪,开始执行主任务\n");
    vTaskDelete(NULL);
}

void app_main() 
{
    event_group = xEventGroupCreate();
    xTaskCreate(task_network, "network", 2048, NULL, 5, NULL);
    xTaskCreate(task_sensor, "sensor", 2048, NULL, 5, NULL);
    xTaskCreate(task_main, "main_task", 2048, NULL, 5, NULL);
}

5. 完整测试代码

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"        // 信号量/互斥量
#include "freertos/event_groups.h"  // 事件组
#include "freertos/queue.h"         // 队列

//---------------------------------------------------------

SemaphoreHandle_t bin_sem;

void task_sender(void *pvParam) 
{
    while (1) 
    {
        printf("Sender: 发送信号\n");
        xSemaphoreGive(bin_sem); // 释放信号量
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

void task_receiver(void *pvParam) 
{
    while (1) 
    {
        if (xSemaphoreTake(bin_sem, pdMS_TO_TICKS(5000)) == pdTRUE) // portMAX_DELAY : 永久等待
        {
            printf("Receiver: 收到信号\n");
        } 
        else 
        {
            printf("Receiver: 超时未收到信号\n");
        }
    }
}


//---------------------------------------------------------
SemaphoreHandle_t mutex;
int shared_counter = 0;

void task_increment(void *pvParam) 
{
    while (1) 
    {
        xSemaphoreTake(mutex, portMAX_DELAY); // 获取锁
        shared_counter++;
        printf("Task %d: Counter = %d\n", (int)pvParam, shared_counter);
        xSemaphoreGive(mutex); // 释放锁
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

//---------------------------------------------------------
EventGroupHandle_t event_group;
#define BIT_NET_CONNECTED (1 << 0)
#define BIT_SENSOR_READY  (1 << 1)

void task_network(void *pvParam) 
{
    vTaskDelay(2000 / portTICK_PERIOD_MS);
    printf("任务1 网络连接成功\n");
    xEventGroupSetBits(event_group, BIT_NET_CONNECTED);
    vTaskDelete(NULL);
}

void task_sensor(void *pvParam) 
{
    vTaskDelay(3000 / portTICK_PERIOD_MS);
    printf("任务2 传感器初始化完成\n");
    xEventGroupSetBits(event_group, BIT_SENSOR_READY);
    vTaskDelete(NULL);
}
void task_main(void *pvParam) 
{
    EventBits_t bits = xEventGroupWaitBits(
        event_group,
        BIT_NET_CONNECTED | BIT_SENSOR_READY,
        pdTRUE, // 清除事件位
        pdTRUE, // 等待所有位
        portMAX_DELAY
    );
    printf("所有条件就绪,开始执行主任务\n");
    vTaskDelete(NULL);
}


//---------------------------------------------------------
QueueHandle_t data_queue;

void task_producer(void *pvParam) 
{
    int value = 0;
    while (1) 
    {
        printf("Producing: %d\n", value);
        xQueueSend(data_queue, &value, portMAX_DELAY); // 发送数据
        value++;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void task_consumer(void *pvParam) 
{
    int received;
    while (1) 
    {
        if( xQueueReceive(data_queue, &received, pdMS_TO_TICKS(2000)) )
        {
            printf("Consumed: %d\n", received);
        } 
        else 
        {
            printf("队列超时\n");
        }
    }
}



void app_main() 
{
    vTaskDelay(pdMS_TO_TICKS(20000));

    bin_sem = xSemaphoreCreateBinary();
    xTaskCreate(task_sender, "sender", 2048, NULL, 5, NULL);
    xTaskCreate(task_receiver, "receiver", 2048, NULL, 5, NULL);


    mutex = xSemaphoreCreateMutex();
    xTaskCreate(task_increment, "task1", 2048, (void*)1, 5, NULL);
    xTaskCreate(task_increment, "task2", 2048, (void*)2, 5, NULL);


    event_group = xEventGroupCreate();
    xTaskCreate(task_network, "network", 2048, NULL, 5, NULL);
    xTaskCreate(task_sensor, "sensor", 2048, NULL, 5, NULL);
    xTaskCreate(task_main, "main_task", 2048, NULL, 5, NULL);


    data_queue = xQueueCreate(5, sizeof(int)); // 队列长度5,存储整数
    xTaskCreate(task_producer, "producer", 2048, NULL, 5, NULL);
    xTaskCreate(task_consumer, "consumer", 2048, NULL, 5, NULL);

    while(1)
    {
        vTaskDelay(pdMS_TO_TICKS(20000));
    }
}
    

代码关键说明

  • 二值信号量:xSemaphoreCreateBinary() 创建信号量,xSemaphoreGive() 释放,xSemaphoreTake() 获取(最大5S等待)
  • 互斥量:xSemaphoreCreateMutex() 创建互斥量,xSemaphoreTake() 锁定资源,xSemaphoreGive() 解锁
  • 事件组:xEventGroupCreate() 创建事件组,xEventGroupSetBits() 设置事件位,xEventGroupWaitBits() 等待事件位(支持 AND/OR 逻辑)
  • 队列:xQueueCreate() 创建队列(指定深度和数据类型),xQueueSend() 发送数据,xQueueReceive() 接收数据(最大2S等待)
  • 参数是 portMAX_DELAY ,则表示永久等待

在这里插入图片描述

总结

同步方法适用场景关键优势
信号量事件通知、资源计数轻量级,支持二进制和计数模式
互斥锁保护共享资源防止数据竞争,支持优先级继承
队列任务间数据传输结构化数据传递,支持超时机制
事件组多事件协同触发高效等待多个条件,位操作灵活

注意

  1. 避免死锁:确保获取锁/信号量的顺序一致。
  2. 超时处理:为 xSemaphoreTakexQueueReceive 设置合理超时时间。
  3. 优先级反转:使用互斥锁时,启用优先级继承(ESP-IDF 默认启用)。

参考
信号量 API:
https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.5/esp32s3/api-reference/system/freertos_idf.html?highlight=xsemaphorecreatebinary#c.xSemaphoreCreateBinary

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值