How Does a Watchdog Timer Work in an Embedded System?

How does a watchdog timer work in an embedded system?

I still remember the first time a watchdog timer saved a project of mine — a field sensor node that occasionally froze after weeks of continuous operation due to a rare bug I never fully tracked down. Instead of requiring someone to physically drive out and power-cycle it, the watchdog quietly detected the freeze and rebooted the device on its own. That’s the entire point of a watchdog: it’s a safety net for when your firmware, despite your best efforts, doesn’t behave the way it should. In this article, I’ll explain exactly how a watchdog timer works, how to configure one, and how to design firmware around it properly.

What Is a Watchdog Timer?

A watchdog timer (WDT) is a hardware counter that counts down (or up) continuously and independently of the main CPU. If it’s ever allowed to reach its terminal value without being reset (“kicked” or “fed” or “petted” — all common terms for the same action) by the application software, it triggers a system reset. The underlying assumption is simple: if your firmware is behaving normally, it will regularly reach the part of its code that feeds the watchdog. If it’s stuck in an infinite loop, crashed, or hung waiting on a peripheral that never responds, it won’t reach that code, the watchdog will expire, and the system resets itself back to a known-good state.

graph TD
    Start[System Boot] --> Init[Initialize Watchdog]
    Init --> Loop[Main Application Loop]
    Loop --> Feed[Feed/Kick Watchdog]
    Feed --> Loop
    Loop -.->|If code hangs here| Stuck[Never reaches Feed]
    Stuck --> Expire[Watchdog Counter Reaches Zero]
    Expire --> Reset[System Reset]
    Reset --> Start

Why Watchdogs Exist

Embedded systems are frequently deployed in places where nobody is watching them continuously — industrial equipment, remote sensor nodes, automotive systems, medical devices, home appliances. Software, no matter how carefully written, can encounter unexpected conditions: a corrupted pointer, a peripheral that never sends an expected interrupt, an unhandled edge case in a state machine, electrical noise causing a stray bit flip. A watchdog doesn’t fix the underlying bug, but it provides an automatic recovery mechanism so the device doesn’t stay stuck indefinitely.

Types of Watchdog Timers

1. Independent Watchdog (IWDG)

Runs off its own dedicated, independent clock source (often a low-speed internal RC oscillator), separate from the main system clock. This is important: if the main system clock fails or is misconfigured, the independent watchdog keeps running regardless, since it doesn’t depend on that clock. STM32’s IWDG is a classic example.

2. Window Watchdog (WWDG)

A more advanced variant that not only resets if the watchdog isn’t fed in time, but also resets if it’s fed too early — outside a defined “window” of acceptable refresh times. This catches a different class of bug: code that’s running too fast, skipping steps, or feeding the watchdog from the wrong place entirely (like an ISR that fires constantly regardless of whether the main application is actually healthy).

gantt
    dateFormat X
    axisFormat %L ms
    title Window Watchdog Valid Refresh Window
    section Too Early (Reset!)
    Invalid Zone :crit, a1, 0, 20
    section Valid Window
    Feed Allowed Here :active, a2, 20, 30
    section Too Late (Reset!)
    Invalid Zone :crit, a3, 50, 20

3. Software Watchdog

Some systems implement a watchdog purely in software, using a regular timer interrupt to check whether certain flags (set by different parts of the application) have been updated recently. This is less robust than a hardware watchdog, since a sufficiently broken system (like one stuck with interrupts disabled) can also disable the software watchdog itself — but it can complement a hardware watchdog by checking more nuanced application-level health, like “did every task actually run this cycle,” not just “did something touch a register.”

Configuring an Independent Watchdog on STM32 (HAL Example)

#include "stm32f4xx_hal.h"

IWDG_HandleTypeDef hiwdg;

void Watchdog_Init(void) {
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // Divides 32kHz LSI clock
    hiwdg.Init.Reload = 1875;                    // ~3.75 second timeout
    HAL_IWDG_Init(&hiwdg);
}

void Watchdog_Feed(void) {
    HAL_IWDG_Refresh(&hiwdg);
}

int main(void) {
    system_init();
    Watchdog_Init();

    while (1) {
        do_sensor_reading();
        do_communication();
        do_display_update();

        Watchdog_Feed();   // Must be reached every loop iteration, in time
    }
}
Timeout period = (Prescaler × Reload) / LSI Clock Frequency
Example: (64 × 1875) / 32000 Hz ≈ 3.75 seconds

If do_communication() ever gets stuck waiting indefinitely on a peripheral that never responds, Watchdog_Feed() never gets called, the IWDG counts down to zero, and the microcontroller resets itself — recovering automatically rather than staying frozen forever.

Configuring a Watchdog on Arduino (AVR-Based)

#include <avr/wdt.h>

void setup() {
    wdt_enable(WDTO_2S);   // 2-second watchdog timeout
}

void loop() {
    do_sensor_reading();
    do_communication();

    wdt_reset();            // Feed the watchdog
}

Configuring a Watchdog on ESP32

#include "esp_task_wdt.h"

void setup() {
    esp_task_wdt_init(5, true);      // 5 second timeout, panic on timeout
    esp_task_wdt_add(NULL);          // Register current task (loopTask)
}

void loop() {
    do_sensor_reading();
    do_wifi_publish();

    esp_task_wdt_reset();            // Feed the watchdog
}

Where NOT to Feed the Watchdog

A mistake I’ve seen (and made) is feeding the watchdog from an interrupt service routine that fires on its own regardless of whether the main application logic is actually healthy — for example, feeding it from a periodic timer ISR. This defeats the entire purpose, because the timer ISR will keep firing and feeding the watchdog even if the main application logic is completely stuck in an infinite loop elsewhere. The watchdog feed should be reached only through the normal, healthy execution path of your actual application logic — ideally after confirming multiple subsystems have progressed correctly, not from a source that runs unconditionally.

graph TD
    Bad[BAD: Timer ISR feeds watchdog unconditionally] -.->|Watchdog never expires, even if main loop hangs| Danger[Hung system, undetected]
    Good[GOOD: Main loop feeds watchdog after completing all critical steps] -->|Only fed when system is actually healthy| Safe[Hangs correctly detected and recovered]

Watchdogs in RTOS-Based Systems

In multitasking systems, a single watchdog feed point in main() isn’t sufficient, because one hung task among several could still allow the watchdog to be fed by a different, healthy task — meaning a genuinely stuck task never gets detected. A more robust approach tracks the health of each individual task, and only feeds the watchdog once all tracked tasks have checked in within their expected period:

#define NUM_TASKS 3
volatile uint8_t task_alive_flags = 0;

#define TASK_SENSOR_BIT  (1 << 0)
#define TASK_COMMS_BIT   (1 << 1)
#define TASK_DISPLAY_BIT (1 << 2)
#define ALL_TASKS_ALIVE  (TASK_SENSOR_BIT | TASK_COMMS_BIT | TASK_DISPLAY_BIT)

void vSensorTask(void *pv) {
    for (;;) {
        read_sensor();
        task_alive_flags |= TASK_SENSOR_BIT;
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vWatchdogFeedTask(void *pv) {
    for (;;) {
        if (task_alive_flags == ALL_TASKS_ALIVE) {
            Watchdog_Feed();
            task_alive_flags = 0;   // Require every task to check in again
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

This “all tasks must check in” pattern is a much stronger guarantee than a single blanket feed call, since a single hung task will correctly prevent the watchdog from being refreshed.

Choosing a Timeout Value

The timeout needs to be long enough to comfortably accommodate your slowest legitimate operation (so you don’t get spurious resets during normal, if occasionally slow, operation) but short enough to recover quickly from an actual hang. A common approach is measuring the worst-case time for one full pass through your main loop (or one full round of all task check-ins) under realistic conditions, then setting the timeout to some safety margin above that — commonly 2x to 5x the worst-case observed time.

What Happens After a Watchdog Reset

It’s important to design firmware so it can recover gracefully from a watchdog-triggered reset, since this reset happens with no warning, potentially mid-operation:

void check_reset_cause(void) {
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        log_event("Watchdog reset occurred!");
        __HAL_RCC_CLEAR_RESET_FLAGS();
    }
}

Real-World Applications

Watchdogs and Debugging

During active development, watchdog resets can be genuinely confusing if you don’t realize they’re happening — the device just seems to “randomly restart.” It’s worth:

Security Considerations

A watchdog is a reliability mechanism, not a security one, but it does have a security-adjacent role: in some fault-injection attack scenarios (deliberately glitching power or clock signals to bypass security checks), a properly configured watchdog can help detect and recover from a device left in a corrupted or inconsistent state, though dedicated tamper-detection hardware is the more direct defense for that class of attack.

Frequently Asked Questions

Can a watchdog timer prevent all software bugs from crashing a device? No — it doesn’t fix the underlying bug at all, and doesn’t help if a bug causes silently wrong behavior without ever hanging execution. It’s specifically effective against hangs, infinite loops, and stuck waits, not against, say, a calculation that’s simply wrong but keeps executing normally.

What’s the difference between feeding a watchdog “too late” and “too early”? An independent watchdog (IWDG) only cares about “too late” — failing to feed it before the timeout expires. A window watchdog (WWDG) also cares about “too early” — feeding it before a minimum allowed time has passed, which catches a different class of bug (like code executing faster or in the wrong order than expected).

Should every embedded project use a watchdog? For anything deployed unattended, in the field, or in any safety/reliability-relevant context, yes — it’s cheap, hardware-supported on virtually every modern microcontroller, and provides a real safety net at very low implementation cost. For quick prototypes and bench-top demos, it’s less critical, though still good practice to include from early on.

Does the watchdog run even if the CPU is halted by a debugger? This depends on the microcontroller — many provide a configuration option (often called “debug watchdog stop” or similar) to freeze the watchdog counter whenever the CPU is halted by a debugger, specifically so debugging sessions aren’t disrupted by unexpected resets.

Summary

A watchdog timer is a small but critical reliability mechanism: an independent hardware counter that resets the system if the application fails to “feed” it within an expected time window, catching hangs and infinite loops that would otherwise leave a device stuck indefinitely. Getting the most value out of a watchdog means feeding it only from genuinely healthy execution paths (not blindly from an unconditional interrupt), choosing a sensible timeout based on real worst-case timing, and designing firmware to recover gracefully — checking the reset cause, preserving important state, and avoiding reset loops — after a watchdog-triggered restart.

References and Further Reading

Exit mobile version