Watchdog-Timer bei Arduino & ESP32: Hänger automatisch erkennen und sauber neu starten
Auf dem Schreibtisch läuft alles. Im Gewächshaus, im Sicherungskasten oder auf dem Dachboden steht das Board nach vier Tagen still – und niemand merkt es, bis die Pflanzen vertrocknet sind. Kein Rauch, kein Fehler, einfach ein Hänger. Der Watchdog-Timer ist die eingebaute Antwort darauf: ein unabhängiger Zähler im Chip, den dein Code regelmäßig zurücksetzen muss. Tut er das nicht, zieht der Watchdog den Reset – kompromisslos und ohne Software. Dieses Tutorial zeigt, wie du ihn auf AVR und ESP32 richtig einsetzt, ohne dir dabei eine Boot-Loop einzuhandeln.
Warum Projekte hängen, ohne abzustürzen
Ein Hänger ist kein Absturz. Der Chip läuft weiter, führt Befehle aus – nur eben die falschen, oder immer dieselben. Die häufigsten Ursachen in Maker-Projekten:
| Ursache | Typisches Symptom | Hilft der Watchdog? |
|---|---|---|
| I2C-Bus blockiert (Sensor hält SDA auf LOW) |
Wire.requestFrom() kehrt nie zurück |
ja, zuverlässig |
while-Schleife wartet auf ein Signal, das nicht kommt |
alles steht, LED bleibt an | ja |
WLAN-Verbindung hängt in reconnect()
|
Webserver antwortet nicht mehr | ja |
| Heap-Fragmentierung, Zuweisung schlägt fehl | Sketch stoppt nach Stunden | ja, kuriert aber nur das Symptom |
| Spannungseinbruch beim Relais-Schalten | Board startet neu oder hängt | teilweise – besser die Ursache beheben |
| Endlosschleife durch Programmierfehler | reproduzierbarer Stillstand | ja, aber der Bug bleibt |
ℹ️ Der Watchdog ist ein Sicherheitsnetz, keine Fehlerbehebung. Wenn dein Board alle 20 Minuten neu startet, hast du kein Watchdog-Problem, sondern einen Bug – der Watchdog macht ihn nur sichtbar und erträglich. Genau deshalb protokollieren wir weiter unten die Reset-Ursache: Der Zähler verrät dir, ob du ein stabiles System hast oder eine tägliche Notbremse.
Das brauchst du
1Verkabelung
| Bauteil | Pin am Modul | Nano / UNO | ESP32 |
|---|---|---|---|
| Relais KY-019 | SIG | D7 | GPIO 26 |
| Relais KY-019 | VCC / GND | 5V / GND | VIN (5V) / GND |
| Heartbeat-LED | Anode | D8 (über 220 Ω) | GPIO 27 (über 220 Ω) |
| DHT22 | DATA | D2 | GPIO 4 |
| BMP280 / OLED / DS3231 | SDA / SCL | A4 / A5 | GPIO 21 / GPIO 22 |
| Taster „Blockade auslösen“ | ein Bein | D3 nach GND | GPIO 25 nach GND |
Der Taster ist reiner Testaufbau: Damit provozierst du gleich absichtlich einen Hänger und siehst, ob der Watchdog wirklich greift.
2AVR: der Watchdog in avr/wdt.h
Auf ATmega328 läuft der Watchdog an einem eigenen 128-kHz-Oszillator – unabhängig vom Hauptquarz. Selbst wenn der Prozessor in einer Endlosschleife festhängt, zählt dieser Timer weiter. Erreicht er den eingestellten Wert und wurde nicht zurückgesetzt, löst er einen Hardware-Reset aus.
Die verfügbaren Zeiten sind fest vorgegeben:
| Konstante | Zeit | Sinnvoll für |
|---|---|---|
WDTO_250MS |
0,25 s | schnelle Regelungen ohne Blockaden |
WDTO_1S |
1 s | reine Sensorschleifen |
WDTO_2S |
2 s | solider Standard |
WDTO_4S |
4 s | mit DHT22 (der braucht selbst 2 s) |
WDTO_8S |
8 s | Maximum, bei SD-Schreibvorgängen |
#include <avr/wdt.h>
#include <DHT.h>
#define DHT_PIN 2
#define TASTER_PIN 3
#define RELAIS_PIN 7
#define LED_PIN 8
DHT dht(DHT_PIN, DHT22);
unsigned long letzteMessung = 0;
const unsigned long INTERVALL = 2000;
void setup() {
// WICHTIG: Watchdog sofort deaktivieren, bevor irgendetwas anderes passiert.
// Bleibt er von einem vorherigen Reset aktiv, laeuft setup() sonst
// moeglicherweise nicht zu Ende und das Board bootet endlos.
wdt_disable();
Serial.begin(115200);
pinMode(TASTER_PIN, INPUT_PULLUP);
pinMode(RELAIS_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(RELAIS_PIN, LOW); // definierter Zustand nach dem Start
dht.begin();
Serial.println(F("Start. Watchdog wird aktiviert."));
delay(1000); // Zeit fuer den seriellen Monitor
// Ab hier ueberwacht die Hardware den Programmablauf
wdt_enable(WDTO_4S);
}
void loop() {
// Der Kick: sagt dem Watchdog "ich lebe noch"
wdt_reset();
// Blockade auf Knopfdruck - zum Testen des Watchdogs
if (digitalRead(TASTER_PIN) == LOW) {
Serial.println(F("Simuliere Haenger..."));
digitalWrite(LED_PIN, HIGH);
while (true) { } // kein wdt_reset() mehr -> Reset nach 4 s
}
if (millis() - letzteMessung >= INTERVALL) {
letzteMessung = millis();
float temp = dht.readTemperature();
if (isnan(temp)) {
Serial.println(F("Sensorfehler"));
} else {
Serial.print(F("Temperatur: "));
Serial.println(temp, 1);
digitalWrite(RELAIS_PIN, temp < 20.0 ? HIGH : LOW);
}
// Heartbeat: LED blinkt nur, solange die Schleife laeuft
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
}
⚠️ Die berüchtigte Boot-Loop mit dem alten Bootloader. Auf vielen Nano- und Pro-Mini-Boards steckt noch der alte optiboot-Vorgänger, der das Watchdog-Flag nach einem Reset nicht löscht. Folge: Der Watchdog bleibt aktiv, läuft während des Bootvorgangs ab, löst den nächsten Reset aus – und das Board ist in einer Endlosschleife gefangen, die sich nicht einmal mehr flashen lässt. Zwei Schutzmaßnahmen: wdt_disable() als allererste Zeile in setup(), und die Timeout-Zeit nie unter 2 Sekunden wählen. Ist es doch passiert, hilft nur: Upload starten und exakt im Moment des Boards-Resets flashen – oder den Bootloader mit einem zweiten Board neu brennen.
💡 Nur ein wdt_reset() pro Sketch – und zwar in der loop(). Der häufigste Anfängerfehler ist, den Kick vorsichtshalber in jede längere Funktion zu streuen. Damit machst du den Watchdog wertlos: Er meldet dann auch dann noch „allles gut“, wenn dein Programm in genau dieser Funktion festhängt. Ein einziger Kick an der Spitze der Hauptschleife überwacht den kompletten Durchlauf.
3Neustarts zählen und die Ursache erkennen
Ein Watchdog-Reset, den niemand bemerkt, ist nur die halbe Lösung. Auf dem AVR verrät das Register MCUSR, warum das Board gestartet ist – aber nur, wenn du es vor der Arduino-Initialisierung sicherst:
#include <avr/wdt.h>
#include <EEPROM.h>
uint8_t resetGrund;
// Laeuft vor setup() und noch vor der Arduino-Initialisierung
void rettungMCUSR(void) __attribute__((naked, used, section(".init3")));
void rettungMCUSR(void) {
resetGrund = MCUSR;
MCUSR = 0;
wdt_disable();
}
const int ADR_ZAEHLER = 0; // EEPROM-Adresse fuer den Watchdog-Zaehler
void setup() {
Serial.begin(115200);
delay(300);
Serial.print(F("Reset-Ursache: "));
if (resetGrund & (1 << PORF)) Serial.println(F("Power-On (Strom angelegt)"));
else if (resetGrund & (1 << EXTRF)) Serial.println(F("Reset-Taster"));
else if (resetGrund & (1 << BORF)) Serial.println(F("Brown-Out (Spannung eingebrochen)"));
else if (resetGrund & (1 << WDRF)) Serial.println(F("WATCHDOG - das Programm hing!"));
else Serial.println(F("unbekannt"));
// Watchdog-Resets dauerhaft mitzaehlen
if (resetGrund & (1 << WDRF)) {
uint8_t zaehler = EEPROM.read(ADR_ZAEHLER);
if (zaehler == 255) zaehler = 0; // frisches EEPROM
EEPROM.update(ADR_ZAEHLER, zaehler + 1);
Serial.print(F("Watchdog-Resets insgesamt: "));
Serial.println(zaehler + 1);
}
wdt_enable(WDTO_4S);
}
void loop() {
wdt_reset();
// ... dein Code ...
}
Auf dem ESP32 ist das deutlich komfortabler – dort gibt es eine fertige Funktion und dazu RTC_DATA_ATTR-Variablen, die einen Neustart überleben:
#include <esp_system.h>
// Liegt im RTC-Speicher und ueberlebt Reset und Deep Sleep,
// nur ein Power-Cycle setzt sie zurueck.
RTC_DATA_ATTR uint32_t watchdogResets = 0;
RTC_DATA_ATTR uint32_t panicResets = 0;
RTC_DATA_ATTR uint32_t startZaehler = 0;
const char *resetTextGeben(esp_reset_reason_t g) {
switch (g) {
case ESP_RST_POWERON: return "Power-On";
case ESP_RST_EXT: return "externer Reset-Pin";
case ESP_RST_SW: return "Software (ESP.restart)";
case ESP_RST_PANIC: return "PANIC - Exception im Code";
case ESP_RST_INT_WDT: return "Interrupt-Watchdog";
case ESP_RST_TASK_WDT: return "Task-Watchdog";
case ESP_RST_WDT: return "anderer Watchdog";
case ESP_RST_BROWNOUT: return "Brown-Out (Spannungseinbruch)";
case ESP_RST_DEEPSLEEP:return "Aufwachen aus Deep Sleep";
default: return "unbekannt";
}
}
void zeigeResetUrsache() {
esp_reset_reason_t grund = esp_reset_reason();
startZaehler++;
Serial.printf("Start Nr. %u - Ursache: %s\n",
startZaehler, resetTextGeben(grund));
if (grund == ESP_RST_TASK_WDT || grund == ESP_RST_INT_WDT ||
grund == ESP_RST_WDT) {
watchdogResets++;
Serial.printf(" !! Watchdog-Resets bisher: %u\n", watchdogResets);
}
if (grund == ESP_RST_PANIC) {
panicResets++;
Serial.printf(" !! Panic-Resets bisher: %u\n", panicResets);
}
if (grund == ESP_RST_BROWNOUT) {
Serial.println(" !! Netzteil zu schwach oder Relais zieht zu viel Strom");
}
}
ℹ️ ESP_RST_BROWNOUT ist kein Software-Problem. Taucht der auf, sobald ein Relais oder ein Motor anläuft, bricht die Versorgungsspannung ein. Kein Watchdog der Welt hilft dagegen – hier braucht es einen dickeren Elko (470–1000 µF) an 5V, eine sauberere Quelle wie den LM2596S oder eine getrennte Versorgung für die Last. Details dazu im Artikel Stabile Stromversorgung für Arduino & ESP32.
4ESP32: der Task-Watchdog
Der ESP32 arbeitet unter FreeRTOS, deshalb überwacht sein Watchdog keine Schleife, sondern Tasks. Dein Sketch läuft im Task loopTask – den musst du ausdrücklich anmelden, denn standardmäßig ist nur der Idle-Task registriert.
#include <esp_task_wdt.h>
#define WDT_TIMEOUT_S 10
#define RELAIS_PIN 26
#define LED_PIN 27
#define TASTER_PIN 25
void setup() {
Serial.begin(115200);
delay(300);
pinMode(RELAIS_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(TASTER_PIN, INPUT_PULLUP);
digitalWrite(RELAIS_PIN, LOW);
zeigeResetUrsache(); // aus dem vorherigen Abschnitt
// Ab Arduino-ESP32 Core 3.x nimmt esp_task_wdt_init eine Konfigurationsstruktur
esp_task_wdt_config_t konfig = {
.timeout_ms = WDT_TIMEOUT_S * 1000,
.idle_core_mask = 0, // Idle-Tasks nicht ueberwachen
.trigger_panic = true // bei Ablauf Reset ausloesen
};
esp_task_wdt_init(&konfig);
// Den aktuellen Task (loopTask) zur Ueberwachung anmelden
esp_task_wdt_add(NULL);
Serial.printf("Task-Watchdog aktiv: %d s\n", WDT_TIMEOUT_S);
}
void loop() {
esp_task_wdt_reset(); // der Kick
if (digitalRead(TASTER_PIN) == LOW) {
Serial.println("Simuliere Haenger...");
digitalWrite(LED_PIN, HIGH);
while (true) { delay(100); } // delay() gibt zwar CPU ab, kickt aber nicht
}
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(500);
}
⚠️ Core 2.x und Core 3.x sind hier inkompatibel. Bis Arduino-ESP32 2.0.x lautete der Aufruf esp_task_wdt_init(WDT_TIMEOUT_S, true) – mit Sekunden als erstem Argument. Ab Core 3.0 erwartet die Funktion die Struktur esp_task_wdt_config_t mit Millisekunden. Kompiliert dein Sketch mit einem „too few arguments“ oder „cannot convert int to const esp_task_wdt_config_t*“, ist genau das der Grund. Welche Version du hast, siehst du im Boardverwalter unter „esp32 by Espressif Systems“.
Willst du versionsunabhängig bleiben, hilft eine Weiche über die Core-Version:
#include <esp_task_wdt.h>
#include <esp_arduino_version.h>
void watchdogStarten(uint32_t sekunden) {
#if ESP_ARDUINO_VERSION_MAJOR >= 3
esp_task_wdt_config_t konfig = {
.timeout_ms = sekunden * 1000,
.idle_core_mask = 0,
.trigger_panic = true
};
esp_task_wdt_init(&konfig);
#else
esp_task_wdt_init(sekunden, true);
#endif
esp_task_wdt_add(NULL);
}
Nutzt du eigene FreeRTOS-Tasks, meldest du jeden davon einzeln an – und jeder kickt für sich:
void sensorTask(void *param) {
esp_task_wdt_add(NULL); // dieser Task wird mit ueberwacht
for (;;) {
esp_task_wdt_reset();
float temp = leseSensor();
if (!isnan(temp)) {
Serial.printf("Temp: %.1f C\n", temp);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void setup() {
Serial.begin(115200);
watchdogStarten(10);
xTaskCreatePinnedToCore(sensorTask, "sensor", 4096, NULL, 1, NULL, 1);
}
void loop() {
esp_task_wdt_reset();
delay(1000);
}
💡 Vorsicht bei langen blockierenden Operationen. Ein WiFi.begin() mit anschließender Warteschleife, ein HTTPS-Handshake oder das Schreiben großer Dateien ins LittleFS kann mehrere Sekunden dauern. Setze das Timeout großzügig (10–30 s) oder kicke innerhalb solcher Warteschleifen – aber nur dort, wo du sicher weißt, dass die Schleife ein eigenes Timeout hat.
5Ausgänge sicher über den Neustart bringen
Ein Watchdog-Reset ist ein harter Neustart. Alle Pins gehen kurz auf INPUT, dein Relais fällt ab – und schaltet in setup() wieder auf den Startwert. Bei einer Bewässerungspumpe ist das genau richtig. Bei einer Heizung mitten im Winter eher nicht.
Die Lösung: Zustand mit Zeitstempel im EEPROM sichern und beim Start prüfen, ob er noch gültig ist.
#include <EEPROM.h>
#include <avr/wdt.h>
struct GesicherterZustand {
uint16_t magie; // Erkennungsmarke: sind die Daten von uns?
uint8_t relaisAn;
uint8_t zaehlerWdt;
uint16_t pruefsumme;
};
const uint16_t MAGIE = 0xA57C;
const int ADRESSE = 16;
uint16_t berechnePruefsumme(const GesicherterZustand &z) {
return (uint16_t)(z.magie + z.relaisAn * 31 + z.zaehlerWdt * 7);
}
void zustandSpeichern(bool relaisAn, uint8_t zaehler) {
GesicherterZustand z;
z.magie = MAGIE;
z.relaisAn = relaisAn ? 1 : 0;
z.zaehlerWdt = zaehler;
z.pruefsumme = berechnePruefsumme(z);
EEPROM.put(ADRESSE, z); // put schreibt nur geaenderte Bytes
}
bool zustandLaden(bool &relaisAn, uint8_t &zaehler) {
GesicherterZustand z;
EEPROM.get(ADRESSE, z);
if (z.magie != MAGIE) return false;
if (z.pruefsumme != berechnePruefsumme(z)) return false;
relaisAn = (z.relaisAn == 1);
zaehler = z.zaehlerWdt;
return true;
}
void setup() {
wdt_disable();
Serial.begin(115200);
pinMode(RELAIS_PIN, OUTPUT);
bool relaisAn = false;
uint8_t zaehler = 0;
if (zustandLaden(relaisAn, zaehler)) {
Serial.print(F("Zustand wiederhergestellt, Relais: "));
Serial.println(relaisAn ? F("AN") : F("AUS"));
digitalWrite(RELAIS_PIN, relaisAn ? HIGH : LOW);
} else {
Serial.println(F("Kein gueltiger Zustand - starte sicher mit AUS"));
digitalWrite(RELAIS_PIN, LOW);
}
wdt_enable(WDTO_4S);
}
⚠️ Nicht in jedem Schleifendurchlauf ins EEPROM schreiben. Eine EEPROM-Zelle hält rund 100.000 Schreibzyklen. Bei einem Schreibvorgang pro Sekunde ist sie nach gut einem Tag durch. Schreibe nur bei tatsächlichen Zustandsänderungen – EEPROM.put() und EEPROM.update() überspringen unveränderte Bytes automatisch, aber der Aufruf selbst sollte trotzdem selten kommen. Auf dem ESP32 nimmst du dafür besser Preferences.
ℹ️ Sicherer Ausgangszustand zuerst. Überlege bei jedem Ausgang: Was ist die harmlosere Stellung, wenn das Board unerwartet neu startet? Pumpe aus, Heizung aus, Rollladen stehen lassen, Alarmanlage scharf. Erst danach wiederherstellen, was der Nutzer wollte. Genau in dieser Reihenfolge – nie umgekehrt.
6Der Software-Watchdog: wenn Hardware nicht reicht
Der Hardware-Watchdog merkt nur, dass die Hauptschleife steht. Er merkt nicht, wenn dein WLAN seit zwei Stunden weg ist, der Sensor nur noch Nullen liefert oder MQTT nicht mehr publiziert – die Schleife läuft ja fleissig weiter. Dafür brauchst du eine Plausibilitätsüberwachung auf Softwareebene:
// Ueberwacht mehrere Lebenszeichen unabhaengig voneinander
struct Lebenszeichen {
const char *name;
unsigned long letzterOk;
unsigned long maxAlterMs;
};
Lebenszeichen wachen[] = {
{ "sensor", 0, 30000UL }, // spaetestens alle 30 s ein gueltiger Messwert
{ "wlan", 0, 60000UL }, // spaetestens jede Minute verbunden
{ "mqtt", 0, 120000UL } // spaetestens alle 2 Minuten publiziert
};
const uint8_t ANZAHL_WACHEN = 3;
void melde(const char *name) {
for (uint8_t i = 0; i < ANZAHL_WACHEN; i++) {
if (strcmp(wachen[i].name, name) == 0) {
wachen[i].letzterOk = millis();
return;
}
}
}
bool allesLebendig() {
unsigned long jetzt = millis();
for (uint8_t i = 0; i < ANZAHL_WACHEN; i++) {
// Beim Start einmal Kulanz geben
if (wachen[i].letzterOk == 0) {
if (jetzt > wachen[i].maxAlterMs * 2) {
Serial.printf("%s hat sich nie gemeldet\n", wachen[i].name);
return false;
}
continue;
}
// Ueberlaufsichere Differenz - funktioniert auch nach 49 Tagen
if (jetzt - wachen[i].letzterOk > wachen[i].maxAlterMs) {
Serial.printf("%s seit %lu ms stumm\n",
wachen[i].name, jetzt - wachen[i].letzterOk);
return false;
}
}
return true;
}
void loop() {
esp_task_wdt_reset(); // Hardware-Watchdog
if (leseSensorErfolgreich()) melde("sensor");
if (WiFi.status() == WL_CONNECTED) melde("wlan");
if (mqttPublizierte()) melde("mqtt");
// Software-Watchdog: kontrollierter Neustart statt stiller Zombie
if (!allesLebendig()) {
Serial.println("Lebenszeichen fehlen - Neustart in 3 s");
sichereAusgaenge();
delay(3000);
ESP.restart();
}
delay(1000);
}
💡 Warum jetzt - letzterOk > maxAlter und nicht letzterOk + maxAlter < jetzt? Weil millis() nach rund 49,7 Tagen überläuft. Die Subtraktion vorzeichenloser Zahlen liefert auch über den Überlauf hinweg das richtige Ergebnis, die Addition nicht – die kippt genau einmal und löst dann einen falschen Alarm aus. Dieselbe Regel gilt für jedes millis()-basierte Timing.
Troubleshooting
⚠️ Nano startet nach dem Watchdog-Test endlos neu und lässt sich nicht flashen: Alter Bootloader, Watchdog-Flag wird nicht gelöscht. Starte den Upload in der IDE und drücke den Reset-Taster genau dann, wenn die Meldung „Uploading“ erscheint. Danach sofort einen Sketch mit wdt_disable() als erster Zeile aufspielen.
⚠️ ESP32: „task_wdt: Task watchdog got triggered“, obwohl alles läuft: Ein anderer Task blockiert die CPU. Häufig ein while ohne vTaskDelay() – der Idle-Task kommt nie dran und meldet Alarm. Baue in jede Endlosschleife mindestens ein vTaskDelay(pdMS_TO_TICKS(1)) ein.
⚠️ Der Watchdog schlägt bei jedem DHT22-Lesevorgang zu: Der DHT22 blockiert während der Messung bis zu 250 ms und braucht 2 s Pause zwischen den Messungen. Mit WDTO_1S wird das eng. Nimm WDTO_4S oder wechsle auf einen BMP280, der in wenigen Millisekunden fertig ist.
⚠️ esp_task_wdt_init kompiliert nicht: Core-Version prüfen. 2.x will (sekunden, panic), 3.x will die Struktur. Die #if ESP_ARDUINO_VERSION_MAJOR-Weiche oben löst das dauerhaft.
⚠️ Nach dem Reset ist der Relais-Zustand zufällig: Der Pin war vor pinMode(..., OUTPUT) hochohmig. Setze bei einem High-Level-Trigger-Modul wie dem KY-019 den Ausgang direkt nach pinMode auf LOW – idealerweise mit einem 10 kΩ Pull-Down am SIG-Pin, damit die Stellung auch während des Bootens definiert ist.
⚠️ Der Watchdog löst aus, sobald das Board ins Deep Sleep geht: Auf dem ESP32 vor dem Schlafen mit esp_task_wdt_delete(NULL) abmelden. Nach dem Aufwachen läuft setup() ohnehin wieder komplett durch. Siehe auch ESP32 Deep Sleep.
⚠️ Der Zähler zeigt Hunderte Watchdog-Resets: Dann ist der Watchdog zur Krücke geworden. Miss den freien Speicher, protokolliere die letzte ausgeführte Funktion vor dem Reset ins EEPROM und behebe die Ursache – der Watchdog soll ein Notfall sein, kein Betriebsmodus.
Praxistipps
- Erst am Ende der Entwicklung aktivieren. Während du debuggst, ist ein Board, das alle vier Sekunden neu startet, die Hölle. Setze den Watchdog, wenn der Sketch funktioniert – und teste ihn dann bewusst mit dem Taster.
- Timeout großzügig wählen. Lieber 8 Sekunden zu spät neu starten als alle zehn Minuten aus Versehen. Miss die längste Durchlaufzeit deiner Schleife und nimm mindestens das Dreifache.
- Heartbeat-LED nicht vergessen. Eine LED, die im Sekundentakt blinkt, verrät dir aus zwei Metern Entfernung, ob die Schleife läuft – ohne Laptop, ohne Kabel.
- Reset-Ursache immer protokollieren. Ohne diese Information weißt du nie, ob dein System stabil läuft oder nur oft genug neu startet, um stabil zu wirken.
- Der Watchdog ersetzt keine ordentliche Stromversorgung. Brown-Out-Resets sehen aus wie Software-Hänger, sind aber ein Netzteilproblem. Ein 470 µF Elko an der 5V-Schiene löst mehr Probleme als jede Codezeile.
- Fernwartung mitdenken. Bei einem Board mit OTA-Update kannst du eine fehlerhafte Firmware aus der Ferne ersetzen. Ohne OTA heißt ein Bug im Gewächshaus: Leiter holen.
- Bei kritischen Anwendungen doppelt absichern. Hardware-Watchdog für Hänger, Software-Watchdog für fehlende Lebenszeichen, und ein täglicher geplanter Neustart um 4 Uhr morgens als dritte Ebene.
🚀 Hardware für dein ausfallsicheres Projekt
-
ESP32 DevKit USB-C – Task-Watchdog und
esp_reset_reason()an Bord -
Nano V3 ATmega328 oder UNO R3 mit USB-C – für
avr/wdt.h - Relais-Modul KY-019 – der Ausgang, den du sicher wiederherstellen willst
- AM2302 (DHT22) und BMP280 I2C – langsam gegen schnell
- 0,96 Zoll OLED SSD1306 – Uptime und Reset-Zähler im Blick
- DS3231 RTC-Modul – Zeitstempel, die den Neustart überleben
- LM2596S Step-Down-Wandler – gegen Brown-Out-Resets
- 3 mm LED-Sortiment, Breadboard 400 Kontakte und Dupont-Jumperkabel
Fazit
Der Watchdog ist die billigste Zuverlässigkeit, die du bekommen kannst: drei Zeilen Code, null Bauteile, und aus einem Projekt, das irgendwann still stehenbleibt, wird eines, das sich selbst wieder einfängt.
Drei Dinge entscheiden dabei über Erfolg oder Frust: wdt_disable() als allererste Zeile in setup(), damit du nie in einer Boot-Loop landest. Genau ein Kick pro Hauptschleife – mehr Kicks bedeuten weniger Überwachung. Und die Reset-Ursache protokollieren, denn ein Watchdog, der still im Hintergrund alle zwei Stunden zuschlägt, versteckt genau den Bug, den du eigentlich finden wolltest. Wer das beherzigt, hat ein Board, das auch dann noch läuft, wenn niemand hinsieht.