Every embedded system will eventually encounter a condition where code execution stalls — an infinite loop waiting for a sensor that never responds, a network timeout that never fires, or a stack overflow that corrupts execution flow. Without a safety net, the system stays locked up until someone manually cycles power. The Arduino watchdog timer is that safety net: a hardware countdown that automatically resets the microcontroller if software does not periodically reset it. Used correctly, the WDT transforms fragile prototypes into robust, self-recovering devices. This guide covers everything from basic WDT setup to advanced patterns for production-grade Arduino firmware.
- What Is a Watchdog Timer?
- WDT Hardware on ATmega Chips
- The avr/wdt.h Library
- WDT Timeout Values
- Basic WDT Usage Pattern
- Detecting a WDT Reset
- Advanced Patterns: Blocking Operations
- WDT in Interrupt Mode
- Common Mistakes to Avoid
- Frequently Asked Questions
What Is a Watchdog Timer?
A watchdog timer is an independent hardware counter that runs from a separate oscillator, independent of the main CPU clock. Software must periodically “pat” or “feed” the watchdog by resetting the counter before it reaches zero. If the counter expires — meaning software failed to pat it within the timeout period — the WDT triggers a system reset.
The watchdog metaphor is apt: just as a guard dog barks if the handler does not check in regularly, the WDT resets the system if the program stops running properly. This handles scenarios like:
- Infinite loops waiting for a condition that never arrives
- Deadlocked RTOS tasks
- Hardware peripheral hangs (UART, SPI, I2C timeouts)
- Stack overflow corrupting the program counter
- Corrupted memory causing the CPU to execute invalid instructions
The key characteristic is independence: because the WDT has its own oscillator, it continues running even if the main clock source fails or the CPU enters an error state that would normally freeze all operation.
WDT Hardware on ATmega Chips
ATmega328P (Arduino Uno/Nano) and ATmega2560 (Mega) both have an integrated WDT driven by an internal 128 kHz oscillator. This oscillator is separate from the 16 MHz crystal used for normal operation — the WDT continues counting even during sleep modes that stop the main clock.
The ATmega WDT has three operating modes:
- Reset Mode: WDT timeout triggers a hardware reset. This is the most common use case for lockup recovery.
- Interrupt Mode: WDT timeout fires an ISR (Interrupt Service Routine) instead of resetting. Useful for logging or state saving before reset.
- Interrupt + Reset Mode: First timeout fires an ISR; if the ISR does not reset the counter, the next timeout resets the chip. Combines graceful shutdown with guaranteed recovery.
The WDT is controlled through the WDTCSR register (Watchdog Timer Control and Status Register). Writing to this register requires a timed sequence — you must write the enable bit and the change bit within 4 CPU cycles to prevent accidental WDT configuration changes caused by noise or code errors.
The avr/wdt.h Library
Arduino’s AVR core includes <avr/wdt.h>, which provides three macros that abstract the hardware register sequence:
wdt_enable(WDTO_xS)— Enable the WDT with the specified timeout periodwdt_reset()— Reset the WDT counter (the “pat” or “feed” operation)wdt_disable()— Disable the WDT entirely
These macros handle the timed register write sequence automatically, so you do not need to write the low-level WDTCSR sequence manually.
An important caveat: the Arduino bootloader on Uno and Nano has a known WDT interaction. When the WDT fires and resets the chip, the bootloader runs first (it is at the top of the reset vector). Some older bootloaders (Optiboot <4.4) do not disable the WDT on startup, causing the bootloader itself to get reset-looped before your sketch loads. Modern bootloaders (Optiboot 4.4+ shipped with modern Arduino IDE distributions) handle this correctly by disabling WDT at boot. If you experience repeated random resets, verify your bootloader version.
WDT Timeout Values
The avr/wdt.h library defines these timeout constants:
| Constant | Timeout Period |
|---|---|
| WDTO_15MS | 15 milliseconds |
| WDTO_30MS | 30 milliseconds |
| WDTO_60MS | 60 milliseconds |
| WDTO_120MS | 120 milliseconds |
| WDTO_250MS | 250 milliseconds |
| WDTO_500MS | 500 milliseconds |
| WDTO_1S | 1 second |
| WDTO_2S | 2 seconds |
| WDTO_4S | 4 seconds |
| WDTO_8S | 8 seconds |
Choose a timeout that is comfortably longer than your longest normal code path but short enough to recover quickly from lockups. For a sketch that normally completes its loop() in under 100 ms, WDTO_1S is a solid choice — it gives a 10x margin while recovering within 1 second of a lockup.
Basic WDT Usage Pattern
The fundamental pattern is: enable WDT in setup(), reset it at the top of loop(), and ensure no code path inside loop() takes longer than the WDT period without a reset call.
#include <avr/wdt.h>
void setup() {
Serial.begin(9600);
// Enable WDT with 2 second timeout
// If loop() does not complete within 2s, system resets
wdt_enable(WDTO_2S);
Serial.println("WDT enabled. Starting main loop.");
}
void loop() {
// Pat the watchdog first thing
wdt_reset();
// Your main application code here
readSensors();
processData();
updateOutputs();
// If any of the above hang, WDT fires and resets
}
This simple pattern prevents most common lockups. However, it is not sufficient for code with blocking operations that legitimately take longer than the WDT period.
Detecting a WDT Reset
When the WDT fires, the microcontroller resets with a special flag set in the MCUSR (MCU Status Register). Reading this flag in setup() lets you detect whether the reset was due to a WDT timeout, a power-on reset, an external reset, or a brownout:
#include <avr/wdt.h>
void setup() {
// Read and clear reset flags immediately
uint8_t resetFlags = MCUSR;
MCUSR = 0; // Clear all flags
wdt_disable(); // MUST disable WDT early in setup
Serial.begin(9600);
if (resetFlags & (1 << WDRF)) {
Serial.println("*** WDT RESET DETECTED - system recovered from lockup ***");
logWdtReset();
} else if (resetFlags & (1 << PORF)) {
Serial.println("Power-on reset");
} else if (resetFlags & (1 << EXTRF)) {
Serial.println("External reset (reset button)");
} else if (resetFlags & (1 << BORF)) {
Serial.println("Brownout reset (supply voltage dipped)");
}
// Re-enable WDT after setup completes
wdt_enable(WDTO_2S);
}
void logWdtReset() {
#include <EEPROM.h>
uint8_t count = EEPROM.read(0);
EEPROM.write(0, count + 1);
}
Note: wdt_disable() at the very start of setup() is critical. After a WDT reset, the WDT is still active with its previous timeout. If your setup() takes longer than that timeout (initialising sensors, opening SD files, establishing WiFi), the WDT will fire again before your code fully initialises — creating a reset boot loop.
Advanced Patterns: Blocking Operations
Real applications have sections that legitimately take longer than a 1-2 second WDT timeout: uploading to a cloud API, writing to an SD card, waiting for a GPS fix. Here are patterns to handle these safely.
Pattern 1: Reset WDT Within Long Operations
If you control the code inside the long operation, add strategic wdt_reset() calls:
void writeToSD(File& file, uint8_t* data, size_t len) {
for (size_t i = 0; i < len; i += 512) {
size_t chunk = min((size_t)512, len - i);
file.write(data + i, chunk);
wdt_reset(); // Pat watchdog after each 512-byte chunk
}
}
Pattern 2: Timeout Wrapper
For third-party blocking calls, wrap them with a timeout using millis():
// Wait for GPS fix with timeout, resetting WDT while waiting
bool waitForGPS(uint32_t timeoutMs) {
uint32_t start = millis();
while (!gps.location.isValid()) {
wdt_reset();
if (millis() - start > timeoutMs) {
return false; // Timeout
}
delay(100);
}
return true;
}
Pattern 3: Watchdog-Aware Task Scheduler
volatile uint32_t taskLastRun[4] = {0, 0, 0, 0};
const uint32_t TASK_TIMEOUT = 5000; // 5 seconds
void checkTasks() {
uint32_t now = millis();
for (int i = 0; i < 4; i++) {
if (now - taskLastRun[i] > TASK_TIMEOUT) {
return; // Task i unhealthy, let WDT fire
}
}
wdt_reset(); // All tasks healthy
}
WDT in Interrupt Mode
Instead of an immediate reset, the WDT can fire an ISR on timeout. This enables logging state to EEPROM before resetting:
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <EEPROM.h>
ISR(WDT_vect) {
// Write diagnostic data to EEPROM before reset
EEPROM.write(1, 0xDE);
EEPROM.write(2, 0xAD);
}
void setup() {
// Enable WDT in interrupt + reset mode, 1 second
cli();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (1 << WDIE) | (1 << WDE) | (1 << WDP2) | (1 << WDP1);
sei();
}
Common Mistakes to Avoid
- Not disabling WDT at the start of setup(): After a WDT reset, WDT is still running. Long setup() initialisation will trigger another reset, creating a boot loop.
- Calling wdt_reset() in an ISR: ISRs run even during lockups. If you call wdt_reset() in a timer ISR, a locked-up loop() will still appear healthy to the WDT. Only reset the WDT from the main application flow.
- Choosing too short a timeout: If your normal loop() ever takes longer than the WDT period — even briefly — the system falsely resets. Always profile the maximum realistic loop duration and set WDT at least 3-5x longer.
- Using delay() inside WDT-protected code: A
delay(3000)call in loop() with WDTO_2S will trigger a reset every 2 seconds. Replace delay() with non-blocking millis() timing patterns.
Frequently Asked Questions
Does the WDT work during Arduino sleep modes?
Yes. The WDT’s internal 128 kHz oscillator continues running in all sleep modes, including Power-Down (the deepest sleep mode that stops the main oscillator). This makes it useful both for lockup recovery and as a low-power wake timer — configure WDT in interrupt mode, sleep the CPU, and the WDT ISR wakes it at intervals. See the Arduino Sleep Modes guide for full low-power patterns.
Can the WDT reset cause EEPROM corruption?
A clean WDT reset does not corrupt EEPROM. EEPROM write operations take ~3.3 ms to complete; an in-progress write interrupted by a WDT reset could leave the affected cell in an undefined state. For critical EEPROM data, use a wear-levelling scheme and validate data with a checksum after reset.
Will the WDT help if my Arduino enters an infinite loop?
Yes — that is exactly what it is designed for. Any infinite loop that does not call wdt_reset() will trigger a system reset when the WDT timeout expires. The system then reboots cleanly and resumes normal operation.
How do I use the WDT with the Servo or Ethernet library?
Both libraries use blocking calls internally. Ensure your WDT timeout is set generously enough to cover the longest blocking call. For Ethernet, connection establishment can take several seconds — use WDTO_8S and call wdt_reset() within your waiting loop. The Servo library uses Timer1 interrupts and is WDT-transparent.
Can I use the WDT on Arduino-compatible boards like ESP8266 or ESP32?
These boards have their own WDT implementations separate from AVR’s avr/wdt.h. ESP8266 has a software WDT (configurable via ESP.wdtEnable()) and hardware WDT. ESP32 has a TWDT (Task WDT). The concepts are identical but the APIs differ — check the board-specific documentation.
Build more reliable Arduino projects — browse our complete collection of Arduino boards and development tools at Zbotic. From the classic Uno to the compact Pro Mini, we have the right board for every reliability requirement.
Add comment