debounced_button/
lib.rs

1#![cfg_attr(not(test), no_std)]
2
3use embedded_hal::digital::v2::InputPin;
4
5#[derive(Copy, Clone)]
6pub enum ButtonState {
7    /// Triggered immediately when a button was pressed down
8    Down,
9    /// Triggered after a button was pressed down and came up again
10    Press,
11    /// Triggered when button is pressed down and held down for a defined time
12    Pressing,
13    /// Triggered when button is pressed down and held down for a longer time
14    LongPress,
15    /// When button is depressed ;)
16    Idle,
17}
18
19/// Whether the pin is pulled up or pulled down by default. A button press would pull it into the
20/// inverted state
21pub enum ButtonPull {
22    PullUp,
23    PullDown,
24}
25
26pub struct ButtonConfig {
27    pub pressing_threshold: f32,
28    pub long_press_threshold: f32,
29    pub pull: ButtonPull,
30}
31
32impl Default for ButtonConfig {
33    fn default() -> Self {
34        Self {
35            /// The time a button press should last to be recognized as a continuous press
36            /// (in seconds)
37            pressing_threshold: 0.2,
38            /// The time a button press should last to be recognized as a long press (in seconds)
39            long_press_threshold: 2.0,
40            /// The idle button pin state (pulled up or pulled down)
41            pull: ButtonPull::PullUp,
42        }
43    }
44}
45
46pub struct Button<PIN> {
47    counter: u16,
48    debounced: u8,
49    long_press_threshold: u16,
50    pin: PIN,
51    pressing_threshold: u16,
52    pull: ButtonPull,
53    reset: bool,
54    state: ButtonState,
55}
56
57impl<PIN> Button<PIN>
58where
59    PIN: InputPin,
60{
61    pub fn new(pin: PIN, f_refresh: u16, config: ButtonConfig) -> Self {
62        Self {
63            counter: 0,
64            debounced: 0,
65            long_press_threshold: (f_refresh as f32 / (1.0 / config.long_press_threshold)) as u16,
66            pin,
67            pressing_threshold: (f_refresh as f32 / (1.0 / config.pressing_threshold)) as u16,
68            pull: config.pull,
69            reset: false,
70            state: ButtonState::Idle,
71        }
72    }
73
74    fn raw_state(&self) -> u8 {
75        match (&self.pull, self.pin.is_low()) {
76            (ButtonPull::PullUp, Ok(true)) => 1,
77            (ButtonPull::PullDown, Ok(false)) => 1,
78            _ => 0,
79        }
80    }
81
82    pub fn poll(&mut self) {
83        let state = self.raw_state();
84        self.debounced |= state;
85        // Button was pressed at least once
86        if self.debounced > 0 {
87            if self.counter == 0 {
88                self.state = ButtonState::Down;
89            }
90            self.counter = self.counter.wrapping_add(1);
91            // Button is being held long enough to be considered a "pressing" action
92            if self.counter >= self.pressing_threshold {
93                self.state = ButtonState::Pressing;
94            }
95            if self.counter >= self.long_press_threshold {
96                self.state = ButtonState::LongPress;
97            }
98            // Button was let go of
99            if state == 0 {
100                if self.reset {
101                    self.state = ButtonState::Idle;
102                    self.reset = false;
103                } else if self.counter <= self.pressing_threshold {
104                    self.state = ButtonState::Press;
105                } else {
106                    self.state = ButtonState::Idle;
107                }
108
109                self.counter = 0;
110                self.debounced = 0;
111            }
112        }
113    }
114
115    pub fn read(&mut self) -> ButtonState {
116        match self.state {
117            ButtonState::Pressing | ButtonState::LongPress | ButtonState::Idle => self.state,
118            _ => {
119                let state = self.state;
120                self.state = ButtonState::Idle;
121                state
122            }
123        }
124    }
125
126    pub fn reset(&mut self) {
127        self.reset = true;
128    }
129}