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 { // 处理非响应类的串口数据 } } } }
关键修改点说明
- 信号量替代volatile bool同步逻辑:
respSem:发送任务通过xSemaphoreTake阻塞等待,接收任务收到响应后用xSemaphoreGive唤醒,完全替代原responsePending的等待逻辑ackSem:接收任务通过xSemaphoreTake阻塞等待,发送任务处理完响应后用xSemaphoreGive唤醒,替代原cmdAccepted的等待逻辑
- 互斥量保护共享状态:
- 用
flagMutex保护responsePending变量的读写,确保多线程下的原子访问,解决volatile bool无法保证原子性的问题
- 用
- 线程安全的缓冲区访问:
- 共享缓冲区的读写完全在信号量同步的时序下进行,确保发送任务读取时接收任务不会修改,接收任务写入时发送任务不会读取
注意事项
- 所有信号量和互斥量必须在任务启动前完成初始化,否则任务会因等待无效句柄而阻塞
- 串口操作建议替换为ESP-IDF官方UART API(如
uart_driver_install、uart_read_bytes),避免使用POSIX风格的open/read/select(除非自行适配) - 拷贝字符串时建议增加长度检查(比如用
strncpy),避免缓冲区溢出
内容的提问来源于stack exchange,提问作者Danny
相关产品推荐
相关产品推荐

