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

ESP32双核分任务后Core 0触发非法指令崩溃重启

ESP32双核任务分配导致Core0崩溃的解决方案

问题出在哪

  1. 任务混乱引发资源冲突:ESP32默认的loop任务本来就运行在Core0,你又手动创建了otaUpdateTask也绑定在Core0,两个任务在同一核心上争抢资源。更关键的是,WebServer的客户端处理逻辑还留在loop里,OTA任务却空转着,这种分工混乱直接触发了非法指令错误。
  2. WebServer线程不安全:ESP32的WebServer库不能在多个任务里同时操作同一个实例,跨任务调用极易导致内存访问出错,进而引发崩溃。

修复方案

  1. 清空默认loop任务的逻辑:把loop里的server.handleClient()和delay(1)删掉,让它空转即可,避免和OTA任务争抢Core0资源。
  2. 把OTA核心逻辑移到专属任务里:在otaUpdateTask中添加循环,专门处理server.handleClient(),确保所有WebServer相关操作都在同一个任务内完成,避免跨任务冲突。
  3. 调整任务优先级:给OTA任务设置比主任务更高的优先级,保证OTA请求能及时响应,不会被LED任务阻塞。

修复后的完整代码

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>

#include <ESPmDNS.h>
#include <Update.h>

const char* host = "ota32";
const char* ssid = "FrisQuiz";
const char* password = "frisquiz2024";

WebServer server(80);

// 任务句柄
TaskHandle_t otaTaskHandle = NULL;

// LED引脚
const int ledPin = 25;

/*
 * 主任务:控制LED
 */
void mainTask(void* pvParameters) {
  pinMode(ledPin, OUTPUT);

  while (1) {
    digitalWrite(ledPin, HIGH);
    Serial.println("Led ON");
    delay(5000);
    digitalWrite(ledPin, LOW);
    Serial.println("Led OFF");
    delay(5000);
  }
}

/*
 * OTA任务:处理WebServer客户端请求
 */
void otaUpdateTask(void* pvParameters) {
  Serial.println("Entering otaUpdateTask");
  while(1) {
    server.handleClient();
    delay(1);
  }
}

/*
 * 登录页面HTML
 */

const char* loginIndex =
  "<form name='loginForm'>"
  "<table width='20%' bgcolor='A09F9F' align='center'>"
  "<tr>"
  "<td colspan=2>"
  "<center><font size=4><b>ESP32 Login Page</b></font></center>"
  "<br>"
  "</td>"
  "<br>"
  "<br>"
  "</tr>"
  "<tr>"
  "<td>Username:</td>"
  "<td><input type='text' size=25 name='userid'><br></td>"
  "</tr>"
  "<br>"
  "<br>"
  "<tr>"
  "<td>Password:</td>"
  "<td><input type='Password' size=25 name='pwd'><br></td>"
  "<br>"
  "<br>"
  "</tr>"
  "<tr>"
  "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
  "</tr>"
  "</table>"
  "</form>"
  "<script>"
  "function check(form)"
  "{"
  "if(form.userid.value=='FrisQuizAdmin' && form.pwd.value=='FrisQuiz2024!')"
  "{"
  "window.open('/serverIndex')"
  "}"
  "else"
  "{"
  " alert('Error Password or Username')"
  "}"
  "}"
  "</script>";

/*
 * OTA升级页面HTML
 */

const char* serverIndex =
  "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
  "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
  "<input type='file' name='update'>"
  "<input type='submit' value='Update'>"
  "</form>"
  "<div id='prg'>progress: 0%</div>"
  "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
  "},"
  "error: function (a, b, c) {"
  "}"
  "}"
  "});"
  "});"
  "</script>";

/*
 * 初始化函数
 */
void setup(void) {
  Serial.begin(115200);

  // 连接WiFi
  WiFi.begin(ssid, password);
  Serial.println("");

  // 等待WiFi连接成功
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // 设置静态IP
  IPAddress staticIP(192, 168, 88, 33);
  IPAddress gateway(192, 168, 88, 1);
  IPAddress subnet(255, 255, 255, 0);

  WiFi.config(staticIP, gateway, subnet);

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // 创建OTA任务,绑在Core0,优先级2(高于主任务的1)
  xTaskCreatePinnedToCore(
    otaUpdateTask,
    "otaTask",
    16384,
    NULL,
    2,
    &otaTaskHandle,
    0);

  // 创建主任务,绑在Core1
  xTaskCreatePinnedToCore(
    mainTask,
    "mainTask",
    8192,
    NULL,
    1,
    NULL,
    1);

  // 初始化mDNS
  if (!MDNS.begin(host)) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");

  // 注册WebServer路由
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });

  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });

  // OTA升级路由
  server.on(
    "/update", HTTP_POST, []() {
      server.sendHeader("Connection", "close");
      server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
      ESP.restart();
    },
    []() {
      HTTPUpload& upload = server.upload();
      if (upload.status == UPLOAD_FILE_START) {
        Serial.printf("Update: %s\n", upload.filename.c_str());
        if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_WRITE) {
        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_END) {
        if (Update.end(true)) {
          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
        } else {
          Update.printError(Serial);
        }
      }
    });

  server.begin();
}

void loop(void) {
  // 默认loop任务空转,不占用Core0资源
  delay(1000);
}

额外提示

  • 所有WebServer相关操作必须放在同一个任务里,不要跨任务调用,避免线程安全问题。
  • 给OTA任务更高优先级,保证升级请求能及时处理。
  • 如果还是崩溃,可开启ESP32的调试功能,查看详细的崩溃回溯信息,定位具体内存问题。

内容的提问来源于stack exchange,提问作者Arno Van Brussel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:08:09