ESP32-WROVER-IE-N8R8 MQTT传感器运行超100天后死机求助
问题背景
- 硬件:定制PCB搭载ESP32-WROVER-IE-N8R8,用于读取过滤系统压差数据
- 功能:每分钟通过MQTT将数据发送至Linux服务器的Mosquitto Broker
- 开发环境:Arduino IDE,代码含德语注释/字符串
- 故障:所有设备运行100多天后均出现代码完全停止的情况,无任何串口输出
- 已尝试措施:实现millis()溢出自动重启(约40-50天触发),启用任务级看门狗(WDT_TIMEOUT=90秒),但看门狗未触发重启;添加大量串口输出无法定位故障点,测试周期过长(每年仅能验证3种方案)
代码片段
#include <WiFiManager.h> #include <WiFi.h> #include <PubSubClient.h> #include <Wire.h> #include <esp_task_wdt.h> #define TRIGGER_PIN 33 #define LEDError 14 #define LEDConnect 5 #define LEDWiFi 15 #define WDT_TIMEOUT 90 #define SensorName "Filtersensor 9" #define SensorLogin "fs-09" #define SensorPasswort "xxx" #define SensorPublish "xxx" #define SensorStatus "Status_FS_09" WiFiManager wm; char Status[30]; //I2C byte ADCAddress = 0x48; const int ADCref = 4096; float ADCvoltage; float Druck; //MQTT const char* mqtt_server = "RandomIP"; WiFiClient espClient; PubSubClient client(espClient); unsigned long lastMsg = 0; char msg[50]; void setup() { Wire.begin(); Serial.begin(115200); delay(100); pinMode(LEDError, OUTPUT); pinMode(LEDConnect, OUTPUT); pinMode(LEDWiFi, OUTPUT); wm.setHostname(SensorLogin); WiFi.mode(WIFI_STA); Serial.setDebugOutput(true); delay(3000); Serial.println("\n Starte..."); pinMode(TRIGGER_PIN, INPUT); //WiFi连接 bool res; res = wm.autoConnect(SensorName,"password"); if(!res) { Serial.println("Verbindung fehlgeschalgen oder Time-Out"); digitalWrite(LEDError, HIGH); Serial.println("Neustarten..."); delay(10000); ESP.restart(); } else { Serial.println("WiFi verbunden!"); digitalWrite(LEDWiFi, HIGH); } //MQTT设置 client.setServer(mqtt_server, 1883); //看门狗初始化 Serial.println("Einrichtung des WDT..."); esp_task_wdt_init(WDT_TIMEOUT, true); esp_task_wdt_add(NULL); } void checkButton(){ if ( digitalRead(TRIGGER_PIN) == LOW ) { delay(50); if( digitalRead(TRIGGER_PIN) == LOW ){ Serial.println("WiFi Button gedrückt"); delay(3000); if( digitalRead(TRIGGER_PIN) == LOW ){ Serial.println("Button gehalten"); Serial.println("Lösche Config, Starte neu...."); wm.resetSettings(); digitalWrite(LEDWiFi, LOW); delay(5000); ESP.restart(); } esp_task_wdt_reset(); Serial.println("Starte Wifi-Konfiguration..."); wm.setConfigPortalTimeout(120); if (!wm.startConfigPortal(SensorName,"password")) { Serial.println("Verbindung fehlgeschalgen oder Time-Out"); delay(3000); digitalWrite(LEDError, HIGH); Serial.println("Neustarten..."); delay(10000); ESP.restart(); } else { Serial.println("WiFi verbunden!"); digitalWrite(LEDWiFi, HIGH); } } } } void reconnect() { while (!client.connected()) { Serial.print("Versuche MQTT-Verbindung herzustellen..."); if (client.connect(SensorName, SensorLogin, SensorPasswort)) { Serial.println("Verbunden!"); digitalWrite(LEDConnect, HIGH); } else { Serial.print("Fehlgeschlagen!, rc="); Serial.print(client.state()); Serial.println(" Neuer Versuch in 5 Sekunden"); digitalWrite(LEDConnect, LOW); delay(5000); } } } void Sensor() { uint16_t ADCresult; Wire.requestFrom(ADCAddress, (uint8_t) 2); if (Wire.available()) { ADCresult = Wire.read(); ADCresult = ADCresult<<8; ADCresult += Wire.read(); ADCvoltage = (ADCresult*(ADCref/4095)); Serial.print("Spannung ADC: "); Serial.print(ADCvoltage); Serial.println(" mV"); ADCvoltage /= 1000; Druck = ((500.0*(ADCvoltage-0.25))/3.75); Druck /= 100; Serial.print("Differenzdruck: "); Serial.print(Druck,3); Serial.println(" mbar"); char DruckMessage[8]; dtostrf(Druck, 1, 3, DruckMessage); strcat (Status," SENT"); if (client.connected()) { esp_task_wdt_reset(); client.publish(SensorPublish, DruckMessage); delay(100); client.publish(SensorStatus, Status); digitalWrite(LEDConnect, LOW); digitalWrite(LEDError, LOW); delay(500); digitalWrite(LEDConnect, HIGH); Serial.print("Gesendete Daten: "); Serial.println(DruckMessage); } } else{ digitalWrite(LEDError, HIGH); strcat (Status," ERROR"); if (client.connected()) { client.publish(SensorStatus, Status); } Serial.println("Keine Verbindung zum ADC herstellbar"); } } void Neustart() { Serial.println("Regelmäßiger Neustart wird ausgeführt"); char StatusMessage[10] ="RESTART"; client.publish(SensorStatus, StatusMessage); delay(500); ESP.restart(); } void getReadableTime(String &readableTime) { unsigned long currentMillis; unsigned long seconds; unsigned long minutes; unsigned long hours; unsigned long days; currentMillis = millis(); seconds = currentMillis / 1000; minutes = seconds / 60; hours = minutes / 60; days = hours / 24; currentMillis %= 1000; seconds %= 60; minutes %= 60; hours %= 24; if (days < 10) { readableTime += "0"; } readableTime = String(days) + "D "; if (hours < 10) { readableTime += "0"; } readableTime += String(hours) + "H "; if (minutes < 10) { readableTime += "0"; } readableTime += String(minutes) + "M "; } void loop() { checkButton(); if (!client.connected()) { digitalWrite(LEDConnect, LOW); reconnect(); } client.loop(); unsigned long now = millis(); if (now > 86400000) { Neustart(); } if (now - lastMsg > 60000) { lastMsg = now; String readableTime; getReadableTime(readableTime); Serial.print("Laufzeit: "); Serial.println(readableTime); readableTime.toCharArray(Status, 20); Sensor(); } }
关键问题分析及解决方案
1. Status数组内存越界(最可能的崩溃原因)
代码中Status是固定长度30字节的数组,每次循环执行readableTime.toCharArray(Status,20)后,直接调用strcat(Status," SENT")或strcat(Status," ERROR"),但未清空数组旧内容。多次循环后,Status会累积字符串(比如变成"01D 02H 03M SENT SENT SENT..."),最终超过30字节的容量,导致内存溢出,破坏系统栈或堆,引发无预兆崩溃。
修复方案:
在readableTime.toCharArray(Status,20)前添加数组清空操作:
// 清空Status数组 memset(Status, 0, sizeof(Status)); readableTime.toCharArray(Status,20);
2. 看门狗未覆盖阻塞场景
当前仅在Sensor()和checkButton()中调用esp_task_wdt_reset(),但reconnect()函数中的while循环如果遇到MQTT长期无法连接,会持续执行delay(5000),主任务会被阻塞在该循环中,超过90秒看门狗超时时间却未触发重启——因为循环内没有喂狗操作。
修复方案:
在reconnect()的循环内添加看门狗喂狗:
void reconnect() { while (!client.connected()) { esp_task_wdt_reset(); // 添加喂狗 Serial.print("Versuche MQTT-Verbindung herzustellen..."); if (client.connect(SensorName, SensorLogin, SensorPasswort)) { Serial.println("Verbunden!"); digitalWrite(LEDConnect, HIGH); } else { Serial.print("Fehlgeschlagen!, rc="); Serial.print(client.state()); Serial.println(" Neuer Versuch in 5 Sekunden"); digitalWrite(LEDConnect, LOW); delay(5000); } } }
同时建议改用硬件看门狗替代任务级看门狗,避免任务阻塞导致喂狗失效:
// 替换原看门狗初始化代码 #include <esp_system.h> void setup() { // ...其他代码 // 启用硬件看门狗,超时300秒 esp_wdt_enable(300000); } // 在loop()末尾添加喂狗 void loop() { // ...其他代码 esp_wdt_reset(); }
3. I2C通信无超时机制
Wire.requestFrom和Wire.read如果遇到I2C总线干扰或ADC模块故障,可能导致ESP32无限阻塞在I2C操作中,无法执行喂狗或其他代码。
修复方案:
在setup()中设置I2C超时:
void setup() { Wire.begin(); Wire.setWireTimeout(500000); // 设置500ms超时 // ...其他代码 }
同时检查Wire.requestFrom的返回值,替代仅判断Wire.available():
void Sensor() { uint16_t ADCresult; // 检查requestFrom的返回值,确认读取到2字节 if (Wire.requestFrom(ADCAddress, (uint8_t)2) == 2) { ADCresult = Wire.read(); ADCresult = ADCresult<<8; ADCresult += Wire.read(); // ...后续代码 } else{ digitalWrite(LEDError, HIGH); // ...错误处理代码 } }
4. 内存泄漏与库稳定性
长期运行中,WiFi/MQTT库可能存在内存泄漏,建议:
- 更新WiFiManager、PubSubClient到最新版本,修复已知内存泄漏问题
- 在每日重启的
Neustart()函数中,先断开MQTT和WiFi连接,避免残留资源:
void Neustart() { Serial.println("Regelmäßiger Neustart wird ausgeführt"); char StatusMessage[10] ="RESTART"; if(client.connected()){ client.publish(SensorStatus, StatusMessage); client.disconnect(); } WiFi.disconnect(true); delay(500); ESP.restart(); }
5. 硬件电源稳定性检查
定制PCB需确保ESP32供电长期稳定:
- 验证3.3V供电纹波小于100mV,电压范围在3.135V-3.465V之间
- 在ESP32的VCC引脚旁添加100nF陶瓷电容+10µF电解电容做滤波
- 检查电源模块的散热设计,避免长期运行过热老化
内容的提问来源于stack exchange,提问作者ThoryF

