Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLE split keyboard #60

Merged
merged 19 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// "examples/use_config/stm32f4/Cargo.toml",
// "examples/use_rust/stm32f1/Cargo.toml",
// "examples/use_rust/nrf52840/Cargo.toml",
// "examples/use_rust/nrf52840_ble/Cargo.toml",
"examples/use_rust/nrf52840_ble/Cargo.toml",
// "examples/use_rust/nrf52832_ble/Cargo.toml",
// "examples/use_rust/rp2040/Cargo.toml"
// "examples/use_rust/rp2040_split/Cargo.toml"
Expand Down
160 changes: 123 additions & 37 deletions docs/src/images/split_keyboard.drawio

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/src/images/split_keyboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 70 additions & 7 deletions docs/src/split_keyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,76 @@ RMK supports multi-split keyboard, which contains at least one master board and

See `examples/use_rust/rp2040_split` for the wired split keyboard example using rp2040.

See `examples/use_rust/nrf52840_ble_split` for the wireless split keyboard example using nRF52840.

## Define master and slaves

In RMK, split keyboard's matrix are defined with row/col number and their offsets in the whole matrix.

### Master

Running split master is quite similar with the general keyboard, the only difference is for split master, total row/col number, master matrix's row/col number, and master matrix's offsets should be passed to `run_rmk_split_master`:

```rust
// nRF52840 split master, arguments might be different for other microcontrollers, check the API docs for the detail.
run_rmk_split_master::<
Input<'_>,
Output<'_>,
Driver<'_, USBD, &SoftwareVbusDetect>,
ROW, // TOTAL_ROW
COL, // TOTAL_COL
2, // MASTER_ROW
2, // MASTER_COL
0, // MASTER_ROW_OFFSET
0, // MASTER_COL_OFFSET
NUM_LAYER,
>(
input_pins,
output_pins,
driver,
crate::keymap::KEYMAP,
keyboard_config,
master_addr,
spawner,
)
```

In slave master, you should also run the slave monitor for each slave. This task monitors the slave key changes and forwards them to master core keyboard task

```rust
run_slave_monitor<
2, // SLAVE_ROW
1, // SLAVE_COL
2, // SLAVE_ROW_OFFSET
2, // SLAVE_COL_OFFSET
>(slave_id, slave_addr)
```

### Slave

Running split slave is simplier. For slave, we don't need to specify slave matrix's offsets(we've done it in master!). So, the split slave API is like:

```rust
run_rmk_split_slave::<Input<'_>, Output<'_>, 2, 2>(
input_pins,
output_pins,
master_addr,
slave_addr,
spawner,
)
```

where `2,2` are the size of slave's matrix.

## Communication

RMK plans to support both wired and wireless communication.
RMK supports both wired and wireless communication.

Currently, the communication type indicates that how split master communicates with split slaves. How the master talks with the host depends only on the master.

When the master & slave talk to each other, the **debounced key states** are sent. The master board receives the key states, converts them to actual keycode and then sends keycodes to the host.
- For communication over BLE: the master talks with the host via BLE or USB, depends on whether the USB cable is connected
- For communication over serial: the master can only use USB to talk with the host

That means the master board should have a full keymap stored in the storage/ram. The slaves just do matrix scanning, debouncing and sending key states over serial/ble.

### Wired split

Expand All @@ -26,18 +89,18 @@ For hardwire connection, the TRRS cable is widely used in split keyboards to con

### Wireless split

This feature is under construction. BLE communication will be supported.
RMK supports BLE wireless split on only nRF chips right now. The [BLE random static address](https://novelbits.io/bluetooth-address-privacy-ble/) for both master and slave should be defined.


## Split keyboard project

A project of split keyboard should like:
A project of split keyboard could be like:

```
src
- bin
- right.rs
- left.rs
- master.rs
- slave.rs
keyboard.toml
Cargo.toml
```
28 changes: 16 additions & 12 deletions docs/src/split_keyboard_design.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
# Split keyboard design

## Overview

![split_keyboard_design](images/split_keyboard.svg)

## Under the hood

In RMK's current implementation, the slave continously scans it's matrix, if there's a key change, A `SplitMessage` is sent to the master.

In master, there's a slave monitor for each slave, which receives the `SplitMessage` from the slave and caches all key states in that slave.

And there's a separate keyboard thread in master, which does the keyboard stuffs. The only difference is, in matrix scanning stage, it synchronizes key states from slave monitor and scans master's matrix.

## Protocol

### Communication protocol

When the master & slave talk to each other, the **debounced key states** are sent. The master board receives the key states, converts them to actual keycode and then sends keycodes to the host.

That means the master board should have a full keymap stored in the storage/ram. The slaves just do matrix scanning, debouncing and sending key states over serial/ble.

A single message can be defined like:

```rust
Expand All @@ -16,18 +32,6 @@ pub enum SplitMessage {
}
```

The slave continously scans it's matrix, if there's a key change, A `SplitMessage` is sent to the master.

In master, there's a key state cache for each slave, and a separate thread running to continously receives the key states from slave and saves key states to cache.

Each slave cache in master runs in different threads, which is an infinite loop that receives all `SplitMessage` from actual slave boards.

For master, the matrix scanning has the following steps:

1. Scan the master's own key matrix
2. Read the all slaves' key state caches
3. Merge them to a final key states, finish matrix scanning. If the slave state is different from main key state, `changed` is true.
4. If the keyboard is running in `async_matrix` mode, each received key states triggers matrix scanning.

## Config file for split(draft)

Expand Down
2 changes: 1 addition & 1 deletion examples/use_config/nrf52840_ble/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ embassy-nrf = { version = "0.2.0", features = [
] }
embassy-executor = { version = "0.6", features = [
"defmt",
"task-arena-size-8192",
"task-arena-size-32768",
"arch-cortex-m",
"executor-thread",
"integrated-timers",
Expand Down
2 changes: 1 addition & 1 deletion examples/use_config/stm32f1/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod keyboard {
let mut config = Config::default();
config.rcc.hse = Some(Hertz(8_000_000));
config.rcc.sys_ck = Some(Hertz(48_000_000));
config.rcc.pclk1 = Some(Hertz(24_000_000));
config.rcc.pclk1 = Some(Hertz(24_000_000));
config
}
}
3 changes: 1 addition & 2 deletions examples/use_rust/hpm5300/src/dummy_flash.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

pub struct DummyFlash {}

impl embedded_storage_async::nor_flash::NorFlash for DummyFlash {
Expand Down Expand Up @@ -38,4 +37,4 @@ impl embedded_storage_async::nor_flash::NorFlashError for DummyFlashError {
fn kind(&self) -> embedded_storage_async::nor_flash::NorFlashErrorKind {
embedded_storage_async::nor_flash::NorFlashErrorKind::Other
}
}
}
1 change: 0 additions & 1 deletion examples/use_rust/hpm5300/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use rmk::{
};
use vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};


bind_interrupts!(struct Irqs {
USB0 => hpm_hal::usb::InterruptHandler<peripherals::USB0>;
});
Expand Down
7 changes: 5 additions & 2 deletions examples/use_rust/nrf52840/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ use embassy_nrf::{
usb::{self, vbus_detect::HardwareVbusDetect, Driver},
};
use panic_probe as _;
use rmk::{run_rmk, config::{RmkConfig, VialConfig}};
use rmk::{
config::{RmkConfig, VialConfig},
run_rmk,
};
use vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};

bind_interrupts!(struct Irqs {
Expand Down Expand Up @@ -52,7 +55,7 @@ async fn main(spawner: Spawner) {

// Keyboard config
let keyboard_config = RmkConfig {
vial_config: VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF,),
vial_config: VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF),
..Default::default()
};

Expand Down
38 changes: 38 additions & 0 deletions examples/use_rust/nrf52840_ble_split/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[target.thumbv7m-none-eabi]
# uncomment this to make `cargo run` execute programs on QEMU
# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# Have to use simmsb's elf2uf2 fork to flash https://github.com/simmsb/elf2uf2-rs
# runner = "elf2uf2-rs -d"
runner = "probe-rs run --chip nRF52840_xxAA"

rustflags = [
# Previously, the linker arguments --nmagic and -Tlink.x were set here.
# They are now set by build.rs instead. The linker argument can still
# only be set here, if a custom linker is needed.

# By default, the LLD linker is used, which is shipped with the Rust
# toolchain. If you run into problems with LLD, you can switch to the
# GNU linker by uncommenting this line:
# "-C", "linker=arm-none-eabi-ld",

# If you need to link to pre-compiled C libraries provided by a C toolchain
# use GCC as the linker by uncommenting the three lines below:
# "-C", "linker=arm-none-eabi-gcc",
# "-C", "link-arg=-Wl,-Tlink.x",
# "-C", "link-arg=-nostartfiles",
]

[build]
# Pick ONE of these default compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)

[env]
DEFMT_LOG = "info"
Loading
Loading