Watchdog Timers for Arduino and ESP32: Hardware WDT, Task Watchdogs, and Recovering from Hangs
Our FreeRTOS tasks guide covers splitting work across the ESP32's two cores, and it's worth reading first because watchdog timers are fundamentally about the same thing: making sure none of those tasks (or the main loop, on simpler Arduino-style sketches) ever gets stuck without anyone noticing. A watchdog timer is a hardware or software countdown that resets the chip if it isn't "fed" (acknowledged) regularly — the assumption being that a task too busy or too broken to feed the watchdog is a task that needs a reboot, not a device quietly hung on a shelf or inside an enclosure somewhere until a human notices.
Why This Matters More Than It Sounds Like
A watchdog is easy to dismiss as boilerplate until you've had a field-deployed sensor node silently stop reporting because of a rare I2C bus lockup, a malloc failure under memory pressure, or a Wi-Fi reconnect routine that blocks forever waiting on a network that never comes back. Without a watchdog, that device stays hung until someone physically power-cycles it. With one configured correctly, it reboots itself within seconds and resumes normal operation — the difference between a self-healing deployment and a support call.
The Watchdog Layers on ESP32
Watchdog TypeWhat It MonitorsTypical Use Hardware / RTC watchdogThe lowest-level safety net — resets the chip if nothing else has reset it in a very long time, catching failures even in the bootloader or a totally hung systemAlmost never configured directly by application code; acts as the last line of defense Interrupt Watchdog (IWDT)Makes sure interrupt-level code isn't blocking so long that the system can't service other interruptsEnabled by default in ESP-IDF; triggers a panic and reboot if tripped, usually indicating a bug in ISR code Task Watchdog Timer (TWDT)Individual FreeRTOS tasks that are registered with it — each must call the "feed" function periodically or it's flagged as stuckThe one you'll interact with most directly; ideal for making sure your main sensor-read loop, network task, etc. haven't hung Software watchdog (Arduino esp_task_wdt)Same underlying TWDT mechanism, exposed through the simpler Arduino APICommon in Arduino-IDE sketches that don't use ESP-IDF directlySetting Up the Task Watchdog in Arduino IDE
#include <esp_task_wdt.h> #define WDT_TIMEOUT 8 // seconds void setup() { esp_task_wdt_config_t twdt_config = { .timeout_ms = WDT_TIMEOUT * 1000, .idle_core_mask = 0, // don't watch idle tasks .trigger_panic = true, // reboot on timeout }; esp_task_wdt_init(&twdt_config); esp_task_wdt_add(NULL); // register the current task (loop task) } void loop() { esp_task_wdt_reset(); // feed the dog every pass doSensorRead(); doNetworkCheck(); delay(1000); }The key design decision is the timeout value: too short, and a normal-but-slow operation (a Wi-Fi reconnect, an NVS write) trips a false reboot; too long, and a genuinely hung device sits unresponsive for minutes before recovering. Size the timeout around your slowest expected normal-case operation, with margin — if your Wi-Fi reconnect logic can legitimately take 10 seconds, don't set an 8 second watchdog around code that calls it.
Registering Multiple Tasks
On a multi-task FreeRTOS design — say, a sensor-read task pinned to core 0 and a networking task pinned to core 1, per the core-pinning patterns in our FreeRTOS guide — register each task individually and feed the watchdog from within that task's own loop, not from a single central point. This is the actual value of the per-task watchdog over a single global one: it tells you specifically which task hung, which is enormously useful when debugging a crash log after the fact instead of just knowing "something, somewhere, stopped responding."
void sensorTask(void *param) { esp_task_wdt_add(NULL); for (;;) { esp_task_wdt_reset(); readSensors(); vTaskDelay(pdMS_TO_TICKS(500)); } } void networkTask(void *param) { esp_task_wdt_add(NULL); for (;;) { esp_task_wdt_reset(); handleMQTT(); vTaskDelay(pdMS_TO_TICKS(100)); } }What Happens on Timeout
By default with trigger_panic = true, a watchdog timeout triggers a panic handler that prints the stack trace of the offending task to serial (invaluable for debugging — capture it over UART during development) and then reboots via the RTC watchdog. You can instead set it to just report without rebooting, which is useful during development to catch and study hangs without losing the debug session, but should not be the configuration you ship — a watchdog that only warns and never recovers defeats the entire purpose.
ESP-IDF vs Arduino Differences
AspectESP-IDFArduino Core Default TWDT stateEnabled by default in sdkconfig, watching idle tasks on both coresNot enabled by default — you opt in with esp_task_wdt_init() Idle task watchdogConfigurable via menuconfig; commonly disabled for tasks that intentionally starve the idle task under heavy loadNot exposed directly; less relevant since Arduino sketches rarely need this level of tuning Brownout detectorSeparate from the watchdog entirely, but related — catches voltage sag rather than a hung taskSame underlying hardware, enabled by default; a common source of unexplained resets on boards powered by a marginal USB supply, easy to mistake for a watchdog issueWatchdogs on Arduino (AVR) Boards
If you're working with a classic Arduino Uno/Nano rather than an ESP32, the same concept exists via <avr/wdt.h>, though the mechanism is simpler — a single hardware watchdog with fixed timeout options (15ms up to 8 seconds) rather than the ESP32's layered, per-task system. wdt_enable(WDTO_8S) arms it, and wdt_reset() feeds it; forgetting to call wdt_reset() anywhere in a long-running loop with a tight timeout is the most common way beginners accidentally reboot-loop their own board.
Common Pitfalls
PitfallFix Watchdog trips during normal Wi-Fi/OTA operationsIncrease the timeout for tasks that legitimately block during network operations, or feed the watchdog from a secondary point inside a long operation rather than only at the top of the loop Watchdog never trips even when the device is clearly hungConfirm the hung code path actually yields to FreeRTOS (a tight while(1) with no vTaskDelay can starve the idle task the watchdog itself depends on, on certain configurations) Reboot loop that never fully recoversThe watchdog is correctly catching a real bug — check the panic stack trace over serial rather than disabling the watchdog to make the symptom go away Watchdog disabled entirely "to stop the crashes"This hides the underlying bug and replaces a clean, fast recovery with a device that hangs forever the next time it happens — treat a tripping watchdog as a bug report, not a nuisanceA properly tuned watchdog is one of the cheapest reliability upgrades available for any ESP32 or Arduino project that runs unattended — a sensor node, a home automation controller, anything you don't want to have to power-cycle by hand. Set the timeout with real margin around your slowest legitimate operation, register every long-running task individually so failures are traceable, and treat every trip as a bug to investigate rather than noise to silence.
Related Guides
- ESP32 I2S Audio: Playing and Recording Sound with a MAX98357A DAC and INMP441 Microphone
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols
- How to Use Sensors with Arduino and ESP32: Temperature, Distance, Load, Current, and Hall Effect
- How to Control Motors with Arduino and ESP32: Stepper, DC, and Servo Drivers
- ESP32: Setting Up for Arduino IDE