Skip to main content

rmk_q1_pro_iso/matrix/
shiftreg_matrix.rs

1//! Shift register matrix scanner using a 74HC595 for column selection.
2
3use crate::matrix::hc595_cols::Hc595Cols;
4use core::{array::from_fn, hint::cold_path};
5use embassy_stm32::{exti::ExtiInput, mode::Async};
6use embassy_time::{Duration, Timer};
7use rmk::{
8    debounce::{DebounceState, DebouncerTrait as _, default_debouncer::DefaultDebouncer},
9    event::KeyboardEvent,
10    macros::input_device,
11    matrix::KeyState,
12};
13
14/// Timing configuration for the [`ShiftRegMatrix`] scanner.
15pub struct ShiftRegMatrixConfig {
16    /// Allows the shift register outputs and PCB traces to settle.
17    col_settle_time: Duration,
18}
19
20impl Default for ShiftRegMatrixConfig {
21    fn default() -> Self { Self { col_settle_time: Duration::from_micros(2) } }
22}
23
24/// A row and column position within the key matrix.
25#[derive(Copy, Clone)]
26struct ScanPos {
27    /// Column index within the matrix.
28    col: usize,
29    /// Row index within the matrix.
30    row: usize,
31}
32
33impl ScanPos {
34    /// Creates a new [`ScanPos`] at the given row and column.
35    const fn new(row: usize, col: usize) -> Self { Self { col, row } }
36}
37
38/// A 2D grid of [`KeyState`] values representing the current state of all keys.
39struct KeyGrid<const ROW: usize, const COL: usize> {
40    /// The key state for each position in the matrix.
41    cells: [[KeyState; COL]; ROW],
42}
43
44impl<const ROW: usize, const COL: usize> KeyGrid<ROW, COL> {
45    /// Returns a mutable reference to the [`KeyState`] at the given row and
46    /// column, or [`None`] if either index is out of bounds.
47    #[inline]
48    const fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut KeyState> {
49        match self.cells.get_mut(row) {
50            None => None,
51            Some(row_arr) => row_arr.get_mut(col),
52        }
53    }
54
55    /// Creates a new [`KeyGrid`] with all keys initialized to the released
56    /// state.
57    fn new() -> Self { Self { cells: from_fn(|_| from_fn(|_| KeyState { pressed: false })) } }
58}
59
60/// Keyboard matrix scanner driven by a 74HC595 shift register for column
61/// selection and GPIO EXTI inputs for row sensing.
62#[input_device(publish = KeyboardEvent)]
63pub struct ShiftRegMatrix<'peripherals, const ROW: usize, const COL: usize> {
64    /// Shift register column driver.
65    cols: Hc595Cols<'peripherals>,
66    /// Debouncer for filtering spurious key state transitions.
67    debouncer: DefaultDebouncer<ROW, COL>,
68    /// Shadow of the current debounced key state for all positions.
69    key_state: KeyGrid<ROW, COL>,
70    /// Row input pins with interrupt support for key sensing.
71    rows: [ExtiInput<'peripherals, Async>; ROW],
72    /// The matrix position to resume scanning from on the next scan pass.
73    scan_pos: ScanPos,
74    /// Time to wait after selecting a column before reading row inputs.
75    col_settle_delay: Duration,
76}
77
78impl<'peripherals, const ROW: usize, const COL: usize> ShiftRegMatrix<'peripherals, ROW, COL> {
79    /// Creates a new [`ShiftRegMatrix`] scanner instance.
80    ///
81    /// Deselects all columns on construction to ensure a clean initial state.
82    pub fn new(
83        rows: [ExtiInput<'peripherals, Async>; ROW],
84        mut cols: Hc595Cols<'peripherals>,
85        config: ShiftRegMatrixConfig,
86    ) -> Self {
87        cols.unselect_all();
88
89        Self {
90            rows,
91            cols,
92            debouncer: DefaultDebouncer::new(),
93            key_state: KeyGrid::new(),
94            scan_pos: ScanPos::new(0, 0),
95            col_settle_delay: config.col_settle_time,
96        }
97    }
98
99    /// Waits until the next debounced [`KeyboardEvent`] is available and
100    /// returns it.
101    ///
102    /// Repeatedly scans the matrix until a key state change is detected.
103    #[optimize(speed)]
104    async fn read_keyboard_event(&mut self) -> KeyboardEvent {
105        loop {
106            if let Some(ev) = self.scan_until_event().await {
107                return ev;
108            }
109        }
110    }
111
112    /// Performs one scan pass over the matrix and returns the first debounced
113    /// [`KeyboardEvent`] detected, or [`None`] if no change occurred.
114    ///
115    /// Scanning resumes from the last recorded [`ScanPos`] to avoid missing
116    /// events when a previous scan was interrupted early. Resets [`ScanPos`]
117    /// to the origin after a full pass with no event.
118    #[optimize(speed)]
119    async fn scan_until_event(&mut self) -> Option<KeyboardEvent> {
120        let start = self.scan_pos;
121
122        for col in (start.col..COL).chain(0..start.col) {
123            self.cols.select_col(col);
124            Timer::after(self.col_settle_delay).await;
125
126            let r_start = if col == start.col { start.row } else { 0 };
127
128            for (row, row_pin) in self.rows.iter().enumerate().skip(r_start) {
129                let pressed = row_pin.is_low();
130
131                let Some(ks) = self.key_state.get_mut(row, col) else {
132                    continue;
133                };
134
135                let st = self.debouncer.detect_change_with_debounce(row, col, pressed, ks);
136                if matches!(st, DebounceState::Debounced) {
137                    cold_path();
138                    ks.pressed = pressed;
139                    self.cols.unselect_all();
140                    self.scan_pos = ScanPos::new(row, col);
141                    return Some(KeyboardEvent::key(
142                        u8::try_from(row).unwrap_or_default(),
143                        u8::try_from(col).unwrap_or_default(),
144                        pressed,
145                    ));
146                }
147            }
148        }
149
150        self.scan_pos = ScanPos::new(0, 0);
151        self.cols.unselect_all();
152        None
153    }
154}