ESP32双核分任务后Core 0触发非法指令崩溃重启
ESP32双核任务分配导致Core0崩溃的解决方案
问题出在哪
- 任务混乱引发资源冲突:ESP32默认的
loop任务本来就运行在Core0,你又手动创建了otaUpdateTask也绑定在Core0,两个任务在同一核心上争抢资源。更关键的是,WebServer的客户端处理逻辑还留在loop里,OTA任务却空转着,这种分工混乱直接触发了非法指令错误。 - WebServer线程不安全:ESP32的WebServer库不能在多个任务里同时操作同一个实例,跨任务调用极易导致内存访问出错,进而引发崩溃。
修复方案
- 清空默认loop任务的逻辑:把
loop里的server.handleClient()和delay(1)删掉,让它空转即可,避免和OTA任务争抢Core0资源。 - 把OTA核心逻辑移到专属任务里:在
otaUpdateTask中添加循环,专门处理server.handleClient(),确保所有WebServer相关操作都在同一个任务内完成,避免跨任务冲突。 - 调整任务优先级:给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
相关产品推荐
相关产品推荐

