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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
// Copyright 2021 Zion Koyl
// Copyright 2021-2022 Jacob Alexander
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use core::ops::Not;
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
On,
Off,
}
impl Not for State {
type Output = Self;
fn not(self) -> Self::Output {
match self {
State::On => State::Off,
State::Off => State::On,
}
}
}
/// The KeyState handles all of the decision making and state changes based on a high or low signal from a GPIO pin
#[derive(Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyState<
const CSIZE: usize,
const SCAN_PERIOD_US: u32,
const DEBOUNCE_US: u32,
const IDLE_MS: u32,
> {
/// Most recently GPIO reading (not debounced)
raw_state: State,
/// True key state after debounce processing (debounced)
state: State,
/// Used to determine if the key is idle (in Off state for IDLE_MS)
idle: bool,
/// Tracking bounce
debounce_tracking: bool,
/// Used to determine the state after debounce
/// Increments if a value is not what's set as state.
/// Decrements if the value is already set as state.
/// If positive set to On
/// If negative set to Off
/// This value is reset to 0 after setting the new state
raw_state_average: i32,
/// Used to track the number of cycles since state has changed.
cycles_since_state_change: u32,
/// This is used to track the list GPIO read bounce
///
/// If cycles * scan_period > DEBOUNCE_US then raw_state is assigned to state.
cycles_since_last_bounce: u32,
}
impl<const CSIZE: usize, const SCAN_PERIOD_US: u32, const DEBOUNCE_US: u32, const IDLE_MS: u32>
KeyState<CSIZE, SCAN_PERIOD_US, DEBOUNCE_US, IDLE_MS>
{
pub fn new() -> Self {
Self {
raw_state: State::Off,
state: State::Off,
idle: false,
debounce_tracking: false,
raw_state_average: 0,
cycles_since_state_change: 0,
cycles_since_last_bounce: 0,
}
}
/// Record the GPIO read event and adjust debounce state machine accordingly
///
/// Returns:
/// (State, idle, cycles_since_state_change)
pub fn record(&mut self, on: bool) -> (State, bool, u32) {
// Track raw state average
// This is used to set the new state
if self.debounce_tracking {
if on && self.state == State::Off || !on && self.state == State::On {
self.raw_state_average += 1;
} else {
self.raw_state_average -= 1;
}
}
// Update the raw state as a bounce event if not the same as the previous scan iteration
// e.g. GPIO read value has changed since the last iteration
if on && self.raw_state == State::Off || !on && self.raw_state == State::On {
// Update raw state
self.raw_state = if on { State::On } else { State::Off };
// Reset bounce cycle counter
self.cycles_since_last_bounce = 0;
// Start debounce tracking (if we haven't already started)
self.debounce_tracking = true;
self.raw_state_average = 0; // Reset average
// Return current state
return self.state();
}
// Increment debounce cycle counter
self.cycles_since_last_bounce += 1;
// If we've hit max, don't wrap-around to 0
// Set to the defined bounce limit so not to affect any logic
if self.cycles_since_last_bounce >= u32::MAX / SCAN_PERIOD_US * CSIZE as u32 {
self.cycles_since_last_bounce = DEBOUNCE_US / SCAN_PERIOD_US / CSIZE as u32;
}
// Update the debounced state if it has changed and exceeded the debounce timer
// (debounce timer resets if there is any bouncing during the debounce interval).
if self.cycles_since_last_bounce * SCAN_PERIOD_US * CSIZE as u32 >= DEBOUNCE_US {
// Since we have hit the cycles_since_last_bounce threshold, we can keep it here
self.cycles_since_last_bounce -= 1;
if self.raw_state != self.state && self.raw_state_average != 0 {
// Update state
// If the average is greater than 0, change the state
let new_state = if self.raw_state_average > 0 {
!self.state
} else {
self.state
};
// No longer idle
self.idle = false;
// Stop debounce tracking
self.debounce_tracking = false;
self.raw_state_average = 0;
// Reset state transition cycle counter
// and update state if it has changed.
if new_state != self.state {
self.state = new_state;
self.cycles_since_state_change = 0;
}
// Return current state
return self.state();
}
}
// Increment state cycle counter
self.cycles_since_state_change += 1;
// If we've hit max, don't wrap-around to 0
// Set the defined idle limit so not to affect any logic
if self.cycles_since_state_change >= u32::MAX / SCAN_PERIOD_US / CSIZE as u32 {
self.cycles_since_state_change = IDLE_MS * 1000 / CSIZE as u32 / SCAN_PERIOD_US;
}
// Determine if key is idle
// Must be both in the off state and have been off >= IDLE_MS
self.idle = self.state == State::Off
&& self.cycles_since_state_change * SCAN_PERIOD_US * CSIZE as u32 / 1000 >= IDLE_MS;
// Return current state
self.state()
}
/// Returns thet current state and cycles since the state changed
///
/// (State, idle, cycles_since_state_change)
pub fn state(&self) -> (State, bool, u32) {
(self.state, self.idle, self.cycles_since_state_change)
}
/// Number of cycles since the last state change
/// 0 when the state first changes
pub fn cycles_since_state_change(&self) -> u32 {
self.cycles_since_state_change
}
/// True if the switch is idle
/// idle indicates the switch is off and there have been no events
pub fn idle(&self) -> bool {
self.idle
}
}
impl<const CSIZE: usize, const SCAN_PERIOD_US: u32, const DEBOUNCE_US: u32, const IDLE_MS: u32>
Default for KeyState<CSIZE, SCAN_PERIOD_US, DEBOUNCE_US, IDLE_MS>
{
fn default() -> Self {
Self::new()
}
}