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

ESP32 FreeRTOS调用xQueueReceive触发LoadProhibited异常排查

ESP32 FreeRTOS调用xQueueReceive触发LoadProhibited异常问题

我正在开发包含两个任务的ESP32 FreeRTOS应用,核心功能为接收外设发来的UART消息,通过MQTT协议转发至中心broker。
第一个任务负责读取Serial1端口输入,将接收内容处理为消息结构体后添加到FreeRTOS队列中,实现代码如下:

typedef struct {
      int length;
      char buffer[AZ_EL_MAX_MESSAGE_LENGTH];
    } tag_message_t;
    
    void uart_read_task(void * pvParameters){
        BaseType_t xStatus;
        tag_message_t tag_message;
        while(true) {
            while(Serial1.available())
            {
                first_char = Serial1.read();
                if (first_char == '+')        // Indicates the beginning of a message
                {
                  for(int i = 0; i < AZ_EL_MAX_MESSAGE_LENGTH; i++)
                  {
                    message_buffer[i] = Serial1.read();
    
                    if (message_buffer[i] == '\n') // End of message received
                    { 
                      ESP_LOGV(TAG, "Message found: %s", message_buffer);
                      strncpy(tag_message.buffer, message_buffer, i + 1);
                      tag_message.length = i + 1;
                      xStatus = xQueueSend(xMessagesToSendQueue, (void*) &tag_message, 0);
                      if (xStatus != pdPASS)
                        ESP_LOGW(TAG, "Failed to queue message.");
                      break;
                    }
                  }
                }
            }
            vTaskDelay(pdMS_TO_TICKS(20)); // Wait the minimum BLE advertisement period for messages to come in, i.e. 20ms
        }
    }

主函数loop()本质上是第二个FreeRTOS任务,负责从上述队列中接收消息,通过MQTT发送至本地broker,实现代码如下:

void setup()
{
  Serial.begin(115200);

  // Configure and start WiFi
  configure_network();
  connect_network();

  // Configure the MQTT connection
  configure_mqtt_client();

  // Configure and create the inter-task queues
  xMessagesToSendQueue = xQueueCreate(100, sizeof(tag_message_t));

  if (xMessagesToSendQueue == NULL) {
    ESP_LOGE(TAG, "Unable to create messaging queue. Will not create UART handling message queue.");
    delay(10000);
    esp_restart();
  } else {
    ESP_LOGI(TAG, "Messaging queue generated");
    configure_uart();
    xTaskCreate(uart_read_task, "UART_Processing", 20000, NULL, 1, NULL);
  }
}

void loop()
{
  const TickType_t xTicksToWait = pdMS_TO_TICKS(100); // milliseconds to wait
  tag_message_t received_message;

  if (network_connected) {
    connect_mqtt_client();
    while(mqtt_client.connected())
    {
      mqtt_client.loop();

      // Process messages on the xMessagesToSendQueue
      if (xMessagesToSendQueue != NULL)
      {
        ESP_LOGI(TAG, "Processing message");
        if (xQueueReceive(xMessagesToSendQueue, &received_message, xTicksToWait) == pdPASS)
        {
          ESP_LOGD(TAG, "Received message, transmitting.");
          if(!mqtt_client.publish("aoa", received_message.buffer, received_message.length));
            ESP_LOGW(TAG, "Failed transmission.");
        }
        else
        {
          vTaskDelay(pdMS_TO_TICKS(50));
        }
      } 
      else 
      {
        ESP_LOGE(TAG, "Messages queue is null.");
      }
    }
  } else {
    ESP_LOGE(TAG, "WARNING Device not connected to the network. Reconnecting.");
    connect_network();
  }
  delay(5000);
}

已完成的验证项:

  • MQTT broker运行正常
  • 设备可正常连接WiFi
  • Serial1端口消息读取逻辑正常
    故障现象:loop()中调用xQueueReceive()时每次都会触发LoadProhibited未处理异常。

问题根因与修复方案

LoadProhibited异常是CPU尝试访问未映射到地址空间的非法内存指针导致的,结合代码实现,按故障概率从高到低排序,问题点如下:

1. 结构体定义不一致导致队列内存越界(最高发必现问题)

如果tag_message_t结构体在uart_read_task所在编译单元、和setup()/loop()所在编译单元的定义不一致(常见于头文件声明不规范、重复定义结构体、修改结构体后未全量重编译工程),会导致两处代码计算出的sizeof(tag_message_t)数值不同。
这种情况下xQueueCreate()会按照偏小的结构体大小分配队列内存,xQueueSend()写入完整结构体时会直接越界,破坏队列内核对象的链表指针、元数据字段,xQueueReceive()访问这些被篡改的非法指针时就会触发异常。

  • 验证方式:在setup()创建队列后、uart_read_task发送消息前分别打印sizeof(tag_message_t),数值不一致即可确认问题。
  • 修复方式:将tag_message_t的typedef声明放在统一的公共头文件中,所有使用队列的代码文件都包含该头文件,执行全量重编译。

2. UART接收缓冲区越界写破坏队列句柄

你贴出的代码中未定义first_char和message_buffer变量,如果message_buffer的实际长度小于AZ_EL_MAX_MESSAGE_LENGTH,当接收到长度超过缓冲区的消息时,写入操作会越界覆盖相邻内存。如果全局队列句柄xMessagesToSendQueue刚好被分配在message_buffer的相邻内存位置,就会被篡改成为非NULL的非法地址,传入xQueueReceive()后直接触发异常。

  • 修复方式:明确定义char message_buffer[AZ_EL_MAX_MESSAGE_LENGTH],不要使用长度不匹配的全局/局部数组,可额外多分配1字节冗余空间避免溢出。

3. loop任务栈溢出导致接收缓冲区地址非法

ESP32 Arduino核心的loop()任务默认栈大小为8KB,如果AZ_EL_MAX_MESSAGE_LENGTH定义过大(比如超过7KB),栈上定义的tag_message_t received_message变量会直接撑爆栈空间,导致栈指针落在未映射内存区域,xQueueReceive()往该地址拷贝消息时触发异常。

  • 验证方式:将received_message改为全局变量/静态变量(分配在全局内存区而非栈上),如果异常消失即可确认。
  • 修复方式:缩小消息缓冲区最大长度,或修改Arduino核心配置调大loop任务栈空间。

4. 任务栈配置不合理导致堆内存破坏

原生FreeRTOS的xTaskCreate()栈深度单位是4字节字而非字节,你传入的20000栈深度实际需要分配80KB栈空间,在开启WiFi、MQTT的场景下很容易出现堆内存不足。你没有检查xTaskCreate()的返回值,若任务创建失败会破坏堆结构,导致后续队列操作访问非法内存。

  • 修复方式:将UART任务栈大小调整为8192即可满足串口读取任务需求,同时检查xTaskCreate()返回值是否为pdPASS,确认任务创建成功。

其他非崩溃类逻辑bug

  • MQTT发布判断逻辑错误:if(!mqtt_client.publish("aoa", received_message.buffer, received_message.length)); 末尾多了一个分号,会导致无论发布成功与否都会打印发布失败的警告,需要删除该多余分号。
  • UART读取逻辑缺少超时保护:检测到'+'起始符后如果一直收不到'\n'结束符,会持续读取串口返回的-1值填满缓冲区,建议增加读取超时判断,避免无效占用CPU。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:06:22