Crate task_watchdog

Source
Expand description

§task-watchdog

A robust, flexible watchdog management library for embedded systems that multiplexes multiple task watchdogs into a single hardware watchdog timer, preventing system lockups when tasks fail to respond

This crate provides a task registration pattern that monitors multiple tasks and ensures they are all still active, feeding the hardware watchdog only if all tasks are healthy.

Tasks can be dynamically registered and deregistered when the system is running, to allow tasks that are created after startup to be monitoring, and to prevent tasks that are expected to block/pause from causing the device to restart.

Multiplexed Task Diagram

§Key Features

  • Hardware Agnostic API: Implements a consistent interface across different embedded microcontrollers with extensible trait system for hardware watchdog and clock types
  • Task Multiplexing: Consolidates multiple independent task watchdogs into a single hardware watchdog, triggering if any task fails to check in
  • Dynamic Task Management: Tasks can be registered and deregistered at runtime, allowing for flexible monitoring configurations
  • Async and Sync Support: Works with both synchronous (via device HALs) and asynchronous (Embassy) execution environments
  • No-Alloc Mode: Functions in both alloc and no_alloc modes for environments with or without heap availability
  • Configurable Timeouts: Individual timeout durations for each registered task
  • no_std Compatible: Designed for resource-constrained embedded environments without an operating system

§Usage

The following is a complete, minimal, example for using the task-watchdog crate using embassy-rs on an RP2040 or RP2350 (Pico or Pico 2). It uses static allocation (no alloc), and creates two tasks with different timeouts, both of which are policed by task-watchdog, and in turn, the hardware watchdog.

#![no_std]
#![no_main]

use task_watchdog::{WatchdogConfig, Id};
use task_watchdog::embassy_rp::{WatchdogRunner, watchdog_run};
use embassy_time::{Duration, Timer};
use embassy_rp::config::Config;
use embassy_executor::Spawner;
use static_cell::StaticCell;
use panic_probe as _;

// Create a static to hold the task-watchdog object, so it has static
// lifetime and can be shared with tasks.
static WATCHDOG: StaticCell<WatchdogRunner<TaskId, NUM_TASKS>> = StaticCell::new();

// Create an object to contain our task IDs.  It must implement the Id
// trait, which, for simply TaskId types means deriving the following
// traits:
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum TaskId {
    Main,
    Second,
}
impl Id for TaskId {}  // Nothing else to implement as we derived the required traits
const NUM_TASKS: usize = 2;

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // Initialize the hardare peripherals
    let p = embassy_rp::init(Config::default());

    // Set up watchdog configuration, with a 5s hardware watchdog timeout, and
    // with the task watchdog checking tasks every second.
    let config = WatchdogConfig {
        hardware_timeout: Duration::from_millis(5000),
        check_interval: Duration::from_millis(1000),
    };

    // Create the watchdog runner and store it in the static cell
    let watchdog = WatchdogRunner::new(p.WATCHDOG, config);
    let watchdog = WATCHDOG.init(watchdog);

    // Register our tasks with the task-watchdog.  Each can have a different timeout.
    watchdog.register_task(&TaskId::Main, Duration::from_millis(2000)).await;
    watchdog.register_task(&TaskId::Second, Duration::from_millis(4000)).await  ;

    // Spawn tasks that will feed the watchdog
    spawner.must_spawn(main_task(watchdog));
    spawner.must_spawn(second_task(watchdog));

    // Finally spawn the watchdog - this will start the hardware watchdog, and feed it
    // for as long as _all_ tasks are healthy.
    spawner.must_spawn(watchdog_task(watchdog));
}

// Provide a simple embassy task for the watchdog
#[embassy_executor::task]
async fn watchdog_task(watchdog: &'static WatchdogRunner<TaskId, NUM_TASKS>) -> ! {
    watchdog_run(watchdog.create_task()).await
}

// Implement your main task
#[embassy_executor::task]
async fn main_task(watchdog: &'static WatchdogRunner<TaskId, NUM_TASKS>) -> !{
   loop {
        // Feed the watchdog
        watchdog.feed(&TaskId::Main).await;

        // Do some work
        Timer::after(Duration::from_millis(1000)).await;
   }
}

// Implement your second task
#[embassy_executor::task]
async fn second_task(watchdog: &'static WatchdogRunner<TaskId, NUM_TASKS>) -> !{
   loop {
        // Feed the watchdog
        watchdog.feed(&TaskId::Second).await;

        // Do some work
        Timer::after(Duration::from_millis(2000)).await;
   }
}

See the README and the examples for more usage examples.

§Targets

For embedded devices you need to install and specify your target when building. Use:

  • RP2040 - thumbv6m-none-eabi
  • RP2350 - thumbv8m.main-none-eabihf
  • STM32 - thumbv7m-none-eabi
  • nRF - thumbv7em-none-eabihf

§Feature Flags

The following feature flags are supported

§Embassy support:

  • rp2040-embassy: Enable the RP2040-specific embassy implementation
  • rp2350-embassy: Enable the RP2350-specific embassy implementation
  • stm32: Enable the STM32-specific embassy implementation
  • nrf: Enable the nRF-specific embassy implementation
  • defmt-embassy-rp: Enable logging with defmt for the RP2040 and RP2350 embassy implementation
  • defmt-embassy-stm32: Enable logging with defmt for the STM32 embassy implementation
  • defmt-embassy-nrf: Enable logging with defmt for the nRF embassy implementation

§HAL/sync support:

  • rp2040-hal: Enable the RP2040 HAL implementation
  • rp2350-hal: Enable the RP2350 HAL implementation
  • defmt: Enable logging with defmt, for use with the HAL implementations

§Other

  • alloc: Enable features that require heap allocation but simplifies usage

§Example Feature/Target combination

This builds the library for RP2040 with embassy and defmt support:

cargo build --features rp2040-embassy,defmt-embassy-rp --target thumbv6m-none-eabi

§Embassy Objects

If you want to use an include, off the shelf implementation that works with Embassy the objects, you need to use are:

  • WatchdogConfig - Used to configure the task-watchdog.
  • embassy_rp::WatchdogRunner - Create with the hardware watchdog peripheral and WatchdogConfig, and then use to operate the task-watchdog, including task management. There is also an embassy_stm32::WatchdogRunner for STM32, and embassy_nrf::WatchdogRunner for nRF.
  • Id - Trait for task identifiers. If you use an enum, derive the Clone, Copy, PartialEq, Eq and Debug/Format traits, and then implement Id for the enum. The Id implementation can be empty, if you derive the required implementations. You must also derive orimplement PartialOrd and Ord if you use the alloc feature.
  • embassy_rp::watchdog_run() - Create and spawn a simple embassy task that just calls this function. This task will handle policing your other tasks and feeding the hardware watchdog. There is also an embassy_stm32::watchdog_run() for STM32 and embassy_nrf::watchdog_run() for nRF.

Modules§

embassy_rp
An async implementation of task-watchdog for use with the RP2040 and RP2350 embassy implementations. There are also stm32 and nRF equivalents of this module.
rp_hal
An syncronous implementation of task-watchdog for use with the RP2040 and RP2350 HALs.

Structs§

CoreClock
A system clock implementation using core time types, which allows task-watchdog to work with different clock implementations.
EmbassyClock
A system clock implementation for Embassy.
Task
Represents a task monitored by the watchdog.
Watchdog
A version of the Watchdog that doesn’t require heap allocation. This uses a fixed-size array for task storage.
WatchdogConfig
Configuration for the watchdog.

Enums§

Error
Errors that can occur when interacting with the watchdog.
ResetReason
Represents the reason for a system reset.

Traits§

Clock
A trait for time-keeping implementations.
HardwareWatchdog
Represents a hardware-level watchdog that can be fed and reset the system.
Id
Trait for task identifiers.