esp32_wdt
The ESP32’s watchdogs.
Three exist: one in the RTC domain (RWDT) and one in each timer group
(MWDT0, MWDT1). startup.S disables all three during boot, because a
half-initialised kernel being reset mid-bring-up is worse than no watchdog —
but nothing re-armed them afterwards, which left the chip less protected
under FlintOS than it is under the ROM.
Two watchdogs, two different failures
Section titled “Two watchdogs, two different failures”They are not redundant. Each catches something the other cannot:
-
RWDT lives in the RTC domain and survives anything short of a power cycle. Fed from the timer interrupt, so it fires when the kernel itself has stopped — interrupts stuck masked, a fault loop, a trap handler that never returns. It cannot catch a spinning task, because the tick still runs and still feeds it.
-
MWDT is fed from the idle task. Idle runs only when nothing else is runnable, so a task that never blocks starves it — and the watchdog fires on a system that is, from the tick’s point of view, perfectly healthy.
Register layout and the unlock keys are from the ESP32 TRM, chapters 12
(timer groups) and 30 (RTC), cross-checked against esp-idf’s
soc/rtc_cntl_reg.h and soc/timer_group_reg.h.
enum Mwdt { Group0, Group1,}Which timer group’s watchdog.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Functions
Section titled “Functions”mwdt_arm
Section titled “mwdt_arm”unsafe fn mwdt_arm(which: Mwdt, ms: u32)Arm a timer-group watchdog to reset the system if not fed within ms.
Safety
Section titled “Safety”Writes the timer group’s watchdog registers, and commits the caller to feeding it.
References: Mwdt
mwdt_disable
Section titled “mwdt_disable”unsafe fn mwdt_disable(which: Mwdt)Disable a timer-group watchdog.
Safety
Section titled “Safety”Writes the timer group’s watchdog registers.
References: Mwdt
mwdt_feed
Section titled “mwdt_feed”unsafe fn mwdt_feed(which: Mwdt)Feed a timer-group watchdog.
Safety
Section titled “Safety”Writes the timer group’s watchdog registers.
References: Mwdt
mwdt_ticks_for_ms
Section titled “mwdt_ticks_for_ms”const fn mwdt_ticks_for_ms(ms: u32) -> u32Watchdog ticks for ms at [MWDT_PRESCALE], saturating.
rtc_cycles_for_ms
Section titled “rtc_cycles_for_ms”const fn rtc_cycles_for_ms(ms: u32) -> u32RTC slow-clock cycles for ms, saturating rather than wrapping.
A wrapped divisor would arm the watchdog with a near-zero timeout, resetting the board immediately and continuously — a boot loop caused by the thing meant to recover from one.
rwdt_arm
Section titled “rwdt_arm”unsafe fn rwdt_arm(ms: u32)Arm the RTC watchdog to reset the system if it is not fed within ms.
Safety
Section titled “Safety”Writes the RTC watchdog registers. Arming it means the board resets unless something feeds it; the caller owns that obligation.
rwdt_disable
Section titled “rwdt_disable”unsafe fn rwdt_disable()Disable the RTC watchdog.
Safety
Section titled “Safety”Writes the RTC watchdog registers. After this nothing recovers a hung system short of a power cycle.
rwdt_feed
Section titled “rwdt_feed”unsafe fn rwdt_feed()Feed the RTC watchdog.
Safety
Section titled “Safety”Writes the RTC watchdog registers. Called from the timer interrupt.
Constants
Section titled “Constants”MWDT_PRESCALE
Section titled “MWDT_PRESCALE”const MWDT_PRESCALE: u32;Prescaler giving a 1 ms watchdog tick off the 80 MHz APB clock.
80_000 APB cycles is exactly 1 ms, and the field is 16 bits — so 80_000 does not fit. 40_000 gives 0.5 ms per tick and does, which is why the timeout conversion below counts half-milliseconds rather than milliseconds.
RTC_SLOW_CLK_HZ
Section titled “RTC_SLOW_CLK_HZ”const RTC_SLOW_CLK_HZ: u32;Nominal RTC slow-clock frequency, in Hz.
The internal 150 kHz RC oscillator, which is not precise: ±10% across parts and it drifts with temperature. That is why the RWDT timeout is specified in seconds rather than milliseconds — at a multi-second timeout a 10% error is irrelevant, and anything needing better accuracy than that wants the MWDT, which counts off APB.