avr-oxide 0.2.2

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::hal::atmega4809::hardware;
//! # use avr_oxide::arduino;
//! # 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 hardware = hardware::instance();
//! #   let arduino = arduino::nanoevery::Arduino::from(hardware);
//!
//!   let wall_clock = Handle::new(OxideWallClock::with_timer(arduino.rtc));
//!
//!   // 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_overflowed()`
//! 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::hal::atmega4809::hardware;
//! # use avr_oxide::arduino;
//! # 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 hardware = hardware::instance();
//! #   let arduino = arduino::nanoevery::Arduino::from(hardware);
//!
//!   let wall_clock = Handle::new(OxideWallClock::with_timer(arduino.rtc));
//!
//!   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();
//! #  }
//! ```


// 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::devices::masterclock::{DelayCallback, DelayEvents};
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::panic_if_none;
use avr_oxide::private::delayq::{DelayQueue, SimpleDelayQueue};
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,Box<dyn DelayCallback + '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::hal::concurrency::interrupt::isolated(||{
      self.running_time
    })
  }

  /// Return true iff the runtime counter has overflowed
  pub fn runtime_overflowed(&self) -> bool {
    avr_oxide::hal::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::hal::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, secs: u16) {
    avr_oxide::hal::concurrency::interrupt::isolated(||{
      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;
        }
      }
    })
  }
}

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
{
  fn after_delay(&self, delay: Duration, bf: Box<dyn DelayCallback + 'wc>) {
    self.delay_events.borrow_mut().insert_at(delay, bf);
  }
}

impl<T,S> EventSource for WallClock<'_,T,S>
where
  T: 'static + TimerControl + RtcTimerCalibration,
  S: EventSink
{
  #[optimize(speed)]
  fn listen(&'static self) {
    self.timer.start(TimerIsrCallback::WithData(|source, ticks, udata| {
      unsafe {
        let clock = &mut *(panic_if_none!(udata) as *mut WallClock<T,S>);
        clock.increment_running_time(ticks);
      }

      S::event(OxideEventEnvelope::to(unsafe { &*(panic_if_none!(udata) 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.
  #[optimize(speed)]
  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() {
          (*handler)(source)
        }

        // Call any tick event handler
        (*f)(source,time_passed)


      },
      _ => {}
    }
  }
}
// Tests =====================================================================