diff --git a/rmk-config/src/toml_config.rs b/rmk-config/src/toml_config.rs index 16d2c483..f11a7fd8 100644 --- a/rmk-config/src/toml_config.rs +++ b/rmk-config/src/toml_config.rs @@ -149,12 +149,6 @@ pub struct SplitBoardConfig { pub ble_addr: Option<[u8; 6]>, /// Serial config, the vector length should be 1 for peripheral pub serial: Option>, - /* - /// Input pin config - pub input_pins: Vec, - /// Output pin config - pub output_pins: Vec, - */ pub matrix: MatrixConfig, } diff --git a/rmk-macro/src/split/central.rs b/rmk-macro/src/split/central.rs index 346ca985..c1f41819 100644 --- a/rmk-macro/src/split/central.rs +++ b/rmk-macro/src/split/central.rs @@ -94,12 +94,11 @@ fn expand_split_central( )); // `generic_arg_infer` is a nightly feature. Const arguments cannot yet be inferred with `_` in stable now. // So we need to declaring them in advance. - /* let rows = keyboard_config.layout.rows as usize; let cols = keyboard_config.layout.cols as usize; let size = keyboard_config.layout.rows as usize * keyboard_config.layout.cols as usize; let layers = keyboard_config.layout.layers as usize; - let low_active = peripheral_config.matrix.direct_pin_low_active; + let low_active = split_config.central.matrix.direct_pin_low_active; matrix_config.extend(quote! { pub(crate) const ROW: usize = #rows; pub(crate) const COL: usize = #cols; @@ -107,7 +106,6 @@ fn expand_split_central( pub(crate) const LAYER_NUM: usize = #layers; let low_active = #low_active; }); - */ } } @@ -189,20 +187,39 @@ fn expand_split_central_entry( } else { format_ident!("{}", "usb") }; - let central_task = quote! { - ::rmk::split::central::run_rmk_split_central::< - ::embassy_stm32::gpio::Input<'_>, - ::embassy_stm32::gpio::Output<'_>, - ::embassy_stm32::#usb_mod_path::Driver<'_, ::embassy_stm32::peripherals::#usb_name>, - ::embassy_stm32::flash::Flash<'_, ::embassy_stm32::flash::Blocking>, - ROW, - COL, - #central_row, - #central_col, - #central_row_offset, - #central_col_offset, - NUM_LAYER, - >(input_pins, output_pins, driver, flash, &mut get_default_keymap(), keyboard_config, spawner) + let low_active = split_config.central.matrix.direct_pin_low_active; + let central_task = match split_config.central.matrix.matrix_type { + MatrixType::normal => quote! { + ::rmk::split::central::run_rmk_split_central::< + ::embassy_stm32::gpio::Input<'_>, + ::embassy_stm32::gpio::Output<'_>, + ::embassy_stm32::#usb_mod_path::Driver<'_, ::embassy_stm32::peripherals::#usb_name>, + ::embassy_stm32::flash::Flash<'_, ::embassy_stm32::flash::Blocking>, + ROW, + COL, + #central_row, + #central_col, + #central_row_offset, + #central_col_offset, + NUM_LAYER, + >(input_pins, output_pins, driver, flash, &mut get_default_keymap(), keyboard_config, , spawner) + }, + MatrixType::direct_pin => quote! { + ::rmk::split::central::run_rmk_split_central_direct_pin::< + ::embassy_stm32::gpio::Input<'_>, + ::embassy_stm32::gpio::Output<'_>, + ::embassy_stm32::#usb_mod_path::Driver<'_, ::embassy_stm32::peripherals::#usb_name>, + ::embassy_stm32::flash::Flash<'_, ::embassy_stm32::flash::Blocking>, + ROW, + COL, + #central_row, + #central_col, + #central_row_offset, + #central_col_offset, + NUM_LAYER, + SIZE, + >(direct_pins, driver, flash, &mut get_default_keymap(), keyboard_config, #low_active, spawner) + }, }; let mut tasks = vec![central_task]; let central_serials = split_config @@ -236,19 +253,37 @@ fn expand_split_central_entry( .central .ble_addr .expect("No ble_addr defined for central"); - let central_task = quote! { - ::rmk::split::central::run_rmk_split_central::< - ::embassy_nrf::gpio::Input<'_>, - ::embassy_nrf::gpio::Output<'_>, - ::embassy_nrf::usb::Driver<'_, ::embassy_nrf::peripherals::USBD, &::embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>, - ROW, - COL, - #central_row, - #central_col, - #central_row_offset, - #central_col_offset, - NUM_LAYER, - >(input_pins, output_pins, driver, &mut get_default_keymap(), keyboard_config, [#(#central_addr), *], spawner) + let low_active = split_config.central.matrix.direct_pin_low_active; + let central_task = match split_config.central.matrix.matrix_type { + MatrixType::normal => quote! { + ::rmk::split::central::run_rmk_split_central::< + ::embassy_nrf::gpio::Input<'_>, + ::embassy_nrf::gpio::Output<'_>, + ::embassy_nrf::usb::Driver<'_, ::embassy_nrf::peripherals::USBD, &::embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>, + ROW, + COL, + #central_row, + #central_col, + #central_row_offset, + #central_col_offset, + NUM_LAYER, + >(input_pins, output_pins, driver, &mut get_default_keymap(), keyboard_config, [#(#central_addr), *], spawner) + }, + MatrixType::direct_pin => quote! { + ::rmk::split::central::run_rmk_split_central_direct_pin::< + ::embassy_nrf::gpio::Input<'_>, + ::embassy_nrf::gpio::Output<'_>, + ::embassy_nrf::usb::Driver<'_, ::embassy_nrf::peripherals::USBD, &::embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>, + ROW, + COL, + #central_row, + #central_col, + #central_row_offset, + #central_col_offset, + NUM_LAYER, + SIZE, + >(direct_pins, driver, &mut get_default_keymap(), keyboard_config, #low_active, [#(#central_addr), *], spawner) + }, }; let mut tasks = vec![central_task]; split_config.peripheral.iter().enumerate().for_each(|(idx, p)| { @@ -267,7 +302,10 @@ fn expand_split_central_entry( join_all_tasks(tasks) } ChipSeries::Rp2040 => { - let central_task = quote! { + let low_active = split_config.central.matrix.direct_pin_low_active; + + let central_task = match split_config.central.matrix.matrix_type { + MatrixType::normal => quote! { ::rmk::split::central::run_rmk_split_central::< ::embassy_rp::gpio::Input<'_>, ::embassy_rp::gpio::Output<'_>, @@ -281,6 +319,23 @@ fn expand_split_central_entry( #central_col_offset, NUM_LAYER, >(input_pins, output_pins, driver, flash, &mut get_default_keymap(), keyboard_config, spawner) + }, + MatrixType::direct_pin => quote! { + ::rmk::split::central::run_rmk_split_central_direct_pin::< + ::embassy_rp::gpio::Input<'_>, + ::embassy_rp::gpio::Output<'_>, + ::embassy_rp::usb::Driver<'_, ::embassy_rp::peripherals::USB>, + ::embassy_rp::flash::Flash<::embassy_rp::peripherals::FLASH, ::embassy_rp::flash::Async, FLASH_SIZE>, + ROW, + COL, + #central_row, + #central_col, + #central_row_offset, + #central_col_offset, + NUM_LAYER, + SIZE, + >(direct_pins, driver, flash, &mut get_default_keymap(), keyboard_config, #low_active, spawner) + }, }; let mut tasks = vec![central_task]; let central_serials = split_config diff --git a/rmk-macro/src/split/peripheral.rs b/rmk-macro/src/split/peripheral.rs index adaf9fe4..9e5d2a40 100644 --- a/rmk-macro/src/split/peripheral.rs +++ b/rmk-macro/src/split/peripheral.rs @@ -150,9 +150,11 @@ fn expand_split_peripheral_entry( .expect("Missing central ble address"); let row = peripheral_config.rows; let col = peripheral_config.cols; + let size = row + col; let peripheral_addr = peripheral_config.ble_addr.expect( "Peripheral should have a ble address, please check the `ble_addr` field in `keyboard.toml`", ); + let low_active = peripheral_config.matrix.direct_pin_low_active; match peripheral_config.matrix.matrix_type { MatrixType::direct_pin => { quote! { @@ -160,11 +162,13 @@ fn expand_split_peripheral_entry( ::embassy_nrf::gpio::Input<'_>, ::embassy_nrf::gpio::Output<'_>, #row, - #col + #col, + #size > ( direct_pins, [#(#central_addr), *], [#(#peripheral_addr), *], + #low_active, spawner, ).await } diff --git a/rmk/src/direct_pin.rs b/rmk/src/direct_pin.rs index bef62175..f621eb77 100644 --- a/rmk/src/direct_pin.rs +++ b/rmk/src/direct_pin.rs @@ -249,7 +249,7 @@ impl< ) -> Self { DirectPinMatrix { direct_pins, - debouncer: debouncer, + debouncer, key_states: [[KeyState::new(); COL]; ROW], scan_start: None, low_active, diff --git a/rmk/src/split/central.rs b/rmk/src/split/central.rs index 6f00f90a..00061abb 100644 --- a/rmk/src/split/central.rs +++ b/rmk/src/split/central.rs @@ -1,12 +1,14 @@ use core::cell::RefCell; -use defmt::error; +use defmt::{error, info}; use embassy_executor::Spawner; +use embassy_futures::select::select_slice; use embassy_time::{Instant, Timer}; use embassy_usb::driver::Driver; use embedded_hal::digital::{InputPin, OutputPin}; #[cfg(feature = "async_matrix")] use embedded_hal_async::digital::Wait; +use heapless::Vec; use rmk_config::RmkConfig; use crate::action::KeyAction; @@ -125,6 +127,118 @@ pub async fn run_rmk_split_central< fut } +/// Run RMK split central keyboard service. This function should never return. +/// +/// # Arguments +/// +/// * `direct_pins` - direct gpio pins, if `async_matrix` is enabled, the input pins should implement `embedded_hal_async::digital::Wait` trait +/// * `usb_driver` - (optional) embassy usb driver instance. Some microcontrollers would enable the `_no_usb` feature implicitly, which eliminates this argument +/// * `flash` - (optional) flash storage, which is used for storing keymap and keyboard configs. Some microcontrollers would enable the `_no_external_storage` feature implicitly, which eliminates this argument +/// * `default_keymap` - default keymap definition +/// * `keyboard_config` - other configurations of the keyboard, check [RmkConfig] struct for details +/// * `low_active`: pin active level +/// * `central_addr` - (optional) central's BLE static address. This argument is enabled only for nRF BLE split central now +/// * `spawner`: (optional) embassy spawner used to spawn async tasks. This argument is enabled for non-esp microcontrollers +#[allow(unused_variables)] +#[allow(unreachable_code)] +pub async fn run_rmk_split_central_direct_pin< + #[cfg(feature = "async_matrix")] In: Wait + InputPin, + #[cfg(not(feature = "async_matrix"))] In: InputPin, + Out: OutputPin, + #[cfg(not(feature = "_no_usb"))] D: Driver<'static>, + #[cfg(not(feature = "_no_external_storage"))] F: NorFlash, + const TOTAL_ROW: usize, + const TOTAL_COL: usize, + const CENTRAL_ROW: usize, + const CENTRAL_COL: usize, + const CENTRAL_ROW_OFFSET: usize, + const CENTRAL_COL_OFFSET: usize, + const NUM_LAYER: usize, + const SIZE: usize, +>( + #[cfg(feature = "col2row")] direct_pins: [[Option; CENTRAL_COL]; CENTRAL_ROW], + #[cfg(not(feature = "col2row"))] direct_pins: [[Option; CENTRAL_ROW]; CENTRAL_COL], + #[cfg(not(feature = "_no_usb"))] usb_driver: D, + #[cfg(not(feature = "_no_external_storage"))] flash: F, + default_keymap: &mut [[[KeyAction; TOTAL_COL]; TOTAL_ROW]; NUM_LAYER], + keyboard_config: RmkConfig<'static, Out>, + low_active: bool, + #[cfg(feature = "_nrf_ble")] central_addr: [u8; 6], + #[cfg(not(feature = "_esp_ble"))] spawner: Spawner, +) -> ! { + // Create the debouncer, use COL2ROW by default + #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))] + let debouncer: RapidDebouncer = RapidDebouncer::new(); + #[cfg(all(not(feature = "col2row"), feature = "rapid_debouncer"))] + let debouncer: RapidDebouncer = RapidDebouncer::new(); + #[cfg(all(feature = "col2row", not(feature = "rapid_debouncer")))] + let debouncer: DefaultDebouncer = DefaultDebouncer::new(); + #[cfg(all(not(feature = "col2row"), not(feature = "rapid_debouncer")))] + let debouncer: DefaultDebouncer = DefaultDebouncer::new(); + + // Keyboard matrix, use COL2ROW by default + #[cfg(feature = "col2row")] + let matrix = CentralDirectPinMatrix::< + In, + _, + CENTRAL_ROW_OFFSET, + CENTRAL_COL_OFFSET, + CENTRAL_ROW, + CENTRAL_COL, + SIZE, + >::new(direct_pins, debouncer, low_active); + #[cfg(not(feature = "col2row"))] + let matrix = CentralDirectPinMatrix::< + In, + _, + CENTRAL_ROW_OFFSET, + CENTRAL_COL_OFFSET, + CENTRAL_COL, + CENTRAL_ROW, + SIZE, + >::new(direct_pins, debouncer, low_active); + + #[cfg(feature = "_nrf_ble")] + let fut = initialize_ble_split_central_and_run::< + _, + _, + D, + TOTAL_ROW, + TOTAL_COL, + CENTRAL_ROW, + CENTRAL_COL, + CENTRAL_ROW_OFFSET, + CENTRAL_COL_OFFSET, + NUM_LAYER, + >( + matrix, + usb_driver, + default_keymap, + keyboard_config, + central_addr, + spawner, + ) + .await; + + #[cfg(not(any(feature = "_nrf_ble", feature = "_esp_ble")))] + let fut = initialize_usb_split_central_and_run::< + _, + _, + D, + F, + TOTAL_ROW, + TOTAL_COL, + CENTRAL_ROW, + CENTRAL_COL, + CENTRAL_ROW_OFFSET, + CENTRAL_COL_OFFSET, + NUM_LAYER, + >(matrix, usb_driver, flash, default_keymap, keyboard_config) + .await; + + fut +} + /// Run central's peripheral monitor task. /// /// # Arguments @@ -390,3 +504,173 @@ impl< } } } + +/// DirectPinMartex only has input pins. +pub(crate) struct CentralDirectPinMatrix< + #[cfg(feature = "async_matrix")] In: Wait + InputPin, + #[cfg(not(feature = "async_matrix"))] In: InputPin, + D: DebouncerTrait, + const ROW_OFFSET: usize, + const COL_OFFSET: usize, + const ROW: usize, + const COL: usize, + const SIZE: usize, +> { + /// Input pins of the pcb matrix + direct_pins: [[Option; COL]; ROW], + /// Debouncer + debouncer: D, + /// Key state matrix + key_states: [[KeyState; COL]; ROW], + /// Start scanning + scan_start: Option, + /// Pin active level + low_active: bool, +} + +impl< + #[cfg(not(feature = "async_matrix"))] In: InputPin, + #[cfg(feature = "async_matrix")] In: Wait + InputPin, + D: DebouncerTrait, + const ROW_OFFSET: usize, + const COL_OFFSET: usize, + const ROW: usize, + const COL: usize, + const SIZE: usize, + > CentralDirectPinMatrix +{ + /// Create a matrix from input and output pins. + pub(crate) fn new( + direct_pins: [[Option; COL]; ROW], + debouncer: D, + low_active: bool, + ) -> Self { + CentralDirectPinMatrix { + direct_pins, + debouncer, + key_states: [[KeyState::new(); COL]; ROW], + scan_start: None, + low_active, + } + } +} + +impl< + #[cfg(not(feature = "async_matrix"))] In: InputPin, + #[cfg(feature = "async_matrix")] In: Wait + InputPin, + D: DebouncerTrait, + const ROW_OFFSET: usize, + const COL_OFFSET: usize, + const ROW: usize, + const COL: usize, + const SIZE: usize, + > MatrixTrait for CentralDirectPinMatrix +{ + const ROW: usize = ROW; + const COL: usize = COL; + + #[cfg(feature = "async_matrix")] + async fn wait_for_key(&mut self) { + if let Some(start_time) = self.scan_start { + // If no key press over 1ms, stop scanning and wait for interupt + if start_time.elapsed().as_millis() <= 1 { + return; + } else { + self.scan_start = None; + } + } + Timer::after_micros(1).await; + info!("Waiting for active level"); + + if self.low_active { + let mut futs: Vec<_, SIZE> = Vec::new(); + for direct_pins_row in self.direct_pins.iter_mut() { + for direct_pin in direct_pins_row.iter_mut() { + if let Some(direct_pin) = direct_pin { + let _ = futs.push(direct_pin.wait_for_low()); + } + } + } + let _ = select_slice(futs.as_mut_slice()).await; + } else { + let mut futs: Vec<_, SIZE> = Vec::new(); + for direct_pins_row in self.direct_pins.iter_mut() { + for direct_pin in direct_pins_row.iter_mut() { + if let Some(direct_pin) = direct_pin { + let _ = futs.push(direct_pin.wait_for_high()); + } + } + } + let _ = select_slice(futs.as_mut_slice()).await; + } + self.scan_start = Some(Instant::now()); + } + + /// Do matrix scanning, the result is stored in matrix's key_state field. + async fn scan(&mut self) { + info!("Central Direct Pin Matrix scanning"); + loop { + #[cfg(feature = "async_matrix")] + self.wait_for_key().await; + + // Scan matrix and send report + for (row_idx, pins_row) in self.direct_pins.iter_mut().enumerate() { + for (col_idx, direct_pin) in pins_row.iter_mut().enumerate() { + if let Some(direct_pin) = direct_pin { + let pin_state = if self.low_active { + direct_pin.is_low().ok().unwrap_or_default() + } else { + direct_pin.is_high().ok().unwrap_or_default() + }; + + let debounce_state = self.debouncer.detect_change_with_debounce( + col_idx, + row_idx, + pin_state, + &self.key_states[row_idx][col_idx], + ); + + match debounce_state { + DebounceState::Debounced => { + self.key_states[row_idx][col_idx].toggle_pressed(); + let (col, row, key_state) = ( + (col_idx + COL_OFFSET) as u8, + (row_idx + ROW_OFFSET) as u8, + self.key_states[row_idx][col_idx], + ); + + // `try_send` is used here because we don't want to block scanning if the channel is full + let send_re = key_event_channel.try_send(KeyEvent { + row, + col, + pressed: key_state.pressed, + }); + if send_re.is_err() { + error!("Failed to send key event: key event channel full"); + } + } + _ => (), + } + + // If there's key still pressed, always refresh the self.scan_start + #[cfg(feature = "async_matrix")] + if self.key_states[row_idx][col_idx].pressed { + self.scan_start = Some(Instant::now()); + } + } + } + } + + Timer::after_micros(100).await; + } + } + + /// Read key state at position (row, col) + fn get_key_state(&mut self, row: usize, col: usize) -> KeyState { + self.key_states[row][col] + } + + fn update_key_state(&mut self, row: usize, col: usize, f: impl FnOnce(&mut KeyState)) { + f(&mut self.key_states[row][col]); + } +} diff --git a/rmk/src/split/peripheral.rs b/rmk/src/split/peripheral.rs index 903f4834..d9c2c464 100644 --- a/rmk/src/split/peripheral.rs +++ b/rmk/src/split/peripheral.rs @@ -108,6 +108,7 @@ pub async fn run_rmk_split_peripheral_direct_pin< const SIZE: usize, >( #[cfg(feature = "col2row")] direct_pins: [[Option; COL]; ROW], + #[cfg(not(feature = "col2row"))] direct_pins: [[Option; ROW]; COL], #[cfg(feature = "_nrf_ble")] central_addr: [u8; 6], #[cfg(feature = "_nrf_ble")] peripheral_addr: [u8; 6], low_active: bool, @@ -125,7 +126,10 @@ pub async fn run_rmk_split_peripheral_direct_pin< let debouncer = DefaultDebouncer::::new(); // Keyboard matrix + #[cfg(feature = "col2row")] let matrix = DirectPinMatrix::<_, _, ROW, COL, SIZE>::new(direct_pins, debouncer, low_active); + #[cfg(not(feature = "col2row"))] + let matrix = DirectPinMatrix::<_, _, COL, ROW, SIZE>::new(direct_pins, debouncer, low_active); #[cfg(not(feature = "_nrf_ble"))] initialize_serial_split_peripheral_and_run::<_, S, ROW, COL>(matrix, serial).await;