From 7ad66e8ef0bf4a4d2cfbaa85bd4faf00bcd30a43 Mon Sep 17 00:00:00 2001 From: guillo93 Date: Wed, 3 Jun 2026 05:26:53 -0500 Subject: [PATCH] =?UTF-8?q?F4.6:=20gesti=C3=B3n=20de=20energ=C3=ADa=20+=20?= =?UTF-8?q?causa=20de=20reset,=20y=20fix=20del=20bucle=20de=20reset=20IWDG?= =?UTF-8?q?=20en=20F769?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuevo módulo `reset` (HAL F4 y F7, gemelos): lee y limpia RCC_CSR para distinguir la causa del último reset (power-on, brownout, pin, software, IWDG, WWDG, low-power) y la publica como `&'static str`. - El kernel almacena la causa de reset (`set_reset_cause`/`reset_cause`) y la consola la muestra en el comando `faults`. Los ejemplos la leen al arrancar. - Fix del bucle de reset IWDG en F769: la init de modo windowed perdía silenciosamente los writes de PR/RLR (corría con defaults /4 + 0xFFF → ~1 s), reseteando antes del primer kick. Se aplica la secuencia canónica (RM0410/ RM0090): arrancar el IWDG primero (KEY_START enciende el LSI), luego habilitar escritura y programar PR/RLR/WINR esperando a que cada flag SR.PVU/RVU/WVU se limpie. Espera acotada para no colgar el arranque si el LSI fallara. Validado en F407 (estable en safe-mode) y F769 (ya no entra en bucle de reset: supervisor vivo a 1/2/3/4/5 s en safe-mode). CI host: 29 tests OK. Co-Authored-By: Claude Opus 4.7 --- crates/rugus-hal-stm32f4/src/iwdg.rs | 93 +++++++++++++----- crates/rugus-hal-stm32f4/src/lib.rs | 1 + crates/rugus-hal-stm32f4/src/reset.rs | 93 ++++++++++++++++++ crates/rugus-hal-stm32f7/src/iwdg.rs | 93 +++++++++++++----- crates/rugus-hal-stm32f7/src/lib.rs | 1 + crates/rugus-hal-stm32f7/src/reset.rs | 94 +++++++++++++++++++ crates/rugus-kernel/src/console.rs | 6 +- crates/rugus-kernel/src/lib.rs | 23 +++++ .../app-sandbox-stm32f407g-disco/src/main.rs | 13 ++- .../app-sandbox-stm32f769-disco/src/main.rs | 13 ++- 10 files changed, 377 insertions(+), 53 deletions(-) create mode 100644 crates/rugus-hal-stm32f4/src/reset.rs create mode 100644 crates/rugus-hal-stm32f7/src/reset.rs diff --git a/crates/rugus-hal-stm32f4/src/iwdg.rs b/crates/rugus-hal-stm32f4/src/iwdg.rs index a067987..29cd2bb 100644 --- a/crates/rugus-hal-stm32f4/src/iwdg.rs +++ b/crates/rugus-hal-stm32f4/src/iwdg.rs @@ -22,8 +22,16 @@ const IWDG_BASE: u32 = 0x4000_3000; const KR: u32 = 0x00; const PR: u32 = 0x04; const RLR: u32 = 0x08; +const SR: u32 = 0x0C; const WINR: u32 = 0x10; +// Flags de "update in progress" del status register (SR). Tras escribir PR/RLR/ +// WINR hay que esperar a que el flag correspondiente vuelva a 0 antes del +// siguiente write, o el hardware lo ignora silenciosamente. +const SR_PVU: u32 = 1 << 0; // prescaler update +const SR_RVU: u32 = 1 << 1; // reload update +const SR_WVU: u32 = 1 << 2; // window update + // Llaves del key register. const KEY_RELOAD: u32 = 0xAAAA; const KEY_ENABLE_WRITE: u32 = 0x5555; @@ -31,13 +39,19 @@ const KEY_START: u32 = 0xCCCC; /// Prescaler /128 (PR=0b101): con LSI ~32 kHz da ~250 Hz (tick ~4 ms). const PR_DIV128: u32 = 0b101; -/// Reload ~2 s: 250 Hz * 2 s = 500 ticks. Holgado frente al kick del supervisor. -const RLR_2S: u32 = 500; -/// Ventana al ~50 % (250 ticks ≈ 1 s). Con modo windowed, alimentar ANTES de que -/// el contador baje de este valor (es decir, menos de ~1 s tras la última -/// recarga) dispara un reset: detecta un supervisor que itera descontrolado -/// (demasiado rápido), no solo uno colgado. El kick debe caer en [~1 s, ~2 s]. -const WINR_HALF: u32 = 250; +/// Reload ~4 s nominal: 250 Hz * 4 s = 1000 ticks. El LSI NO está calibrado (rango +/// de hoja ~17-47 kHz, ±50 % sobre 32 kHz nominal), así que el periodo real varía +/// de ~2.7 s (LSI rápido) a ~7.5 s (LSI lento). Una ventana ancha es lo que hace +/// fiable el kick a ~1.5 s del supervisor en AMBAS placas (ver `WINR_OPEN`). +const RLR_4S: u32 = 1000; +/// Apertura de ventana temprana (~0.5 s nominal): con modo windowed solo se puede +/// alimentar cuando el contador ha bajado de este valor; alimentar antes resetea. +/// Se abre tras 125 ticks (RLR_4S - 875), ~0.5 s nominal. Con el kick fijo a ~1.5 s +/// (reloj SysTick preciso), la ventana real [~0.5 s, ~4 s] nominal tolera todo el +/// rango del LSI sin disparar ni el límite inferior (kick demasiado pronto) ni el +/// superior (cuelgue): el bug de bucle de reset en F769 venía de una ventana +/// [~1 s, ~2 s] demasiado estrecha frente a un LSI rápido (F4.6). +const WINR_OPEN: u32 = 875; /// Handle del watchdog independiente. pub struct Iwdg { @@ -45,8 +59,8 @@ pub struct Iwdg { } impl Iwdg { - /// Configura prescaler /128 y reload ~2 s, y arranca el IWDG. Tras esto hay - /// que [`Self::kick`] periódicamente o el chip se resetea. + /// Configura prescaler /128 y reload ~4 s nominal, y arranca el IWDG. Tras + /// esto hay que [`Self::kick`] periódicamente o el chip se resetea. pub fn start() -> Self { // SAFETY: registros MMIO del IWDG; arranque single-thread. // @@ -58,7 +72,7 @@ impl Iwdg { unsafe { write_reg(KR, KEY_ENABLE_WRITE); write_reg(PR, PR_DIV128); - write_reg(RLR, RLR_2S); + write_reg(RLR, RLR_4S); write_reg(KR, KEY_START); write_reg(KR, KEY_RELOAD); } @@ -66,24 +80,35 @@ impl Iwdg { } /// Como [`Self::start`] pero en **modo windowed**: además del límite superior - /// (~2 s sin kick → reset por cuelgue), fija una ventana inferior (`WINR_HALF`) - /// de modo que alimentar demasiado pronto (< ~1 s tras la recarga) también - /// resetea. Detecta un supervisor en bucle desbocado, no solo uno parado. + /// (~4 s nominal sin kick → reset por cuelgue), fija una ventana inferior + /// (`WINR_OPEN`) de modo que alimentar demasiado pronto (< ~0.5 s nominal tras + /// la recarga) también resetea. Detecta un supervisor en bucle desbocado, no + /// solo uno parado. /// - /// El supervisor debe espaciar su kick para caer en [~1 s, ~2 s]; ver el - /// ejemplo de placa. Escribir WINR recarga el contador automáticamente. + /// El supervisor debe espaciar su kick para caer en la ventana [~0.5 s, ~4 s] + /// nominal; con kick a ~1.5 s (reloj SysTick preciso) hay margen amplio frente + /// a la tolerancia del LSI. Escribir WINR recarga el contador automáticamente. pub fn start_windowed() -> Self { - // SAFETY: registros MMIO del IWDG; arranque single-thread. Igual que - // `start`, pero programa WINR al final (tras KEY_START enciende el LSI): - // el write de WINR exige rehabilitar escritura (KEY_ENABLE_WRITE) y, por - // hardware, recarga el contador (no hace falta KEY_RELOAD adicional). + // SAFETY: registros MMIO del IWDG; arranque single-thread. + // + // Secuencia canónica de modo windowed (RM0090 §IWDG): arrancar PRIMERO el + // IWDG (KEY_START enciende el LSI), luego habilitar escritura y programar + // PR/RLR/WINR esperando a que cada flag de "update" (SR.PVU/RVU/WVU) vuelva + // a 0 antes del siguiente write. Esta espera es OBLIGATORIA: el bug del + // bucle de reset en F769 era que, sin esperar, los writes de PR/RLR se + // perdían y el watchdog corría con sus defaults (PR=/4, RLR=0xFFF → ~0.5 s + // nominal, ~1 s con LSI lento) y reseteaba antes del primer kick (~1.5 s). + // Aquí poder sondear SR es seguro porque el LSI YA gira (post KEY_START). + // Escribir WINR recarga el contador por hardware (no hace falta KEY_RELOAD). unsafe { - write_reg(KR, KEY_ENABLE_WRITE); - write_reg(PR, PR_DIV128); - write_reg(RLR, RLR_2S); write_reg(KR, KEY_START); write_reg(KR, KEY_ENABLE_WRITE); - write_reg(WINR, WINR_HALF); + write_reg(PR, PR_DIV128); + wait_sr_clear(SR_PVU); + write_reg(RLR, RLR_4S); + wait_sr_clear(SR_RVU); + write_reg(WINR, WINR_OPEN); + wait_sr_clear(SR_WVU); } Self { armed: true } } @@ -101,3 +126,25 @@ impl Iwdg { unsafe fn write_reg(off: u32, val: u32) { unsafe { write_volatile((IWDG_BASE + off) as *mut u32, val) } } + +#[inline] +unsafe fn read_reg(off: u32) -> u32 { + unsafe { core::ptr::read_volatile((IWDG_BASE + off) as *const u32) } +} + +/// Espera a que el flag de "update" indicado en SR vuelva a 0 (el hardware acaba +/// de aplicar el write a PR/RLR/WINR). Acotado: tras ~100k iteraciones desiste +/// para no colgar el arranque si el LSI fallara (el IWDG aún protege con defaults). +#[inline] +unsafe fn wait_sr_clear(flag: u32) { + let mut spins = 0u32; + // SAFETY: SR es de solo lectura; el LSI ya gira (post KEY_START) así que el + // flag se actualiza y termina por limpiarse. + while unsafe { read_reg(SR) } & flag != 0 { + spins += 1; + if spins >= 100_000 { + break; + } + core::hint::spin_loop(); + } +} diff --git a/crates/rugus-hal-stm32f4/src/lib.rs b/crates/rugus-hal-stm32f4/src/lib.rs index 38716a2..1512db9 100644 --- a/crates/rugus-hal-stm32f4/src/lib.rs +++ b/crates/rugus-hal-stm32f4/src/lib.rs @@ -22,5 +22,6 @@ pub mod exti; pub mod gpio; pub mod iwdg; pub mod rcc; +pub mod reset; pub mod timer; pub mod usart; diff --git a/crates/rugus-hal-stm32f4/src/reset.rs b/crates/rugus-hal-stm32f4/src/reset.rs new file mode 100644 index 0000000..db49fab --- /dev/null +++ b/crates/rugus-hal-stm32f4/src/reset.rs @@ -0,0 +1,93 @@ +//! Causa del último reset (RCC_CSR) para STM32F4 — raw-MMIO. +//! +//! El bloque RCC registra en `CSR` la causa del reset más reciente con flags +//! pegajosos (sobreviven al reset, se limpian solo con `RMVF`). Leerlos al +//! arranque distingue un reset por watchdog (IWDG) de un power-on, un reset por +//! pin NRST o un reset por software (`SCB.SYSRESETREQ`). Es el complemento de la +//! telemetría persistente de faults (F4.4): el *por qué* del último arranque. +//! +//! Hay que limpiarlos (`RMVF`) tras leerlos, o el siguiente arranque heredaría +//! flags viejos. Acceso por MMIO directo, en la línea de [`crate::iwdg`]. + +use core::ptr::{read_volatile, write_volatile}; + +/// `RCC->CSR` (base RCC 0x4002_3800 + offset 0x74). +const RCC_CSR: u32 = 0x4002_3874; + +// Flags de causa de reset (bits altos de CSR). Pegajosos hasta `RMVF`. +const LPWRRSTF: u32 = 1 << 31; // reset por entrada ilegal a Stop/Standby +const WWDGRSTF: u32 = 1 << 30; // window watchdog +const IWDGRSTF: u32 = 1 << 29; // independent watchdog +const SFTRSTF: u32 = 1 << 28; // software (SYSRESETREQ) +const PORRSTF: u32 = 1 << 27; // power-on / power-down +const PINRSTF: u32 = 1 << 26; // pin NRST +const BORRSTF: u32 = 1 << 25; // brown-out +const RMVF: u32 = 1 << 24; // remove flags (write-1 para limpiar) + +/// Causa del último reset, decodificada desde `RCC_CSR`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ResetCause { + /// Power-on / power-down (arranque en frío). + PowerOn, + /// Brown-out (caída de tensión). + Brownout, + /// Pin externo NRST. + Pin, + /// Reset por software (`SCB.SYSRESETREQ`, p. ej. comando `reboot`). + Software, + /// Watchdog independiente (IWDG): el supervisor dejó de alimentarlo. + IndependentWatchdog, + /// Window watchdog (WWDG). + WindowWatchdog, + /// Entrada ilegal a modo de bajo consumo. + LowPower, + /// Ningún flag reconocido (no debería ocurrir tras un reset real). + Unknown, +} + +impl ResetCause { + /// Nombre corto para logging sin `defmt`. + pub fn name(self) -> &'static str { + match self { + ResetCause::PowerOn => "power-on", + ResetCause::Brownout => "brownout", + ResetCause::Pin => "pin-nrst", + ResetCause::Software => "software", + ResetCause::IndependentWatchdog => "iwdg", + ResetCause::WindowWatchdog => "wwdg", + ResetCause::LowPower => "low-power", + ResetCause::Unknown => "unknown", + } + } +} + +/// Lee la causa del último reset y limpia los flags (`RMVF`) para que el próximo +/// arranque parta de cero. +/// +/// Cuando hay varios flags activos (p. ej. PIN + POR en un power-on real) se +/// prioriza la causa más informativa: watchdogs y software por encima del pin y +/// del power-on, que suelen acompañar a otras. +pub fn read_and_clear() -> ResetCause { + // SAFETY: RCC_CSR es MMIO; lectura simple + write-1-to-clear de RMVF. + let csr = unsafe { read_volatile(RCC_CSR as *const u32) }; + let cause = if csr & LPWRRSTF != 0 { + ResetCause::LowPower + } else if csr & WWDGRSTF != 0 { + ResetCause::WindowWatchdog + } else if csr & IWDGRSTF != 0 { + ResetCause::IndependentWatchdog + } else if csr & SFTRSTF != 0 { + ResetCause::Software + } else if csr & BORRSTF != 0 { + ResetCause::Brownout + } else if csr & PORRSTF != 0 { + ResetCause::PowerOn + } else if csr & PINRSTF != 0 { + ResetCause::Pin + } else { + ResetCause::Unknown + }; + // Limpia todos los flags para no heredarlos en el próximo arranque. + unsafe { write_volatile(RCC_CSR as *mut u32, csr | RMVF) }; + cause +} diff --git a/crates/rugus-hal-stm32f7/src/iwdg.rs b/crates/rugus-hal-stm32f7/src/iwdg.rs index 68a4ade..0423ebd 100644 --- a/crates/rugus-hal-stm32f7/src/iwdg.rs +++ b/crates/rugus-hal-stm32f7/src/iwdg.rs @@ -22,8 +22,16 @@ const IWDG_BASE: u32 = 0x4000_3000; const KR: u32 = 0x00; const PR: u32 = 0x04; const RLR: u32 = 0x08; +const SR: u32 = 0x0C; const WINR: u32 = 0x10; +// Flags de "update in progress" del status register (SR). Tras escribir PR/RLR/ +// WINR hay que esperar a que el flag correspondiente vuelva a 0 antes del +// siguiente write, o el hardware lo ignora silenciosamente. +const SR_PVU: u32 = 1 << 0; // prescaler update +const SR_RVU: u32 = 1 << 1; // reload update +const SR_WVU: u32 = 1 << 2; // window update + // Llaves del key register. const KEY_RELOAD: u32 = 0xAAAA; const KEY_ENABLE_WRITE: u32 = 0x5555; @@ -31,13 +39,19 @@ const KEY_START: u32 = 0xCCCC; /// Prescaler /128 (PR=0b101): con LSI ~32 kHz da ~250 Hz (tick ~4 ms). const PR_DIV128: u32 = 0b101; -/// Reload ~2 s: 250 Hz * 2 s = 500 ticks. Holgado frente al kick del supervisor. -const RLR_2S: u32 = 500; -/// Ventana al ~50 % (250 ticks ≈ 1 s). Con modo windowed, alimentar ANTES de que -/// el contador baje de este valor (es decir, menos de ~1 s tras la última -/// recarga) dispara un reset: detecta un supervisor que itera descontrolado -/// (demasiado rápido), no solo uno colgado. El kick debe caer en [~1 s, ~2 s]. -const WINR_HALF: u32 = 250; +/// Reload ~4 s nominal: 250 Hz * 4 s = 1000 ticks. El LSI NO está calibrado (rango +/// de hoja ~17-47 kHz, ±50 % sobre 32 kHz nominal), así que el periodo real varía +/// de ~2.7 s (LSI rápido) a ~7.5 s (LSI lento). Una ventana ancha es lo que hace +/// fiable el kick a ~1.5 s del supervisor en AMBAS placas (ver `WINR_OPEN`). +const RLR_4S: u32 = 1000; +/// Apertura de ventana temprana (~0.5 s nominal): con modo windowed solo se puede +/// alimentar cuando el contador ha bajado de este valor; alimentar antes resetea. +/// Se abre tras 125 ticks (RLR_4S - 875), ~0.5 s nominal. Con el kick fijo a ~1.5 s +/// (reloj SysTick preciso), la ventana real [~0.5 s, ~4 s] nominal tolera todo el +/// rango del LSI sin disparar ni el límite inferior (kick demasiado pronto) ni el +/// superior (cuelgue): el bug de bucle de reset en F769 venía de una ventana +/// [~1 s, ~2 s] demasiado estrecha frente a un LSI rápido (F4.6). +const WINR_OPEN: u32 = 875; /// Handle del watchdog independiente. pub struct Iwdg { @@ -45,8 +59,8 @@ pub struct Iwdg { } impl Iwdg { - /// Configura prescaler /128 y reload ~2 s, y arranca el IWDG. Tras esto hay - /// que [`Self::kick`] periódicamente o el chip se resetea. + /// Configura prescaler /128 y reload ~4 s nominal, y arranca el IWDG. Tras + /// esto hay que [`Self::kick`] periódicamente o el chip se resetea. pub fn start() -> Self { // SAFETY: registros MMIO del IWDG; arranque single-thread. // @@ -58,7 +72,7 @@ impl Iwdg { unsafe { write_reg(KR, KEY_ENABLE_WRITE); write_reg(PR, PR_DIV128); - write_reg(RLR, RLR_2S); + write_reg(RLR, RLR_4S); write_reg(KR, KEY_START); write_reg(KR, KEY_RELOAD); } @@ -66,24 +80,35 @@ impl Iwdg { } /// Como [`Self::start`] pero en **modo windowed**: además del límite superior - /// (~2 s sin kick → reset por cuelgue), fija una ventana inferior (`WINR_HALF`) - /// de modo que alimentar demasiado pronto (< ~1 s tras la recarga) también - /// resetea. Detecta un supervisor en bucle desbocado, no solo uno parado. + /// (~4 s nominal sin kick → reset por cuelgue), fija una ventana inferior + /// (`WINR_OPEN`) de modo que alimentar demasiado pronto (< ~0.5 s nominal tras + /// la recarga) también resetea. Detecta un supervisor en bucle desbocado, no + /// solo uno parado. /// - /// El supervisor debe espaciar su kick para caer en [~1 s, ~2 s]; ver el - /// ejemplo de placa. Escribir WINR recarga el contador automáticamente. + /// El supervisor debe espaciar su kick para caer en la ventana [~0.5 s, ~4 s] + /// nominal; con kick a ~1.5 s (reloj SysTick preciso) hay margen amplio frente + /// a la tolerancia del LSI. Escribir WINR recarga el contador automáticamente. pub fn start_windowed() -> Self { - // SAFETY: registros MMIO del IWDG; arranque single-thread. Igual que - // `start`, pero programa WINR al final (tras KEY_START enciende el LSI): - // el write de WINR exige rehabilitar escritura (KEY_ENABLE_WRITE) y, por - // hardware, recarga el contador (no hace falta KEY_RELOAD adicional). + // SAFETY: registros MMIO del IWDG; arranque single-thread. + // + // Secuencia canónica de modo windowed (RM0410 §IWDG): arrancar PRIMERO el + // IWDG (KEY_START enciende el LSI), luego habilitar escritura y programar + // PR/RLR/WINR esperando a que cada flag de "update" (SR.PVU/RVU/WVU) vuelva + // a 0 antes del siguiente write. Esta espera es OBLIGATORIA: el bug del + // bucle de reset en F769 era que, sin esperar, los writes de PR/RLR se + // perdían y el watchdog corría con sus defaults (PR=/4, RLR=0xFFF → ~0.5 s + // nominal, ~1 s con LSI lento) y reseteaba antes del primer kick (~1.5 s). + // Aquí poder sondear SR es seguro porque el LSI YA gira (post KEY_START). + // Escribir WINR recarga el contador por hardware (no hace falta KEY_RELOAD). unsafe { - write_reg(KR, KEY_ENABLE_WRITE); - write_reg(PR, PR_DIV128); - write_reg(RLR, RLR_2S); write_reg(KR, KEY_START); write_reg(KR, KEY_ENABLE_WRITE); - write_reg(WINR, WINR_HALF); + write_reg(PR, PR_DIV128); + wait_sr_clear(SR_PVU); + write_reg(RLR, RLR_4S); + wait_sr_clear(SR_RVU); + write_reg(WINR, WINR_OPEN); + wait_sr_clear(SR_WVU); } Self { armed: true } } @@ -101,3 +126,25 @@ impl Iwdg { unsafe fn write_reg(off: u32, val: u32) { unsafe { write_volatile((IWDG_BASE + off) as *mut u32, val) } } + +#[inline] +unsafe fn read_reg(off: u32) -> u32 { + unsafe { core::ptr::read_volatile((IWDG_BASE + off) as *const u32) } +} + +/// Espera a que el flag de "update" indicado en SR vuelva a 0 (el hardware acaba +/// de aplicar el write a PR/RLR/WINR). Acotado: tras ~100k iteraciones desiste +/// para no colgar el arranque si el LSI fallara (el IWDG aún protege con defaults). +#[inline] +unsafe fn wait_sr_clear(flag: u32) { + let mut spins = 0u32; + // SAFETY: SR es de solo lectura; el LSI ya gira (post KEY_START) así que el + // flag se actualiza y termina por limpiarse. + while unsafe { read_reg(SR) } & flag != 0 { + spins += 1; + if spins >= 100_000 { + break; + } + core::hint::spin_loop(); + } +} diff --git a/crates/rugus-hal-stm32f7/src/lib.rs b/crates/rugus-hal-stm32f7/src/lib.rs index a892034..f00767d 100644 --- a/crates/rugus-hal-stm32f7/src/lib.rs +++ b/crates/rugus-hal-stm32f7/src/lib.rs @@ -28,5 +28,6 @@ pub mod fmc; pub mod gpio; pub mod iwdg; pub mod rcc; +pub mod reset; pub mod timer; pub mod usart; diff --git a/crates/rugus-hal-stm32f7/src/reset.rs b/crates/rugus-hal-stm32f7/src/reset.rs new file mode 100644 index 0000000..7db7ca2 --- /dev/null +++ b/crates/rugus-hal-stm32f7/src/reset.rs @@ -0,0 +1,94 @@ +//! Causa del último reset (RCC_CSR) para STM32F7 — raw-MMIO, gemelo del de F4. +//! +//! El bloque RCC registra en `CSR` la causa del reset más reciente con flags +//! pegajosos (sobreviven al reset, se limpian solo con `RMVF`). Leerlos al +//! arranque distingue un reset por watchdog (IWDG) de un power-on, un reset por +//! pin NRST o un reset por software (`SCB.SYSRESETREQ`). Es el complemento de la +//! telemetría persistente de faults (F4.4): el *por qué* del último arranque. +//! +//! El mapa de `CSR` (offset 0x74, mismos bits altos) es idéntico al de F4, así +//! que este driver es un gemelo exacto. Acceso por MMIO directo, en la línea de +//! [`crate::iwdg`]. + +use core::ptr::{read_volatile, write_volatile}; + +/// `RCC->CSR` (base RCC 0x4002_3800 + offset 0x74). +const RCC_CSR: u32 = 0x4002_3874; + +// Flags de causa de reset (bits altos de CSR). Pegajosos hasta `RMVF`. +const LPWRRSTF: u32 = 1 << 31; // reset por entrada ilegal a Stop/Standby +const WWDGRSTF: u32 = 1 << 30; // window watchdog +const IWDGRSTF: u32 = 1 << 29; // independent watchdog +const SFTRSTF: u32 = 1 << 28; // software (SYSRESETREQ) +const PORRSTF: u32 = 1 << 27; // power-on / power-down +const PINRSTF: u32 = 1 << 26; // pin NRST +const BORRSTF: u32 = 1 << 25; // brown-out +const RMVF: u32 = 1 << 24; // remove flags (write-1 para limpiar) + +/// Causa del último reset, decodificada desde `RCC_CSR`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ResetCause { + /// Power-on / power-down (arranque en frío). + PowerOn, + /// Brown-out (caída de tensión). + Brownout, + /// Pin externo NRST. + Pin, + /// Reset por software (`SCB.SYSRESETREQ`, p. ej. comando `reboot`). + Software, + /// Watchdog independiente (IWDG): el supervisor dejó de alimentarlo. + IndependentWatchdog, + /// Window watchdog (WWDG). + WindowWatchdog, + /// Entrada ilegal a modo de bajo consumo. + LowPower, + /// Ningún flag reconocido (no debería ocurrir tras un reset real). + Unknown, +} + +impl ResetCause { + /// Nombre corto para logging sin `defmt`. + pub fn name(self) -> &'static str { + match self { + ResetCause::PowerOn => "power-on", + ResetCause::Brownout => "brownout", + ResetCause::Pin => "pin-nrst", + ResetCause::Software => "software", + ResetCause::IndependentWatchdog => "iwdg", + ResetCause::WindowWatchdog => "wwdg", + ResetCause::LowPower => "low-power", + ResetCause::Unknown => "unknown", + } + } +} + +/// Lee la causa del último reset y limpia los flags (`RMVF`) para que el próximo +/// arranque parta de cero. +/// +/// Cuando hay varios flags activos (p. ej. PIN + POR en un power-on real) se +/// prioriza la causa más informativa: watchdogs y software por encima del pin y +/// del power-on, que suelen acompañar a otras. +pub fn read_and_clear() -> ResetCause { + // SAFETY: RCC_CSR es MMIO; lectura simple + write-1-to-clear de RMVF. + let csr = unsafe { read_volatile(RCC_CSR as *const u32) }; + let cause = if csr & LPWRRSTF != 0 { + ResetCause::LowPower + } else if csr & WWDGRSTF != 0 { + ResetCause::WindowWatchdog + } else if csr & IWDGRSTF != 0 { + ResetCause::IndependentWatchdog + } else if csr & SFTRSTF != 0 { + ResetCause::Software + } else if csr & BORRSTF != 0 { + ResetCause::Brownout + } else if csr & PORRSTF != 0 { + ResetCause::PowerOn + } else if csr & PINRSTF != 0 { + ResetCause::Pin + } else { + ResetCause::Unknown + }; + // Limpia todos los flags para no heredarlos en el próximo arranque. + unsafe { write_volatile(RCC_CSR as *mut u32, csr | RMVF) }; + cause +} diff --git a/crates/rugus-kernel/src/console.rs b/crates/rugus-kernel/src/console.rs index 2147609..ca41431 100644 --- a/crates/rugus-kernel/src/console.rs +++ b/crates/rugus-kernel/src/console.rs @@ -26,7 +26,7 @@ //! - `ps` — tabla de tareas (idx, prioridad, modo, estado, stack usado/total). //! - `mem` — uso del heap (usado/libre/total). //! - `faults` — telemetría persistente (arranques, faults totales, por tarea, -//! último post-mortem, safe-mode). +//! último post-mortem, safe-mode, causa del último reset). //! - `respawn ` — revive la tarea `n` si está `KILL`. //! - `reboot` — reset del sistema (`SCB.AIRCR.SYSRESETREQ`). @@ -263,6 +263,10 @@ fn cmd_faults(out: &mut impl ConsoleOut) { "" }); out.write_str("\r\n"); + // Causa del último reset (F4.6): power-on / iwdg / software / pin / brownout. + out.write_str("ultimo reset: "); + out.write_str(crate::reset_cause()); + out.write_str("\r\n"); for idx in 0..crate::task_count() { let c = crate::faults_for(idx); if c > 0 { diff --git a/crates/rugus-kernel/src/lib.rs b/crates/rugus-kernel/src/lib.rs index d1f53d8..9dbe814 100644 --- a/crates/rugus-kernel/src/lib.rs +++ b/crates/rugus-kernel/src/lib.rs @@ -167,6 +167,29 @@ fn telemetry_ref() -> &'static FaultTelemetry { unsafe { (*addr_of!(FAULT_TELEMETRY)).assume_init_ref() } } +/// Nombre de la causa del último reset (la placa la lee de su RCC_CSR y la +/// publica aquí). El kernel no conoce el periférico: solo guarda el `&str` para +/// que la consola y el log de arranque lo muestren. +static mut RESET_CAUSE: &str = "?"; + +/// Publica la causa del último reset (F4.6). La placa la obtiene de su HAL +/// (`reset::read_and_clear().name()`) al arrancar. +/// +/// # Safety +/// Llamar una sola vez en el arranque single-thread, antes de `start`. +pub unsafe fn set_reset_cause(name: &'static str) { + // SAFETY: escritura única en arranque cooperativo single-thread. + unsafe { + RESET_CAUSE = name; + } +} + +/// Causa del último reset publicada por la placa, o `"?"` si no se fijó. +pub fn reset_cause() -> &'static str { + // SAFETY: se fija una vez al arranque; lecturas cooperativas posteriores. + unsafe { RESET_CAUSE } +} + /// Trampolín de preempción invocado por la ISR de SysTick: rutea al scheduler. fn preempt_tick() { // SAFETY: corre en la ISR de SysTick; el modo hilo enmascara SysTick diff --git a/examples/app-sandbox-stm32f407g-disco/src/main.rs b/examples/app-sandbox-stm32f407g-disco/src/main.rs index efd6a35..720244b 100644 --- a/examples/app-sandbox-stm32f407g-disco/src/main.rs +++ b/examples/app-sandbox-stm32f407g-disco/src/main.rs @@ -151,8 +151,9 @@ fn kernel_task() -> ! { let mut last_btn = exti::events(); let mut hog_pings = 0u32; // Cadencia del kick del IWDG windowed: hay que alimentar DENTRO de la ventana - // [~1 s, ~2 s] tras la última recarga. Alimentar antes (bucle desbocado) o - // después (cuelgue) resetea. ~1.5 s deja margen frente al jitter del muestreo. + // [~0.5 s, ~4 s] nominal tras la última recarga. Alimentar antes (bucle + // desbocado) o después (cuelgue) resetea. ~1.5 s queda centrado y deja margen + // amplio frente al jitter del muestreo y a la tolerancia del LSI sin calibrar. const IWDG_KICK_MS: u32 = 1_500; let mut last_kick = time::now_ms(); loop { @@ -372,6 +373,12 @@ fn main() -> ! { if rugus_kernel::safe_mode() { defmt::error!("SAFE-MODE activo: demasiados faults acumulados"); } + // Causa del último reset (F4.6): leer+limpiar RCC_CSR distingue power-on + // de un reset por IWDG (cuelgue contenido), por software (`reboot`) o por + // pin NRST. Se publica al kernel para la consola (`faults`) y se loguea. + let cause = rugus_hal_stm32f4::reset::read_and_clear(); + rugus_kernel::set_reset_cause(cause.name()); + defmt::info!("reset cause: {=str}", cause.name()); } static mut HEAP: [u8; 32 * 1024] = [0; 32 * 1024]; @@ -416,7 +423,7 @@ fn main() -> ! { unsafe { WATCHDOG = Some(Iwdg::start_windowed()); } - defmt::info!("IWDG armed (windowed, kick window ~1-2 s)"); + defmt::info!("IWDG armed (windowed, ventana kick ~0.5-4 s nominal)"); // Consola de operador interactiva (F4.5): PA2 TX / PA3 RX @ 115200 8N1, RX por // IRQ. El supervisor drena el anillo y procesa los comandos (ps/mem/faults/ diff --git a/examples/app-sandbox-stm32f769-disco/src/main.rs b/examples/app-sandbox-stm32f769-disco/src/main.rs index 4c78957..ce81665 100644 --- a/examples/app-sandbox-stm32f769-disco/src/main.rs +++ b/examples/app-sandbox-stm32f769-disco/src/main.rs @@ -148,8 +148,9 @@ fn kernel_task() -> ! { let mut recoveries = 0u32; let mut last_btn = exti::events(); // Cadencia del kick del IWDG windowed: hay que alimentar DENTRO de la ventana - // [~1 s, ~2 s] tras la última recarga. Alimentar antes (bucle desbocado) o - // después (cuelgue) resetea. ~1.5 s deja margen frente al jitter del muestreo. + // [~0.5 s, ~4 s] nominal tras la última recarga. Alimentar antes (bucle + // desbocado) o después (cuelgue) resetea. ~1.5 s queda centrado y deja margen + // amplio frente al jitter del muestreo y a la tolerancia del LSI sin calibrar. const IWDG_KICK_MS: u32 = 1_500; let mut last_kick = time::now_ms(); loop { @@ -338,6 +339,12 @@ fn main() -> ! { if rugus_kernel::safe_mode() { defmt::error!("SAFE-MODE activo: demasiados faults acumulados"); } + // Causa del último reset (F4.6): leer+limpiar RCC_CSR distingue power-on + // de un reset por IWDG (cuelgue contenido), por software (`reboot`) o por + // pin NRST. Se publica al kernel para la consola (`faults`) y se loguea. + let cause = rugus_hal_stm32f7::reset::read_and_clear(); + rugus_kernel::set_reset_cause(cause.name()); + defmt::info!("reset cause: {=str}", cause.name()); } static mut HEAP_FALLBACK: [u8; 64 * 1024] = [0; 64 * 1024]; @@ -394,7 +401,7 @@ fn main() -> ! { unsafe { WATCHDOG = Some(Iwdg::start_windowed()); } - defmt::info!("IWDG armed (windowed, kick window ~1-2 s)"); + defmt::info!("IWDG armed (windowed, ventana kick ~0.5-4 s nominal)"); // Consola de operador interactiva (F4.5): PA2 TX / PA3 RX @ 115200 8N1, RX por // IRQ. El supervisor drena el anillo y procesa los comandos (ps/mem/faults/