ESP32 FreeRTOS Arduino IDE下字符数组入队列及串口输出问题
ESP32 FreeRTOS队列解决多任务数据输出冲突与丢失问题
一、字符数组入队的正确实现
字符数组不能直接传指针入队(Task1的栈数组会被后续采集覆盖),必须拷贝数组完整内容到队列存储空间:
定义队列参数:
按单条消息最大长度(40字节,留足35字符+格式余量)创建队列,长度按Task1的消息生成速率预留缓冲(比如50条,应对Task2短暂阻塞的情况):#define MAX_MSG_LEN 40 QueueHandle_t gpioDataQueue; // 初始化队列 gpioDataQueue = xQueueCreate(50, MAX_MSG_LEN);Task1入队操作:
生成字符数组后,用xQueueSend()拷贝内容入队,设置短阻塞时间避免采集任务被长时间阻塞:char gpioMsg[MAX_MSG_LEN]; snprintf(gpioMsg, MAX_MSG_LEN, "ADC:%d,Digital:%d,PWM:%d", adcVal, digitalVal, pwmVal); // 入队,阻塞10ms,队列满时短暂等待后放弃(避免影响采集) xQueueSend(gpioDataQueue, gpioMsg, pdMS_TO_TICKS(10));
二、Task2无冲突处理双数据源
Task2需同时处理CAN数据和队列数据,采用非阻塞批量读取队列+优先处理CAN的逻辑,避免单数据源阻塞整体流程:
void Task2(void *pvParameters) { // MCP2515初始化代码... mcp2515.reset(); mcp2515.setBitrate(CAN_1000KBPS); mcp2515.setNormalMode(); char canMsg[MAX_MSG_LEN]; char queueMsg[MAX_MSG_LEN]; while (1) { // 1. 优先处理CAN数据(非阻塞读取) can_frame canFrame; if (mcp2515.readMessage(&canFrame)) { // 格式化CAN帧为目标字符串 snprintf(canMsg, MAX_MSG_LEN, "CAN:ID=0x%03X,DATA=", canFrame.can_id); for (int i = 0; i < canFrame.can_dlc; i++) { char hex[3]; sprintf(hex, "%02X", canFrame.data[i]); strcat(canMsg, hex); if (i < canFrame.can_dlc -1) strcat(canMsg, ","); } Serial.write(canMsg); Serial.write("\n"); } // 2. 批量非阻塞读取队列数据(一次最多处理10条,避免占用核心过久) int processed = 0; while (xQueueReceive(gpioDataQueue, queueMsg, 0) == pdTRUE && processed < 10) { Serial.write(queueMsg); Serial.write("\n"); processed++; } // 短延时释放核心,给其他任务运行机会 vTaskDelay(pdMS_TO_TICKS(1)); } }
三、避免数据丢失的核心配置
- 任务优先级:Task2优先级设为高于Task1(比如Task1=1,Task2=2),确保数据处理任务能及时抢占CPU,避免队列溢出。
- 串口优化:用
Serial.write()替代Serial.print(),减少格式化开销,匹配1Mbps波特率的输出能力(1Mbps波特率每秒可处理约3400条35字符消息,完全覆盖2200条的总生成量)。 - 队列长度调整:如果仍出现丢数据,可将队列长度增大至100,进一步提升缓冲能力。
完整可运行代码示例
#include <Arduino.h> #include <SPI.h> #include <mcp2515.h> #define MAX_MSG_LEN 40 #define TASK1_STACK 2048 #define TASK2_STACK 4096 #define TASK1_PRIO 1 #define TASK2_PRIO 2 QueueHandle_t gpioDataQueue; MCP2515 mcp2515(10); // 假设CS引脚为10 void Task1(void *pvParameters) { // GPIO/ADC/PWM初始化 pinMode(2, INPUT); analogReadResolution(12); ledcSetup(0, 1000, 8); ledcAttachPin(4, 0); while (1) { // 采集数据 int adcVal = analogRead(34); int digitalVal = digitalRead(2); int pwmVal = ledcRead(0); // 生成消息 char gpioMsg[MAX_MSG_LEN]; snprintf(gpioMsg, MAX_MSG_LEN, "ADC:%d,Digital:%d,PWM:%d", adcVal, digitalVal, pwmVal); // 入队 xQueueSend(gpioDataQueue, gpioMsg, pdMS_TO_TICKS(10)); // 控制采集频率(每秒200次) vTaskDelay(pdMS_TO_TICKS(5)); } } void Task2(void *pvParameters) { // CAN初始化 mcp2515.reset(); mcp2515.setBitrate(CAN_1000KBPS); mcp2515.setNormalMode(); char canMsg[MAX_MSG_LEN]; char queueMsg[MAX_MSG_LEN]; while (1) { // 处理CAN数据 can_frame canFrame; if (mcp2515.readMessage(&canFrame)) { snprintf(canMsg, MAX_MSG_LEN, "CAN:ID=0x%03X,DATA=", canFrame.can_id); for (int i = 0; i < canFrame.can_dlc; i++) { char hex[3]; sprintf(hex, "%02X", canFrame.data[i]); strcat(canMsg, hex); if (i < canFrame.can_dlc -1) strcat(canMsg, ","); } Serial.write(canMsg); Serial.write("\n"); } // 处理队列数据 int processed = 0; while (xQueueReceive(gpioDataQueue, queueMsg, 0) == pdTRUE && processed < 10) { Serial.write(queueMsg); Serial.write("\n"); processed++; } vTaskDelay(pdMS_TO_TICKS(1)); } } void setup() { Serial.begin(1000000); while (!Serial); // 调试用,可注释 // 创建队列 gpioDataQueue = xQueueCreate(50, MAX_MSG_LEN); if (!gpioDataQueue) { Serial.println("Queue creation failed!"); while(1); } // 创建分核任务 xTaskCreatePinnedToCore(Task1, "GPIO采集", TASK1_STACK, NULL, TASK1_PRIO, NULL, 0); xTaskCreatePinnedToCore(Task2, "CAN+队列处理", TASK2_STACK, NULL, TASK2_PRIO, NULL, 1); } void loop() { vTaskDelete(NULL); // 删除空闲loop任务 }
内容的提问来源于stack exchange,提问作者Pedro Silva
相关产品推荐
相关产品推荐

