avr-oxide 0.3.0

An extremely simple Rusty operating system for AVR microcontrollers
/* wallclock.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! A simple RTC device designed to keep wall-clock time.  It generates
//! events at a fixed frequency of 1Hz, using the reliable RTC clock
//! generator rather than the MasterClock device's generic Timers.
//!
//! # Usage
//! Create the WallClock device using the [`with_timer()`] method, ensuring you
//! pass in an AVR RTC device, rather than a generic timer.
//!
//! The frequency of events is hard-coded at 1Hz (i.e. one per second) -
//! the WallClock is intended to maintain clock time, not for precise or
//! high-frequency measurements.
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::boards;
//! # use avr_oxide::devices::{ OxideWallClock, Handle};
//! # use avr_oxide::devices::masterclock::TickEvents;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!   let wall_clock = Handle::new(OxideWallClock::with_timer(avr_oxide::hardware::timer::rtc::instance()));
//!
//!   // An event handler every time the master clock ticks
//!   wall_clock.on_tick(Box::new(move |_timerid, _duration|{
//!     // Do something once a second
//!   }));
//!
//!  supervisor.listen_handle(wall_clock);
//!  supervisor.run();
//! #  }
//! ```
//!
//! In addition to generating tick events, the WallClock also maintains a
//! counter of the time elapsed since the clock was first started.  You can
//! access via the [`runtime()`] method.
//!
//! ## Counter Overflow
//! The [`avr_oxide::time::Duration`] type stores duration in seconds as a u32,
//! thus the maximum possible duration is (2^32)-1 seconds, or just over
//! 136 years.
//!
//! Thus while it is *quite* unlikely that an AVRoxide application will be
//! running for over a century it is not entirely impossible - the internal
//! counter may thus overflow in 136 years' time.  In the event that this
//! happens, the counter will wrap around to 1, and a flag will be set
//! indicating that the counter has overflowed ([`runtime_overflowed()`] method
//! to access.)  The application developer may thus detect this occurence
//! and choose to handle it in application specific ways.
//!
//! The `runtime_overflowed` flag may be cleared using the [`clear_runtime_overflow()`]
//! method, in which case it will be set again in another 136 years.
//!
//! # Delay Events
//! The WallClock 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::boards;
//! # use avr_oxide::devices::{ OxideWallClock, Handle};
//! # use avr_oxide::devices::masterclock::DelayEvents;
//! # use avr_oxide::time::Duration;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!   let wall_clock = Handle::new(OxideWallClock::with_timer(avr_oxide::hardware::timer::rtc::instance()));
//!
//!   wall_clock.after_delay(Duration::from_secs(60), Box::new(move |_timerid|{
//!     // Do something after a minute
//!   }));
//!   wall_clock.after_delay(Duration::from_secs(3600), Box::new(move |_timerid|{
//!     // Do something in an hour
//!   }));
//!
//!  supervisor.listen_handle(wall_clock);
//!  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::{ OxideWallClock, Handle};
//! # use avr_oxide::devices::masterclock::DelayEvents;
//! # use avr_oxide::time::Duration;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() -> ! {
//! #   let supervisor = avr_oxide::oxide::instance();
//!
//!  let master_clock = Handle::new(OxideWallClock::with_timer(avr_oxide::hardware::timer::rtc::instance()));
//!  supervisor.listen_handle(master_clock);
//!
//!  avr_oxide::thread::spawn(move||{
//!    master_clock.wait(Duration::from_millis(3000));
//!    0
//!  });
//!
//!  supervisor.run();
//! #  }
//! ```

//!
//! [`wait()`]: WallClock::wait
//! [`with_timer()`]: WallClock::with_timer
//! [`after_delay()`]: WallClock::after_delay
//! [`runtime()`]: WallClock::runtime
//! [`runtime_overflowed()`]: WallClock::runtime_overflowed
//! [`clear_runtime_overflow()`]: WallClock::clear_runtime_overflow
//! [`supervisor.listen_handle()`]: avr_oxide::oxide::OxideSupervisor::listen_handle
//! [`spawn()`]: avr_oxide::concurrency::thread::spawn



// Imports ===================================================================
use core::marker::PhantomData;
use core::cell::RefCell;
use core::ops::DerefMut;
use avr_oxide::devices::internal::StaticShareable;
use avr_oxide::alloc::boxed::Box;
use avr_oxide::concurrency::interrupt;
use avr_oxide::devices::masterclock::{DelayCallback, DelayEvents, DelayResponder};
use avr_oxide::event::{EventSink, EventSource, OxideEvent, OxideEventEnvelope};
use avr_oxide::hal::generic::timer::{RtcCalibration, RtcPrescaler, RtcSource, RtcTimerCalibration, TimerControl, TimerIsrCallback};
use avr_oxide::hal::generic::timer::TimerMode::Periodic;
use avr_oxide::{halt_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;

use super::masterclock::TickCallback;
use super::masterclock::TickEvents;

// Declarations ==============================================================
pub struct WallClock<'wc,T,S>
where
  T: 'static + TimerControl + RtcTimerCalibration,
  S: EventSink
{
  timer: OwnOrBorrowMut<'static,T>,
  phantom: PhantomData<S>,

  running_time: Duration,
  running_time_overflow: bool,

  delay_events: RefCell<SimpleDelayQueue<Duration,DelayResponder<'wc>>>,

  on_tick: RefCell<Option<Box<dyn TickCallback + 'wc>>>
}

impl<'wc,T,S> StaticShareable for WallClock<'wc,T,S>
  where
    T: 'static + TimerControl + RtcTimerCalibration,
    S: EventSink
{}


// Code ======================================================================
impl<T,S> WallClock<'_,T,S>
where
  T: 'static + TimerControl + RtcTimerCalibration,
  S: EventSink
{
  /**
   * Create an instance that will use the given RTC timer device for timing.
   */
  pub fn using<OT: Into<OwnOrBorrowMut<'static,T>>>(timer: OT) -> Self {
    let mut timer : OwnOrBorrowMut<T> = timer.into();

    // Set up clock to use:
    //   1.024 KHz internal clock
    //   No calibration offset
    //   Prescaler value of 4
    timer.set_clock_calibration(RtcSource::Int1k,
                                RtcCalibration::Fast(0),
                                RtcPrescaler::Div4);

    timer.set_mode(Periodic);
    timer.set_interrupt_period(1024);

    // Note that the prescaler value has no effect on PIT interrupts,
    // so we need a period of 1024 on periodic timer interrupts to generate
    // one interrupt per second.
    Self {
      timer,
      running_time: Duration::ZERO,
      running_time_overflow: false,
      phantom: PhantomData::default(),
      on_tick: RefCell::new(None),
      delay_events: RefCell::new(SimpleDelayQueue::new())
    }
  }

  /**
   * Return a static reference to an instance that will use the given RTC
   * device for timing.
   */
  pub fn static_using<OT: Into<OwnOrBorrowMut<'static,T>>>(timer: OT) -> &'static mut Self {
    let alloc = Box::new(Self::using(timer));
    Box::leak(alloc)
  }

  pub fn with_timer(timer: &'static mut T) -> Self {
    Self::using(timer)
  }

  pub fn static_with_timer(timer: &'static mut T) -> &'static mut Self {
    Self::static_using(timer)
  }

  /// Return the total duration for which the clock has been running
  pub fn runtime(&self) -> Duration {
    avr_oxide::concurrency::interrupt::isolated(|_|{
      self.running_time
    })
  }

  /// Return true iff the runtime counter has overflowed
  pub fn runtime_overflowed(&self) -> bool {
    avr_oxide::concurrency::interrupt::isolated(|_|{
      self.running_time_overflow
    })
  }

  /// Clear the flag indicating that the runtime counter overflowed (until
  /// the next time...)
  pub fn clear_runtime_overflow(&mut self) {
    avr_oxide::concurrency::interrupt::isolated(|_|{
      self.running_time_overflow = false
    })
  }

  /// Increment the running time by the given number of seconds; if the
  /// counter overflows, we will set a flag so the recipient can know that
  /// but then we just keep on trucking.
  fn increment_running_time(&mut self, _isotoken: avr_oxide::concurrency::Isolated, secs: u16) {
    match self.running_time.checked_add(Duration::from_secs(secs as u32)) {
      None => { // The addition would overflow
        self.running_time_overflow = true;
        self.running_time = Duration::from_secs(secs as u32);
      },
      Some(new_running_time) => {
        self.running_time = new_running_time;
      }
    }
  }


  /// 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<'wc, T, S> TickEvents<'wc> for WallClock<'wc, T, S>
where
  T: 'static + TimerControl + RtcTimerCalibration,
  S: EventSink
{
  fn on_tick(&self, bf: Box<dyn TickCallback + 'wc>) {
    self.on_tick.replace(Some(bf));
  }
}

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

  fn after_delay(&self, delay: Duration, bf: Box<dyn DelayCallback + 'wc>) -> 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 WallClock<'_,T,S>
where
  T: 'static + TimerControl + RtcTimerCalibration,
  S: EventSink
{
  fn listen(&'static self) {
    self.timer.start(TimerIsrCallback::WithData(|isotoken, source, ticks, udata| {
      unsafe {
        let clock = &mut *(halt_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *mut WallClock<T,S>);
        clock.increment_running_time(isotoken, ticks);
      }

      S::event(isotoken, OxideEventEnvelope::to(unsafe { &*(halt_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *const WallClock<T,S> as *const dyn EventSource) },
                                                OxideEvent::ClockTick(source,ticks)));
      true
    }, self as *const dyn core::any::Any ));
  }

  /// Process the clock events (in userland).  This means calling any explicit
  /// on-event handlers, but we also do the processing of any delay queue
  /// events entirely in userland.
  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_secs(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)
            }
          }
        }

        // Call any tick event handler
        (*f)(source,time_passed)
      },
      _ => {}
    }
  }
}

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

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

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