use esp_hal::{ Async, gpio::{Input, InputConfig, Pull}, i2c::master::{BusTimeout, I2c, I2cAddress, SoftwareTimeout}, time::Rate, }; use log::info; use rmk::{ debounce::{DebounceState, DebouncerTrait}, embassy_futures::yield_now, event::{Event, KeyboardEvent}, input_device::InputDevice, matrix::{KeyState, MatrixTrait}, }; use crate::config::{MATRIX_AREA, MATRIX_COLS, MATRIX_ROWS}; pub struct RaiiGuard { on_drop: Option, } impl RaiiGuard { pub fn new(on_drop: F) -> Self { Self { on_drop: Some(on_drop), } } } impl Drop for RaiiGuard { fn drop(&mut self) { if let Some(on_drop) = self.on_drop.take() { (on_drop)(); } } } /// IO Expander Matrix pub struct IoeMatrix where D: DebouncerTrait, { /// Goes low when the matrix was changed and should be read. matrix_interrupt_low: Input<'static>, i2c: I2c<'static, Async>, /// IO expander addresses. ioe_addresses: [I2cAddress; 2], debouncer: D, /// States of key switches as detected by IO expanders. input_states: [[bool; MATRIX_ROWS]; MATRIX_COLS], /// Debounced key states. key_states: [[KeyState; MATRIX_ROWS]; MATRIX_COLS], /// Index of the current key switch being processed: row * columns + column scan_pos: usize, /// Re-scan needed flag #[cfg(feature = "async_matrix")] rescan_needed: bool, } impl IoeMatrix where D: DebouncerTrait, { pub async fn new( mut matrix_interrupt_low: Input<'static>, mut i2c: I2c<'static, Async>, debouncer: D, ioe_addresses: [I2cAddress; 2], ) -> Self { matrix_interrupt_low.apply_config(&InputConfig::default().with_pull(Pull::Up)); i2c.apply_config( &esp_hal::i2c::master::Config::default() .with_frequency(Rate::from_khz(400)) .with_timeout(BusTimeout::Disabled) .with_software_timeout(SoftwareTimeout::None), ) .unwrap(); for addr in ioe_addresses { loop { let Err(error) = i2c .write_async(addr, &[0b10010000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) .await else { break; }; info!("I2C timed out when writing the inversion mask to an IO expander: {error:?}"); } } Self { matrix_interrupt_low, i2c, ioe_addresses, debouncer, input_states: Default::default(), key_states: Default::default(), scan_pos: 0, #[cfg(feature = "async_matrix")] rescan_needed: false, } } } impl InputDevice for IoeMatrix where D: DebouncerTrait, { async fn read_event(&mut self) -> Event { loop { if self.scan_pos == 0 { // TODO: Only execute if interrupt received. // Timer::after(Duration::from_millis(100)).await; yield_now().await; // Load data from IO expanders. let mut input_register_banks = [0_u8; 10]; loop { let Err(esp_hal::i2c::master::Error::Timeout) = self .i2c .write_read_async( self.ioe_addresses[0], &[0x80], &mut input_register_banks[..5], ) .await else { break; }; info!("I2C timed out when reading the left IO expander."); } loop { let Err(esp_hal::i2c::master::Error::Timeout) = self .i2c .write_read_async( self.ioe_addresses[1], &[0x80], &mut input_register_banks[5..], ) .await else { break; }; info!("I2C timed out when reading the right IO expander."); } for (row_idx, (left, right)) in (0..).zip(core::iter::zip( &input_register_banks[..5], &input_register_banks[5..], )) { for col_idx_half in 0..(MATRIX_COLS / 2) { self.input_states[col_idx_half][row_idx] = left & (1 << col_idx_half) != 0; self.input_states[col_idx_half + MATRIX_COLS / 2][row_idx] = right & (1 << col_idx_half) != 0; } } // info!("rows: {rows:x?}") } let (row_idx, col_idx) = (self.scan_pos / MATRIX_COLS, self.scan_pos % MATRIX_COLS); let on_iteration_end = RaiiGuard::new(|| { // Process the next key switch. self.scan_pos = (self.scan_pos + 1) % MATRIX_AREA; }); let debounce_state = self.debouncer.detect_change_with_debounce( row_idx, col_idx, self.input_states[col_idx][row_idx], &self.key_states[col_idx][row_idx], ); if let DebounceState::Debounced = debounce_state { self.key_states[col_idx][row_idx].toggle_pressed(); #[cfg(feature = "async_matrix")] { self.rescan_needed = true; } return Event::Key(KeyboardEvent::key( row_idx as u8, col_idx as u8, self.key_states[col_idx][row_idx].pressed, )); } // // Process the next key switch. // self.scan_pos = (self.scan_pos + 1) % MATRIX_AREA; // If there's key still pressed, always refresh the self.scan_start #[cfg(feature = "async_matrix")] if self.key_states[col_idx][row_idx].pressed { self.rescan_needed = true; } #[cfg(feature = "async_matrix")] { if !self.rescan_needed { self.wait_for_key().await; } self.rescan_needed = false; } drop(on_iteration_end); } } } impl MatrixTrait for IoeMatrix where D: DebouncerTrait, { #[cfg(feature = "async_matrix")] async fn wait_for_key(&mut self) { // TODO // First, set all output pins to high for out in self.get_output_pins_mut().iter_mut() { out.set_high().ok(); } // Wait for any key press self.wait_input_pins().await; // Set all output pins back to low for out in self.get_output_pins_mut().iter_mut() { out.set_low().ok(); } } }