avr-oxide 0.2.1

An extremely simple Rusty operating system for AVR microcontrollers
/* debouncer.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! A wrapper around a standard Pin which adds simple software debouncing.
//!
//! # Usage
//! Anywhere you would use a Pin, you can use a Debouncer instead.  Create
//! the debouncer using one of the methods provided by the [`avr_oxide::devices::UsesPin`] trait,
//! passing the pin you wish to wrap to the constructor.
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! #
//! # use avr_oxide::hal::atmega4809::hardware;
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::devices::UsesPin;
//! # use avr_oxide::devices::debouncer::Debouncer;
//! # use avr_oxide::devices::{ Handle, OxideButton, button::ButtonState };
//! # use avr_oxide::arduino;
//! #
//! # #[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 mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(arduino.a2)));
//! #
//! #   // Now enter the event loop
//! #   supervisor.run();
//! # }
//! ```


// Imports ===================================================================
use core::any::Any;
use core::cell::Cell;
use avr_oxide::hal::generic::callback::IsrCallback;
use avr_oxide::hal::generic::port::{InterruptMode, Pin, PinIsrCallback, PinMode};
use avr_oxide::{isr_cb_invoke, panic_if_none};
use avr_oxide::devices::UsesPin;
use avr_oxide::util::OwnOrBorrowMut;
use avr_oxide::devices::internal::StaticShareable;

// Declarations ==============================================================
pub struct Debouncer<P>
where
  P: 'static + Pin
{
  pin: OwnOrBorrowMut<'static,P>,
  last_event_state: Cell<Option<bool>>,
  handler: Cell<PinIsrCallback<Self>>
}

impl<P> StaticShareable for Debouncer<P>
where
  P: 'static + Pin
{}

// Code ======================================================================
impl<P> UsesPin<P> for Debouncer<P>
where
  P: 'static + Pin
{
  /**
   * Create a Debouncer which will wrap the given underlying Pin instance to
   * provide a debounced version of it.
   */
  fn using<OP: Into<OwnOrBorrowMut<'static, P>>>(pin: OP) -> Self {
    let pin : OwnOrBorrowMut<P> = pin.into();

    Debouncer {
      pin,
      last_event_state: Cell::new(Option::None),
      handler: Cell::new(IsrCallback::Nop(()))
    }
  }
}

impl<P> Pin for Debouncer<P>
where
  P: 'static + Pin
{
  fn set_mode(&self, mode: PinMode) {
    self.pin.set_mode(mode)
  }

  fn toggle(&self) {
    self.pin.toggle()
  }

  fn set_high(&self) {
    self.pin.set_high()
  }

  fn set_low(&self) {
    self.pin.set_low()
  }

  fn set(&self, high: bool) {
    self.pin.set(high)
  }

  /**
   * Gets the pin state.  Reads multiple samples and only returns once the
   * pin has reached a steady state.
   */
  fn get(&self) -> bool {
    let mut state : u8 = 0b10101010;

    while (state != 0x00) && (state != 0xff) {
      state <<= 1;

      state |= match self.pin.get() { false => 0, true => 1};
    }

    match state {
      0xff => true,
      0x00 => false,
      _ => panic!()
    }
  }

  fn set_interrupt_mode(&self, mode: InterruptMode) {
    self.pin.set_interrupt_mode(mode)
  }

  /**
   * Listen for interrupts, calling the given callback once we receive one.
   * We will filter out unwanted ('bounce') interrupts by waiting for the
   * underlying pin to become stable and then determining whether or not
   * the callback should be called.
   */
  fn listen(&'static self, handler: PinIsrCallback<Self>) {
    self.handler.replace(handler);

    // If we've not been called before, there is no prior state to compare to,
    // so we initialise.
    if self.last_event_state.get().is_none() {
      self.last_event_state.replace(Some(self.get()));
    }
    // OK, set up the interrupt handler.  Our callback will be called when
    // the underlying pin generates an event.  We will then wait for the
    // line to settle down, and then decide to call OUR listener only if
    // it's actually changed state stably.
    self.pin.listen(IsrCallback::WithData(|_source,id,_state,udata|{
      let myself = unsafe { &*(panic_if_none!(udata) as *const Self) };

      let old_state = panic_if_none!(myself.last_event_state.get());
      let new_state = myself.get();

      if old_state != new_state {

        myself.last_event_state.replace(Some(new_state));

        isr_cb_invoke!(myself.handler.get(), myself, id, new_state);
      }
    }, self as &dyn Any));
  }
}


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