Skip to main content

oxstack_drivers/
tca8418.rs

1//! TI TCA8418 I²C keypad scanner.
2//!
3//! The chip scans a key matrix of up to 8 rows × 10 columns on its own and
4//! queues press/release events in a 10-deep FIFO, pulling `INT` low while
5//! events are pending. The host only has to drain the FIFO, so a keyboard
6//! costs nothing until a key actually changes.
7//!
8//! ```ignore
9//! let mut kp = Tca8418::new(i2c, DEFAULT_ADDRESS);
10//! kp.configure(Matrix::new(7, 8)).await?;
11//! loop {
12//!     int.wait_for_low().await?;
13//!     let mut fifo = [0; FIFO_DEPTH];
14//!     for raw in kp.drain(&mut fifo).await? {
15//!         if let Some(Event::Key(k)) = Event::decode(*raw) { /* ... */ }
16//!     }
17//!     kp.clear_interrupts().await?;
18//! }
19//! ```
20
21use embedded_hal_async::i2c::I2c;
22
23/// Factory-fixed 7-bit I²C address.
24pub const DEFAULT_ADDRESS: u8 = 0x34;
25
26/// Depth of the on-chip event FIFO.
27pub const FIFO_DEPTH: usize = 10;
28
29mod reg {
30    pub const CFG: u8 = 0x01;
31    pub const INT_STAT: u8 = 0x02;
32    pub const KEY_LCK_EC: u8 = 0x03;
33    pub const KEY_EVENT_A: u8 = 0x04;
34    pub const KP_GPIO1: u8 = 0x1D;
35    pub const KP_GPIO2: u8 = 0x1E;
36    pub const KP_GPIO3: u8 = 0x1F;
37}
38
39mod cfg {
40    /// Key-event interrupt enable.
41    pub const KE_IEN: u8 = 0x01;
42    /// Re-assert INT 50 µs after a clear if events are still pending, so a
43    /// host that clears the flag early still sees a fresh edge.
44    pub const INT_CFG: u8 = 0x10;
45}
46
47/// All interrupt-status bits; each is write-1-to-clear.
48const INT_STAT_ALL: u8 = 0x1F;
49
50/// The rows × columns window of the chip's pins used as a key matrix.
51///
52/// Construct it in a `const` so a bad size is a compile error:
53/// `const KEYS: Matrix = Matrix::new(7, 8);`
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56pub struct Matrix {
57    rows: u8,
58    cols: u8,
59}
60
61impl Matrix {
62    /// A matrix on `ROW0..rows` × `COL0..cols`.
63    ///
64    /// # Panics
65    /// If `rows` is not in `1..=8` or `cols` is not in `1..=10`
66    /// (at compile time when used in a `const`).
67    pub const fn new(rows: u8, cols: u8) -> Self {
68        assert!(rows >= 1 && rows <= 8, "TCA8418 has 8 row pins");
69        assert!(cols >= 1 && cols <= 10, "TCA8418 has 10 column pins");
70        Self { rows, cols }
71    }
72
73    /// Values for the `KP_GPIO1..3` pin-function registers.
74    const fn kp_gpio(self) -> [u8; 3] {
75        let rows = ((1u16 << self.rows) - 1) as u8;
76        let cols = (1u16 << self.cols) - 1;
77        [rows, cols as u8, (cols >> 8) as u8]
78    }
79}
80
81/// A key press or release at a position in the scan matrix.
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83#[cfg_attr(feature = "defmt", derive(defmt::Format))]
84pub struct KeyEvent {
85    /// Row pin (`ROW0..ROW7`).
86    pub row: u8,
87    /// Column pin (`COL0..COL9`).
88    pub col: u8,
89    /// `true` on press, `false` on release.
90    pub pressed: bool,
91}
92
93/// One entry popped from the event FIFO.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
96pub enum Event {
97    /// A matrix key changed state.
98    Key(KeyEvent),
99    /// A pin configured as a general-purpose input changed level.
100    Gpi {
101        /// Pin index: 0..=7 are `ROW0..7`, 8..=17 are `COL0..9`.
102        pin: u8,
103        /// `true` when the input became active.
104        active: bool,
105    },
106}
107
108impl Event {
109    /// Decodes a raw `KEY_EVENT_A` byte: bit 7 is press/release, bits 0..6
110    /// are the key code — `1..=80` for matrix keys (`row * 10 + col + 1`)
111    /// and `97..=114` for GPI events. Returns `None` for the empty-FIFO
112    /// marker `0` and any reserved code.
113    pub const fn decode(raw: u8) -> Option<Self> {
114        let pressed = raw & 0x80 != 0;
115        match raw & 0x7F {
116            code @ 1..=80 => {
117                let n = code - 1;
118                Some(Event::Key(KeyEvent {
119                    row: n / 10,
120                    col: n % 10,
121                    pressed,
122                }))
123            }
124            code @ 97..=114 => Some(Event::Gpi {
125                pin: code - 97,
126                active: pressed,
127            }),
128            _ => None,
129        }
130    }
131}
132
133/// TCA8418 driver.
134pub struct Tca8418<I2C> {
135    i2c: I2C,
136    addr: u8,
137}
138
139impl<I2C: I2c> Tca8418<I2C> {
140    /// Wraps a bus handle. No I/O happens until [`configure`](Self::configure).
141    pub const fn new(i2c: I2C, addr: u8) -> Self {
142        Self { i2c, addr }
143    }
144
145    /// Puts `matrix` into keypad mode, discards stale events, and enables
146    /// the key-event interrupt.
147    pub async fn configure(&mut self, matrix: Matrix) -> Result<(), I2C::Error> {
148        let [g1, g2, g3] = matrix.kp_gpio();
149        self.write(reg::KP_GPIO1, g1).await?;
150        self.write(reg::KP_GPIO2, g2).await?;
151        self.write(reg::KP_GPIO3, g3).await?;
152        // Events captured before configuration (e.g. at power-up) are noise.
153        let mut scratch = [0; FIFO_DEPTH];
154        self.drain(&mut scratch).await?;
155        self.clear_interrupts().await?;
156        self.write(reg::CFG, cfg::KE_IEN | cfg::INT_CFG).await
157    }
158
159    /// Number of events waiting in the FIFO.
160    pub async fn pending(&mut self) -> Result<u8, I2C::Error> {
161        Ok(self.read(reg::KEY_LCK_EC).await? & 0x0F)
162    }
163
164    /// Pops every pending event into `buf` and returns the filled prefix.
165    /// Feed each byte to [`Event::decode`].
166    pub async fn drain<'b>(
167        &mut self,
168        buf: &'b mut [u8; FIFO_DEPTH],
169    ) -> Result<&'b [u8], I2C::Error> {
170        let count = usize::from(self.pending().await?).min(FIFO_DEPTH);
171        for slot in &mut buf[..count] {
172            // Each read of KEY_EVENT_A pops the FIFO head.
173            *slot = self.read(reg::KEY_EVENT_A).await?;
174        }
175        Ok(&buf[..count])
176    }
177
178    /// Acknowledges all interrupt sources, releasing `INT` if the FIFO is empty.
179    pub async fn clear_interrupts(&mut self) -> Result<(), I2C::Error> {
180        self.write(reg::INT_STAT, INT_STAT_ALL).await
181    }
182
183    /// Returns the bus handle.
184    pub fn release(self) -> I2C {
185        self.i2c
186    }
187
188    async fn write(&mut self, reg: u8, value: u8) -> Result<(), I2C::Error> {
189        self.i2c.write(self.addr, &[reg, value]).await
190    }
191
192    async fn read(&mut self, reg: u8) -> Result<u8, I2C::Error> {
193        let mut out = [0];
194        self.i2c.write_read(self.addr, &[reg], &mut out).await?;
195        Ok(out[0])
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    extern crate std;
202    use super::*;
203    use crate::test_util::block_on;
204    use embedded_hal_async::i2c::{ErrorType, Operation};
205    use std::collections::VecDeque;
206    use std::vec::Vec;
207
208    /// Register-level model of the chip: a register file plus a FIFO behind
209    /// `KEY_EVENT_A` and an event count in `KEY_LCK_EC`.
210    #[derive(Default)]
211    struct FakeChip {
212        regs: [u8; 32],
213        fifo: VecDeque<u8>,
214        writes: Vec<(u8, u8)>,
215    }
216
217    impl ErrorType for FakeChip {
218        type Error = core::convert::Infallible;
219    }
220
221    impl I2c for FakeChip {
222        async fn transaction(
223            &mut self,
224            addr: u8,
225            ops: &mut [Operation<'_>],
226        ) -> Result<(), Self::Error> {
227            assert_eq!(addr, DEFAULT_ADDRESS);
228            let mut ptr = 0u8;
229            for op in ops {
230                match op {
231                    Operation::Write(bytes) => {
232                        ptr = bytes[0];
233                        if let [reg, value] = bytes[..] {
234                            self.writes.push((reg, value));
235                            self.regs[usize::from(reg)] = value;
236                        }
237                    }
238                    Operation::Read(out) => {
239                        out[0] = match ptr {
240                            reg::KEY_LCK_EC => self.fifo.len() as u8,
241                            reg::KEY_EVENT_A => self.fifo.pop_front().unwrap_or(0),
242                            r => self.regs[usize::from(r)],
243                        };
244                    }
245                }
246            }
247            Ok(())
248        }
249    }
250
251    #[test]
252    fn decode_key_codes() {
253        // Code 1 is ROW0/COL0; code 80 is ROW7/COL9.
254        assert_eq!(
255            Event::decode(0x81),
256            Some(Event::Key(KeyEvent {
257                row: 0,
258                col: 0,
259                pressed: true
260            }))
261        );
262        assert_eq!(
263            Event::decode(80),
264            Some(Event::Key(KeyEvent {
265                row: 7,
266                col: 9,
267                pressed: false
268            }))
269        );
270        assert_eq!(
271            Event::decode(0x80 | 23),
272            Some(Event::Key(KeyEvent {
273                row: 2,
274                col: 2,
275                pressed: true
276            }))
277        );
278        assert_eq!(
279            Event::decode(0x80 | 97),
280            Some(Event::Gpi {
281                pin: 0,
282                active: true
283            })
284        );
285        assert_eq!(Event::decode(0), None);
286        assert_eq!(Event::decode(85), None);
287    }
288
289    #[test]
290    fn matrix_pin_masks() {
291        assert_eq!(Matrix::new(7, 8).kp_gpio(), [0x7F, 0xFF, 0x00]);
292        assert_eq!(Matrix::new(8, 10).kp_gpio(), [0xFF, 0xFF, 0x03]);
293        assert_eq!(Matrix::new(1, 9).kp_gpio(), [0x01, 0xFF, 0x01]);
294    }
295
296    #[test]
297    #[should_panic]
298    fn matrix_rejects_oversize() {
299        let _ = Matrix::new(9, 8);
300    }
301
302    #[test]
303    fn configure_flushes_stale_events_then_enables_irq() {
304        let mut chip = FakeChip::default();
305        chip.fifo.extend([0x81, 0x01]);
306        let mut kp = Tca8418::new(chip, DEFAULT_ADDRESS);
307        block_on(kp.configure(Matrix::new(7, 8))).unwrap();
308        let chip = kp.release();
309
310        assert!(chip.fifo.is_empty(), "stale events must be discarded");
311        assert_eq!(
312            chip.writes,
313            [
314                (reg::KP_GPIO1, 0x7F),
315                (reg::KP_GPIO2, 0xFF),
316                (reg::KP_GPIO3, 0x00),
317                (reg::INT_STAT, INT_STAT_ALL),
318                (reg::CFG, cfg::KE_IEN | cfg::INT_CFG),
319            ]
320        );
321    }
322
323    #[test]
324    fn drain_returns_events_in_fifo_order() {
325        let mut chip = FakeChip::default();
326        chip.fifo.extend([0x81, 0x8C, 0x01]);
327        let mut kp = Tca8418::new(chip, DEFAULT_ADDRESS);
328        let mut buf = [0; FIFO_DEPTH];
329        let events = block_on(kp.drain(&mut buf)).unwrap();
330        assert_eq!(events, [0x81, 0x8C, 0x01]);
331        assert_eq!(block_on(kp.pending()).unwrap(), 0);
332    }
333}