diff --git a/Cargo.lock b/Cargo.lock index ce740b9..dc289e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + [[package]] name = "zel-tca8418" version = "0.1.0" +dependencies = [ + "embedded-hal", +] diff --git a/Cargo.toml b/Cargo.toml index d9191de..210a175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +embedded-hal = "1.0.0" diff --git a/src/lib.rs b/src/lib.rs index 1ef6109..69650c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,159 @@ #![no_std] +use embedded_hal::i2c::SevenBitAddress; + +pub const I2C_ADDR: SevenBitAddress = 0b0_0110100; + +pub struct TCA8418 { + i2c: I2C, +} + +#[derive(Clone, Copy)] +pub struct MatrixConfig { + rows: u8, + columns: u16, +} + +impl MatrixConfig { + /// A new config where the matrix has no rows or columns and all of the pins are + /// allocated for GPIO. + pub const fn new_empty() -> Self { + MatrixConfig { + rows: 0, + columns: 0, + } + } + + /// Add a row to the keyboard matrix, meaning its pin will not be available for GPIO. + /// + /// `row_number` must be in the range `0..=7` + pub const fn with_row(self, row_number: u8) -> Self { + if row_number >= 8 { + panic!("row_number out of bounds"); + } + MatrixConfig { + rows: self.rows | (1 << row_number), + columns: self.columns, + } + } + + /// Add a column to the keyboard matrix, meaning its pin will not be available for GPIO. + /// + /// `column_number` must be in the range `0..=9` + pub const fn with_column(self, column_number: u8) -> Self { + if column_number >= 10 { + panic!("column_number out of bounds"); + } + MatrixConfig { + rows: self.rows, + columns: self.columns | (1 << column_number), + } + } +} + +#[derive(Clone, Copy)] +pub struct InterruptConfig(u8); + +impl InterruptConfig { + /// A new interrupt with the following configuration: + /// - GPI events are tracked when keypad is locked + /// - Overflow data is lost + /// - Processor interrupt remains asserted (or low) if host tries to clear + /// interrupt while there is still a pending key press, key release or + /// GPI interrupt + /// - ~INT is not asserted if the FIFO overflows + /// - ~INT is not asserted after a correct unlock key sequence + /// - ~INT is not asserted for a change on a GPI + /// - ~INT is not asserted when a key event occurs + pub const fn new() -> Self { + Self(0) + } + + pub const fn without_gpi_event_tracking_when_locked(self) -> Self { + InterruptConfig(self.0 | 0b0100_0000) + } + + pub const fn with_overflow_data_shifting(self) -> Self { + InterruptConfig(self.0 | 0b0010_0000) + } + + pub const fn with_temporary_interrupt_deassertion(self) -> Self { + InterruptConfig(self.0 | 0b0001_0000) + } +} + +#[derive(Clone, Copy)] +pub struct KeySet(u128); + +impl KeySet { + fn contains(&self, key_number: u8) -> bool { + (self.0 & (1 << key_number)) != 0 + } +} + +impl core::ops::Not for KeySet { + type Output = KeySet; + + fn not(self) -> Self::Output { + KeySet(!self.0) + } +} + +pub struct KeySetIter { + key_set: KeySet, + shift: u8, +} + +impl core::iter::Iterator for KeySetIter { + type Item = u8; + + fn next(&mut self) -> Option { + let next_shift = self.key_set.0.trailing_zeros() as u8 + 1; + if next_shift >= 128 { + return None; + } + + self.key_set.0 >>= next_shift; + self.shift += next_shift; + + Some(self.shift - 1) + } +} + +impl core::iter::IntoIterator for KeySet { + type Item = u8; + + type IntoIter = KeySetIter; + + fn into_iter(self) -> Self::IntoIter { + KeySetIter { + key_set: self, + shift: 0 + } + } +} + +impl TCA8418 { + pub fn new( + i2c: I2C, + matrix_config: MatrixConfig, + interrupt_config: InterruptConfig, + ) -> Self { + let mut self_ = Self { i2c }; + self_.init(matrix_config, interrupt_config); + self_ + } + + fn init(&mut self, matrix_config: MatrixConfig, interrupt_config: InterruptConfig) { + self.i2c.write(I2C_ADDR, &[0x1D, matrix_config.rows]); + self.i2c + .write(I2C_ADDR, &[0x1E, matrix_config.columns as u8]); + self.i2c + .write(I2C_ADDR, &[0x1F, (matrix_config.columns >> 8) as u8]); + self.i2c.write(I2C_ADDR, &[0x01, interrupt_config.0]); + self.i2c.write(I2C_ADDR, &[0x0F, 0]); + self.i2c.write(I2C_ADDR, &[0x10, 0]); + self.i2c.write(I2C_ADDR, &[0x0E, 0]); + } +} +