#![no_std]
#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/unflappable/0.2.0")]
use core::cell::UnsafeCell;
use core::convert::Infallible;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ops::{AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Shl, Shr, SubAssign};
use embedded_hal::digital::v2::InputPin;
pub trait Debounce {
type Storage: From<u8>
+ BitAnd<Output = Self::Storage>
+ BitAndAssign
+ BitOr<Output = Self::Storage>
+ BitOrAssign
+ Not<Output = Self::Storage>
+ Shl<u8, Output = Self::Storage>
+ Shr<u8, Output = Self::Storage>
+ AddAssign
+ SubAssign
+ Eq
+ Copy;
const MAX_COUNT: Self::Storage;
const INIT_HIGH: bool;
}
trait DebounceExt: Debounce {
fn zero() -> Self::Storage;
fn state_mask() -> Self::Storage;
fn init_mask() -> Self::Storage;
fn integrator_mask() -> Self::Storage;
fn integrator_one() -> Self::Storage;
fn integrator_max() -> Self::Storage;
}
impl<D: Debounce> DebounceExt for D {
#[inline(always)]
fn zero() -> Self::Storage {
Self::Storage::from(0)
}
#[inline(always)]
fn state_mask() -> Self::Storage {
Self::Storage::from(1)
}
#[inline(always)]
fn init_mask() -> Self::Storage {
Self::Storage::from(1 << 1)
}
#[inline(always)]
fn integrator_mask() -> Self::Storage {
let mut mask = Self::integrator_one();
mask -= Self::Storage::from(1);
!mask
}
#[inline(always)]
fn integrator_one() -> Self::Storage {
Self::Storage::from(1 << 2)
}
#[inline(always)]
fn integrator_max() -> Self::Storage {
Self::MAX_COUNT << 2
}
}
pub mod default {
pub struct ActiveHigh;
impl super::Debounce for ActiveHigh {
type Storage = u8;
const MAX_COUNT: Self::Storage = 4;
const INIT_HIGH: bool = false;
}
pub struct ActiveLow;
impl super::Debounce for ActiveLow {
type Storage = u8;
const MAX_COUNT: Self::Storage = 4;
const INIT_HIGH: bool = true;
}
pub struct OriginalKuhn;
impl super::Debounce for OriginalKuhn {
type Storage = u8;
const MAX_COUNT: Self::Storage = 3;
const INIT_HIGH: bool = false;
}
}
#[derive(Debug)]
pub struct InitError;
#[derive(Debug)]
pub enum PollError<PinError> {
Init,
Pin(PinError),
}
pub enum DeinitError<'a, Cfg: Debounce> {
Init,
Pin(Debounced<'a, Cfg>),
}
impl<'a, Cfg: Debounce> core::fmt::Debug for DeinitError<'a, Cfg> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DeinitError::Init => f.write_str("Init"),
DeinitError::Pin(_) => f.write_str("Pin(_)"),
}
}
}
pub struct Debouncer<Pin, Cfg: Debounce> {
cfg: PhantomData<Cfg>,
pin: UnsafeCell<MaybeUninit<Pin>>,
storage: UnsafeCell<Cfg::Storage>,
}
unsafe impl<Pin, Cfg: Debounce> Sync for Debouncer<Pin, Cfg> {}
impl<Pin: InputPin, Cfg: Debounce> Debouncer<Pin, Cfg> {
#[inline]
pub unsafe fn init(&self, pin: Pin) -> Result<Debounced<Cfg>, InitError> {
assert!(
Cfg::MAX_COUNT != Cfg::zero(),
"Debounce::MAX_COUNT cannot be zero"
);
assert!(
(Cfg::MAX_COUNT << 2) >> 2 == Cfg::MAX_COUNT,
"Debounce::MAX_COUNT must be represented in two bits fewer than Debounce::Storage"
);
self.init_linted(pin)
}
#[inline(always)]
fn init_linted(&self, pin: Pin) -> Result<Debounced<Cfg>, InitError> {
if self.init_flag() {
return Err(InitError);
}
let pin_cell_ptr = self.pin.get();
let pin_cell = unsafe { &mut *pin_cell_ptr };
let pin_ptr = pin_cell.as_mut_ptr();
unsafe {
pin_ptr.write(pin);
}
let mut new_state = if Cfg::INIT_HIGH {
Cfg::state_mask() | Cfg::integrator_max()
} else {
Cfg::zero()
};
new_state |= Cfg::init_mask();
let state_ptr = self.storage.get();
unsafe {
*state_ptr = new_state;
}
Ok(Debounced {
cfg: PhantomData,
storage: &self.storage,
})
}
#[inline]
pub unsafe fn poll(&self) -> Result<(), PollError<Pin::Error>> {
self.poll_linted()
}
#[inline(always)]
fn poll_linted(&self) -> Result<(), PollError<Pin::Error>> {
if !self.init_flag() {
return Err(PollError::Init);
}
let pin_cell_ptr = self.pin.get();
let pin_cell = unsafe { &*pin_cell_ptr };
let pin_ptr = pin_cell.as_ptr();
let pin = unsafe { &*pin_ptr };
if pin.is_low().map_err(PollError::Pin)? {
self.decrement_integrator();
if self.integrator_is_zero() {
self.clear_state_flag();
}
} else {
self.increment_integrator();
if self.integrator_is_max() {
self.set_state_flag();
}
}
Ok(())
}
#[inline]
pub const fn uninit(zero: Cfg::Storage) -> Self {
Debouncer {
cfg: PhantomData,
pin: UnsafeCell::new(MaybeUninit::uninit()),
storage: UnsafeCell::new(zero),
}
}
#[inline]
pub unsafe fn deinit<'a>(&self, pin: Debounced<'a, Cfg>) -> Result<Pin, DeinitError<'a, Cfg>> {
self.deinit_linted(pin)
}
#[inline(always)]
fn deinit_linted<'a>(&self, pin: Debounced<'a, Cfg>) -> Result<Pin, DeinitError<'a, Cfg>> {
if !self.init_flag() {
return Err(DeinitError::Init);
}
if self.storage.get() != pin.storage.get() {
return Err(DeinitError::Pin(pin));
}
let state_ptr = self.storage.get();
unsafe {
*state_ptr = Cfg::zero();
}
let pin = {
let pin_cell_ptr = self.pin.get();
let pin_cell = unsafe { &*pin_cell_ptr };
let pin_ptr = pin_cell.as_ptr();
unsafe { pin_ptr.read() }
};
let pin_cell_ptr = self.pin.get();
unsafe {
*pin_cell_ptr = MaybeUninit::uninit();
}
Ok(pin)
}
#[inline]
fn init_flag(&self) -> bool {
let state_ptr = self.storage.get();
let state = unsafe { *state_ptr };
state & Cfg::init_mask() != Cfg::zero()
}
#[inline(always)]
fn set_state_flag(&self) {
let state_ptr = self.storage.get();
unsafe {
*state_ptr |= Cfg::state_mask();
}
}
#[inline(always)]
fn clear_state_flag(&self) {
let state_ptr = self.storage.get();
unsafe {
*state_ptr &= !Cfg::state_mask();
}
}
#[inline(always)]
fn integrator_is_zero(&self) -> bool {
let state_ptr = self.storage.get();
let state = unsafe { *state_ptr };
let integrator = state & Cfg::integrator_mask();
integrator == Cfg::zero()
}
#[inline(always)]
fn integrator_is_max(&self) -> bool {
let state_ptr = self.storage.get();
let state = unsafe { *state_ptr };
let integrator = state & Cfg::integrator_mask();
integrator == Cfg::integrator_max()
}
#[inline(always)]
fn decrement_integrator(&self) {
let state_ptr = self.storage.get();
if !self.integrator_is_zero() {
unsafe {
*state_ptr -= Cfg::integrator_one();
}
}
}
#[inline(always)]
fn increment_integrator(&self) {
let state_ptr = self.storage.get();
if !self.integrator_is_max() {
unsafe {
*state_ptr += Cfg::integrator_one();
}
}
}
}
#[macro_export]
macro_rules! debouncer_uninit {
() => {
$crate::Debouncer::uninit(0)
};
}
pub struct Debounced<'state, Cfg: Debounce> {
cfg: PhantomData<Cfg>,
storage: &'state UnsafeCell<Cfg::Storage>,
}
impl<'state, Cfg: Debounce> InputPin for Debounced<'state, Cfg> {
type Error = Infallible;
#[inline(always)]
fn is_high(&self) -> Result<bool, Self::Error> {
let state_ptr = self.storage.get();
let state = unsafe { *state_ptr };
let flag = state & Cfg::state_mask();
Ok(flag != Cfg::zero())
}
#[inline(always)]
fn is_low(&self) -> Result<bool, Self::Error> {
let state_ptr = self.storage.get();
let state = unsafe { *state_ptr };
let flag = state & Cfg::state_mask();
Ok(flag == Cfg::zero())
}
}
#[cfg(test)]
mod test {
use super::*;
use embedded_hal_mock::pin;
#[test]
fn simple() {
struct Cfg;
impl Debounce for Cfg {
type Storage = u8;
const MAX_COUNT: u8 = 3;
const INIT_HIGH: bool = false;
}
let expectations = [
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::Low),
pin::Transaction::get(pin::State::Low),
pin::Transaction::get(pin::State::Low),
];
let pin = pin::Mock::new(&expectations);
let debouncer: Debouncer<_, Cfg> = debouncer_uninit!();
let debounced = unsafe { debouncer.init(pin) }.expect("debounced pin");
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { debouncer.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
let mut pin = unsafe { debouncer.deinit(debounced) }.unwrap();
pin.done();
}
struct Cfg;
impl Debounce for Cfg {
type Storage = u8;
const MAX_COUNT: u8 = 3;
const INIT_HIGH: bool = false;
}
static SIMPLE_STATIC_TEST: Debouncer<pin::Mock, Cfg> = debouncer_uninit!();
#[test]
fn simple_static() {
let expectations = [
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::High),
pin::Transaction::get(pin::State::Low),
pin::Transaction::get(pin::State::Low),
pin::Transaction::get(pin::State::Low),
];
let pin = pin::Mock::new(&expectations);
let debounced = unsafe { SIMPLE_STATIC_TEST.init(pin) }.expect("debounced pin");
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(false, debounced.is_low().unwrap());
assert_eq!(true, debounced.is_high().unwrap());
unsafe { SIMPLE_STATIC_TEST.poll() }.unwrap();
assert_eq!(true, debounced.is_low().unwrap());
assert_eq!(false, debounced.is_high().unwrap());
let mut pin = unsafe { SIMPLE_STATIC_TEST.deinit(debounced) }.unwrap();
pin.done();
}
#[test]
fn zero_sized_pin_type() {
struct Pin;
impl InputPin for Pin {
type Error = core::convert::Infallible;
fn is_high(&self) -> Result<bool, Self::Error> {
Ok(true)
}
fn is_low(&self) -> Result<bool, Self::Error> {
Ok(false)
}
}
type MyDebouncer = Debouncer<Pin, default::ActiveLow>;
assert_eq!(1, core::mem::size_of::<MyDebouncer>());
}
}