You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32/FreeRTOS下C++命令/响应服务器的任务同步问题

基于ESP-IDF信号量的串口命令/响应任务同步方案

你之前用volatile bool的问题在于,它仅能保证变量的内存可见性,无法提供线程安全的同步机制(比如原子操作、内存屏障),而FreeRTOS信号量是ESP-IDF原生支持的线程安全同步工具,完全适配你的场景。

以下是替换后的完整代码,核心用二进制信号量实现任务间唤醒通知,用互斥量保护共享状态变量:

全局资源定义与初始化

先定义所需信号量、互斥量和共享缓冲区,在任务启动前完成初始化(比如在app_main()中):

#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"

// 信号量:通知发送任务响应已就绪
SemaphoreHandle_t respSem = NULL;
// 信号量:通知接收任务响应已处理完成
SemaphoreHandle_t ackSem = NULL;
// 互斥量:保护responsePending变量的线程安全访问
SemaphoreHandle_t flagMutex = NULL;
// 标记是否有发送任务在等待响应(受flagMutex保护)
bool responsePending = false;
// 共享缓冲区:传递串口响应数据
char sharedBuffer[100];

// 初始化函数(在app_main中调用)
void sync_init() {
    respSem = xSemaphoreCreateBinary();
    ackSem = xSemaphoreCreateBinary();
    flagMutex = xSemaphoreCreateMutex();
}

发送任务(Task1)修改

替换原volatile bool等待逻辑,改用信号量阻塞等待响应:

// SENDER //
void Task1()
{
    char localBuffer[100];
    
    while (1)
    {
        // 线程安全标记:当前需要等待响应
        xSemaphoreTake(flagMutex, portMAX_DELAY);
        responsePending = true;
        xSemaphoreGive(flagMutex);
        
        sendMessage();  // 执行串口命令发送
        
        // 阻塞等待接收任务的响应通知,收到信号才继续
        xSemaphoreTake(respSem, portMAX_DELAY);
        
        // 安全拷贝共享缓冲区的响应数据到本地
        strcpy(localBuffer, sharedBuffer);
        
        // 线程安全标记:响应已处理完成
        xSemaphoreTake(flagMutex, portMAX_DELAY);
        responsePending = false;
        xSemaphoreGive(flagMutex);
        
        // 通知接收任务:可继续处理后续数据
        xSemaphoreGive(ackSem);
    }
}

接收任务(Task2)修改

替换原volatile bool判断逻辑,改用互斥量读取状态,用信号量等待处理完成通知:

// RECEIVER //
void Task2()
{
    char localBuf[100];
    int fd = open();  // 替换为ESP-IDF串口初始化逻辑,比如uart_driver_install
    
    while (1)
    {
        if (select())  // 替换为ESP-IDF串口可读检查,比如uart_get_buffered_data_len
        {
            read(fd, localBuf);  // 替换为ESP-IDF串口读取API,比如uart_read_bytes
            
            // 线程安全读取:是否有等待的响应请求
            xSemaphoreTake(flagMutex, portMAX_DELAY);
            bool isPending = responsePending;
            xSemaphoreGive(flagMutex);
            
            if (isPending)
            {
                // 将响应数据写入共享缓冲区
                strcpy(sharedBuffer, localBuf);
                
                // 通知发送任务:响应已就绪
                xSemaphoreGive(respSem);
                
                // 阻塞等待发送任务处理完响应,避免缓冲区被重复覆盖
                xSemaphoreTake(ackSem, portMAX_DELAY);
            }
            else
            {
                // 处理非响应类的串口数据
            }
        }
    }
}

关键修改点说明

  1. 信号量替代volatile bool同步逻辑:
    • respSem:发送任务通过xSemaphoreTake阻塞等待,接收任务收到响应后用xSemaphoreGive唤醒,完全替代原responsePending的等待逻辑
    • ackSem:接收任务通过xSemaphoreTake阻塞等待,发送任务处理完响应后用xSemaphoreGive唤醒,替代原cmdAccepted的等待逻辑
  2. 互斥量保护共享状态:
    • 用flagMutex保护responsePending变量的读写,确保多线程下的原子访问,解决volatile bool无法保证原子性的问题
  3. 线程安全的缓冲区访问:
    • 共享缓冲区的读写完全在信号量同步的时序下进行,确保发送任务读取时接收任务不会修改,接收任务写入时发送任务不会读取

注意事项

  • 所有信号量和互斥量必须在任务启动前完成初始化,否则任务会因等待无效句柄而阻塞
  • 串口操作建议替换为ESP-IDF官方UART API(如uart_driver_install、uart_read_bytes),避免使用POSIX风格的open/read/select(除非自行适配)
  • 拷贝字符串时建议增加长度检查(比如用strncpy),避免缓冲区溢出

内容的提问来源于stack exchange,提问作者Danny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 03:45:40