Your Arduino sketch worked perfectly during testing. But after 3 days running unattended, it froze — the display went blank, the sensors stopped updating, and a remote reset was impossible. This is one of the most frustrating problems in embedded development, and the watchdog timer (WDT) is the hardware solution that’s been on your Arduino all along, waiting to be used.
The watchdog timer is a simple but powerful mechanism: a hardware countdown timer that automatically resets the microcontroller if the software fails to periodically signal that everything is still running. Think of it as a deadman switch for your Arduino — if your program doesn’t regularly check in, the hardware assumes something has gone wrong and restarts the system.
This guide covers everything from the fundamentals of how the WDT works to production-ready code patterns for reliable, self-recovering Arduino applications.
How the Watchdog Timer Works
The ATmega2560 and ATmega328P (used in Mega and Uno respectively) contain an independent hardware watchdog timer driven by a separate internal oscillator — not the main system clock. This is crucial: even if your main clock fails or the CPU gets stuck in an infinite loop, the watchdog timer continues counting.
The WDT operates on a simple principle:
- You configure the watchdog with a timeout period (ranging from 15ms to 8 seconds on AVR Arduinos)
- Your program must call
wdt_reset()— commonly called “kicking” or “petting” the watchdog — before the timeout expires - If the timer reaches zero without being reset, the WDT triggers a system reset
- After the reset, your program starts from the beginning as if power was just applied
The watchdog can be configured in three modes:
- Interrupt mode — triggers an ISR instead of a reset, useful for saving state before restart
- Reset mode — directly resets the MCU when the timer expires
- Interrupt + Reset mode — fires the ISR first, then resets if ISR doesn’t disable the WDT
For most applications, Reset mode is what you want. The WDT is always operating from its own 128kHz internal oscillator (on AVR), making it immune to any software or main clock failure.
Setting Up the Watchdog Timer
Arduino’s avr/wdt.h library provides three key functions for AVR-based boards (Uno, Mega, Nano, Leonardo, etc.):
wdt_enable(timeout)— enables the WDT with a specified timeout periodwdt_disable()— disables the WDT (requires a timed sequence for security)wdt_reset()— resets the watchdog counter (“pet” the dog)
The timeout constants available are:
| 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 worst-case main loop execution time. If your loop normally completes in 100ms but could take up to 500ms during a slow SD card write, set the watchdog to 2S or 4S to give adequate margin.
Basic Implementation with wdt.h
Here is a minimal working watchdog implementation:
#include <avr/wdt.h>
void setup() {
Serial.begin(9600);
// Disable WDT first (safe practice during setup)
wdt_disable();
// Your normal setup code here
Serial.println("System starting...");
delay(2000); // Long delays are safe before WDT is enabled
// Enable watchdog with 4 second timeout
wdt_enable(WDTO_4S);
Serial.println("Watchdog enabled. 4 second timeout.");
}
void loop() {
// Pet the watchdog at the start of each loop iteration
wdt_reset();
// Your main program logic
readSensors();
updateDisplay();
handleCommunication();
// If any of the above block for more than 4 seconds,
// the watchdog will reset the Arduino
}
void readSensors() {
// Sensor reading code...
}
void updateDisplay() {
// Display update code...
}
void handleCommunication() {
// Network/serial communication code...
}
The critical rule: never call delay() for more than a fraction of your WDT timeout. A delay(5000) with a 4-second watchdog will guarantee a reset. Use millis()-based non-blocking timing instead.
Here’s the non-blocking pattern that works safely with the watchdog:
#include <avr/wdt.h>
unsigned long lastSensorRead = 0;
unsigned long lastDisplayUpdate = 0;
void setup() {
Serial.begin(9600);
wdt_disable();
// ... setup code ...
wdt_enable(WDTO_2S);
}
void loop() {
wdt_reset(); // Always first thing in loop
unsigned long now = millis();
// Read sensors every 500ms (non-blocking)
if (now - lastSensorRead >= 500) {
lastSensorRead = now;
readSensors();
}
// Update display every 200ms (non-blocking)
if (now - lastDisplayUpdate >= 200) {
lastDisplayUpdate = now;
updateDisplay();
}
// Loop returns quickly — WDT stays happy
}
Common Pitfalls and Bootloader Issues
The watchdog timer has several gotchas that trip up even experienced developers.
The Bootloader Reset Loop Problem
This is the most notorious WDT issue on Arduino. When the WDT triggers a reset, the WDTON fuse bit may remain set, meaning the WDT is still active when the bootloader starts running. If the bootloader takes longer than the WDT timeout to hand control to your sketch, the WDT fires again — triggering another reset — creating an infinite reset loop where your Arduino never successfully boots.
The solution for older bootloaders is to disable the WDT immediately at the start of your sketch using a special technique:
#include <avr/wdt.h>
// This runs BEFORE setup() — must be placed at global scope
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
void wdt_init(void) {
MCUSR = 0; // Clear reset source flags
wdt_disable(); // Disable watchdog immediately
}
void setup() {
Serial.begin(9600);
// Now safely enable WDT with your desired timeout
wdt_enable(WDTO_4S);
}
Modern Arduino bootloaders (Optiboot, used in most current Uno R3 and Mega R3 boards) handle the WDT reset correctly and don’t require this workaround, but adding it is harmless and makes your code more portable across different board revisions.
Long Operations Inside Loops
Watch out for these WDT-killers in your loop:
- WiFi/Ethernet connections that can time out (add wdt_reset() inside retry loops)
- SD card operations on slow cards
- Waiting for serial data without a timeout
- Blocking library calls (some sensor libraries have internal delays)
Interrupts and the WDT
The WDT timer continues to count during interrupt service routines. If an ISR takes longer than the WDT timeout (unlikely but possible with a short timeout), a reset will occur during the ISR. Use the longest practical timeout for your application.
Advanced Patterns for Production Code
Task Monitoring with Per-Task Timeouts
For complex applications with multiple subsystems, you can implement software watchdogs at the task level, with the hardware WDT as the final backstop:
#include <avr/wdt.h>
// Software task watchdog
struct TaskWatchdog {
unsigned long lastKick;
unsigned long timeout;
const char* name;
bool triggered;
};
TaskWatchdog tasks[] = {
{0, 5000, "SensorTask", false},
{0, 10000, "NetworkTask", false},
{0, 3000, "DisplayTask", false}
};
void kickTask(int taskIndex) {
tasks[taskIndex].lastKick = millis();
tasks[taskIndex].triggered = false;
}
void checkTasks() {
unsigned long now = millis();
for (int i = 0; i < 3; i++) {
if (now - tasks[i].lastKick > tasks[i].timeout) {
if (!tasks[i].triggered) {
tasks[i].triggered = true;
Serial.print("WARN: Task ");
Serial.print(tasks[i].name);
Serial.println(" overdue!");
// Log to EEPROM, then let hardware WDT catch it
}
}
}
}
void loop() {
wdt_reset(); // Hardware WDT - 8 second backstop
checkTasks(); // Software task monitoring
runSensorTask();
runNetworkTask();
runDisplayTask();
}
Watchdog Timer and Sleep Mode
The WDT has a special role in low-power Arduino designs: it can be used as a wake-up timer even in the deepest sleep modes. This is entirely separate from its reset function.
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
volatile bool wdt_fired = false;
// WDT interrupt service routine (interrupt mode, not reset mode)
ISR(WDT_vect) {
wdt_fired = true;
}
void setupWDTInterrupt(uint8_t timeout) {
cli(); // Disable interrupts during setup
MCUSR &= ~(1 << WDRF); // Clear WDT reset flag
// Set WDT in interrupt mode (WDIE bit, not WDE bit)
WDTCSR |= (1 << WDCE) | (1 << WDE); // Enable change sequence
WDTCSR = (1 << WDIE) | timeout; // Interrupt only, no reset
sei(); // Re-enable interrupts
}
void sleepForSeconds(int seconds) {
int wakes = 0;
int target = (seconds + 7) / 8; // WDTO_8S = ~8 second wakes
while (wakes < target) {
wdt_fired = false;
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu(); // CPU stops here
sleep_disable(); // Resumes here after WDT fires
if (wdt_fired) wakes++;
}
}
This pattern lets you sleep for extended periods (hours, days) while waking periodically for sensor readings — with the WDT serving as an alarm clock rather than a reset trigger. Battery-powered sensor nodes use this technique to achieve months of operation on a single cell.
Important: When using WDT for sleep wakeup, configure it in interrupt mode (WDIE bit), not reset mode (WDE bit). Using both simultaneously enables the interrupt+reset mode — the ISR fires first, and if it doesn’t clear the WDT, a reset follows.
Detecting and Logging Watchdog Resets
Knowing that a WDT reset happened is crucial for diagnosing why your program froze. The MCU Status Register (MCUSR) stores the cause of the last reset, including a specific bit for WDT resets:
#include <avr/wdt.h>
#include <EEPROM.h>
#define EEPROM_RESET_COUNT_ADDR 0
#define EEPROM_RESET_CAUSE_ADDR 2
void setup() {
uint8_t resetCause = MCUSR; // Read reset cause BEFORE clearing
MCUSR = 0; // Clear all reset flags
wdt_disable(); // Disable WDT immediately
Serial.begin(9600);
delay(500);
// Report reset cause
if (resetCause & (1 << WDRF)) {
Serial.println("ALERT: Reset caused by Watchdog Timer!");
// Increment WDT reset counter in EEPROM
uint16_t count;
EEPROM.get(EEPROM_RESET_COUNT_ADDR, count);
if (count == 0xFFFF) count = 0; // First use after flash
count++;
EEPROM.put(EEPROM_RESET_COUNT_ADDR, count);
Serial.print("Total WDT resets: ");
Serial.println(count);
} else if (resetCause & (1 << EXTRF)) {
Serial.println("INFO: External reset (reset button)");
} else if (resetCause & (1 << PORF)) {
Serial.println("INFO: Power-on reset");
}
// Now enable WDT for normal operation
wdt_enable(WDTO_4S);
}
By logging WDT resets to EEPROM, you can connect your Arduino via USB days later and read out exactly how many times it recovered itself — invaluable data for reliability engineering on deployed projects.
Frequently Asked Questions
Will the watchdog timer interfere with the Arduino bootloader during programming?
On modern Arduinos using the Optiboot bootloader (most current Uno R3 and Mega R3 boards), the bootloader correctly handles WDT resets and the WDT is disabled during the bootloader phase. On older boards with the original bootloader, rapid WDT resets could interfere with programming. If you have trouble uploading sketches to a board with WDT enabled, use the .init3 technique described in this article to disable the WDT before the bootloader can be affected, or simply power-cycle while holding reset to trigger normal programming mode.
What is the minimum watchdog timeout I should use?
Choose the shortest timeout that your worst-case loop execution time safely fits within, with at least 50% margin. If your loop can take up to 300ms in extreme conditions, use WDTO_1S or WDTO_2S. Extremely short timeouts (15ms, 30ms) are only appropriate for simple, very fast control loops. In general, WDTO_2S or WDTO_4S are good defaults for most projects. Setting the timeout too short causes frequent false resets; too long means it takes too long to recover from a genuine freeze.
Does the watchdog timer work if the Arduino is stuck in an interrupt service routine?
Yes. The WDT operates from its own independent oscillator and continues counting even when the CPU is inside an ISR or when global interrupts are disabled. This is one of the key advantages of hardware watchdog timers over software timeout mechanisms. If your ISR hangs or takes too long, the WDT will fire and reset the system. This is why it’s important to keep ISRs short and to not do long operations inside them.
Can I use the watchdog timer on ESP32 or ESP8266 Arduino boards?
Yes, but the API is different. ESP32 and ESP8266 have their own WDT implementations through the Arduino-ESP32 framework. ESP32 has both a hardware WDT and a task WDT managed by FreeRTOS. The esp_task_wdt_reset() function is used to pet the task WDT on ESP32, while ESP.wdtFeed() is used on ESP8266. The avr/wdt.h library is AVR-specific and won’t work on these boards.
Can I use the watchdog timer to make my Arduino reboot every 24 hours for a fresh start?
Yes, though it’s a bit of a workaround. The WDT maximum timeout is 8 seconds on AVR Arduinos. To achieve a daily reboot, use the WDT interrupt mode to wake from sleep periodically, count the wakeups, and after 24 hours’ worth of intervals, deliberately avoid calling wdt_reset() — the next WDT expiry then triggers a full reset. However, this is better handled with proper software state management so reboots aren’t needed. If you need daily reboots, it usually means there’s a memory leak or resource management issue worth fixing properly.
Build more reliable, self-recovering Arduino projects with the right hardware from Zbotic.in’s Arduino & Microcontrollers collection — explore our range of Arduino boards, sensor modules, and development kits with fast delivery across India.
Add comment