From 71288814ffb28a01e799c39e11fd38acb00a3cb7 Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:42:46 -0400 Subject: [PATCH] direct pin matrix support --- .../docs/getting-started/matrix-and-layout.md | 66 ++++++-- rumcake-macros/src/hw/nrf.rs | 2 + rumcake-macros/src/hw/rp.rs | 2 + rumcake-macros/src/hw/stm32.rs | 2 + rumcake-macros/src/keyboard.rs | 108 +++++++++--- rumcake-macros/src/lib.rs | 12 +- rumcake/src/keyboard.rs | 154 ++++++++++++------ rumcake/src/lib.rs | 1 + 8 files changed, 262 insertions(+), 85 deletions(-) diff --git a/docs/src/content/docs/getting-started/matrix-and-layout.md b/docs/src/content/docs/getting-started/matrix-and-layout.md index 61201b1..18bb445 100644 --- a/docs/src/content/docs/getting-started/matrix-and-layout.md +++ b/docs/src/content/docs/getting-started/matrix-and-layout.md @@ -41,7 +41,7 @@ impl Keyboard for MyKeyboard { In the [templates](https://github.com/Univa/rumcake-templates), you will see that to implement a keyboard matrix, you need to implement the `KeyboardMatrix` trait -using the `build_matrix!` macro: +using one of the `build__matrix!` macros: ```rust ins={13-20} use rumcake::keyboard; @@ -56,18 +56,15 @@ impl Keyboard for MyKeyboard { const SERIAL_NUMBER: &'static str = "1"; } -use rumcake::keyboard::{build_matrix, KeyboardMatrix}; +use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { - build_matrix! { + build_standard_matrix! { { PB2 PB10 PB11 PA3 } // Rows { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns } } ``` -Rows are defined first, followed by the columns. Row and columns are enumerated left-to-right, starting -from 0. In this example, `PB2` is row 0 and `PA3` is row 3. - The identifiers used for the matrix pins must match the identifiers used by the respective HAL (hardware abstraction library) for your MCU. The linked sites below have a dropdown menu at the top to allow you to select a chip. Choose your chip to see what pins are available: @@ -78,6 +75,14 @@ the top to allow you to select a chip. Choose your chip to see what pins are ava After defining your matrix, you can set up your [keyboard layout](#keyboard-layout). If you have a duplex matrix, consider [checking that section](#duplex-matrix) before setting up your keyboard layout. +:::note +The example above assumes a matrix a standard matrix (switches wired in rows and columns, with diodes). +Rows are defined first, followed by the columns. Row and columns are enumerated left-to-right, starting +from 0. In this example, `PB2` is row 0 and `PA3` is row 3. + +For other matrix types, see the [Other Matrix Types](#other-matrix-types) section. +::: + # Keyboard Layout To implement a keyboard layout, you must implement the `KeyboardLayout` trait. @@ -100,9 +105,9 @@ impl Keyboard for MyKeyboard { const SERIAL_NUMBER: &'static str = "1"; } -use rumcake::keyboard::{build_matrix, KeyboardMatrix}; +use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { - build_matrix! { + build_standard_matrix! { { PB2 PB10 PB11 PA3 } // Rows { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns } @@ -137,6 +142,45 @@ impl KeyboardLayout for MyKeyboard { Congratulations! You have implemented a basic keyboard. You can now move onto building and flashing your firmware, or try implementing additional features in the "Features" sidebar. +# Other matrix types + +## Direct pin matrix (diodeless matrix) + +If your MCU pins are connected directly to a switch (as opposed to pins being connected to a row / column of switches), +then you can use the `build_direct_pin_matrix!` macro instead. + +```rust ins={3-9} +// rest of your config... + +use rumcake::keyboard::{build_direct_pin_matrix, KeyboardMatrix}; +impl KeyboardMatrix for MyKeyboard { + build_direct_pin_matrix! { + [ PB2 PB10 PB11 PA3 ] + [ PB12 PB1 PB0 No ] + } +} + +use rumcake::keyboard::{build_layout, KeyboardLayout}; +impl KeyboardLayout for MyKeyboard { + build_layout! { + { + [ Tab Q W E ] + [ LCtrl A S D ] + } + { + [ LGui F1 F2 F3 ] + [ t t t t ] + } + } +} +``` + +Each pin will map directly to a (row, column) position, which determines the key in the layout it corresponds to. +Each row must have the same number of columns. If there are matrix positions that are unused, you can use `No` to ignore them. + +In this example, the switch connected to `PB10` maps to row 0, column 1. Based on the implementation of `KeyboardLayout`, this +switch will correspond to the `Q`/`F1` key. + # Revisualizing a matrix (e.g. duplex matrix) Sometimes, your keyboard might have a complicated matrix scheme that could make it @@ -161,7 +205,7 @@ This can be useful for your keyboard layout config, or your backlight matrix con ```rust del={50-63} ins={1-26,64-75} // This creates a `remap!` macro that you can use in other parts of your config. remap_matrix! { - // This has the same number of rows and columns that you specified in `build_matrix!` + // This has the same number of rows and columns that you specified in your matrix. // Note that `No` is used to denote an unused matrix position. { [ K00 K01 K02 K03 K04 K05 K06 K07 ] @@ -198,9 +242,9 @@ impl Keyboard for MyKeyboard { const SERIAL_NUMBER: &'static str = "1"; } -use rumcake::keyboard::{build_matrix, KeyboardMatrix}; +use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { - build_matrix! { + build_standard_matrix! { { PB3 PB4 PA15 PB5 PA0 PA1 PB2 PB10 PB11 PA3 } // Rows { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 } // Columns } diff --git a/rumcake-macros/src/hw/nrf.rs b/rumcake-macros/src/hw/nrf.rs index 4cb82f9..6370c2c 100644 --- a/rumcake-macros/src/hw/nrf.rs +++ b/rumcake-macros/src/hw/nrf.rs @@ -4,6 +4,8 @@ use quote::quote; use syn::punctuated::Punctuated; use syn::Token; +pub const HAL_CRATE: &'static str = "embassy_nrf"; + pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { diff --git a/rumcake-macros/src/hw/rp.rs b/rumcake-macros/src/hw/rp.rs index d91c934..b19f44f 100644 --- a/rumcake-macros/src/hw/rp.rs +++ b/rumcake-macros/src/hw/rp.rs @@ -4,6 +4,8 @@ use quote::quote; use syn::punctuated::Punctuated; use syn::Token; +pub const HAL_CRATE: &'static str = "embassy_rp"; + pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { diff --git a/rumcake-macros/src/hw/stm32.rs b/rumcake-macros/src/hw/stm32.rs index 2bc46ef..d765d99 100644 --- a/rumcake-macros/src/hw/stm32.rs +++ b/rumcake-macros/src/hw/stm32.rs @@ -4,6 +4,8 @@ use quote::quote; use syn::punctuated::Punctuated; use syn::Token; +pub const HAL_CRATE: &'static str = "embassy_stm32"; + pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { diff --git a/rumcake-macros/src/keyboard.rs b/rumcake-macros/src/keyboard.rs index 725115e..c62b159 100644 --- a/rumcake-macros/src/keyboard.rs +++ b/rumcake-macros/src/keyboard.rs @@ -7,7 +7,7 @@ use proc_macro_error::OptionExt; use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, Parser}; use syn::spanned::Spanned; -use syn::{braced, bracketed, ItemStruct}; +use syn::{braced, bracketed, ItemStruct, PathSegment}; #[derive(Debug, FromMeta, Default)] #[darling(default)] @@ -364,12 +364,9 @@ pub(crate) fn keyboard_main( // Keyboard setup, and matrix polling task if !keyboard.no_matrix { - initialization.extend(quote! { - let (matrix, debouncer) = ::rumcake::keyboard::setup_keyboard_matrix(#kb_name); - }); spawning.extend(quote! { spawner - .spawn(::rumcake::matrix_poll!(#kb_name, matrix, debouncer)) + .spawn(::rumcake::matrix_poll!(#kb_name)) .unwrap(); }); } @@ -727,14 +724,14 @@ impl ToTokens for OptionalItem { } #[derive(Debug)] -pub struct MatrixDefinition { +pub struct StandardMatrixDefinition { pub row_brace: syn::token::Brace, - pub rows: Vec, + pub rows: Vec, pub col_brace: syn::token::Brace, - pub cols: Vec, + pub cols: Vec, } -impl syn::parse::Parse for MatrixDefinition { +impl syn::parse::Parse for StandardMatrixDefinition { fn parse(input: syn::parse::ParseStream) -> syn::Result { let row_content; let row_brace = braced!(row_content in input); @@ -771,26 +768,93 @@ impl syn::parse::Parse for MatrixDefinition { } } -pub fn build_matrix(input: MatrixDefinition) -> TokenStream { - let MatrixDefinition { rows, cols, .. } = input; +pub fn build_standard_matrix(input: StandardMatrixDefinition) -> TokenStream { + let StandardMatrixDefinition { rows, cols, .. } = input; let row_count = rows.len(); let col_count = cols.len(); + let hal_name: PathSegment = syn::parse_str(crate::hw::HAL_CRATE).unwrap(); + quote! { const MATRIX_ROWS: usize = #row_count; const MATRIX_COLS: usize = #col_count; - fn build_matrix( - ) -> Result<::rumcake::keyberon::matrix::Matrix, impl ::rumcake::embedded_hal::digital::v2::OutputPin, { Self::MATRIX_COLS }, { Self::MATRIX_ROWS }>, core::convert::Infallible> { - ::rumcake::keyberon::matrix::Matrix::new([ - #( - ::rumcake::hw::mcu::input_pin!(#cols) - ),* - ], [ - #( - ::rumcake::hw::mcu::output_pin!(#rows) - ),* - ]) + fn get_matrix() -> &'static ::rumcake::keyboard::PollableMatrix { + static MATRIX: ::rumcake::once_cell::sync::OnceCell< + ::rumcake::keyboard::PollableMatrix< + ::rumcake::keyboard::PollableStandardMatrix< + ::rumcake::hw::mcu::#hal_name::gpio::Input<'static>, + ::rumcake::hw::mcu::#hal_name::gpio::Output<'static>, + #col_count, + #row_count + > + > + > = ::rumcake::once_cell::sync::OnceCell::new(); + MATRIX.get_or_init(|| { + ::rumcake::keyboard::PollableMatrix::new( + ::rumcake::keyboard::setup_standard_keyboard_matrix( + [ + #( + ::rumcake::hw::mcu::input_pin!(#cols) + ),* + ], + [ + #( + ::rumcake::hw::mcu::output_pin!(#rows) + ),* + ], + Self::DEBOUNCE_MS + ).unwrap() + ) + }) + } + } +} + +pub fn build_direct_pin_matrix(input: MatrixLike>) -> TokenStream { + let values = input.rows.iter().map(|row| { + let items = row.cols.iter().map(|item| match item { + OptionalItem::None => quote! { None }, + OptionalItem::Some(pin_ident) => { + quote! { Some(::rumcake::hw::mcu::input_pin!(#pin_ident)) } + } + }); + quote! { #(#items),* } + }); + + let row_count = input.rows.len(); + let col_count = input + .rows + .first() + .expect_or_abort("At least one row is required.") + .cols + .len(); + + let hal_name: PathSegment = syn::parse_str(crate::hw::HAL_CRATE).unwrap(); + + quote! { + const MATRIX_ROWS: usize = #row_count; + const MATRIX_COLS: usize = #col_count; + fn get_matrix() -> &'static ::rumcake::keyboard::PollableMatrix { + static MATRIX: ::rumcake::once_cell::sync::OnceCell< + ::rumcake::keyboard::PollableMatrix< + ::rumcake::keyboard::PollableDirectPinMatrix< + ::rumcake::hw::mcu::#hal_name::gpio::Input<'static>, + #col_count, + #row_count + > + > + > = ::rumcake::once_cell::sync::OnceCell::new(); + MATRIX.get_or_init(|| { + ::rumcake::keyboard::PollableMatrix::new( + ::rumcake::keyboard::setup_direct_pin_keyboard_matrix( + [ + #([ #values ]),* + ], + Self::DEBOUNCE_MS + ).unwrap() + ) + }) } } } diff --git a/rumcake-macros/src/lib.rs b/rumcake-macros/src/lib.rs index 35cedf0..b3293c7 100644 --- a/rumcake-macros/src/lib.rs +++ b/rumcake-macros/src/lib.rs @@ -132,9 +132,15 @@ pub fn keyboard_main( } #[proc_macro] -pub fn build_matrix(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let matrix = parse_macro_input!(input as keyboard::MatrixDefinition); - keyboard::build_matrix(matrix).into() +pub fn build_standard_matrix(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let matrix = parse_macro_input!(input as keyboard::StandardMatrixDefinition); + keyboard::build_standard_matrix(matrix).into() +} + +#[proc_macro] +pub fn build_direct_pin_matrix(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let matrix = parse_macro_input!(input as keyboard::MatrixLike>); + keyboard::build_direct_pin_matrix(matrix).into() } #[proc_macro] diff --git a/rumcake/src/keyboard.rs b/rumcake/src/keyboard.rs index cc3a4b9..e207b23 100644 --- a/rumcake/src/keyboard.rs +++ b/rumcake/src/keyboard.rs @@ -13,7 +13,7 @@ use embedded_hal::digital::v2::{InputPin, OutputPin}; use heapless::Vec; use keyberon::debounce::Debouncer; use keyberon::layout::{CustomEvent, Event, Layers, Layout as KeyberonLayout}; -use keyberon::matrix::Matrix; +use keyberon::matrix::{DirectPinMatrix, Matrix}; use usbd_human_interface_device::device::consumer::MultipleConsumerReport; use usbd_human_interface_device::{ device::keyboard::NKROBootKeyboardReport, page::Keyboard as KeyboardKeycode, @@ -25,7 +25,9 @@ pub use usbd_human_interface_device::page::Consumer; use crate::hw::mcu::RawMutex; use crate::hw::CURRENT_OUTPUT_STATE; -pub use rumcake_macros::{build_layout, build_matrix, remap_matrix}; +pub use rumcake_macros::{ + build_direct_pin_matrix, build_layout, build_standard_matrix, remap_matrix, +}; /// Basic keyboard trait that must be implemented to use rumcake. Defines basic keyboard information. pub trait Keyboard { @@ -115,26 +117,18 @@ pub trait KeyboardMatrix { /// Number of matrix columns. /// - /// It is recommended to use [`build_matrix`] to set this constant. + /// It is recommended to use one of the `build_*_matrix` macros to set this constant. const MATRIX_COLS: usize; /// Number of matrix rows. /// - /// It is recommended to use [`build_matrix`] to set this constant. + /// It is recommended to use one of the `build_*_matrix` macros to set this constant. const MATRIX_ROWS: usize; /// Create the keyboard matrix by initializing a set of GPIO pins to use for columns and rows. /// - /// It is recommended to use [`build_matrix`] to implement this function. - fn build_matrix() -> Result< - Matrix< - impl InputPin, - impl OutputPin, - { Self::MATRIX_COLS }, - { Self::MATRIX_ROWS }, - >, - Infallible, - >; + /// It is recommended to use one of the `build_*_matrix` macros to set this constant. + fn get_matrix() -> &'static PollableMatrix; /// Optional function to remap a matrix position to a position on the keyboard layout defined /// by [`KeyboardLayout::get_layout`]. @@ -146,24 +140,38 @@ pub trait KeyboardMatrix { } } -pub fn setup_keyboard_matrix( - _k: K, -) -> ( - Matrix< - impl InputPin, - impl OutputPin, - { K::MATRIX_COLS }, - { K::MATRIX_ROWS }, - >, - Debouncer<[[bool; K::MATRIX_COLS]; K::MATRIX_ROWS]>, -) { - let matrix = K::build_matrix().unwrap(); - let debouncer = Debouncer::new( - [[false; K::MATRIX_COLS]; K::MATRIX_ROWS], - [[false; K::MATRIX_COLS]; K::MATRIX_ROWS], - K::DEBOUNCE_MS, - ); - (matrix, debouncer) +/// Setup a traditional keyboard matrix with diodes, with a debouncer. The output of this function +/// can be passed to the matrix polling task directly. +pub fn setup_standard_keyboard_matrix< + E, + I: InputPin, + O: OutputPin, + const CS: usize, + const RS: usize, +>( + cols: [I; CS], + rows: [O; RS], + debounce_ms: u16, +) -> Result, E> { + let matrix = Matrix::new(cols, rows)?; + let debouncer = Debouncer::new([[false; CS]; RS], [[false; CS]; RS], debounce_ms); + Ok((matrix, debouncer)) +} + +/// Setup a diodeless keyboard matrix, with a debouncer. The output of this function can be passed +/// to the matrix polling task directly. +pub fn setup_direct_pin_keyboard_matrix< + E, + I: InputPin, + const CS: usize, + const RS: usize, +>( + pins: [[Option; CS]; RS], + debounce_ms: u16, +) -> Result, E> { + let matrix = DirectPinMatrix::new(pins)?; + let debouncer = Debouncer::new([[false; CS]; RS], [[false; CS]; RS], debounce_ms); + Ok((matrix, debouncer)) } /// Custom keycodes used to interact with other rumcake features. @@ -203,6 +211,66 @@ pub enum Keycode { Bluetooth(crate::bluetooth::BluetoothCommand), } +pub struct PollableMatrix { + matrix: Mutex, +} + +impl PollableMatrix { + pub const fn new(m: T) -> Self { + Self { + matrix: Mutex::new(m), + } + } +} + +/// Trait that allows you to implement matrix polling functionality. This trait is already +/// implemented for all of the existing [`keyberon::matrix`] structs. You can also implement this +/// trait for your own types to write custom matrix polling logic that can be used with the matrix +/// polling task. +pub trait Pollable { + /// Poll the matrix for events + fn events(&mut self) -> impl Iterator; +} + +pub type PollableStandardMatrix< + I: InputPin, + O: OutputPin, + const CS: usize, + const RS: usize, +> = (Matrix, Debouncer<[[bool; CS]; RS]>); + +impl< + I: InputPin, + O: OutputPin, + const CS: usize, + const RS: usize, + > Pollable for PollableStandardMatrix +{ + fn events(&mut self) -> impl Iterator { + self.1.events( + self.0 + .get_with_delay(|| { + embassy_time::block_for(Duration::from_ticks(2)); + }) + .unwrap(), + ) + } +} + +pub type PollableDirectPinMatrix< + I: InputPin, + const CS: usize, + const RS: usize, +> = (DirectPinMatrix, Debouncer<[[bool; CS]; RS]>); + +impl, const CS: usize, const RS: usize> Pollable + for PollableDirectPinMatrix +{ + fn events(&mut self) -> impl Iterator { + self.1.events(self.0.get().unwrap()) + } +} + /// Channel with keyboard events polled from the swtich matrix /// /// The coordinates received will be remapped according to the implementation of @@ -210,26 +278,14 @@ pub enum Keycode { pub(crate) static POLLED_EVENTS_CHANNEL: Channel = Channel::new(); #[rumcake_macros::task] -pub async fn matrix_poll( - _k: K, - mut matrix: Matrix< - impl InputPin, - impl OutputPin, - { K::MATRIX_COLS }, - { K::MATRIX_ROWS }, - >, - mut debouncer: Debouncer<[[bool; K::MATRIX_COLS]; K::MATRIX_ROWS]>, -) { +pub async fn matrix_poll(_k: K) { + let matrix = K::get_matrix(); + loop { { debug!("[KEYBOARD] Scanning matrix"); - let events = debouncer.events( - matrix - .get_with_delay(|| { - embassy_time::block_for(Duration::from_ticks(2)); - }) - .unwrap(), - ); + let mut matrix = matrix.matrix.lock().await; + let events = matrix.events(); for e in events { let (row, col) = e.coord(); let (new_row, new_col) = K::remap_to_layout(row, col); diff --git a/rumcake/src/lib.rs b/rumcake/src/lib.rs index 19a0655..6bd7f9f 100644 --- a/rumcake/src/lib.rs +++ b/rumcake/src/lib.rs @@ -116,6 +116,7 @@ pub use embedded_hal_async; pub use embedded_io_async; pub use embedded_storage_async; pub use keyberon; +pub use once_cell; pub use rumcake_macros::keyboard_main as keyboard;