use crate::pac::peripherals::iwdg::IWDG;
use embassy_hal_internal::{Peri, PeripheralType};
const START: u16 = 0xcccc;
const WRITE_ACCESS: u16 = 0x5555;
const RELOAD: u16 = 0xaaaa;
const SYNC_POLLS: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Prescaler {
Divide4 = 0,
Divide8 = 1,
Divide16 = 2,
Divide32 = 3,
Divide64 = 4,
Divide128 = 5,
Divide256 = 6,
Divide512 = 7,
Divide1024 = 8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub prescaler: Prescaler,
pub reload: u16,
}
impl Default for Config {
fn default() -> Self {
Self {
prescaler: Prescaler::Divide256,
reload: 4095,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidReload,
SyncTimeout,
}
pub(crate) trait SealedInstance {
fn regs() -> IWDG;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {}
macro_rules! impl_watchdog {
($instance:ident, $base:expr, $irq:expr) => {
impl crate::watchdog::SealedInstance for crate::peripherals::$instance {
fn regs() -> crate::pac::peripherals::iwdg::IWDG {
unsafe { crate::pac::peripherals::iwdg::IWDG::from_ptr($base as *mut ()) }
}
}
impl crate::watchdog::Instance for crate::peripherals::$instance {}
};
}
pub(crate) use impl_watchdog;
pub struct Watchdog {
regs: IWDG,
}
impl Watchdog {
pub fn new<T: Instance>(peri: Peri<'static, T>) -> Self {
let _ = peri;
Self { regs: T::regs() }
}
pub fn start(&mut self, config: Config) -> Result<(), Error> {
if config.reload > 4095 {
return Err(Error::InvalidReload);
}
self.key(START);
self.key(WRITE_ACCESS);
self.wait_ready()?;
self.regs.PR().write(|w| w.set_PR(config.prescaler as u8));
self.regs.RLR().write(|w| w.set_RL(config.reload));
self.wait_ready()?;
self.regs.ICR().write(|w| w.set_EWIC(true));
self.regs.EWCR().write_value(Default::default());
self.wait_ready()?;
if self.regs.WINR().read().WIN() != 4095 {
self.regs.WINR().write(|w| w.set_WIN(4095));
self.wait_ready()?;
} else {
self.feed();
}
self.key(0);
Ok(())
}
pub fn feed(&mut self) {
self.key(RELOAD);
}
pub fn is_running(&self) -> bool {
self.regs.SR().read().ONF()
}
fn key(&self, key: u16) {
self.regs.KR().write(|w| w.set_KEY(key));
}
fn wait_ready(&self) -> Result<(), Error> {
for _ in 0..SYNC_POLLS {
let status = self.regs.SR().read();
if !status.PVU() && !status.RVU() && !status.WVU() && !status.EWU() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::SyncTimeout)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn configures_maximum_divider_and_disables_window_and_interrupt() {
let mut words = [0u32; 7];
let regs = unsafe { IWDG::from_ptr(words.as_mut_ptr().cast()) };
let mut watchdog = Watchdog { regs };
watchdog
.start(Config {
prescaler: Prescaler::Divide1024,
reload: 1234,
})
.unwrap();
assert_eq!(words[1], 8);
assert_eq!(words[2], 1234);
assert_eq!(words[4], 4095);
assert_eq!(words[5], 0);
assert_eq!(words[6], 1 << 15);
assert_eq!(words[0], 0); watchdog.feed();
assert_eq!(words[0], 0xaaaa);
}
#[test]
fn rejects_out_of_range_reload_without_starting() {
let mut words = [0u32; 7];
let regs = unsafe { IWDG::from_ptr(words.as_mut_ptr().cast()) };
let mut watchdog = Watchdog { regs };
assert_eq!(
watchdog.start(Config {
reload: 4096,
..Config::default()
}),
Err(Error::InvalidReload)
);
assert_eq!(words, [0; 7]);
}
#[test]
fn stuck_synchronization_returns_handle_that_can_still_feed() {
let mut words = [0u32; 7];
words[3] = 1 | (1 << 8); let regs = unsafe { IWDG::from_ptr(words.as_mut_ptr().cast()) };
let mut watchdog = Watchdog { regs };
assert_eq!(watchdog.start(Config::default()), Err(Error::SyncTimeout));
assert!(watchdog.is_running());
watchdog.feed();
assert_eq!(words[0], 0xaaaa);
}
}