1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
/* 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::alloc::boxed::Box;
//! # use avr_oxide::devices::UsesPin;
//! # use avr_oxide::devices::debouncer::Debouncer;
//! # use avr_oxide::devices::{ OxideButton, button::ButtonState };
//! # use avr_oxide::boards::board;
//! # use avr_oxide::StaticWrap;
//! #
//! # #[avr_oxide::main(chip="atmega4809")]
//! # pub fn main() {
//! let supervisor = avr_oxide::oxide::instance();
//!
//! let mut green_button = StaticWrap::new(OxideButton::using(Debouncer::with_pin(board::pin_a(2))));
//! #
//! # // Now enter the event loop
//! # supervisor.run();
//! # }
//! ```
// Imports ===================================================================
use core::any::Any;
use core::cell::Cell;
use avr_oxide::alloc::boxed::Box;
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::OwnOrBorrow;
// Declarations ==============================================================
pub struct Debouncer {
pin: OwnOrBorrow<'static,dyn Pin>,
last_event_state: Cell<Option<bool>>,
handler: Cell<PinIsrCallback>
}
// Code ======================================================================
impl Into<OwnOrBorrow<'static, dyn Pin + 'static>> for Debouncer {
fn into(self) -> OwnOrBorrow<'static, dyn Pin> {
OwnOrBorrow::Own(Box::new(self))
}
}
impl UsesPin for Debouncer {
/**
* Create a Debouncer which will wrap the given underlying Pin instance to
* provide a debounced version of it.
*/
fn using<OP: Into<OwnOrBorrow<'static, dyn Pin>>>(pin: OP) -> Self {
let pin : OwnOrBorrow<dyn Pin> = pin.into();
Debouncer {
pin,
last_event_state: Cell::new(Option::None),
handler: Cell::new(IsrCallback::Nop(()))
}
}
}
impl Pin for Debouncer {
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,
_ => avr_oxide::oserror::halt(avr_oxide::oserror::OsError::InternalError)
}
}
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.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(|isotoken,_source,id,_state,udata|{
let myself = unsafe { &*(panic_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *const Self) };
let old_state = panic_if_none!(myself.last_event_state.get(), avr_oxide::oserror::OsError::InternalError);
let new_state = myself.get();
if old_state != new_state {
myself.last_event_state.replace(Some(new_state));
isr_cb_invoke!(isotoken, myself.handler.get(), myself, id, new_state);
}
}, self as &dyn Any));
}
}
// Tests =====================================================================