acid/firmware/acid-firmware/src/matrix.rs

231 lines
7.1 KiB
Rust
Raw Normal View History

2025-12-24 02:07:21 +01:00
use esp_hal::{
Async,
2025-12-27 21:03:52 +01:00
gpio::{Input, InputConfig, Pull},
2025-12-24 02:07:21 +01:00
i2c::master::{BusTimeout, I2c, I2cAddress, SoftwareTimeout},
time::Rate,
};
use log::info;
use rmk::{
debounce::{DebounceState, DebouncerTrait},
2025-12-27 21:03:52 +01:00
embassy_futures::yield_now,
2025-12-24 02:07:21 +01:00
event::{Event, KeyboardEvent},
input_device::InputDevice,
matrix::{KeyState, MatrixTrait},
};
2026-02-13 02:50:32 +01:00
use crate::config::{MATRIX_AREA, MATRIX_COLS, MATRIX_ROWS};
2025-12-24 02:07:21 +01:00
pub struct RaiiGuard<F: FnOnce()> {
on_drop: Option<F>,
}
impl<F: FnOnce()> RaiiGuard<F> {
pub fn new(on_drop: F) -> Self {
Self {
on_drop: Some(on_drop),
}
}
}
impl<F: FnOnce()> Drop for RaiiGuard<F> {
fn drop(&mut self) {
if let Some(on_drop) = self.on_drop.take() {
(on_drop)();
}
}
}
/// IO Expander Matrix
pub struct IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
2025-12-27 21:03:52 +01:00
/// Goes low when the matrix was changed and should be read.
matrix_interrupt_low: Input<'static>,
2025-12-24 02:07:21 +01:00
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<D> IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
pub async fn new(
2025-12-27 21:03:52 +01:00
mut matrix_interrupt_low: Input<'static>,
2025-12-24 02:07:21 +01:00
mut i2c: I2c<'static, Async>,
debouncer: D,
ioe_addresses: [I2cAddress; 2],
) -> Self {
2025-12-27 21:03:52 +01:00
matrix_interrupt_low.apply_config(&InputConfig::default().with_pull(Pull::Up));
2025-12-24 02:07:21 +01:00
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 {
2025-12-27 21:03:52 +01:00
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:?}");
}
2025-12-24 02:07:21 +01:00
}
Self {
2025-12-27 21:03:52 +01:00
matrix_interrupt_low,
2025-12-24 02:07:21 +01:00
i2c,
ioe_addresses,
debouncer,
input_states: Default::default(),
key_states: Default::default(),
scan_pos: 0,
#[cfg(feature = "async_matrix")]
rescan_needed: false,
}
}
}
impl<D> InputDevice for IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
async fn read_event(&mut self) -> Event {
loop {
if self.scan_pos == 0 {
2025-12-27 21:03:52 +01:00
// TODO: Only execute if interrupt received.
// Timer::after(Duration::from_millis(100)).await;
yield_now().await;
2025-12-24 02:07:21 +01:00
// 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<D> MatrixTrait<MATRIX_ROWS, MATRIX_COLS> for IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
#[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();
}
}
}