The watchdog (WDT) is a counter that restarts the board if the program stops “feeding” it within a set delay. It is the only mechanism of a microcontroller that acts when the program is no longer in a state to act: where error handling assumes code still alive to trigger it, the watchdog expects nothing from it: it expects only a sign of life, and the absence of that sign is the trigger.
What is it for?
An embedded system that runs with nobody around to press reset — a remote station, a machine in service, a buried object — does not survive a lock-up. A library waiting for an answer that never comes, a sensor that freezes the loop, an incomplete frame: the program stays inert, and it cannot report its own halt, precisely because it has halted. The watchdog reverses the burden of proof. It is no longer up to the program to say that it is unwell. It is up to it to prove regularly that it is well, and silence counts as failure.
It is a last resort mechanism, armed during the hardening phase on a system that already works, never to hide a bug that ought to be fixed.
What is the watchdog independent of?
All the trust placed in it rests on a single question: what can fall without taking it down too? And the answer is not the same from one family to another: that is why it has to be looked up rather than assumed.
On an AVR, the watchdog is clocked by an RC oscillator of its own that runs permanently. A crash of the system clock does not reach it: the core can freeze, the counter keeps going and the board eventually restarts. The price of that separation is that this oscillator is free-running, hence imprecise: the quoted delays hold at 5 V, and they stretch as the supply voltage drops. A margin computed at 5 V is no longer the right one at 3.3 V: an invisible trap as long as the build is not tested at its real voltage.
On an RP2040, the architecture is the other way round. The watchdog counter is clocked by a derivative of the reference clock, itself normally tied to the crystal. The delay is therefore exact, measured in milliseconds rather than in approximate steps. But the watchdog shares its source with the rest of the chip, and whatever the crystal takes down, it takes down too.
Neither option is better: they are two trade-offs between fault coverage and delay accuracy, and the engineer’s job is to know which one is in hand. The question to put to the datasheet is always the same: where does the watchdog clock come from, and what stops it?
How does it work?
Three gestures, the same everywhere.
Arming. A delay is set and the counter starts. That delay must be longer than the worst legitimate loop pass, margin included: otherwise the watchdog restarts a system that was working. The ceiling is low, a few seconds at most on every common family, because a supervision counter is not made to time long durations.
Feeding. One instruction resets the counter to zero. It is placed where only healthy operation leads: that is the decisive choice of the whole mechanism, and the next section is devoted to it.
Letting the deadline fall. The counter reaches its term, the reset line goes active, the board restarts from its start-up point.
| Family | Arming | Feeding | Can it be disarmed? |
|---|---|---|---|
| Arduino / AVR | wdt_enable(), steps from 15 ms to 8 s | wdt_reset() | yes, wdt_disable() |
| MicroPython | WDT(timeout=…), up to 8,388 ms on RP2 | feed() | no, the API forbids it |
That last column is no API detail: the MicroPython documentation states the ban at class level, all boards included: once started, the watchdog can be neither stopped nor reconfigured. Hence a rule that also holds where disarming is possible: never make an architecture depend on being able to call the watchdog back. In practice, arming happens late, once initialisations are finished and the loop is ready to feed. And no long operation is planned that would be “disarmed for as long as it takes”. On AVR, disarming serves as a workaround at start-up (some old bootloaders left the watchdog active with a very short delay after a reset, hence a restart loop), not as an architectural licence.
What the watchdog does not detect
This is the limit discovered latest, and it is structural: the watchdog watches a rhythm, not a result. It observes that an instruction is reached at regular intervals. It knows nothing of what the program does between two passes, nor of how correct that work is.
Hence the nastiest failure mode: a loop that keeps turning and feeding while one of its activities is dead. The sensor no longer answers but its read fails silently, the link is down but transmission does not block: the loop passes, the watchdog is fed, the system declares itself alive and no longer does anything useful. No restart will happen, and that is logical: from its point of view, all is well.
The countermeasure is to feed only after checking that every activity has shown a sign of life since the last cycle: each task raises its flag when it has really worked, and the watchdog is fed only if all of them are raised. What is then watched is what the service depends on, not the loop that calls them.
That idea is industrialised on some families under the name task watchdog: each task registers with the watchdog and must sign in on its own behalf, failing which the deadline falls. On ESP32, that watchdog reports the offending task on the console by default without restarting: restarting is a configuration option. It is already an admission that the real service rendered is as much a diagnosis as a return to service.
Example — The build that thinks it is alive
A station reads a temperature every five seconds and transmits it. An eight-second watchdog is armed, fed on every loop pass. After three days, the station has transmitted nothing, and yet it has never restarted.
The sensor had stopped answering. Its read function, instead of blocking, returned an error value. The loop kept going round, transmission went out on an invalid value, and the watchdog got its food on time. The mechanism worked exactly as designed, and it served no purpose: what had stopped was not the loop, but an activity inside the loop.
Fed on condition (“the three readings of the cycle succeeded”), the same watchdog would have restarted the board after eight seconds, and brought back a sensor that a power cut was enough to unstick. The watchdog only becomes useful once it is given to watch what the service really depends on, and that list is not in the microcontroller: it is in the specification.
A silent reboot teaches nothing
The watchdog makes a system resilient and silent in the same gesture: the board restarts from scratch, the trace disappears with the RAM, and nothing tells a normal start-up from a recovery after a lock-up. A system that restarted forty times last night looks in every respect like a system that never faltered, until somebody notices that the defect does not fix itself.
Every family in principle offers a way to read the cause of the last reset at start-up. In practice, the information slips away at both ends of the journey, for opposite reasons:
- on the AVR side, the flag does exist in a status register, but the bootloader of Arduino boards often clears it before the program runs: whoever reads it in their initialisation finds zero and wrongly concludes that the watchdog did not bite;
- on the MicroPython on RP2 side, the reset cause function returns “watchdog” after a deliberate software restart as well, since that restart itself goes through the watchdog. The cause therefore does not tell the two situations apart.
The conclusion to keep is not “read the reset cause” but count your restarts yourself: a counter incremented at start-up and stored in persistent memory, transmitted or displayed with the readings. It is three lines of code, it depends on no supplied mechanism, and it is what turns a board that picks itself up in silence into a board that says it picked itself up. Without that, the robustness gained is paid for in blindness.
Pitfalls
Feeding from an interrupt or a scheduled task. A timer interrupt keeps running even while the main program is stuck. A watchdog fed from there never detects anything again: the mechanism is in place, armed, and without effect.
Arming before being ready to feed. A long initialisation — waiting for a module, mounting a file system, connecting to a network — can exceed the delay and cause a restart loop before the program has even started. Arm afterwards.
Computing the margin at the wrong voltage. On families with a free-running oscillator, the real delay stretches at low voltage. The margin is checked on the build powered as it will be in service, not on the bench over USB.
Confusing the safety net and the fix. Restarting periodically to “work around” a memory leak or a recurring lock-up treats the symptom and hides the cause, all the more so because the restart is silent.
Forgetting the watchdog during a debugging session. A breakpoint freezes the processor but not necessarily the watchdog counter: the board resets in the middle of an inspection. Some tools disarm watchdogs on every halt and do not re-arm them afterwards: enough to believe the mechanism absent when it is merely suspended (see debugging an embedded system).
Special case — Reboot, or warn
On some families, the watchdog offers a second mode: at the deadline, it raises an interrupt instead of resetting. Two uses follow from that, opposite in spirit.
The first is the last word: a few instructions before the reset to cut a power output, store a state or log the failure, which answers directly to the silent reboot described above. The second is the periodic wake-up of a sleeping microcontroller, where the watchdog no longer plays any safety role but acts as a low-power alarm clock (see deep sleep).
The same peripheral therefore fills two contrary roles — a safety net and an alarm clock — and that mode does not exist everywhere: on the MicroPython side, the standard interface only exposes restarting. Keeping the distinction in mind avoids confusing a restart suffered with a wake-up wanted when reading the behaviour of a board.
See also
- Timer — the hardware counter of which the watchdog is a special case devoted to supervision
- Interrupt — the second mode of the watchdog, and the place where it must never be fed
- Non-blocking programming — the brief loop that makes the watchdog feedable, and the wait that trips it wrongly
- Firmware — robustness as an architecture choice, of which the watchdog is the last step
- Hardening and debugging — the build step where the watchdog is armed
- Deep sleep — the periodic wake-up, another use of the same peripheral
- Watchdog on Arduino — the implementation in C++ (
avr/wdt.h, disarming possible) - Watchdog in MicroPython — the same mechanics on the MicroPython side (
machine.WDT, no disarming) - Microcontroller — the chip that carries the peripheral