rpk_firmware/
key_scanner.rs

1use embassy_futures::select::select_slice;
2use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel};
3use embassy_time::{Instant, Timer};
4use embedded_hal::digital::{InputPin, OutputPin};
5use embedded_hal_async::digital::Wait;
6use heapless::Vec;
7
8use crate::info;
9
10const WAIT_NANOS: u64 = 100;
11
12#[derive(Debug, Clone, Copy)]
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14pub struct ScanKey {
15    row: u8,
16    col: u8,
17}
18impl ScanKey {
19    pub fn new(row: u8, col: u8, is_down: bool) -> Self {
20        Self {
21            row: row | if is_down { 0x80 } else { 0 },
22            col,
23        }
24    }
25
26    pub fn none() -> Self {
27        Self {
28            row: 0xff,
29            col: 0xff,
30        }
31    }
32
33    pub fn is_none(&self) -> bool {
34        self.col == 0xff
35    }
36
37    pub fn row(&self) -> usize {
38        (self.row & 0x7f) as usize
39    }
40
41    pub fn column(&self) -> usize {
42        self.col as usize
43    }
44
45    pub fn is_down(&self) -> bool {
46        self.row & 0x80 == 0x80
47    }
48
49    pub fn is_same_key(&self, other: ScanKey) -> bool {
50        self.col == other.col && self.row & 0x7f == other.row & 0x7f
51    }
52
53    pub fn same_key(&self, other: ScanKey) -> bool {
54        self.col == other.col && self.row & 0x7f == other.row & 0x7f
55    }
56
57    pub fn as_memo(&self) -> u16 {
58        self.row as u16 | ((self.col as u16) << 8)
59    }
60
61    pub fn from_memo(memo: u16) -> Self {
62        Self {
63            row: memo as u8,
64            col: (memo >> 8) as u8,
65        }
66    }
67}
68
69pub struct KeyScannerChannel<M: RawMutex, const N: usize>(Channel<M, ScanKey, N>);
70
71pub struct KeyScanner<
72    'c,
73    I: InputPin + Wait,
74    O: OutputPin,
75    M: RawMutex,
76    const INPUT_N: usize,
77    const OUTPUT_N: usize,
78    const PS: usize,
79> {
80    input_pins: [I; INPUT_N],
81    output_pins: [O; OUTPUT_N],
82    /// Keeps track of all key switch changes and debounce settling timer.  Bit 7 indicates debouncing,
83    /// bits 6-2 are debounce counter, bit 1 indicates last reported switch position, bit 0 indicates
84    /// actual switch position.
85    state: [[u8; INPUT_N]; OUTPUT_N],
86    scan_start: Option<Instant>,
87    channel: &'c KeyScannerChannel<M, PS>,
88    debounce: usize,
89}
90
91impl<M: RawMutex, const N: usize> Default for KeyScannerChannel<M, N> {
92    fn default() -> Self {
93        Self(Channel::new())
94    }
95}
96
97impl<M: RawMutex, const N: usize> KeyScannerChannel<M, N> {
98    pub async fn receive(&self) -> ScanKey {
99        self.0.receive().await
100    }
101
102    pub fn try_send(&self, msg: ScanKey) {
103        self.0.try_send(msg).ok();
104    }
105
106    pub async fn get_offset(&self) -> u32 {
107        let key1 = self.receive().await;
108        let key2 = self.receive().await;
109        u32::from_le_bytes([key1.row, key1.col, key2.row, key2.col])
110    }
111}
112
113impl<
114        'c,
115        I: InputPin + Wait,
116        O: OutputPin,
117        M: RawMutex,
118        const INPUT_N: usize,
119        const OUTPUT_N: usize,
120        const PS: usize,
121    > KeyScanner<'c, I, O, M, INPUT_N, OUTPUT_N, PS>
122{
123    pub fn new(
124        input_pins: [I; INPUT_N],
125        output_pins: [O; OUTPUT_N],
126        channel: &'c KeyScannerChannel<M, PS>,
127    ) -> Self {
128        Self {
129            input_pins,
130            output_pins,
131            state: [[0; INPUT_N]; OUTPUT_N],
132            scan_start: None,
133            channel,
134            debounce: 0,
135        }
136    }
137
138    pub async fn run<const ROW_IS_OUTPUT: bool, const DEBOUNCE_TUNE: usize>(&mut self) {
139        assert!(DEBOUNCE_TUNE < usize::BITS as usize);
140        loop {
141            // If no key for over 2 secs, wait for interupt
142            if self.scan_start.is_some_and(|s| s.elapsed().as_secs() > 1) {
143                let _waited = self.wait_for_key().await;
144            }
145
146            self.scan::<ROW_IS_OUTPUT, DEBOUNCE_TUNE>().await;
147        }
148    }
149
150    pub async fn wait_for_key(&mut self) -> bool {
151        self.scan_start = None;
152
153        // First, set all output pin to low
154        for out in self.output_pins.iter_mut() {
155            let _ = out.set_low();
156        }
157        Timer::after_micros(1).await;
158        info!("Waiting for low");
159
160        let mut futs: Vec<_, INPUT_N> = self
161            .input_pins
162            .iter_mut()
163            .map(|input_pin| input_pin.wait_for_low())
164            .collect();
165        let _ = select_slice(futs.as_mut_slice()).await;
166
167        // Set all output pins back to low
168        for out in self.output_pins.iter_mut() {
169            let _ = out.set_high();
170        }
171
172        true
173    }
174
175    pub async fn scan<const ROW_IS_OUTPUT: bool, const DEBOUNCE_TUNE: usize>(&mut self) {
176        // debounce on, down cleared for compare
177        let debounce = debounce_sensitivity::<DEBOUNCE_TUNE>(self.debounce);
178
179        // We will soon sleep if all up
180        let mut is_all_up = true;
181
182        for (output_idx, (op, s)) in self
183            .output_pins
184            .iter_mut()
185            .zip(self.state.iter_mut())
186            .enumerate()
187        {
188            let _ = op.set_low();
189            Timer::after_nanos(WAIT_NANOS).await;
190
191            for (input_idx, (ip, s)) in self.input_pins.iter_mut().zip(s.iter_mut()).enumerate() {
192                let settle = *s & !3; // down states cleared for compare
193
194                let is_down = if ip.is_low().unwrap_or(false) { 1 } else { 0 };
195                let mut changed = *s & 1 != is_down;
196
197                if settle != 0 {
198                    if settle == debounce {
199                        // we are now settled; just keep down states
200                        *s &= 3;
201                        changed = matches!(*s, 1 | 2);
202                    } else {
203                        // settling keys need to be polled
204                        is_all_up = false;
205                        if changed {
206                            // restart settle counter
207                            *s = debounce_start::<DEBOUNCE_TUNE>(is_down, *s & 2, self.debounce);
208                        }
209                        continue;
210                    }
211                }
212                if is_down == 1 {
213                    is_all_up = false;
214                }
215
216                if changed {
217                    // set debounce state and counter to prev value
218                    *s = debounce_start::<DEBOUNCE_TUNE>(is_down, is_down << 1, self.debounce);
219                    self.channel
220                        .0
221                        .send(if ROW_IS_OUTPUT {
222                            ScanKey::new(output_idx as u8, input_idx as u8, is_down == 1)
223                        } else {
224                            ScanKey::new(input_idx as u8, output_idx as u8, is_down == 1)
225                        })
226                        .await;
227                }
228            }
229
230            let _ = op.set_high();
231        }
232
233        self.debounce = self.debounce.wrapping_add(2);
234
235        if is_all_up {
236            if self.scan_start.is_none() {
237                self.scan_start = Some(Instant::now());
238            }
239        } else if self.scan_start.is_some() {
240            self.scan_start = None;
241        }
242    }
243}
244
245fn debounce_sensitivity<const DEBOUNCE_TUNE: usize>(debounce: usize) -> u8 {
246    ((if DEBOUNCE_TUNE < 4 {
247        debounce << (4 - DEBOUNCE_TUNE) // more sensitive
248    } else {
249        debounce >> (DEBOUNCE_TUNE - 4) // less sensitive
250    } as u8)
251        << 2)
252        | 0x80
253}
254
255fn debounce_start<const DEBOUNCE_TUNE: usize>(
256    is_down: u8,
257    reported_down: u8,
258    debounce: usize,
259) -> u8 {
260    debounce_sensitivity::<DEBOUNCE_TUNE>(debounce.wrapping_sub(if DEBOUNCE_TUNE < 4 {
261        0
262    } else {
263        1 << (DEBOUNCE_TUNE - 3)
264    })) | (is_down | reported_down)
265}
266
267#[cfg(test)]
268extern crate std;
269
270#[cfg(test)]
271#[path = "key_scanner_test.rs"]
272mod test;