#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::collections::BTreeMap;
#[cfg(feature = "defmt")]
#[allow(unused_imports)]
use defmt::{debug, error, info, trace, warn};
#[cfg(not(feature = "defmt"))]
mod log_impl {
#![allow(unused_macros)]
#![allow(unused_imports)]
macro_rules! _trace {
($($arg:tt)*) => {};
}
macro_rules! _debug {
($($arg:tt)*) => {};
}
macro_rules! _info {
($($arg:tt)*) => {};
}
macro_rules! _warn {
($($arg:tt)*) => {};
}
macro_rules! _error {
($($arg:tt)*) => {};
}
pub(crate) use _debug as debug;
pub(crate) use _error as error;
pub(crate) use _info as info;
pub(crate) use _trace as trace;
pub(crate) use _warn as warn;
}
#[cfg(not(feature = "defmt"))]
use log_impl::*;
pub trait HardwareWatchdog<C: Clock> {
fn start(&mut self, timeout: C::Duration);
fn feed(&mut self);
fn trigger_reset(&mut self) -> !;
fn reset_reason(&self) -> Option<ResetReason>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ResetReason {
Forced,
TimedOut,
}
#[derive(Debug, Clone, Copy)]
pub struct WatchdogConfig<C: Clock> {
pub hardware_timeout: C::Duration,
pub check_interval: C::Duration,
}
impl<C: Clock> WatchdogConfig<C> {
pub fn new(hardware_timeout_ms: u64, check_interval_ms: u64, clock: &C) -> Self {
Self {
hardware_timeout: clock.duration_from_millis(hardware_timeout_ms),
check_interval: clock.duration_from_millis(check_interval_ms),
}
}
pub fn default(clock: &C) -> Self {
Self::new(5000, 1000, clock)
}
}
#[cfg(all(feature = "alloc", feature = "defmt"))]
pub trait Id: PartialEq + Eq + Ord + defmt::Format + Clone + Copy {}
#[cfg(all(feature = "alloc", not(feature = "defmt")))]
pub trait Id: PartialEq + Eq + Ord + core::fmt::Debug + Clone + Copy {}
#[cfg(all(not(feature = "alloc"), feature = "defmt"))]
pub trait Id: PartialEq + Eq + defmt::Format + Clone + Copy {}
#[cfg(all(not(feature = "alloc"), not(feature = "defmt")))]
pub trait Id: PartialEq + Eq + core::fmt::Debug + Clone + Copy {}
#[derive(Debug, Clone)]
pub struct Task<I: Id, C: Clock> {
#[allow(dead_code)]
id: I,
last_feed: C::Instant,
max_duration: C::Duration,
}
impl<I: Id, C: Clock> Task<I, C> {
pub fn new(id: I, max_duration: C::Duration, clock: &C) -> Self {
Self {
id,
last_feed: clock.now(),
max_duration,
}
}
fn feed(&mut self, clock: &C) {
self.last_feed = clock.now();
}
fn is_starved(&self, clock: &C) -> bool {
clock.has_elapsed(self.last_feed, &self.max_duration)
}
}
pub trait Clock {
type Instant: Copy;
type Duration: Copy;
fn now(&self) -> Self::Instant;
fn elapsed_since(&self, instant: Self::Instant) -> Self::Duration;
fn has_elapsed(&self, instant: Self::Instant, duration: &Self::Duration) -> bool;
fn duration_from_millis(&self, millis: u64) -> Self::Duration;
}
#[cfg(feature = "alloc")]
pub struct Watchdog<I: Id, W: HardwareWatchdog<C>, C: Clock> {
hw_watchdog: W,
tasks: BTreeMap<I, Task<I, C>>,
config: WatchdogConfig<C>,
clock: C,
}
#[cfg(feature = "alloc")]
impl<I: Id, W: HardwareWatchdog<C>, C: Clock> Watchdog<I, W, C> {
pub fn new(hw_watchdog: W, config: WatchdogConfig<C>, clock: C) -> Self {
Self {
hw_watchdog,
tasks: BTreeMap::new(),
config,
clock,
}
}
pub fn register_task(&mut self, id: &I, max_duration: C::Duration) {
let task = Task::new(*id, max_duration, &self.clock);
self.tasks.insert(*id, task);
debug!("Registered task: {:?}", id);
}
pub fn deregister_task(&mut self, id: &I) {
#[allow(clippy::if_same_then_else)]
if self.tasks.remove(id).is_some() {
debug!("Deregistered task: {:?}", id);
} else {
debug!("Attempted to deregister unknown task: {:?}", id);
}
}
pub fn feed(&mut self, id: &I) {
if let Some(task) = self.tasks.get_mut(id) {
task.feed(&self.clock);
} else {
warn!("Attempt to feed unknown task: {:?}", id);
}
}
pub fn start(&mut self) {
for task in self.tasks.values_mut() {
task.feed(&self.clock);
}
self.hw_watchdog.start(self.config.hardware_timeout);
info!("Watchdog started");
}
pub fn check(&mut self) -> bool {
let mut starved = false;
for task in self.tasks.values() {
if task.is_starved(&self.clock) {
error!("Task {:?} has starved the watchdog", task.id);
starved = true;
}
}
if !starved {
self.hw_watchdog.feed();
}
starved
}
pub fn trigger_reset(&mut self) -> ! {
warn!("Triggering watchdog reset");
self.hw_watchdog.trigger_reset()
}
pub fn reset_reason(&self) -> Option<ResetReason> {
self.hw_watchdog.reset_reason()
}
}
#[cfg(not(feature = "alloc"))]
pub struct Watchdog<I, const N: usize, W, C>
where
I: Id,
W: HardwareWatchdog<C>,
C: Clock,
{
hw_watchdog: W,
tasks: [Option<Task<I, C>>; N],
config: WatchdogConfig<C>,
clock: C,
}
pub enum Error {
NoSlotsAvailable,
}
#[cfg(not(feature = "alloc"))]
impl<I: Id, W: HardwareWatchdog<C>, C: Clock, const N: usize> Watchdog<I, N, W, C> {
pub fn new(hw_watchdog: W, config: WatchdogConfig<C>, clock: C) -> Self {
Self {
hw_watchdog,
tasks: [const { None }; N],
config,
clock,
}
}
pub fn register_task(&mut self, id: &I, max_duration: C::Duration) -> Result<(), Error> {
for slot in &mut self.tasks {
if slot.is_none() {
*slot = Some(Task::new(*id, max_duration, &self.clock));
debug!("Registered task: {:?}", id);
return Ok(());
}
}
error!("Failed to register task: {:?} - no slots available", id);
Err(Error::NoSlotsAvailable)
}
pub fn deregister_task(&mut self, id: &I) {
for slot in &mut self.tasks {
if let Some(task) = slot {
if core::mem::discriminant(&task.id) == core::mem::discriminant(id) {
*slot = None;
debug!("Deregistered task: {:?}", id);
return;
}
}
}
info!("Attempted to deregister unknown task: {:?}", id);
}
pub fn feed(&mut self, id: &I) {
let fed = self.tasks.iter_mut().flatten().any(|task| {
if core::mem::discriminant(&task.id) == core::mem::discriminant(id) {
task.feed(&self.clock);
true
} else {
false
}
});
if !fed {
warn!("Attempt to feed unknown task: {:?}", id);
}
}
pub fn start(&mut self) {
self.tasks.iter_mut().flatten().for_each(|task| {
task.feed(&self.clock);
});
self.hw_watchdog.start(self.config.hardware_timeout);
info!("Watchdog started");
}
pub fn check(&mut self) -> bool {
let mut starved = false;
self.tasks.iter_mut().flatten().for_each(|task| {
if task.is_starved(&self.clock) {
error!("Task {:?} has starved the watchdog", task.id);
starved = true;
}
});
if !starved {
self.hw_watchdog.feed();
}
starved
}
pub fn trigger_reset(&mut self) -> ! {
warn!("Triggering watchdog reset");
self.hw_watchdog.trigger_reset()
}
pub fn reset_reason(&self) -> Option<ResetReason> {
self.hw_watchdog.reset_reason()
}
}
pub struct CoreClock;
impl Clock for CoreClock {
type Instant = u64; type Duration = core::time::Duration;
fn now(&self) -> Self::Instant {
static mut MILLIS: u64 = 0;
unsafe {
MILLIS += 1;
MILLIS
}
}
fn elapsed_since(&self, instant: Self::Instant) -> Self::Duration {
let now = self.now();
let elapsed_ms = now.saturating_sub(instant);
core::time::Duration::from_millis(elapsed_ms)
}
fn has_elapsed(&self, instant: Self::Instant, duration: &Self::Duration) -> bool {
self.elapsed_since(instant) >= *duration
}
fn duration_from_millis(&self, millis: u64) -> Self::Duration {
core::time::Duration::from_millis(millis)
}
}
#[cfg(feature = "embassy")]
pub struct EmbassyClock;
#[cfg(feature = "embassy")]
impl Clock for EmbassyClock {
type Instant = embassy_time::Instant;
type Duration = embassy_time::Duration;
fn now(&self) -> Self::Instant {
embassy_time::Instant::now()
}
fn elapsed_since(&self, instant: Self::Instant) -> Self::Duration {
embassy_time::Instant::now() - instant
}
fn has_elapsed(&self, instant: Self::Instant, duration: &Self::Duration) -> bool {
(embassy_time::Instant::now() - instant) >= *duration
}
fn duration_from_millis(&self, millis: u64) -> Self::Duration {
embassy_time::Duration::from_millis(millis)
}
}
#[cfg(any(feature = "rp2040-hal", feature = "rp2350-hal"))]
pub mod rp_hal {
use super::{Clock, HardwareWatchdog, ResetReason};
use hal::fugit::{Duration as RpHalDuration, MicrosDurationU32};
#[cfg(feature = "rp2350-hal")]
use hal::timer::CopyableTimer0;
use hal::timer::{Instant as RpHalInstant, Timer as RpHalTimer};
use hal::watchdog::Watchdog as RpHalWatchdog;
#[cfg(feature = "rp2040-hal")]
use rp2040_hal as hal;
#[cfg(feature = "rp2350-hal")]
use rp235x_hal as hal;
#[cfg(feature = "rp2040-hal")]
pub struct RpHalClock {
inner: RpHalTimer,
}
#[cfg(feature = "rp2040-hal")]
impl RpHalClock {
pub fn new(timer: RpHalTimer) -> Self {
Self { inner: timer }
}
}
#[cfg(feature = "rp2350-hal")]
pub struct RpHalClock {
inner: RpHalTimer<CopyableTimer0>,
}
#[cfg(feature = "rp2350-hal")]
impl RpHalClock {
pub fn new(timer: RpHalTimer<CopyableTimer0>) -> Self {
Self { inner: timer }
}
}
impl Clock for RpHalClock {
type Instant = RpHalInstant;
type Duration = RpHalDuration<u64, 1, 1_000_000>;
fn now(&self) -> Self::Instant {
self.inner.get_counter()
}
fn elapsed_since(&self, instant: Self::Instant) -> Self::Duration {
self.now().checked_duration_since(instant).unwrap()
}
fn has_elapsed(&self, instant: Self::Instant, duration: &Self::Duration) -> bool {
(self.now() - instant) >= *duration
}
fn duration_from_millis(&self, millis: u64) -> Self::Duration {
RpHalDuration::<u64, 1, 1_000_000>::millis(millis as u64)
}
}
pub struct RpHalTaskWatchdog {
inner: RpHalWatchdog,
}
impl RpHalTaskWatchdog {
pub fn new(watchdog: RpHalWatchdog) -> Self {
Self { inner: watchdog }
}
}
impl HardwareWatchdog<RpHalClock> for RpHalTaskWatchdog {
fn start(&mut self, timeout: <RpHalClock as Clock>::Duration) {
let timeout_micros = timeout.to_micros();
assert!(timeout_micros <= u32::MAX as u64);
let micros_dur_u32: MicrosDurationU32 =
MicrosDurationU32::micros(timeout_micros as u32);
self.inner.start(micros_dur_u32);
}
fn feed(&mut self) {
self.inner.feed();
}
fn trigger_reset(&mut self) -> ! {
hal::reset()
}
fn reset_reason(&self) -> Option<ResetReason> {
None
}
}
}
#[cfg(any(feature = "rp2040-embassy", feature = "rp2350-embassy"))]
pub mod embassy_rp {
use super::{
info, Clock, EmbassyClock, HardwareWatchdog, Id, ResetReason, Watchdog, WatchdogConfig,
};
use embassy_rp::peripherals::WATCHDOG as P_RpWatchdog;
use embassy_rp::watchdog as rp_watchdog;
use embassy_time::{Instant, Timer};
pub struct RpWatchdog {
inner: rp_watchdog::Watchdog,
}
impl RpWatchdog {
#[must_use]
pub fn new(peripheral: P_RpWatchdog) -> Self {
Self {
inner: rp_watchdog::Watchdog::new(peripheral),
}
}
}
impl HardwareWatchdog<EmbassyClock> for RpWatchdog {
fn start(&mut self, timeout: <EmbassyClock as Clock>::Duration) {
self.inner.start(timeout);
}
fn feed(&mut self) {
self.inner.feed();
}
fn trigger_reset(&mut self) -> ! {
self.inner.trigger_reset();
panic!("Triggering reset via watchdog failed");
}
fn reset_reason(&self) -> Option<ResetReason> {
self.inner.reset_reason().map(|reason| match reason {
embassy_rp::watchdog::ResetReason::Forced => ResetReason::Forced,
embassy_rp::watchdog::ResetReason::TimedOut => ResetReason::TimedOut,
})
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogRunner<I>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, RpWatchdog, EmbassyClock>>,
>,
}
#[cfg(not(feature = "alloc"))]
pub struct WatchdogRunner<I, const N: usize>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, N, RpWatchdog, EmbassyClock>>,
>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: Id + 'static,
{
pub fn new(hw_watchdog: P_RpWatchdog, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = RpWatchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration);
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: Id,
{
pub fn new(hw_watchdog: P_RpWatchdog, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = RpWatchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration)
.ok();
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogTask<I>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> WatchdogTask<I> {
WatchdogTask { runner: self }
}
}
#[cfg(feature = "alloc")]
pub async fn watchdog_run<I>(task: WatchdogTask<I>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
#[cfg(not(feature = "alloc"))]
pub struct NoAllocWatchdogTask<I, const N: usize>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I, N>,
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> NoAllocWatchdogTask<I, N> {
NoAllocWatchdogTask { runner: self }
}
}
#[cfg(not(feature = "alloc"))]
pub async fn watchdog_run<I, const N: usize>(task: NoAllocWatchdogTask<I, N>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
}
#[cfg(feature = "stm32-embassy")]
pub mod embassy_stm32 {
use super::{
info, Clock, EmbassyClock, HardwareWatchdog, Id, ResetReason, Watchdog, WatchdogConfig,
};
use embassy_stm32::peripherals::IWDG;
use embassy_stm32::wdg::IndependentWatchdog;
use embassy_time::{Instant, Timer};
pub struct Stm32Watchdog {
peripheral: Option<IWDG>,
inner: Option<IndependentWatchdog<'static, IWDG>>,
}
impl Stm32Watchdog {
#[must_use]
pub fn new(peripheral: IWDG) -> Self {
Self {
peripheral: Some(peripheral),
inner: None,
}
}
}
impl HardwareWatchdog<EmbassyClock> for Stm32Watchdog {
fn start(&mut self, timeout: embassy_time::Duration) {
let timeout = timeout.as_micros();
if timeout > u32::MAX as u64 {
panic!("Watchdog timeout too large for STM32");
}
let peripheral = self
.peripheral
.take()
.expect("STM32 Watchdog not properly initialized");
let mut wdg = IndependentWatchdog::new(peripheral, timeout as u32);
wdg.unleash();
self.inner = Some(wdg);
}
fn feed(&mut self) {
self.inner.as_mut().expect("Watchdog not started").pet();
}
fn trigger_reset(&mut self) -> ! {
cortex_m::peripheral::SCB::sys_reset();
}
fn reset_reason(&self) -> Option<ResetReason> {
None
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogRunner<I>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, Stm32Watchdog, EmbassyClock>>,
>,
}
#[cfg(not(feature = "alloc"))]
pub struct WatchdogRunner<I, const N: usize>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, N, Stm32Watchdog, EmbassyClock>>,
>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: Id + 'static,
{
pub fn new(hw_watchdog: IWDG, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = Stm32Watchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration);
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: Id,
{
pub fn new(hw_watchdog: IWDG, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = Stm32Watchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration)
.ok();
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogTask<I>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> WatchdogTask<I> {
WatchdogTask { runner: self }
}
}
#[cfg(feature = "alloc")]
pub async fn watchdog_run<I>(task: WatchdogTask<I>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
#[cfg(not(feature = "alloc"))]
pub struct NoAllocWatchdogTask<I, const N: usize>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I, N>,
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> NoAllocWatchdogTask<I, N> {
NoAllocWatchdogTask { runner: self }
}
}
#[cfg(not(feature = "alloc"))]
pub async fn watchdog_run<I, const N: usize>(task: NoAllocWatchdogTask<I, N>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
}
#[cfg(feature = "nrf-embassy")]
pub mod embassy_nrf {
use super::{
info, warn, Clock, EmbassyClock, HardwareWatchdog, Id, ResetReason, Watchdog, WatchdogConfig,
};
use embassy_nrf::peripherals::WDT;
use embassy_nrf::wdt::{Watchdog as NrfWatchdogStruct, WatchdogHandle, Config};
use embassy_time::{Instant, Timer};
pub struct NrfWatchdog {
peripheral: Option<WDT>,
inner: Option<WatchdogHandle>,
}
impl NrfWatchdog {
#[must_use]
pub fn new(peripheral: WDT) -> Self {
Self {
peripheral: Some(peripheral),
inner: None,
}
}
}
impl HardwareWatchdog<EmbassyClock> for NrfWatchdog {
fn start(&mut self, timeout: embassy_time::Duration) {
let ticks = timeout.as_micros() * 1_000_000 / 32_768_000;
if ticks < 15 {
warn!("Watchdog timeout {} ticks too small for nRF - will be set to 15 ticks", ticks);
panic!("Watchdog timeout too large for nRF");
}
let mut config = Config::default();
if ticks > u32::MAX as u64 {
panic!("Watchdog timeout {} ticks too large for nRF", ticks);
}
config.timeout_ticks = ticks as u32;
let peripheral = self
.peripheral
.take()
.expect("nRF Watchdog not properly initialized");
let (_wdt, [handle]) = NrfWatchdogStruct::try_new(peripheral, config).unwrap_or_else(|_| panic!("Failed to create nRF watchdog"));
self.inner = Some(handle);
}
fn feed(&mut self) {
self.inner.as_mut().expect("Watchdog not started").pet();
}
fn trigger_reset(&mut self) -> ! {
cortex_m::peripheral::SCB::sys_reset();
}
fn reset_reason(&self) -> Option<ResetReason> {
None
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogRunner<I>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, NrfWatchdog, EmbassyClock>>,
>,
}
#[cfg(not(feature = "alloc"))]
pub struct WatchdogRunner<I, const N: usize>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, N, NrfWatchdog, EmbassyClock>>,
>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: Id + 'static,
{
pub fn new(hw_watchdog: WDT, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = NrfWatchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration);
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: Id,
{
pub fn new(hw_watchdog: WDT, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = NrfWatchdog::new(hw_watchdog);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration)
.ok();
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogTask<I>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> WatchdogTask<I> {
WatchdogTask { runner: self }
}
}
#[cfg(feature = "alloc")]
pub async fn watchdog_run<I>(task: WatchdogTask<I>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
#[cfg(not(feature = "alloc"))]
pub struct NoAllocWatchdogTask<I, const N: usize>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I, N>,
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> NoAllocWatchdogTask<I, N> {
NoAllocWatchdogTask { runner: self }
}
}
#[cfg(not(feature = "alloc"))]
pub async fn watchdog_run<I, const N: usize>(task: NoAllocWatchdogTask<I, N>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
}
#[cfg(feature = "esp32-embassy")]
pub mod embassy_esp32 {
use super::{
info, Clock, EmbassyClock, HardwareWatchdog, Id, ResetReason, Watchdog, WatchdogConfig,
};
use embassy_time::{Instant, Timer};
use esp_hal::peripherals::TIMG0;
use esp_hal::timer::timg::MwdtStage;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::timer::timg::Wdt;
pub struct Esp32Watchdog {
inner: Wdt<TIMG0>,
}
impl Esp32Watchdog {
#[must_use]
pub fn new(timg0: TimerGroup<TIMG0>) -> Self {
let wdt = timg0.wdt;
Self { inner: wdt }
}
}
impl HardwareWatchdog<EmbassyClock> for Esp32Watchdog {
fn start(&mut self, timeout: embassy_time::Duration) {
self.inner.set_timeout(
MwdtStage::Stage0,
esp_hal::time::Duration::from_millis(timeout.as_millis()),
);
self.inner.enable();
}
fn feed(&mut self) {
self.inner.feed();
}
fn trigger_reset(&mut self) -> ! {
esp_hal::system::software_reset();
}
fn reset_reason(&self) -> Option<ResetReason> {
None
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogRunner<I>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, Esp32Watchdog, EmbassyClock>>,
>,
}
#[cfg(not(feature = "alloc"))]
pub struct WatchdogRunner<I, const N: usize>
where
I: Id,
{
watchdog: embassy_sync::mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Watchdog<I, N, Esp32Watchdog, EmbassyClock>>,
>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: Id + 'static,
{
pub fn new(timg0: TimerGroup<TIMG0>, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = Esp32Watchdog::new(timg0);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration);
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: Id,
{
pub fn new(timg0: TimerGroup<TIMG0>, config: WatchdogConfig<EmbassyClock>) -> Self {
let hw_watchdog = Esp32Watchdog::new(timg0);
let watchdog = Watchdog::new(hw_watchdog, config, EmbassyClock);
Self {
watchdog: embassy_sync::mutex::Mutex::new(core::cell::RefCell::new(watchdog)),
}
}
pub async fn register_task(&self, id: &I, max_duration: <EmbassyClock as Clock>::Duration) {
self.watchdog
.lock()
.await
.borrow_mut()
.register_task(id, max_duration)
.ok();
}
pub async fn deregister_task(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().deregister_task(id);
}
pub async fn feed(&self, id: &I) {
self.watchdog.lock().await.borrow_mut().feed(id);
}
pub async fn start(&self) {
self.watchdog.lock().await.borrow_mut().start();
}
pub async fn trigger_reset(&self) -> ! {
self.watchdog.lock().await.borrow_mut().trigger_reset()
}
pub async fn reset_reason(&self) -> Option<ResetReason> {
self.watchdog.lock().await.borrow().reset_reason()
}
pub async fn get_check_interval(&self) -> <EmbassyClock as Clock>::Duration {
self.watchdog.lock().await.borrow().config.check_interval
}
pub async fn check_tasks(&self) -> bool {
self.watchdog.lock().await.borrow_mut().check()
}
}
#[cfg(feature = "alloc")]
pub struct WatchdogTask<I>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I>,
}
#[cfg(feature = "alloc")]
impl<I> WatchdogRunner<I>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> WatchdogTask<I> {
WatchdogTask { runner: self }
}
}
#[cfg(feature = "alloc")]
pub async fn watchdog_run<I>(task: WatchdogTask<I>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
#[cfg(not(feature = "alloc"))]
pub struct NoAllocWatchdogTask<I, const N: usize>
where
I: 'static + Id,
{
runner: &'static WatchdogRunner<I, N>,
}
#[cfg(not(feature = "alloc"))]
impl<I, const N: usize> WatchdogRunner<I, N>
where
I: 'static + Id,
{
pub fn create_task(&'static self) -> NoAllocWatchdogTask<I, N> {
NoAllocWatchdogTask { runner: self }
}
}
#[cfg(not(feature = "alloc"))]
pub async fn watchdog_run<I, const N: usize>(task: NoAllocWatchdogTask<I, N>) -> !
where
I: 'static + Id,
{
info!("Watchdog runner started");
task.runner.start().await;
let interval = task.runner.get_check_interval().await;
let mut check_time = Instant::now() + interval;
loop {
let _ = task.runner.check_tasks().await;
Timer::at(check_time).await;
check_time += interval;
}
}
}