From 53615fcf6a8e10d17923e8079fb69d139d08bf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Sun, 22 Sep 2024 17:58:23 +0200 Subject: [PATCH] Create keypad handler --- src/keypad.rs | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 178 +++++++++++++------------ 2 files changed, 449 insertions(+), 80 deletions(-) create mode 100644 src/keypad.rs diff --git a/src/keypad.rs b/src/keypad.rs new file mode 100644 index 0000000..07c604c --- /dev/null +++ b/src/keypad.rs @@ -0,0 +1,351 @@ +#[doc(hidden)] +pub extern crate core as _core; + +use core::cell::RefCell; + +use esp_hal::gpio::{Input, InputPin, OutputOpenDrain, OutputPin}; + +/// A virtual `embedded-hal` input pin representing one key of the keypad. +/// +/// A `KeypadInput` stores references to one row and one column pin. When you +/// read from it with `.is_low()` or `.is_high()`, it secretly sets the column +/// pin low, reads from the row pin, and then sets the column pin high again. +/// The column pin is actually stored inside a `RefCell` in the keypad struct, +/// so that multiple `KeypadInput`s can mutate the column pin's state as needed, +/// even though they only have a shared/immutable reference to it. +/// +/// This has several implications. +/// +/// 1) Reading from `KeypadInput`s is not reentrant. If we were in the middle +/// of reading a `KeypadInput` and entered an interrupt service routine that +/// read any `KeypadInput` of the same keypad, we might read an incorrect value +/// or cause a `panic`. +/// +/// 2) Reading from a `KeypadInput` is slower than reading from a real input +/// pin, because it needs to change the output pin state twice for every read. +pub struct KeypadInput<'a> { + row: &'a RefCell>, + col: &'a RefCell>, +} + +impl<'a, E> KeypadInput<'a, E> { + /// Create a new `KeypadInput`. For use in macros. + pub fn new(row: &'a dyn InputPin, col: &'a RefCell) -> Self { + Self { row, col } + } +} + +impl<'a, E> InputPin for KeypadInput<'a, E> { + type Error = E; + /// Read the state of the key at this row and column. Not reentrant. + fn is_high(&self) -> Result { + Ok(!self.is_low()?) + } + + /// Read the state of the key at this row and column. Not reentrant. + fn is_low(&self) -> Result { + self.col.borrow_mut().set_low()?; + let out = self.row.is_low()?; + self.col.borrow_mut().set_high()?; + Ok(out) + } +} + +/// Define a new struct representing your keypad matrix circuit. +/// +/// Every pin has a unique type, depending on its pin number and its current +/// mode. This struct is where you specify which pin types will be used in the +/// rows and columns of the keypad matrix. All the row pins must implement the +/// `InputPin` trait, and the column pins must implement the `OutputPin` trait. +/// The associated `Error` type of the `InputPin` and `OutputPin` traits must be +/// the same for every row and column pin, and you must specify it after your +/// struct name with `` +/// +/// You can specify the visibility of the struct (eg. `pub`) as usual, and add +/// doc comments using the `#[doc="..."]` attribute. +/// +/// Don't access or modify the struct's fields directly. Instead, use +/// the methods implemented by this macro, documented here: +/// [`example_generated::ExampleKeypad`](./example_generated/struct.ExampleKeypad.html) +/// +/// # Example +/// +/// ``` +/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))] +/// #[macro_use] +/// extern crate keypad; +/// +/// use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp}; +/// use core::convert::Infallible; +/// +/// keypad_struct! { +/// #[doc="My super-special keypad."] +/// pub struct ExampleKeypad { +/// rows: ( +/// mock_hal::gpioa::PA0>, +/// mock_hal::gpioa::PA1>, +/// mock_hal::gpioa::PA2>, +/// mock_hal::gpioa::PA3>, +/// ), +/// columns: ( +/// mock_hal::gpioa::PA4>, +/// mock_hal::gpioa::PA5>, +/// mock_hal::gpioa::PA6>, +/// mock_hal::gpioa::PA7>, +/// mock_hal::gpioa::PA8>, +/// ), +/// } +/// } +/// +/// # fn main() { +/// # } +/// ``` +/// +/// # Safety +/// +/// This macro uses `unsafe` to create an array with uninitialized memory, which +/// is then immediately initialized in a loop. This is fine as long as there is +/// not a bug in how the macro calculates the dimensions of the array. + +// There are two reasons why this big, scary macro is necessary: +// +// 1) Every single pin has a unique type, and we don't know which pins will be used. We know that +// they all implement a certain trait, but that doesn't help much because it doesn't tell us the +// size of the type, so we can't directly stick it into the struct. If we could use dynamic +// allocation, we could just store pins on the heap as boxed trait objects. But this crate needs +// to work on embedded platforms without an allocator! So, we use this macro to generate a +// struct containing the exact, concrete pin types the user provides. +// +// 2) We don't know how many pins there will be, because the keypad could have any number of rows +// and columns. That makes it hard to implement `decompose()`, which needs to iterate over the +// row and column pins. We can't store pins in arrays because they all have unique types, but we +// can store them in tuples instead. The problem is that you can't actually iterate over a tuple, +// and even indexing into arbitrary fields of a tuple using a macro is stupidly hard. The best +// approach I could come up with was to repeatedly destructure the tuple with different patterns, +// like this: +// +// let tuple = (0, 1, 2); +// let array = [ +// { +// let (ref x, ..) = tuple; +// x +// }, +// { +// let (_, ref x, ..) = tuple; +// x +// }, +// { +// let (_, _, ref x, ..) = tuple; +// x +// }, +// ]; +// for reference in array.into_iter() { +// // ... +// } +// +// So that's how the `keypad_struct!()` macro iterates over tuples of pins. It counts the length of +// the tuple (also tricky!), creates patterns with increasing numbers of underscores, and uses them +// to build up a temporary array of references that it can iterate over. Luckily this code only +// needs to be run once, in `decompose()`, and not every time we read from a pin. +// +// I can't think of any simpler design that still has a convenient API and allows the keypad struct +// to own the row and column pins. If they weren't owned, the crate would be less convenient to use +// but could provide a generic Keypad struct like this: +// +// pub struct Keypad<'a, E, const R: usize, const C: usize> { +// rows: [&'a dyn InputPin; R], +// columns: [&'a RefCell>; C], +// } +// +#[macro_export] +macro_rules! keypad_struct { + ( + $(#[$attributes:meta])* $visibility:vis struct $struct_name:ident { + rows: ( $($row_type:ty),* $(,)* ), + columns: ( $($col_type:ty),* $(,)* ), + } + ) => { + compile_error!("You must specify the associated `Error` type of the row and column pins'\ + `InputPin` and `OutputPin` traits.\n\ + Example: `struct MyStruct { ... }`"); + }; + ( + $(#[$attributes:meta])* $visibility:vis struct $struct_name:ident { + rows: ( $($row_type:ty),* $(,)* ), + columns: ( $($col_type:ty),* $(,)* ), + } + ) => { + $(#[$attributes])* $visibility struct $struct_name { + /// The input pins used for reading each row. + rows: ($($row_type),* ,), + /// The output pins used for scanning through each column. They're + /// wrapped in RefCells so that we can change their state even if we + /// only have shared/immutable reference to them. This lets us + /// actively scan the matrix when reading the state of a virtual + /// `KeypadInput` pin. + columns: ($($crate::_core::cell::RefCell<$col_type>),* ,), + } + + impl $struct_name { + /// Get a 2d array of embedded-hal input pins, each representing one + /// key in the keypad matrix. + #[allow(dead_code)] + $visibility fn decompose<'a>(&'a self) -> + keypad_struct!( + @array2d_type + $crate::KeypadInput<'a, $error_type>, + ($($row_type),*) + ($($crate::_core::cell::RefCell<$col_type>),*) + ) + { + + let rows: [ + &dyn $crate::embedded_hal::digital::v2::InputPin; + keypad_struct!(@count $($row_type)*) + ] + = keypad_struct!(@tuple self.rows, ($($row_type),*)); + + let columns: [ + &$crate::_core::cell::RefCell>; + keypad_struct!(@count $($col_type)*) + ] + = keypad_struct!(@tuple self.columns, ($($col_type),*)); + + // Create an uninitialized 2d array of MaybeUninit. + let mut out: keypad_struct!( + @array2d_type + $crate::_core::mem::MaybeUninit<$crate::KeypadInput<'a, $error_type>>, + ($($row_type),*) + ($($crate::_core::cell::RefCell<$col_type>),*) + ) = unsafe { + $crate::_core::mem::MaybeUninit::uninit().assume_init() + }; + + // Initialize each element with a KeypadInput struct + for r in 0..rows.len() { + for c in 0..columns.len() { + out[r][c].write($crate::KeypadInput::new(rows[r], columns[c])); + } + } + // All elements are initialized. Transmute the array to the initialized type. + unsafe { $crate::_core::mem::transmute::<_, _>(out) } + } + + /// Give back ownership of the row and column pins. + /// + /// This consumes the keypad struct. All references to its virtual + /// `KeypadInput` pins must have gone out of scope before you try to + /// call `.release()`, or it will fail to compile. + /// + /// The column pins will be returned inside of `RefCell`s (because + /// macros are hard). You can use `.into_inner()` to extract + /// each column pin from its `RefCell`. + #[allow(dead_code)] + $visibility fn release(self) ->(($($row_type),* ,), ($($crate::_core::cell::RefCell<$col_type>),* ,)) { + (self.rows, self.columns) + } + } + }; + (@array2d_type $element_type:ty, ($($row:ty),*) ($($col:ty),*) ) => { + [keypad_struct!(@array1d_type $element_type, ($($col),*)) ; keypad_struct!(@count $($row)*)] + }; + (@array1d_type $element_type:ty, ($($col:ty),*)) => { + [$element_type ; keypad_struct!(@count $($col)*)] + }; + (@count $($token_trees:tt)*) => { + 0usize $(+ keypad_struct!(@replace $token_trees 1usize))* + }; + (@replace $_t:tt $sub:expr) => { + $sub + }; + (@underscore $unused:tt) => { + _ + }; + (@destructure_ref $tuple:expr, ($($repeat_n:ty),*)) => { + { + let ( + $(keypad_struct!(@underscore $repeat_n),)* + ref nth, ..) = $tuple; + nth + } + }; + (@tuple_helper $tuple:expr, ($head:ty), ($($result:expr),* $(,)*)) => { + [ + keypad_struct!(@destructure_ref $tuple, ()), + $($result),* + ] + }; + (@tuple_helper $tuple:expr, ($head:ty $(,$repeats:ty)* $(,)*), ($($result:expr),* $(,)*)) => { + keypad_struct!( + @tuple_helper $tuple, ($($repeats),*), + ( + keypad_struct!(@destructure_ref $tuple, ($($repeats),*)), + $($result),* + ) + ) + }; + (@tuple $tuple:expr, ($($repeats:ty),*)) => { + keypad_struct!(@tuple_helper $tuple, ($($repeats),*) , ()) + }; +} + +/// Create an instance of the struct you defined with the `keypad_struct!()` macro.. +/// +/// The pin numbers and modes will need to match the ones you specified with `keypad_struct!()`. +/// +/// ``` +/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))] +/// # #[macro_use] +/// # extern crate keypad; +/// # use core::convert::Infallible; +/// # use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp}; +/// # use keypad::mock_hal::{GpioExt, GPIOA}; +/// # keypad_struct!{ +/// # pub struct ExampleKeypad{ +/// # rows: ( +/// # mock_hal::gpioa::PA0>, +/// # mock_hal::gpioa::PA1>, +/// # mock_hal::gpioa::PA2>, +/// # mock_hal::gpioa::PA3>, +/// # ), +/// # columns: ( +/// # mock_hal::gpioa::PA4>, +/// # mock_hal::gpioa::PA5>, +/// # mock_hal::gpioa::PA6>, +/// # mock_hal::gpioa::PA7>, +/// # mock_hal::gpioa::PA8>, +/// # ), +/// # } +/// # } +/// # fn main() { +/// let pins = GPIOA::split(); +/// +/// let keypad = keypad_new!(ExampleKeypad { +/// rows: ( +/// pins.pa0.into_pull_up_input(), +/// pins.pa1.into_pull_up_input(), +/// pins.pa2.into_pull_up_input(), +/// pins.pa3.into_pull_up_input(), +/// ), +/// columns: ( +/// pins.pa4.into_open_drain_output(), +/// pins.pa5.into_open_drain_output(), +/// pins.pa6.into_open_drain_output(), +/// pins.pa7.into_open_drain_output(), +/// pins.pa8.into_open_drain_output(), +/// ), +/// }); +/// # } +/// ``` +#[macro_export] +macro_rules! keypad_new { + ( $struct_name:ident { + rows: ( $($row_val:expr),* $(,)* ), + columns: ( $($col_val:expr),* $(,)* ), + }) => { + $struct_name { + rows: ($($row_val),* ,), + columns: ($($crate::_core::cell::RefCell::new($col_val)),* ,), + } + }; +} diff --git a/src/main.rs b/src/main.rs index 03545d3..bf3118e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use esp_hal::{ delay::Delay, gpio::{ Gpio10, Gpio2, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, Input, InputPin, Io, Level, - Output, OutputPin, Pull, NO_PIN, + Output, OutputOpenDrain, OutputPin, Pull, NO_PIN, }, peripherals::Peripherals, prelude::*, @@ -96,26 +96,43 @@ fn main() -> ! { log::info!("Printing welcome msg (DONE)"); log::info!("Full update..."); - let _ = driver.full_update(&display); + // let _ = driver.full_update(&display); log::info!("Full update (DONE)"); log::info!("Sleeping for 0.5s..."); - delay.delay(500.millis()); + // delay.delay(500.millis()); - let mut kbd = Keyboard { - rows: Rows { - gpio2: Input::new(io.pins.gpio2, Pull::Up), - gpio3: Input::new(io.pins.gpio3, Pull::Up), - gpio4: Input::new(io.pins.gpio4, Pull::Up), - gpio5: Input::new(io.pins.gpio5, Pull::Up), - }, - cols: Cols { - gpio6: Output::new(io.pins.gpio6, Level::Low), - gpio7: Output::new(io.pins.gpio7, Level::Low), - gpio8: Output::new(io.pins.gpio8, Level::Low), - gpio10: Output::new(io.pins.gpio10, Level::Low), - }, - }; + let mut gpio2 = Input::new(io.pins.gpio2, Pull::Up); + let mut gpio3 = Input::new(io.pins.gpio3, Pull::Up); + let mut gpio4 = Input::new(io.pins.gpio4, Pull::Up); + let mut gpio5 = Input::new(io.pins.gpio5, Pull::Up); + + let mut gpio6 = OutputOpenDrain::new(io.pins.gpio6, Level::Low, Pull::Up); + let mut gpio7 = OutputOpenDrain::new(io.pins.gpio7, Level::Low, Pull::Up); + let mut gpio8 = OutputOpenDrain::new(io.pins.gpio8, Level::Low, Pull::Up); + let mut gpio10 = OutputOpenDrain::new(io.pins.gpio10, Level::Low, Pull::Up); + + let mut kbd = Keypad(( + (&mut gpio2, &mut gpio6), + (&mut gpio3, &mut gpio6), + (&mut gpio4, &mut gpio6), + (&mut gpio5, &mut gpio6), + // -------------------------------------- + (&mut gpio2, &mut gpio7), + (&mut gpio3, &mut gpio7), + (&mut gpio4, &mut gpio7), + (&mut gpio5, &mut gpio7), + // -------------------------------------- + (&mut gpio2, &mut gpio8), + (&mut gpio3, &mut gpio8), + (&mut gpio4, &mut gpio8), + (&mut gpio5, &mut gpio8), + // -------------------------------------- + (&mut gpio2, &mut gpio10), + (&mut gpio3, &mut gpio10), + (&mut gpio4, &mut gpio10), + (&mut gpio5, &mut gpio10), + )); let mut ctx = Context { button_pressed: None, @@ -125,7 +142,7 @@ fn main() -> ! { display.clear(TriColor::White); app.draw(&mut display); - driver.full_update(&display).unwrap(); + // driver.full_update(&display).unwrap(); driver.sleep().unwrap(); loop { @@ -188,79 +205,78 @@ struct Rows<'d> { gpio5: Input<'d, Gpio5>, // } struct Cols<'d> { - gpio6: Output<'d, Gpio6>, // - gpio7: Output<'d, Gpio7>, // - gpio8: Output<'d, Gpio8>, // - gpio10: Output<'d, Gpio10>, // + gpio6: OutputOpenDrain<'d, Gpio6>, // + gpio7: OutputOpenDrain<'d, Gpio7>, // + gpio8: OutputOpenDrain<'d, Gpio8>, // + gpio10: OutputOpenDrain<'d, Gpio10>, // } -struct Keyboard<'d> { - rows: Rows<'d>, - cols: Cols<'d>, -} -impl<'d> Keyboard<'d> { +pub type OOD<'d, T> = OutputOpenDrain<'d, T>; + +struct Keypad<'d>( + pub ( + // -------------------------------------- + (&'d mut Input<'d, Gpio2>, &'d mut OOD<'d, Gpio6>), + (&'d mut Input<'d, Gpio3>, &'d mut OOD<'d, Gpio6>), + (&'d mut Input<'d, Gpio4>, &'d mut OOD<'d, Gpio6>), + (&'d mut Input<'d, Gpio5>, &'d mut OOD<'d, Gpio6>), + // -------------------------------------- + (&'d mut Input<'d, Gpio2>, &'d mut OOD<'d, Gpio7>), + (&'d mut Input<'d, Gpio3>, &'d mut OOD<'d, Gpio7>), + (&'d mut Input<'d, Gpio4>, &'d mut OOD<'d, Gpio7>), + (&'d mut Input<'d, Gpio5>, &'d mut OOD<'d, Gpio7>), + // -------------------------------------- + (&'d mut Input<'d, Gpio2>, &'d mut OOD<'d, Gpio8>), + (&'d mut Input<'d, Gpio3>, &'d mut OOD<'d, Gpio8>), + (&'d mut Input<'d, Gpio4>, &'d mut OOD<'d, Gpio8>), + (&'d mut Input<'d, Gpio5>, &'d mut OOD<'d, Gpio8>), + // -------------------------------------- + (&'d mut Input<'d, Gpio2>, &'d mut OOD<'d, Gpio10>), + (&'d mut Input<'d, Gpio3>, &'d mut OOD<'d, Gpio10>), + (&'d mut Input<'d, Gpio4>, &'d mut OOD<'d, Gpio10>), + (&'d mut Input<'d, Gpio5>, &'d mut OOD<'d, Gpio10>), + ), +); +impl<'d> Keypad<'d> { pub fn pressed(&mut self) -> Option