In this guide
Your sensor node has been running for three weeks. Then it stops — not a clean failure, just silence. You drive to the site, press reset, and it works again. Two weeks later, silence again. The bug is a rare hang: a sensor that occasionally never responds, a deadlock that needs exactly the wrong timing, a memory corruption that takes days to manifest. You will never find it by testing, because it needs weeks of runtime to appear.
The watchdog timer is the embedded world's admission that software has bugs: an independent hardware timer that resets the chip unless your code proves, over and over, that it is still alive. This guide explains how watchdogs work, the ESP32's watchdog options, how to feed them correctly, the design patterns that make them actually useful, and the mistakes that turn a safety net into a nuisance.
The concept: a dead man's switch
A watchdog timer (WDT) is a countdown timer in hardware, independent of the CPU. Your code must reset ("feed" or "kick") the timer periodically. If the timer ever reaches zero — because your code hung, crashed, or got stuck — it resets the processor automatically.
The key property is independence: the watchdog keeps counting even if the CPU is locked in an infinite loop, because it is separate hardware (or, on multi-core chips, supervised separately). A software-only "reset myself if stuck" scheme cannot work — stuck software cannot reset itself.
Note: a watchdog converts hangs into resets. It does not fix the bug; it converts an unrecoverable state (needs a human) into a recoverable one (automatic reboot). For deployed devices, that conversion is everything.
What watchdogs catch (and what they do not)
| Caught | Not caught |
|---|---|
| Infinite loops | Bugs that corrupt data but keep feeding the watchdog |
| Deadlocks (two tasks waiting on each other) | Wrong-but-running behavior (bad sensor calibration) |
| A task that crashed or never runs | Power supply failures (nothing to reset with) |
| Runaway code that never returns to the feed point | Feeding from an interrupt while the main loop is dead |
| Stack overflows that halt execution | Gradual memory leaks (until they finally hang — then caught) |
The pattern: watchdogs catch liveness failures (the system stopped making progress), not correctness failures (the system confidently does the wrong thing). Design your feeding strategy around proving progress, not just proving the CPU is warm.
The ESP32's watchdog options
The ESP32 has several, and the distinction matters:
| Watchdog | Watches | Typical timeout | Notes |
|---|---|---|---|
| Task watchdog (TWDT) | Individual FreeRTOS tasks | Seconds (configurable) | Each watched task must feed; catches single-task hangs |
| Interrupt watchdog | Interrupt handlers | ~300 ms default | Catches ISRs that run too long |
| RTC watchdog | Main system / RTC domain | Configurable | Can trigger reset even in some sleep states |
In Arduino-ESP32, the task watchdog is enabled by default for the loop task — which is why beginners sometimes meet mysterious resets: a delay(10000) or a blocking sensor read longer than the watchdog timeout triggers it. Understanding this turns a confusing crash into a configured safety feature.
In ESP-IDF you configure the TWDT explicitly: which tasks are watched, the timeout, and the behavior on expiry (reset, or panic with debug info during development).
Feeding correctly: prove progress
The naive approach — feed the watchdog at the top of loop() — proves only that the loop iterates. If your sensor read hangs inside the loop iteration, the feed never happens and the watchdog fires: correct. But if you feed from a timer interrupt while the main task is deadlocked, the watchdog never fires: useless.
Rules for meaningful feeding:
- Feed from the task whose liveness matters, after it completes its real work — not from interrupts, not from a separate "feeder" task.
- Feed at the end of a successful work cycle: read sensors → process → transmit → feed → sleep. The feed means "I completed a full cycle."
- Timeout longer than the worst legitimate cycle, with margin: if a normal cycle takes up to 5 seconds (slow sensor + WiFi), a 10-second timeout with a 30-second panic threshold is sane. Too short = false resets during normal slow operations.
- One watchdog per critical task in FreeRTOS designs: the sensor task feeds its own watch, the network task feeds its own. A hang in either resets the system.
#include "esp_task_wdt.h"
void setup() {
// Watch the current task with a 15-second timeout
esp_task_wdt_init(15, true); // true = reset on expiry
esp_task_wdt_add(NULL); // NULL = current task
}
void loop() {
readSensors(); // if this hangs forever, no feed -> reset
transmitReadings(); // slow WiFi is fine, within timeout
esp_task_wdt_reset(); // full cycle completed: prove it
enterDeepSleep();
}
Watchdogs and sleep: an important interaction
Deep sleep stops the CPUs — and the task watchdog with them. The standard battery-node pattern (wake → work → feed → sleep) is naturally compatible: the watchdog only supervises the awake window. But two gotchas:
- Feeding before sleep is pointless but harmless; the meaningful feed is after the work completes.
- The RTC watchdog can supervise sleep itself — if the chip should wake every 10 minutes but does not (a deeper hardware hang), the RTC watchdog can force a reset. Consider it for nodes that must never stay dark.
Design patterns that make watchdogs useful
Log the reset cause. On boot, read the reset reason: if it was a watchdog reset, log it (to flash, or transmit it with the next telemetry). A node that watchdog-resets weekly has a bug you should know about — the watchdog is a safety net, not a fix. Telemetry like {"resets": 3, "last_reset": "watchdog"} turns invisible hangs into visible data.
Degrade gracefully after watchdog resets. Keep a reset counter in RTC memory: if the device has watchdog-reset three times in an hour, something is systematically wrong — fall back to a safe mode (minimal functionality, longer sleeps) instead of reset-looping forever. A device that resets every 15 seconds is arguably worse than one that stays down; it can spam your backend and drain its battery.
Watchdog during development vs deployment. During development, configure the watchdog to panic with a backtrace instead of silently resetting — you want the debugger's information. Switch to reset behavior for deployment builds.
Testing that the watchdog actually works
A safety net you have never tested is a rumor. Deliberate fault injection during development:
- The infinite-loop test: add a debug command (a serial input or a special MQTT message) that enters an endless loop. The watchdog should reset the chip within the configured timeout. If it does not, your feeding strategy has a hole — often a feed happening in an interrupt or a second task keeping the system alive.
- The slow-operation test: simulate your worst-case legitimate timing (slowest sensor read, longest WiFi connect, biggest OTA download) and confirm the watchdog does NOT fire. This validates the timeout margin.
- The deadlock test: in FreeRTOS builds, have two tasks take two mutexes in opposite order under a debug trigger. The task watchdog should catch the resulting hang.
- The reset-cause check: after each test, verify the boot code correctly identifies the reset as watchdog-caused and logs it. A watchdog that resets but is misreported as a power-on reset hides the data you need.
Run these on the bench, then remove the debug triggers for deployment — or guard them behind a physical button press, since a remotely triggerable hang is a self-inflicted denial of service. Document the test results in your report: evaluated claims about reliability, backed by a repeatable procedure, are what separate engineering from assertion.
Common mistakes checklist
- Feeding from an interrupt. Proves the interrupt runs, not that the work completes.
- Timeout shorter than legitimate slow operations. OTA downloads and first WiFi connects are slow — size the timeout for the worst case.
- Disabling the watchdog to "fix" resets. The resets were the symptom; find the hang.
- No reset-cause logging. Invisible watchdog resets hide the bug forever.
- No degradation policy. Reset-looping forever drains batteries and spams backends.
- One feed point for a multi-task system. A deadlocked secondary task goes unnoticed.
- Blocking sensor reads with no timeout. The hang the watchdog catches should ideally never happen — add timeouts to I2C/SPI reads too.
Where to go from here
- FreeRTOS basics on ESP32 — task watchdogs make the most sense once you understand tasks, and this is where multi-task feeding strategies come from.
- ESP32 low-power battery design guide — the power side of the wake-work-feed-sleep pattern for battery nodes.
- More robust firmware designs in the IoT & Embedded branch hub.