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

ESP32 FreeRTOS Arduino IDE下字符数组入队列及串口输出问题

ESP32 FreeRTOS队列解决多任务数据输出冲突与丢失问题

一、字符数组入队的正确实现

字符数组不能直接传指针入队(Task1的栈数组会被后续采集覆盖),必须拷贝数组完整内容到队列存储空间:

  1. 定义队列参数:
    按单条消息最大长度(40字节,留足35字符+格式余量)创建队列,长度按Task1的消息生成速率预留缓冲(比如50条,应对Task2短暂阻塞的情况):

    #define MAX_MSG_LEN 40
    QueueHandle_t gpioDataQueue;
    
    // 初始化队列
    gpioDataQueue = xQueueCreate(50, MAX_MSG_LEN);
    
  2. 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));
  }
}

三、避免数据丢失的核心配置

  1. 任务优先级:Task2优先级设为高于Task1(比如Task1=1,Task2=2),确保数据处理任务能及时抢占CPU,避免队列溢出。
  2. 串口优化:用Serial.write()替代Serial.print(),减少格式化开销,匹配1Mbps波特率的输出能力(1Mbps波特率每秒可处理约3400条35字符消息,完全覆盖2200条的总生成量)。
  3. 队列长度调整:如果仍出现丢数据,可将队列长度增大至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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 15:33:26