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事件线程中剥离,用消息队列传递任务指令:
- 定义全局消息队列和任务结构体
#include <queue.h> // 电机任务指令结构体 typedef struct { int driveToPos; int driveVerticalCount; } MotorTaskMsg; QueueHandle_t motorTaskQueue;
- 创建独立的电机执行任务
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(); } } }
- 在初始化阶段启动队列和任务
void setup() { // ... 其他初始化代码(串口、SPIFFS、WiFi等) ... // 创建消息队列,最多缓存5条任务指令 motorTaskQueue = xQueueCreate(5, sizeof(MotorTaskMsg)); // 将电机任务绑定到核心1,避免和async_tcp任务(核心0)冲突 xTaskCreatePinnedToCore(motorTask, "MotorTask", 4096, NULL, 1, NULL, 1); initWebServer(); }
- 修改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
相关产品推荐
相关产品推荐

