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

ESP32 WebSocket服务器触发Task Watchdog问题求助

解决ESP32 WebSocket处理电机任务触发看门狗问题

问题根源

报错显示async_tcp任务未及时喂狗,核心原因是WebSocket的WS_EVT_DATA事件处理函数handleEvent直接执行了大量耗时操作:

  • 多次delay(2000)阻塞线程
  • 电机驱动的循环(driveMotor内的for/while循环)长期占用CPU,现有yield()的调用位置无法有效触发看门狗喂狗
  • 整个电机任务在async_tcp线程内执行,导致该线程无法及时处理TCP栈的看门狗维护逻辑

紧急解决方法(优先满足稳定需求)

方法1:将耗时任务移到独立FreeRTOS任务

这是最彻底的解决方案,把电机驱动逻辑从WebSocket事件线程中剥离,用消息队列传递任务指令:

  1. 定义全局消息队列和任务结构体
#include <queue.h>

// 电机任务指令结构体
typedef struct {
  int driveToPos;
  int driveVerticalCount;
} MotorTaskMsg;

QueueHandle_t motorTaskQueue;
  1. 创建独立的电机执行任务
void motorTask(void *parameter) {
  MotorTaskMsg msg;
  while (true) {
    // 等待队列中的任务指令,无限阻塞
    if (xQueueReceive(motorTaskQueue, &msg, portMAX_DELAY) == pdTRUE) {
      // 执行原handleEvent中的电机驱动逻辑
      vAchse = msg.driveToPos;
      for(int i=0; i<msg.driveVerticalCount; i++) {
        hAchse + 300;
        vTaskDelay(pdMS_TO_TICKS(2000)); // 用FreeRTOS延迟代替delay,主动让出CPU
        hAchse - 300;          
        vTaskDelay(pdMS_TO_TICKS(2000));
      }                      
      vTaskDelay(pdMS_TO_TICKS(500));
      vAchse.driveHome();
    }
  }
}
  1. 在初始化阶段启动队列和任务
void setup() {
  // ... 其他初始化代码(串口、SPIFFS、WiFi等) ...
  
  // 创建消息队列,最多缓存5条任务指令
  motorTaskQueue = xQueueCreate(5, sizeof(MotorTaskMsg));
  // 将电机任务绑定到核心1,避免和async_tcp任务(核心0)冲突
  xTaskCreatePinnedToCore(motorTask, "MotorTask", 4096, NULL, 1, NULL, 1);
  
  initWebServer();
}
  1. 修改handleEvent函数,仅发送任务指令,不执行耗时操作
void handleEvent(void *arg, uint8_t *data, size_t len, AsyncWebSocketClient *client) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  // 仅处理完整的文本帧,过滤分片或非文本消息
  if (!(info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)) {
    return;
  }
  data[len] = 0;
  
  DynamicJsonDocument json(1024);
  deserializeJson(json, (char*)data);
  serializeJson(json, Serial); Serial.println("<- event");
  
  if(strcmp(json["event"], "driveTask") == 0) {
    MotorTaskMsg msg;
    JsonArray taskArray = json["data"].as<JsonArray>();
    if (!taskArray.isEmpty()) {
      JsonVariant elem = taskArray[0];
      msg.driveToPos = elem["driveToPos"];
      msg.driveVerticalCount = elem["driveVerticalCount"];
      // 发送任务到队列,不等待执行完成,立即返回释放WebSocket线程
      xQueueSend(motorTaskQueue, &msg, 0);
    }
  }
}

方法2:临时禁用async_tcp任务的看门狗(仅临时调试用)

如果暂时无法重构代码,可修改ESPAsyncTCP库的配置,解除该任务的看门狗绑定:
找到AsyncTCP.cpp中的async_tcp_task函数,添加一行代码:

void async_tcp_task(void *pvParameters) {
  // ... 原有初始化代码 ...
  esp_task_wdt_delete(NULL); // 删除当前任务的看门狗绑定
  while(1) {
    // ... 原有循环逻辑 ...
  }
}

注意:此方法会降低系统故障检测能力,不能作为长期解决方案。

方法3:优化yield()调用位置(适用于小改动场景)

如果不想引入FreeRTOS任务,需确保在所有阻塞/循环逻辑中频繁调用yield():

  • 在driveMotor的for循环中,每执行固定步数就调用一次yield():
for (int index =1; index <= ( abs( distance ) ) && !(this->endStop != -1 && digitalRead(this->endStop) == 1); index++ ){
  this->drive();
  if (index % 100 == 0) { // 每100步触发一次喂狗
    yield();
  }
}
  • 把所有delay(xxx)替换为vTaskDelay(pdMS_TO_TICKS(xxx)),该函数会主动让出CPU,更可靠地触发系统调度。

为什么启动时校准无崩溃?

启动时的校准代码运行在loopTask(核心1),而async_tcp任务运行在核心0,两个任务独立调度,因此loopTask的耗时操作不会影响async_tcp的看门狗;但WebSocket事件处理逻辑运行在async_tcp线程(核心0),阻塞后直接导致该任务无法完成看门狗喂狗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:03:37