avr-oxide 0.4.1

An extremely simple Rusty operating system for AVR microcontrollers
/* masterclock.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! A simple master clock device designed to produce regular, frequent clock
//! tick events.
//!
//! # Usage
//! The frequency, in Hz, is passed as a constant parameter when the
//! device is constructed using the [`using()`] constructor methods.  Also
//! passed is the underlying AVR hardware timer device which it should use
//! to generate the clock.
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::boards;
//! # use avr_oxide::devices::{ OxideMasterClock };
//! # use avr_oxide::devices::masterclock::TickEvents;
//! # use avr_oxide::StaticWrap;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!   let master_clock = StaticWrap::new(OxideMasterClock::with_timer::<20>(avr_oxide::hardware::timer::tcb0::instance()));
//!
//!   // An event handler every time the master clock ticks
//!   master_clock.borrow().on_tick(Box::new(move |_timerid, _duration|{
//!     // Do something 20 times every second
//!   }));
//!
//!  supervisor.listen(master_clock.borrow());
//!  supervisor.run();
//! #  }
//! ```
//!
//! # Delay Events
//! The MasterClock device can be used to efficiently schedule events which
//! should be triggered in the future.  A closure can be passed to the
//! [`after_delay()`] method, which will be executed after the given duration
//! has elapsed.  Internally, WallClock uses a Delay Queue implementation,
//! meaning there is no limit to the number of such events which may be
//! scheduled (other than memory to allocate the queue elements.)
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::devices::OxideMasterClock;
//! # use avr_oxide::devices::masterclock::DelayEvents;
//! # use avr_oxide::time::Duration;
//! # use avr_oxide::StaticWrap;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!   let master_clock = StaticWrap::new(OxideMasterClock::with_timer::<20>(avr_oxide::hardware::timer::tcb0::instance()));
//!
//!   master_clock.borrow().after_delay(Duration::from_millis(100), Box::new(move |_timerid|{
//!     // Do something after 100ms
//!   }));
//!
//!  supervisor.listen(master_clock.borrow());
//!  supervisor.run();
//! #  }
//! ```
//!
//! # Blocking API
//! A blocking [`wait()`] API is also provided, which will block the calling
//! thread for the given duration.  Note that the clock must be running -
//! i.e. the [`supervisor.listen_handle()`] method was called already - *before*
//! you use the [`wait()`] method, or you can expect to block forever, and that
//! this method depends on the main supervisor to be running.  In other words,
//! this must be used in threads you have [`spawn()`]ed, not the main thread.
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::devices::OxideMasterClock;
//! # use avr_oxide::devices::masterclock::DelayEvents;
//! # use avr_oxide::time::Duration;
//! # use avr_oxide::StaticWrap;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!  let master_clock = StaticWrap::new(OxideMasterClock::with_timer::<20>(avr_oxide::hardware::timer::tcb0::instance()));
//!
//!  {
//!    let master_clock = master_clock.borrow();
//!    avr_oxide::thread::spawn(move||{
//!      master_clock.wait(Duration::from_millis(2500));
//!      0
//!    });
//!  }
//!
//!  supervisor.listen(master_clock.borrow());
//!  supervisor.run();
//! #  }
//! ```
//!
//! # Pre-Emptive Multithreading
//! A `MasterClock` instance is required to schedule thread context switches
//! pre-emptively.  By default, any MasterClock instance will trigger
//! context switches at its configured frequency.  This can be disabled
//! with the [`disable_preemption()`] method if not desired.
//!
//! # Features
//! For accurate frequency calculations, this depends on various constants
//! defined in the [`avr_oxide::deviceconsts::clock`] module.  These depend
//! on the correct AVRoxide clockspeed feature being enabled in your `Cargo.toml`.
//!
//! | CPU Feature | Clockspeed features (pick 1) |
//! | ----------- | ---------------------------- |
//! | `atmega4809` | `16MHz`, `20MHz` |
//! | `atmega328p` | `16MHz` |
//!
//! # Maximum Frequency
//! The maximum frequency for which you can create a MasterClock is
//! limited by the maximum frequency of the Oxide internal tick.  This is
//! determined by the CPU frequency and the power-saving feature you have
//! built AVRoxide with, as follows:
//!
//! | Frequency | Power-saving feature | Maximum MasterClock frequency |
//! | --------- | -------------------- | ----------------------------- |
//! | `16MHz`   | None | 1000 Hz |
//! |           | `power_med` | 500 Hz |
//! |           | `power_low` | 500 Hz |
//! | `20MHz`   | None        | 1000 Hz |
//! |           | `power_med` | 500 Hz |
//! |           | `power_low` | 500 Hz |
//!
//! [`wait()`]: MasterClock::wait
//! [`using()`]: MasterClock::using
//! [`disable_preemption()`]: MasterClock::disable_preemption
//! [`after_delay()`]: MasterClock::after_delay
//! [`supervisor.listen_handle()`]: avr_oxide::oxide::OxideSupervisor::listen_handle
//! [`spawn()`]: avr_oxide::concurrency::thread::spawn

// Imports ===================================================================
use avr_oxide::hal::generic::timer::{TimerControl, TimerIdentity, TimerIsrCallback};
use avr_oxide::hal::generic::timer::TimerMode::Periodic;
use avr_oxide::event::{EventSink, EventSource, OxideEvent, OxideEventEnvelope};
use avr_oxide::deviceconsts::clock::{ MASTER_CLOCK_PRESCALER, MASTER_CLOCK_HZ, MASTER_TICK_FREQ_HZ };
use core::marker::PhantomData;
use core::ops::DerefMut;
use core::cell::RefCell;
use avr_oxide::alloc::boxed::Box;
use avr_oxide::concurrency::interrupt;
use avr_oxide::{panic_if_none, thread};
use avr_oxide::private::delayq::{DelayQueue, SimpleDelayQueue};
use avr_oxide::sync::EventWait;
use avr_oxide::util::OwnOrBorrowMut;
use avr_oxide::time::Duration;

// Declarations ==============================================================
pub trait TickCallback = FnMut(TimerIdentity,Duration) -> ();

pub trait DelayCallback = FnMut(TimerIdentity) -> ();

pub struct MasterClock<'mc,T,S>
where
  T: 'static + TimerControl,
  S: EventSink
{
  freq_hz: u16,
  timer: OwnOrBorrowMut<'static,T>,
  phantom: PhantomData<S>,
  context_switch: bool,

  delay_events: RefCell<SimpleDelayQueue<Duration,DelayResponder<'mc>>>,
  on_tick: RefCell<Option<Box<dyn TickCallback + 'mc>>>
}


/**
 * Trait implemented by devices which can run code every time the clock ticks.
 */
pub trait TickEvents<'c> {
  /**
   * Call the given closure every time the clock ticks.
   */
  fn on_tick(&self, bf: Box<dyn TickCallback + 'c>);
}

/**
 * Trait implemented by devices which can run code after a delay has passed.
 */
pub trait DelayEvents<'c> {
  type DelayHandle;

  /**
   * Call the given callback after /at least/ `delay` time has passed.
   */
  fn after_delay(&self, delay: Duration, bf: Box<dyn DelayCallback + 'c>) -> Self::DelayHandle;

  /**
   * Cancel a given delay event, if possible.  Returns `true` if the event
   * was cancelled, or `false` if not (because it either never existed,
   * or it has already been executed.)
   */
  fn cancel_delay(&self, handle: Self::DelayHandle) -> bool;
}

pub(crate) enum DelayResponder<'mc> {
  WaitingThread(EventWait),
  HandlerCallback(Box<dyn DelayCallback + 'mc>)
}

// Code ======================================================================
impl<T, S> MasterClock<'_, T, S>
where
  T: 'static + TimerControl,
  S: EventSink
{
  /**
   * Create a MasterClock that uses the given AVR TimerControl device
   * to schedule clock events.  The desired frequency in Hz is provided
   * as a static parameter.  The timer will generate timer events to be
   * consumed by the supervisor, which you can handle with the `on_event`
   * method.
   */
  pub fn using<OT: Into<OwnOrBorrowMut<'static,T>>, const FREQ_HZ: u16>(timer: OT) -> Self {
    const CYCLES_PER_TICK: u16       = (MASTER_CLOCK_HZ as u32/(2u32 * MASTER_CLOCK_PRESCALER as u32 * MASTER_TICK_FREQ_HZ as u32)) as u16; // We use CLK_PER/2 in timer config, hence the 2* term

    if FREQ_HZ > MASTER_TICK_FREQ_HZ {
      avr_oxide::oserror::halt(avr_oxide::oserror::OsError::BadParams);
    }

    let mut timer : OwnOrBorrowMut<T> = timer.into();

    timer.set_mode(Periodic);
    timer.set_count_max(CYCLES_PER_TICK);
    timer.set_interrupt_period((MASTER_TICK_FREQ_HZ/FREQ_HZ) as u16);

    Self {
      timer,
      freq_hz: FREQ_HZ,
      phantom: PhantomData::default(),
      on_tick: RefCell::new(None),
      context_switch: true,
      delay_events: RefCell::new(SimpleDelayQueue::new())
    }
  }

  pub fn static_using<OT: Into<OwnOrBorrowMut<'static,T>>, const FREQ_HZ: u16>(timer: OT) -> &'static mut Self {
    Box::leak(Box::new(Self::using::<_,FREQ_HZ>(timer)))
  }

  pub fn with_timer<const FREQ_HZ: u16>(timer: &'static mut T) -> Self {
    Self::using::<_, FREQ_HZ>(timer)
  }

  pub fn static_with_timer<const FREQ_HZ: u16>(timer: &'static mut T) -> &'static mut Self {
    Box::leak(Box::new(Self::with_timer::<FREQ_HZ>(timer)))
  }

  /// Configure this MasterClock to drive the pre-emptive scheduling of
  /// threads (i.e. this clock will context switch each time it ticks.)
  pub fn enable_preemption(&mut self) {
    interrupt::isolated(|_isotoken|{
      self.context_switch = true;
    });
  }

  /// Disable the preemption of threads using this clock.
  pub fn disable_preemption(&mut self) {
    interrupt::isolated(|_isotoken|{
      self.context_switch = false;
    });
  }


  /// Block the calling thread for (at least) the given duration.
  ///
  /// # Important
  /// Note that the clock must be running -
  /// i.e. the [`supervisor.listen_handle()`] method was called already - *before*
  /// you use the `wait()` method, or you can expect to block forever, , and that
  /// this method depends on the main supervisor to be running.  In other words,
  /// this must be used in threads you have [`spawn()`]ed, not the main thread.
  ///
  /// [`supervisor.listen_handle()`]: avr_oxide::oxide::OxideSupervisor::listen_handle
  /// [`spawn()`]: avr_oxide::concurrency::thread::spawn
  pub fn wait(&self, delay: Duration){
    interrupt::isolated(|isotoken|{
      let mut waiter = EventWait::new();
      waiter.add_to_waitlist(isotoken);
      self.delay_events.borrow_mut().insert_at(delay.saturating_add(Duration::MILLISECOND),
                                               DelayResponder::WaitingThread(waiter));
    });
    thread::yield_now();
  }
}

impl<'mc, T, S> TickEvents<'mc> for MasterClock<'mc, T, S>
where
  T: 'static + TimerControl,
  S: EventSink
{
  fn on_tick(&self, bf: Box<dyn TickCallback + 'mc>) {
    self.on_tick.replace(Some(bf));
  }
}

impl<'mc,T,S> DelayEvents<'mc> for MasterClock<'mc,T,S>
where
  T: 'static + TimerControl,
  S: EventSink
{
  type DelayHandle = avr_oxide::private::delayq::DelayQueueHandle;

  fn after_delay(&self, delay: Duration, bf: Box<dyn DelayCallback + 'mc>) -> Self::DelayHandle {
    self.delay_events.borrow_mut().insert_at(delay.saturating_add(Duration::MILLISECOND), DelayResponder::HandlerCallback(bf))
  }

  fn cancel_delay(&self, handle: Self::DelayHandle) -> bool {
    self.delay_events.borrow_mut().remove(handle)
  }
}

impl<T,S> EventSource for MasterClock<'_,T,S>
where
  T: 'static + TimerControl,
  S: EventSink
{
  fn listen(&'static self) {

    self.timer.start(TimerIsrCallback::WithData(|isotoken, source, _ticks, udata| {
      // This is the callback on each tick (runs within interrupt context)

      // First we inject an inevent into the event queue
      S::event(isotoken, OxideEventEnvelope::to(unsafe { &*(panic_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *const MasterClock<T,S> as *const dyn EventSource) },
                                      OxideEvent::ClockTick(source, 1)));

      // Then if requested, we can also trigger a context switch
      unsafe {
        let myself = &*(panic_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *const MasterClock<T,S>);
        if myself.context_switch {
          interrupt::preemptive_reschedule(isotoken);
        }
      }

      true
    }, self as *const dyn core::any::Any ));
  }

  fn process_event(&self, evt: OxideEvent) {
    match (self.on_tick.borrow_mut().deref_mut(), evt) {
      (Some(f), OxideEvent::ClockTick(source, ticks)) => {
        let time_passed = Duration::from_millis((1000u32 / self.freq_hz as u32) * ticks as u32);

        // See if any delay-queue event handlers are released
        self.delay_events.borrow_mut().decrement(time_passed);
        while let Some(mut handler) = self.delay_events.borrow_mut().consume_next_ready() {
          match &mut handler {
            DelayResponder::WaitingThread(eventwait) => {
              eventwait.release_all()
            },
            DelayResponder::HandlerCallback(handler) => {
              (*handler)(source)
            }
          }
        }

        (*f)(source,time_passed);
      },
      _ => {}
    }
  }
}

unsafe impl<T,S> Send for MasterClock<'_,T,S>
  where
    T: 'static + TimerControl,
    S: EventSink
{}

unsafe impl<T,S> Sync for MasterClock<'_,T,S>
  where
    T: 'static + TimerControl,
    S: EventSink
{}

// Tests =====================================================================