diff --git a/examples/independent_watchdog.rs b/examples/independent_watchdog.rs new file mode 100644 index 00000000..5fd5783f --- /dev/null +++ b/examples/independent_watchdog.rs @@ -0,0 +1,46 @@ +// Originaly from stm32h7xx-hal, adapted for stm32g4xx-hal + +#![no_main] +#![no_std] + +//#[macro_use] +//mod utils; +use stm32g4xx_hal::{independent_watchdog::IndependentWatchdog, prelude::*, stm32::Peripherals}; + +use cortex_m_rt::entry; + +// TODO: Use utils instead +use defmt::Logger; +use defmt_rtt as _; // global logger +use panic_probe as _; + +use defmt::info; // TODO: Use utils::logger instead + +#[entry] +fn main() -> ! { + //utils::logger::init(); + let dp = Peripherals::take().unwrap(); + + let mut watchdog = IndependentWatchdog::new(dp.IWDG); + + info!(""); + info!("stm32g4xx-hal example - Watchdog"); + info!(""); + + // If the watchdog is working correctly this print should + // appear again and again as the chip gets restarted + info!("Watchdog restarted! "); + + // Enable the watchdog with a limit of 32.76 seconds (which is the maximum this watchdog can do) and wait forever + // -> restart the chip + watchdog.start(32_760.ms()); + + // Alternatively, there's also a windowed option where if the watchdog is fed before the window time, it will reset the chip as well + // watchdog.start_windowed(100.millis(), 200.millis()); + + loop { + // We can feed the watchdog like this: + // watchdog.feed(); + cortex_m::asm::nop() + } +} diff --git a/src/independent_watchdog.rs b/src/independent_watchdog.rs new file mode 100644 index 00000000..58151f3a --- /dev/null +++ b/src/independent_watchdog.rs @@ -0,0 +1,150 @@ +//! Independent Watchdog +//! +//! This module implements the embedded-hal +//! [Watchdog](https://docs.rs/embedded-hal/latest/embedded_hal/watchdog/trait.Watchdog.html) +//! trait for the Independent Watchdog peripheral. +//! +//! The Independent Watchdog peripheral triggers a system reset when its internal counter expires. +//! +//! # Examples +//! +//! - [IWDG Example](todo-insert-link-here) +//! +//! Originally from stm32h7-hal, adapted for stm32g4xx-hal +use crate::{ + stm32::{iwdg::pr::PR_A, IWDG}, + time::{MicroSecond, U32Ext}, +}; + +/// The implementation of the hardware IWDG +pub struct IndependentWatchdog { + iwdg: IWDG, +} + +impl IndependentWatchdog { + const CLOCK_SPEED: u32 = 32000; + const MAX_COUNTER_VALUE: u32 = 0x00000FFF; + const MAX_MILLIS_FOR_PRESCALER: [(PR_A, u32); 8] = [ + ( + PR_A::DivideBy4, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 4), + ), + ( + PR_A::DivideBy8, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 8), + ), + ( + PR_A::DivideBy16, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 16), + ), + ( + PR_A::DivideBy32, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 32), + ), + ( + PR_A::DivideBy64, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 64), + ), + ( + PR_A::DivideBy128, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 128), + ), + ( + PR_A::DivideBy256, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 256), + ), + ( + PR_A::DivideBy256bis, + (Self::MAX_COUNTER_VALUE * 1000) / (Self::CLOCK_SPEED / 256), + ), + ]; + + /// Create a new instance + pub fn new(iwdg: IWDG) -> Self { + Self { iwdg } + } + + /// Feed the watchdog, resetting the timer to 0 + pub fn feed(&mut self) { + self.iwdg.kr.write(|w| w.key().reset()); + } + + /// Start the watchdog where it must be fed before the max time is over and + /// not before the min time has passed + pub fn start_windowed>(&mut self, min_window_time: T, max_window_time: T) { + let min_window_time: MicroSecond = min_window_time.into(); + let max_window_time: MicroSecond = max_window_time.into(); + + // Start the watchdog + self.iwdg.kr.write(|w| w.key().start()); + // Enable register access + self.iwdg.kr.write(|w| w.key().enable()); + + // Set the prescaler + let (prescaler, _) = Self::MAX_MILLIS_FOR_PRESCALER + .iter() + .find(|(_, max_millis)| *max_millis >= max_window_time.0 / 1000) + .expect("IWDG max time is greater than is possible"); + while self.iwdg.sr.read().pvu().bit_is_set() { + cortex_m::asm::nop(); + } + self.iwdg.pr.write(|w| w.pr().variant(*prescaler)); + + // Reset the window value + while self.iwdg.sr.read().wvu().bit_is_set() { + cortex_m::asm::nop(); + } + self.iwdg + .winr + .write(|w| w.win().bits(Self::MAX_COUNTER_VALUE as u16)); + + // Calculate the counter values + let reload_value = (max_window_time.0 / 1000) * (Self::CLOCK_SPEED / 1000) + / Self::get_prescaler_divider(prescaler); + let window_value = (min_window_time.0 / 1000) * (Self::CLOCK_SPEED / 1000) + / Self::get_prescaler_divider(prescaler); + + // Set the reload value + while self.iwdg.sr.read().rvu().bit_is_set() { + cortex_m::asm::nop(); + } + self.iwdg.rlr.write(|w| w.rl().bits(reload_value as u16)); + + self.feed(); + // Enable register access + self.iwdg.kr.write(|w| w.key().enable()); + + // Set the window value + while self.iwdg.sr.read().wvu().bit_is_set() { + cortex_m::asm::nop(); + } + self.iwdg + .winr + .write(|w| w.win().bits((reload_value - window_value) as u16)); + + // Wait until everything is set + while self.iwdg.sr.read().bits() != 0 { + cortex_m::asm::nop(); + } + + self.feed(); + } + + /// Start the watchdog with the given max time and no minimal time + pub fn start>(&mut self, max_time: T) { + self.start_windowed(0_u32.ms(), max_time.into()); + } + + fn get_prescaler_divider(prescaler: &PR_A) -> u32 { + match prescaler { + PR_A::DivideBy4 => 4, + PR_A::DivideBy8 => 8, + PR_A::DivideBy16 => 16, + PR_A::DivideBy32 => 32, + PR_A::DivideBy64 => 64, + PR_A::DivideBy128 => 128, + PR_A::DivideBy256 => 256, + PR_A::DivideBy256bis => 256, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0b7d3ee4..43640ce6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,3 +85,4 @@ pub mod syscfg; pub mod time; pub mod timer; // pub mod watchdog; +pub mod independent_watchdog;