Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 70 additions & 23 deletions crates/rugus-hal-stm32f4/src/iwdg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,45 @@ 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;
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 {
armed: bool,
}

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.
//
Expand All @@ -58,32 +72,43 @@ 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);
}
Self { armed: true }
}

/// 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 }
}
Expand All @@ -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();
}
}
1 change: 1 addition & 0 deletions crates/rugus-hal-stm32f4/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
93 changes: 93 additions & 0 deletions crates/rugus-hal-stm32f4/src/reset.rs
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 70 additions & 23 deletions crates/rugus-hal-stm32f7/src/iwdg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,45 @@ 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;
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 {
armed: bool,
}

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.
//
Expand All @@ -58,32 +72,43 @@ 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);
}
Self { armed: true }
}

/// 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 }
}
Expand All @@ -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();
}
}
1 change: 1 addition & 0 deletions crates/rugus-hal-stm32f7/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading