#![cfg_attr(not(test), no_std)]
#![deny(unsafe_code, missing_docs)]
use doc_comment::doc_comment;
#[repr(transparent)]
pub struct Debouncer<S, M> {
state: S,
mask: core::marker::PhantomData<M>,
}
pub struct DebouncerStateful<S, M> {
debouncer: Debouncer<S, M>,
last_edge: Edge,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Edge {
Rising,
Falling,
}
macro_rules! impl_logic {
($T:ty, $count:expr, $M:ident, $name:ident, $name_stateful:ident, $mask:expr) => {
doc_comment! {
concat!(
"Detect ",
$count,
" consecutive logical-high states.\n\n",
"This type should not be used directly. ",
"Instead, construct a [`Debouncer`](struct.Debouncer.html) through [`debounce_",
$count,
"()`](fn.debounce_",
$count,
".html).",
),
pub struct $M;
}
doc_comment! {
concat!(
"Create a new debouncer that can detect a rising or falling edge of ",
$count,
" consecutive logical states.",
),
pub fn $name(initial_state_pressed: bool) -> Debouncer<$T, $M> {
Debouncer {
state: if initial_state_pressed { $mask } else { 0 },
mask: core::marker::PhantomData,
}
}
}
doc_comment! {
concat!(
"Create a new stateful debouncer that can detect stable state changes after ",
$count,
" consecutive logical states.",
),
pub fn $name_stateful(initial_state_pressed: bool) -> DebouncerStateful<$T, $M> {
DebouncerStateful {
debouncer: $name(initial_state_pressed),
last_edge: if initial_state_pressed {Edge::Rising} else {Edge::Falling},
}
}
}
impl Debouncer<$T, $M> {
pub fn update(&mut self, pressed: bool) -> Option<Edge> {
if self.state == $mask && pressed {
return None;
}
if self.state == 0 && !pressed {
return None;
}
self.state = ((self.state << 1) | (pressed as $T)) & $mask;
if self.state == $mask {
Some(Edge::Rising)
} else if self.state == 0 {
Some(Edge::Falling)
} else {
None
}
}
pub fn is_high(&self) -> bool {
self.state == $mask
}
pub fn is_low(&self) -> bool {
self.state == 0
}
}
impl DebouncerStateful<$T, $M> {
pub fn update(&mut self, pressed: bool) -> Option<Edge> {
self.debouncer.update(pressed).and_then(|edge| {
if edge != self.last_edge {
self.last_edge = edge;
Some(edge)
} else {
None
}
})
}
pub fn is_high(&self) -> bool {
self.debouncer.is_high()
}
pub fn is_low(&self) -> bool {
self.debouncer.is_low()
}
}
};
}
impl_logic!(u8, 2, Repeat2, debounce_2, debounce_stateful_2, 0b0000_0011);
impl_logic!(u8, 3, Repeat3, debounce_3, debounce_stateful_3, 0b0000_0111);
impl_logic!(u8, 4, Repeat4, debounce_4, debounce_stateful_4, 0b0000_1111);
impl_logic!(u8, 5, Repeat5, debounce_5, debounce_stateful_5, 0b0001_1111);
impl_logic!(u8, 6, Repeat6, debounce_6, debounce_stateful_6, 0b0011_1111);
impl_logic!(u8, 7, Repeat7, debounce_7, debounce_stateful_7, 0b0111_1111);
impl_logic!(u8, 8, Repeat8, debounce_8, debounce_stateful_8, 0b1111_1111);
impl_logic!(u16, 9, Repeat9, debounce_9, debounce_stateful_9, 0b0000_0001_1111_1111);
impl_logic!(u16, 10, Repeat10, debounce_10, debounce_stateful_10, 0b0000_0011_1111_1111);
impl_logic!(u16, 11, Repeat11, debounce_11, debounce_stateful_11, 0b0000_0111_1111_1111);
impl_logic!(u16, 12, Repeat12, debounce_12, debounce_stateful_12, 0b0000_1111_1111_1111);
impl_logic!(u16, 13, Repeat13, debounce_13, debounce_stateful_13, 0b0001_1111_1111_1111);
impl_logic!(u16, 14, Repeat14, debounce_14, debounce_stateful_14, 0b0011_1111_1111_1111);
impl_logic!(u16, 15, Repeat15, debounce_15, debounce_stateful_15, 0b0111_1111_1111_1111);
impl_logic!(u16, 16, Repeat16, debounce_16, debounce_stateful_16, 0b1111_1111_1111_1111);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rising_edge() {
let mut debouncer: Debouncer<u8, Repeat3> = debounce_3(false);
assert!(debouncer.is_low());
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
}
#[test]
fn test_falling_edge() {
let mut debouncer: Debouncer<u8, Repeat3> = debounce_3(false);
assert!(debouncer.is_low());
assert_eq!(debouncer.update(false), None);
assert!(debouncer.is_low());
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
assert_eq!(debouncer.update(false), None);
assert!(debouncer.is_low());
}
#[test]
fn test_debounce_16() {
let mut debouncer: Debouncer<u16, Repeat16> = debounce_16(false);
assert!(debouncer.is_low());
for _ in 0..15 {
assert_eq!(debouncer.update(true), None);
assert!(!debouncer.is_high());
}
assert_eq!(debouncer.update(true), Some(Edge::Rising));
assert!(debouncer.is_high());
assert_eq!(debouncer.update(true), None);
assert!(debouncer.is_high());
}
#[test]
fn test_is_low_high() {
let mut debouncer: Debouncer<u8, Repeat8> = debounce_8(false);
assert!(debouncer.is_low());
assert!(!debouncer.is_high());
debouncer.update(false);
assert!(debouncer.is_low());
assert!(!debouncer.is_high());
for _ in 0..7 {
assert!(debouncer.update(true).is_none());
assert!(!debouncer.is_low());
assert!(!debouncer.is_high());
}
assert_eq!(debouncer.update(true), Some(Edge::Rising));
assert!(!debouncer.is_low());
assert!(debouncer.is_high());
assert!(debouncer.update(true).is_none());
assert!(!debouncer.is_low());
assert!(debouncer.is_high());
}
#[test]
fn test_ram_consumption() {
assert_eq!(std::mem::size_of_val(&debounce_2(false)), 1);
assert_eq!(std::mem::size_of_val(&debounce_8(false)), 1);
assert_eq!(std::mem::size_of_val(&debounce_9(false)), 2);
assert_eq!(std::mem::size_of_val(&debounce_16(false)), 2);
assert_eq!(std::mem::size_of_val(&debounce_stateful_8(false)), 2);
assert_eq!(std::mem::size_of_val(&debounce_stateful_9(false)), 4);
}
#[test]
fn test_initial_state() {
let mut debouncer = debounce_2(false);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
let mut debouncer = debounce_2(false);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
let mut debouncer = debounce_2(true);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
let mut debouncer = debounce_2(true);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
let mut debouncer = debounce_stateful_2(false);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
let mut debouncer = debounce_stateful_2(false);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
let mut debouncer = debounce_stateful_2(true);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), Some(Edge::Rising));
let mut debouncer = debounce_stateful_2(true);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(true), None);
assert_eq!(debouncer.update(false), None);
assert_eq!(debouncer.update(false), Some(Edge::Falling));
}
}