diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 0ea97aec..9e99b2fb 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -36,6 +36,7 @@ pub mod clocks; pub mod flash; pub mod multicore; mod reset; +pub mod watchdog; // Reexports @@ -119,6 +120,8 @@ embassy_hal_common::peripherals! { PIO0, PIO1, + + WATCHDOG, } #[link_section = ".boot2"] diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs new file mode 100644 index 00000000..01509ff4 --- /dev/null +++ b/embassy-rp/src/watchdog.rs @@ -0,0 +1,111 @@ +//! Watchdog +//! +//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the +//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to +//! stop it from reaching zero. +//! +//! Credit: based on `rp-hal` implementation (also licensed Apache+MIT) + +use core::marker::PhantomData; + +use embassy_time::Duration; + +use crate::pac; +use crate::peripherals::WATCHDOG; + +/// Watchdog peripheral +pub struct Watchdog<'d> { + phantom: PhantomData<&'d WATCHDOG>, + load_value: u32, // decremented by 2 per tick (µs) +} + +impl<'d> Watchdog<'d> { + /// Create a new `Watchdog` + pub fn new(_watchdog: WATCHDOG) -> Self { + Self { + phantom: PhantomData, + load_value: 0, + } + } + + /// Start tick generation on clk_tick which is driven from clk_ref. + /// + /// # Arguments + /// + /// * `cycles` - Total number of tick cycles before the next tick is generated. + /// It is expected to be the frequency in MHz of clk_ref. + pub fn enable_tick_generation(&mut self, cycles: u8) { + const WATCHDOG_TICK_ENABLE_BITS: u32 = 0x200; + unsafe { + let watchdog = pac::WATCHDOG; + watchdog + .tick() + .write_value(pac::watchdog::regs::Tick(WATCHDOG_TICK_ENABLE_BITS | cycles as u32)) + } + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + pub fn pause_on_debug(&mut self, pause: bool) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_pause_dbg0(pause); + w.set_pause_dbg1(pause); + w.set_pause_jtag(pause); + }) + } + } + + fn load_counter(&self, counter: u32) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.load().write_value(pac::watchdog::regs::Load(counter)); + } + } + + fn enable(&self, bit: bool) { + unsafe { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| w.set_enable(bit)) + } + } + + // Configure which hardware will be reset by the watchdog + // (everything except ROSC, XOSC) + unsafe fn configure_wdog_reset_triggers(&self) { + let psm = pac::PSM; + psm.wdsel().write_value(pac::psm::regs::Wdsel( + 0x0001ffff & !(0x01 << 0usize) & !(0x01 << 1usize), + )); + } + + /// Feed the watchdog timer + pub fn feed(&mut self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog timer + pub fn start(&mut self, period: Duration) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.as_micros() as u32; + if delay_us > MAX_PERIOD / 2 { + panic!( + "Period cannot exceed maximum load value of {} ({} microseconds))", + MAX_PERIOD, + MAX_PERIOD / 2 + ); + } + // Due to a logic error, the watchdog decrements by 2 and + // the load value must be compensated; see RP2040-E1 + self.load_value = delay_us * 2; + + self.enable(false); + unsafe { + self.configure_wdog_reset_triggers(); + } + self.load_counter(self.load_value); + self.enable(true); + } +} diff --git a/examples/rp/src/bin/watchdog.rs b/examples/rp/src/bin/watchdog.rs new file mode 100644 index 00000000..13af22a2 --- /dev/null +++ b/examples/rp/src/bin/watchdog.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello world!"); + + let mut watchdog = Watchdog::new(p.WATCHDOG); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led.set_high(); + Timer::after(Duration::from_secs(2)).await; + + // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, refreshing the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after(Duration::from_millis(500)).await; + led.set_high(); + Timer::after(Duration::from_millis(500)).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds, or 5 blinks time + loop { + led.set_low(); + Timer::after(Duration::from_millis(100)).await; + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + } +}