Home › Embedded, industrial & energy › Firmware & peripherals
algorithm low

Watchdog timer

Problem Unattended devices (field sensors, motor controllers, vehicle ECUs) eventually hang and must recover without a human. This detects a stuck program within a bounded time and forces a reset to a defined state.

A hardware down-counter that resets the chip unless software proves, in time and in a plausible pattern, that it is still healthy.

O(1) per kick (one register write); detection latency bounded by the timeout; false-positive margin set by worst-case loop time
firmwarereliabilityresetsupervisionsafety
Overview

Unattended firmware eventually hangs: a spin on a dead peripheral, a stack overflow. A watchdog is a counter on its own clock that counts down; software must "kick" (reload) it before zero, otherwise the hardware resets the CPU. The art is not the counter but the question of when a kick is honest: kick from a timer interrupt and you prove only that interrupts work, not that the application does.

Key facts

Use when

  • Any device or service that must recover without a human: spacecraft and field sensors, motor controllers, gateways, vehicle ECUs (AUTOSAR Watchdog Manager), Linux servers (systemd WatchdogSec=), IoT boards (ESP-IDF).
  • A hung task must be detected within a bounded time (the timeout), with a defined safe state after reset.

Avoid when

  • It is the only protection of a safety function; a reset is not a safe state. Pair it with an independent safety monitor.
  • You need to identify which subsystem failed and keep the rest running; use per-task supervision and fault isolation and recovery before the last-resort reset.

Deep dive

How it works

In: timeout T (optional window opening W), periodic kicks from supervision code · Out: a reset signal when kicks stop, come too early, or arrive out of pattern · State: the down-counter (own clock) and, in software, one alive count per task · Cost: O(1) per kick (one register write); O(tasks) per supervision pass

  1. Configure timeout T (and, for a window watchdog, opening time W < T), then start it. Typical independent watchdogs are started by a key write and then cannot be disabled until reset.
  2. The counter runs from its own oscillator and decrements; reaching zero asserts reset. A kick reloads it. In window mode a kick before W has elapsed also asserts reset.
  3. Software supervision decides when to kick. Each task reports "alive" once per pass of its own loop; a supervisor, run at a fixed period from the main loop or lowest-priority context, kicks only if every task reported a count inside its expected [min, max] range, then clears all counts.
  4. A count above max catches a task that runs away or is called too often; below min catches a stuck or starved task. A failed period gives no credit to the next one.
  5. After reset, read the reset-cause register first (watchdog vs. power-on vs. brown-out), store a reason code in no-init RAM or flash, and count consecutive watchdog resets so a boot loop falls back to a safe image or safe mode.
time ──►   0        W (window opens)        T (timeout)
           │  kick = RESET  │  kick OK  │  no kick = RESET
kick path: main loop ► Supervisor.healthy_and_clear() ► WDT reload

Code

Software model of a window watchdog plus alive-count supervision (std only; the register write becomes kick on target):

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Reset { TooEarly, Timeout }

/// Model of a windowed hardware watchdog. Time is a wrapping u32 tick counter (ms).
pub struct WindowWatchdog { open: u32, timeout: u32, last: u32 }

impl WindowWatchdog {
    /// A kick is legal only in [open, timeout) after the previous kick.
    pub fn new(open: u32, timeout: u32, now: u32) -> Self {
        assert!(open < timeout);
        WindowWatchdog { open, timeout, last: now }
    }
    /// Call from the hardware's point of view on every tick: expiry resets the CPU.
    pub fn poll(&self, now: u32) -> Option<Reset> {
        (now.wrapping_sub(self.last) >= self.timeout).then_some(Reset::Timeout)
    }
    /// The reload register write.
    pub fn kick(&mut self, now: u32) -> Result<(), Reset> {
        let dt = now.wrapping_sub(self.last);
        if dt >= self.timeout { return Err(Reset::Timeout); }
        if dt < self.open { return Err(Reset::TooEarly); }
        self.last = now;
        Ok(())
    }
}

/// Software supervisor: one hardware kick only if every task reported
/// an in-range number of times since the last kick.
pub struct Supervisor<const N: usize> {
    count: [u8; N],
    min: [u8; N],
    max: [u8; N],
}

impl<const N: usize> Supervisor<N> {
    pub fn new(min: [u8; N], max: [u8; N]) -> Self { Supervisor { count: [0; N], min, max } }
    /// Each task calls this once per pass of its own loop.
    pub fn alive(&mut self, task: usize) { self.count[task] = self.count[task].saturating_add(1); }
    /// Called from the lowest-priority/idle-level check, never from a task that
    /// could keep running while another is dead. Returns true if the HW may be kicked.
    pub fn healthy_and_clear(&mut self) -> bool {
        let ok = (0..N).all(|i| (self.min[i]..=self.max[i]).contains(&self.count[i]));
        self.count = [0; N]; // a failed period must not carry credit forward
        ok
    }
}

Tested with a simulated run: a task that stops reporting at t=2 s causes a reset at t=3 s; early kicks, late kicks and a runaway task are all rejected.

Complexity and constants

  • Kick: one store to a peripheral register (an APB write; a few bus cycles, order of 10-100 ns on a Cortex-M). The supervisor is O(tasks) compares per period.
  • Detection latency from hang to reset is at most T plus the time since the last good kick; with supervision period P it is order of T + P.
  • Timeout choice: T >= worst-case time between supervisor runs + margin; typical values run from tens of milliseconds (motor control) to seconds (gateways). Tolerance of the low-speed RC oscillator is large (order of tens of percent on common MCU RC oscillators), so size margins accordingly and check the datasheet.
  • Counts are bytes: 3N bytes of RAM for N tasks.

Efficiency

  • Cost: one kick is a single peripheral register write, order of 10-100 ns on a Cortex-M; the supervisor is O(tasks) compares per period and 3N bytes of RAM. Bound: none in the data path; the real cost is energy when kicks force wake-ups from sleep.
  • Levers:
    • kick at the supervision period (tens to hundreds of ms), not every loop pass — fewer bus writes and wake-ups — all designs
    • pause the counter in sleep (Zephyr WDT_OPT_PAUSE_IN_SLEEP) or use the longest T the safety case allows — fewer wake-ups on a coin cell — long-sleeping devices
    • Linux: one write() or sd_notify datagram per half timeout (systemd's recommended interval) — negligible load — services
    • single timeout without window or per-task counts — a few instructions — when a reset loop is harmless and tasks cannot false-kick
  • Naive → standard → state of the art:
    • kick from a timer ISR · cheapest, detects nothing useful
    • window watchdog kicked by a supervisor after every task checked in · one write per period
    • window plus flow supervision (AUTOSAR style) and an external challenge-response watchdog · adds a monitor chip or core, catches skipped steps and a dead MCU

Implementation notes

  • Start the watchdog as early as possible in boot, before slow init (flash erase, radio calibration); lengthen the timeout during init rather than disabling it, or kick between init stages.
  • Long blocking operations (flash page erase, firmware update) need a timeout that fits them, or an explicit supervised step that kicks between pages.
  • Freeze the counter when halted by a debugger (Zephyr: WDT_OPT_PAUSE_HALTED_BY_DBG; most vendors have a debug-freeze bit) or breakpoints will reset the target.
  • External watchdog ICs (separate chip, own supply monitoring) cover the case where the MCU itself is dead or its internal watchdog is misconfigured; they usually require a toggling pin, so make the toggle come from the same supervisor.
  • Challenge-response ("question and answer") watchdogs make the supervisor send an answer derived from a challenge; this catches a kick path stuck in a loop that always writes the same value.
  • On Linux, /dev/watchdog arms the timer on open; writing any character kicks it, and writing V before closing disables it if the driver supports "magic close" (not if CONFIG_WATCHDOG_NOWAYOUT or the nowayout module parameter is set). WDIOC_SETTIMEOUT changes the timeout where the driver supports it. systemd arms per-service watchdogs with WatchdogSec=; the service sends WATCHDOG=1 via sd_notify and should do so at about half the interval.

Pitfalls

  • Kicking from a timer ISR: the interrupt keeps firing while the application is dead, so the watchdog never trips.
  • A dedicated "kick task" at high priority with the same problem; it measures scheduler health, not application health.
  • Kicking inside every driver or in loops that can spin: one stuck loop with a kick inside is invisible.
  • Timeout shorter than the worst-case path (flash erase, EEPROM write, retry loops): resets in the field that never appear on the bench.
  • Taking the RC oscillator at its nominal frequency: at the slow end of its tolerance the real timeout is much longer (or shorter) than computed.
  • Reset loops: a persistent fault (bad config in flash) makes every boot reset again. Count resets and fall back.
  • Disabling the watchdog "for debugging" and shipping that build.
  • Treating the reset as recovery: outputs (valves, heaters, motors) must default to a safe state while the CPU is in reset.

Variants and alternatives

Window watchdogalso detects too-early kicks; the right default for cyclic control code.
Task/alive supervision (AUTOSAR Watchdog Manager style, ESP-IDF task watchdog)per-task checkpoints; the bigger structure on top of one hardware watchdog.
Logical (flow) supervisioncheckpoints must occur in a valid order, catching skipped steps that a count cannot see.
External watchdog / safety monitorindependent device or core with its own clock and a challenge-response protocol.
Software heartbeat with a failure detectoracross machines, use heartbeats and a phi accrual detector or a lease, both of which end in a decision rather than a hardware reset.

In production

  • Spaceflight: Ganssle cites the Clementine spacecraft, whose hung software fired thrusters, as the case for watchdogs as integral parts of a system.
  • Automotive: ECUs under the AUTOSAR Watchdog Manager (alive, deadline and logical supervision).
  • Embedded and IoT: Zephyr's wdt_* API with per-timeout window.min/max and reset flags; ESP-IDF interrupt, task and RTC watchdogs; STM32 independent and window watchdogs.
  • Servers and gateways: the Linux watchdog framework (/dev/watchdog) with systemd.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
Zephyr watchdog APICzephyrproject-rtos/zephyrwest init (Zephyr getting started)————wdt_install_timeout, wdt_setup, wdt_feed; per-timeout window and reset mode
ESP-IDF watchdogsCespressif/esp-idfinstall.sh in the repo————interrupt WDT, task WDT with per-task subscription (esp_task_wdt_reset), RTC WDT
Linux watchdog frameworkCtorvalds/linuxn/a (in kernel)————/dev/watchdog, magic close, nowayout; docs at docs.kernel.org/watchdog
systemdCsystemd/systemddistro package————service watchdog via WatchdogSec= and sd_notify; also feeds the hardware watchdog (RuntimeWatchdogSec=)
FreeRTOS kernelCFreeRTOS/FreeRTOS-Kernelgit submodule————no built-in watchdog; supervision is built from tasks, hooks and the idle task

Sources

  • Jack Ganssle, Great Watchdog Timers for Embedded Systems. article
  • AUTOSAR, Specification of Watchdog Manager (Classic Platform). spec
  • Linux kernel documentation, The Linux WatchDog Timer Driver Core / Watchdog API. docs
  • systemd documentation, systemd.service(5) (WatchdogSec) and sd_watchdog_enabled(3). man page
  • Espressif, ESP-IDF Programming Guide, Watchdogs. docs
  • Zephyr Project, Watchdog API. docs

Used by

A/B partition updatesA maker of devices in homes, vehicles or plants must replace the software on units it cannot reach by hand. A power cut, a bad download or a faulty release in the middle must never leave a unit dead.
fault detection isolation recovery · plannedMonitor/response tables, fault isolation and safe-mode recovery hierarchies for autonomous systems
Hot-standby redundancyA controller whose stop or restart is unacceptable (PLCs, gateways, servers in a control loop) needs a second unit that takes over seamlessly after a crash, while never letting two units drive the outputs.
plc scan cycle · plannedCyclic read-inputs, execute-logic, write-outputs execution with IEC 61131-3 programs
safety monitor architecture · plannedIndependent doer/checker safety monitor that forces a safe state when the main channel misbehaves

Systems (26):

Alternatives and related