How do you use a watchdog timer to recover a wedged device without hiding the bug that wedged it?
Watchdog recovery in firmware only works if the code that kicks the watchdog can detect task progress. Kick from one supervisor after checking heartbeats, record reset cause and diagnostics in retained memory, and treat watchdog resets as defects to investigate.
5 min readUpdated 2026-08-15
0
Answer
Short answer
Have one supervisor kick the watchdog only after every critical task proves progress, preserve reset diagnostics, and use the reboot to recover while still fixing the underlying hang.
Mention reliability where it changes the risk, the owner, or the next check. A useful reliability point should make the answer more testable, not merely longer. Tie reliability back to the scenario so the interviewer can see why it matters here.
Was this answer helpful?
Likely follow-ups
→Two tasks run at 1Hz and 100Hz. How do you build a single check-in scheme that catches either one stalling without the fast task masking the slow one?
→How would you distinguish a genuine firmware hang from a brownout that also resets the part?
→What is a windowed watchdog protecting against that a conventional one does not catch?
→Your device reboots every ninety seconds in the field but never on the bench. What do you instrument first?
A watchdog is a bet that one specific thing cannot fail
A hardware watchdog is a counter clocked independently of the CPU, usually from its own low-speed oscillator, that resets the part if firmware does not refresh it within a timeout. Its value comes entirely from that independence: it keeps counting through a spin loop, a corrupted stack, a deadlock, a wild jump into flash, and a PLL that lost lock.
So the design question is never "do we have a watchdog". It is: which code refreshes it, and is that code capable of running correctly while the device is nonetheless useless? Every failure of a watchdog design in the field is a failure to answer the second half.
Consider a device whose job is to sample a sensor at 100Hz and publish over a radio. The radio task deadlocks on a mutex. The 1kHz system tick keeps firing, the idle task keeps running, and the CPU is perfectly healthy. If the watchdog is refreshed by anything on that healthy path, the device sits there indefinitely publishing nothing, and the watchdog you paid for reports that everything is fine.
Kicking from a timer interrupt is the same as disabling it
This is the specific mistake to name, because it is extremely common and it is usually introduced as a fix. Somebody sees spurious resets during a long flash erase, moves the refresh into the periodic timer ISR so it can never be starved, and the resets stop. Both statements are true and the second is the problem: the resets stopped because the watchdog no longer observes anything except whether interrupts are still being serviced.
An ISR is nearly the worst possible kick site. It preempts application code, so it keeps running while the application is deadlocked, live-locked, stuck in an unbounded loop, or corrupt. It converts a watchdog into a check that the interrupt controller works.
The correct structure is one kick site in the main loop or in a dedicated low-priority supervisor task, which refreshes the watchdog only after confirming that every task it supervises has made progress since the last check. Each task sets its own bit on completing a unit of real work — not on entering its loop, which every stuck task also does — and the supervisor clears the set once satisfied.
/* One bit per supervised task. Each task sets its bit after finishing
a real unit of work, never on merely being scheduled. */#define TASK_SENSOR (1u << 0)#define TASK_RADIO (1u << 1)#define TASK_CONTROL (1u << 2)#define ALL_TASKS (TASK_SENSOR | TASK_RADIO | TASK_CONTROL)staticvolatileuint32_t checked_in;
voidtask_checkin(uint32_t bit) { checked_in |= bit; } /* single-bit set */voidsupervisor_step(void) {
if ((checked_in & ALL_TASKS) == ALL_TASKS) {
checked_in = 0;
watchdog_refresh(); /* the only call site in the firmware */
}
/* Otherwise: do nothing. Doing nothing is the mechanism. */
}
The supervisor's own period and the watchdog timeout must then be chosen from the slowest supervised task's deadline. If the sensor task legitimately runs once every two seconds, a one-second timeout resets a working device, and the pressure to "fix" that by loosening the check-in is where designs quietly rot back into unconditional kicks.
Making the reset informative rather than invisible
A watchdog reset that leaves no trace is a bug deleting its own evidence. On restart, firmware must read the reset-cause status the hardware provides and branch on it, because a power-on reset, an external pin reset, a brownout and a watchdog reset each mean something different about what just happened.
Beyond the cause, you want the forensics. Most toolchains let you place variables in a section that the start-up code does not clear — conventionally .noinit — so a small crash record survives a reset that does not cut power. Write the check-in bitmask at the moment the supervisor gave up, the last task to run, a fault-status register value, a wrapping log of the last few state transitions, and a reset counter. Then on the next boot, publish it.
Loading diagram…
The interesting branch is the bottom one rather than the logging. A device that reboots and resumes forever is indistinguishable from a healthy device in most telemetry, so the escalation path matters: after a few watchdog resets in a short window, stop retrying the same thing. Boot into a degraded mode that keeps the radio and the diagnostics alive and leaves the failing subsystem off, so the fault can be read out instead of being cycled through repeatedly.
The reset does not reset the world
The CPU restarting is only part of recovery, and this is where an embedded answer separates from a server one. A heater relay energised before the hang is still energised through the reset. A motor driver holding a duty cycle keeps holding it until something reprogrammes it. A GPIO configured as an output returns to its reset state, which may be a floating input that lets an external pull resistor decide the actuator's position. A device on a shared bus may still hold the line low, wedging every other node.
So the boot path has to drive outputs to a defined safe state before it does anything else, and the hardware has to be designed so that "the MCU is not running" is itself safe: pull resistors chosen deliberately, drivers with their own enable that fails off, external supervisors on anything genuinely dangerous. Peripherals with independent state, such as an external radio or a bus expander, often need an explicit hard reset during boot, because they did not restart when the MCU did and may still be part-way through a transaction.
Two further details signal experience. First, the watchdog should be enabled as early in boot as is practical, ideally by hardware configuration rather than by a firmware register write, so a hang during initialisation is also covered — the alternative leaves a window where a wedged bring-up loop runs forever. Second, a long blocking operation such as a flash erase during a firmware update needs the timeout planned around it rather than the kick relocated into it: either the timeout accommodates the longest erase, or the erase is chunked so the supervisor still runs between chunks.
Treat every watchdog reset as a defect with a stack of evidence attached. The moment the count becomes a metric someone tolerates rather than an alarm someone investigates, the watchdog has stopped being a safety net and become a way of not finding out.