kiibohd-keyscanning 0.1.4

Kiibohd gpio keyscanning module for momentary push button switches (keyboards)
Documentation
// 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()
    }
}