Arduino: the watchdog timer, recover from hangs automatically
Add a watchdog timer to your Arduino sketch so the chip resets itself when it hangs. The pattern that keeps field-deployed projects from being bricked by a stuck sensor.
A friend of mine had a weather station in a field that ran for eight months, then hung. The display still showed numbers. The sensor readings still updated. The only problem was the numbers were stuck on the value from eight months ago, because the I2C read had wedged the main loop.
He drove out, power-cycled it, and it ran another six months. Then it hung again.
The fix is a watchdog timer. It is a piece of hardware on the chip that counts down from a value you set, and if it ever hits zero, the chip resets. Your code has to “pet” the watchdog (the slang is actually “kick” or “feed”) on a regular schedule to keep it from firing. If your code hangs, it stops petting, the watchdog fires, and the chip reboots.
This is the part of embedded work that separates “I made it work on my desk” from “I made it work in a field where I cannot push the reset button.”
What you need
- Any Arduino board (Uno, Nano, Mega). The watchdog API differs on ESP8266, ESP32, and SAMD boards. This tutorial covers the AVR (Uno/Nano/Mega) API. The pattern is the same elsewhere; the calls are different.
- USB cable
- A sketch that does something that can hang (e.g. an I2C read with no timeout, a blocking call to a sensor)
What a watchdog is
The ATmega328P has a built-in hardware timer separate from the
timers you use for PWM and millis(). It runs off an internal
128 kHz oscillator (separate from the main 16 MHz crystal, which
matters: if the main clock is stuck, the watchdog can still fire).
You set a timeout. If your code does not call wdt_reset() before
the timeout expires, the chip resets. The whole thing takes one
line to enable and one line to keep alive.
The code
#include <avr/wdt.h>
void setup() {
Serial.begin(9600);
wdt_enable(WDTO_2S); // 2-second timeout
}
void loop() {
// do the work that might hang
int reading = readSensor(); // assume this can wedge
Serial.println(reading);
delay(500);
wdt_reset(); // pet the dog
}
That is the entire pattern. Enable in setup(), reset (the polite
term is “pet,” but the API says “reset”) in loop(). If loop()
ever stops running, the watchdog fires and the chip reboots.
The full list of timeouts is in the header file. The common ones:
WDTO_15MS // 15 milliseconds
WDTO_30MS // 30 ms
WDTO_60MS // 60 ms
WDTO_120MS
WDTO_250MS
WDTO_500MS
WDTO_1S
WDTO_2S
WDTO_4S
WDTO_8S
Pick a timeout that is longer than your longest legitimate work cycle, but short enough that a real hang gets noticed. For a weather station that reads every 30 seconds, 4 or 8 seconds is reasonable.
The “infinite loop rescue” pattern
If you have a sensor that you know can hang (a flaky I2C device, a Wi-Fi module that sometimes wedges), wrap the read in a tighter watchdog. Enable a short watchdog (say, 250 ms) right before the risky call, pet it during the call if you can, and disable it after. If the call hangs, the watchdog fires.
void readSensorSafely() {
wdt_enable(WDTO_250MS);
// pet repeatedly inside the call
for (int i = 0; i < 5; i++) {
wdt_reset();
// do a small chunk of the read
}
wdt_disable(); // back to the long timeout in loop()
}
wdt_disable() exists but be careful: it is not symmetric with
wdt_enable() on every chip. On the AVR, wdt_disable() actually
works. On the ESP32, you have to use esp_task_wdt_delete and the
API is more involved.
What NOT to put inside a watchdogged section
The point of a watchdog is to recover from a stuck chip. If the watchdog fires while you are in the middle of an EEPROM write, the write gets corrupted. Same for an SD card write, an I2C transaction that has not been closed, or a relay that is currently energized.
Two rules:
- Never enable a watchdog with a timeout shorter than the longest operation that must complete.
- Always think about “what state am I leaving things in if the watchdog fires right now.” If the answer is “halfway through a critical write,” the watchdog is too aggressive.
The 8-second timeout is the safety net. The 250-millisecond timeout is the local rescue for one specific risky call. Layer them.
The bootloader reset delay
When the watchdog fires on an AVR, the chip resets and the bootloader runs before your sketch does. That takes about a second on the Uno, longer on some boards. The board “looks” unresponsive for that second. If your project uses serial, the serial port disconnects and reconnects, which can confuse a host program listening on the other end.
The fix is to use a software reset (asm("jmp 0")) instead of the
hardware watchdog if you need a clean restart. For an actual hang
recovery, the hardware reset is what you want.
ESP8266 and ESP32 differences
The ESP8266 has a software watchdog (ESP.wdtFeed(), etc.) and a
hardware one. The ESP32 has esp_task_wdt_init and friends. The
APIs are completely different from the AVR ones.
The patterns are the same (enable, pet, recover from a hang), but you cannot copy-paste code from an Uno sketch to an ESP32 sketch. Look at the chip-specific docs.
When watchdogs save you vs mask real bugs
This is the part I want to be honest about. A watchdog is a recovery tool, not a debugging tool. If your code hangs every 4 seconds and the watchdog keeps rebooting it, the project “works” in the sense that the LED blinks, but the actual bug is still there. The watchdog is just hiding it.
Use the watchdog for: field deployment, where rebooting beats driving out. Use the watchdog for: libraries you do not control, where you cannot fix the underlying bug. Do not use the watchdog to cover up a bug you could fix with a timeout or a state check.
The infinite-loop rescue example
A common pattern is to add a software timeout around a blocking call. If the call returns, you are good. If it does not return in N milliseconds, you reset. The watchdog is the “if the loop is so wedged it cannot even check the timeout” safety net.
unsigned long startedWaiting = millis();
while (sensor.busy()) {
if (millis() - startedWaiting > 1000) {
// 1 second is too long; assume hang
wdt_enable(WDTO_15MS); // tight reset
while (true); // let the watchdog fire
}
}
That pattern is the right shape. Detect the hang in code if you can. Use the watchdog as the last resort.
When something breaks
- The board keeps rebooting in a loop. The watchdog is firing
because
loop()is not running. Add aSerial.println("alive")early inloop()and watch for the pattern. If the print appears once and then the board reboots, the hang is inloop(). If the print never appears, the hang is insetup(). - The board reboots when you upload a new sketch. The watchdog is still enabled from the old sketch. Hold the reset button while you click upload. Most boards let you upload while the watchdog is running; some do not.
- The 15-millisecond timeout feels like 1 second. It is not. The 15 ms is real, but the bootloader delay after reset adds a second. From the outside, the “hang” looks like a 1-second pause, not 15 ms.
wdt_disable()does nothing. On the AVR, you have to also reset the configuration register. The library handles it, but if you bypass the library and write to the register directly, you needWDTCSR = 0.
What to build next
- A weather station that records a hang to EEPROM before resetting, so you can see the failure mode after the fact.
- A greenhouse controller that reboots gracefully on hang, then sends a Wi-Fi message saying “I just reset, here is why.”
- A robot drive train where a stalled motor is detected by a timeout, and the watchdog is the last-resort reset.