//! Button matrix scanner for CMDR Keyboard 42 //! //! Ported from the joystick firmware for consistent behaviour and structure. //! Scans a row/column matrix and produces a debounced boolean state for each //! button using per-button counters and small inter-column delays. use core::convert::Infallible; use cortex_m::delay::Delay; use embedded_hal::digital::{InputPin, OutputPin}; /// Row/column scanned button matrix driver with debounce counters. pub struct ButtonMatrix<'a, const R: usize, const C: usize, const N: usize> { rows: &'a mut [&'a mut dyn InputPin; R], cols: &'a mut [&'a mut dyn OutputPin; C], pressed: [bool; N], press_threshold: u8, release_threshold: u8, debounce_counter: [u8; N], // Additional protection: minimum time between same-key presses last_press_scan: [u32; N], scan_counter: u32, } impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { pub fn new( rows: &'a mut [&'a mut dyn InputPin; R], cols: &'a mut [&'a mut dyn OutputPin; C], press_threshold: u8, release_threshold: u8, ) -> Self { Self { rows, cols, pressed: [false; N], press_threshold, release_threshold, debounce_counter: [0; N], last_press_scan: [0; N], scan_counter: 0, } } pub fn init_pins(&mut self) { for col in self.cols.iter_mut() { col.set_high().unwrap(); } } pub fn scan_matrix(&mut self, delay: &mut Delay) { self.scan_counter = self.scan_counter.wrapping_add(1); for col_index in 0..self.cols.len() { self.cols[col_index].set_low().unwrap(); delay.delay_us(1); self.process_column(col_index); self.cols[col_index].set_high().unwrap(); delay.delay_us(1); } } fn process_column(&mut self, col_index: usize) { for row_index in 0..self.rows.len() { let button_index: usize = col_index + (row_index * C); let current_state = self.rows[row_index].is_low().unwrap(); if current_state == self.pressed[button_index] { self.debounce_counter[button_index] = 0; continue; } self.debounce_counter[button_index] = self.debounce_counter[button_index].saturating_add(1); let threshold = if current_state { self.press_threshold } else { self.release_threshold }; if self.debounce_counter[button_index] >= threshold { // Additional protection for press events: minimum 20 scans (5ms) between presses if current_state { // Pressing let scans_since_last = self.scan_counter.wrapping_sub(self.last_press_scan[button_index]); if scans_since_last >= 20 { // 5ms at 250μs scan rate self.pressed[button_index] = current_state; self.last_press_scan[button_index] = self.scan_counter; } } else { // Releasing self.pressed[button_index] = current_state; } self.debounce_counter[button_index] = 0; } } } pub fn buttons_pressed(&mut self) -> [bool; N] { self.pressed } } #[cfg(all(test, feature = "std"))] mod tests { use super::*; use core::cell::Cell; use embedded_hal::digital::ErrorType; use std::rc::Rc; struct MockInputPin { state: Rc>, } impl MockInputPin { fn new(state: Rc>) -> Self { Self { state } } } impl ErrorType for MockInputPin { type Error = Infallible; } impl InputPin for MockInputPin { fn is_high(&mut self) -> Result { Ok(!self.state.get()) } fn is_low(&mut self) -> Result { Ok(self.state.get()) } } struct MockOutputPin { state: Rc>, } impl MockOutputPin { fn new(state: Rc>) -> Self { Self { state } } } impl ErrorType for MockOutputPin { type Error = Infallible; } impl OutputPin for MockOutputPin { fn set_high(&mut self) -> Result<(), Self::Error> { self.state.set(true); Ok(()) } fn set_low(&mut self) -> Result<(), Self::Error> { self.state.set(false); Ok(()) } } fn matrix_fixture() -> ( ButtonMatrix<'static, 1, 1, 1>, Rc>, Rc>, ) { let row_state = Rc::new(Cell::new(false)); let col_state = Rc::new(Cell::new(false)); let row_pin: &'static mut dyn InputPin = Box::leak(Box::new(MockInputPin::new(row_state.clone()))); let col_pin: &'static mut dyn OutputPin = Box::leak(Box::new(MockOutputPin::new(col_state.clone()))); let rows: &'static mut [&'static mut dyn InputPin; 1] = Box::leak(Box::new([row_pin])); let cols: &'static mut [&'static mut dyn OutputPin; 1] = Box::leak(Box::new([col_pin])); let matrix = ButtonMatrix::new(rows, cols, 5, 5); (matrix, row_state, col_state) } #[test] fn init_sets_columns_high() { let (mut matrix, _row_state, col_state) = matrix_fixture(); assert!(!col_state.get()); matrix.init_pins(); assert!(col_state.get()); } #[test] fn debounce_respects_threshold() { let (mut matrix, row_state, _col_state) = matrix_fixture(); let mut states = matrix.buttons_pressed(); assert!(!states[0]); // Set scan counter to start with enough history matrix.scan_counter = 100; row_state.set(true); // Need 5 scans to register press for _ in 0..4 { matrix.scan_counter = matrix.scan_counter.wrapping_add(1); matrix.process_column(0); states = matrix.buttons_pressed(); assert!(!states[0]); // Still not pressed } matrix.scan_counter = matrix.scan_counter.wrapping_add(1); matrix.process_column(0); // 5th scan states = matrix.buttons_pressed(); assert!(states[0]); // Now pressed row_state.set(false); // Need 5 scans to register release for _ in 0..4 { matrix.scan_counter = matrix.scan_counter.wrapping_add(1); matrix.process_column(0); states = matrix.buttons_pressed(); assert!(states[0]); // Still pressed } matrix.scan_counter = matrix.scan_counter.wrapping_add(1); matrix.process_column(0); // 5th scan states = matrix.buttons_pressed(); assert!(!states[0]); // Now released } }