Split keyboards
A split keyboard consists of one or more peripheral devices communicating matrix events to one central device, which then creates HID keyboard reports to send to a host device.
Generally, a split keyboard will require compiling multiple binaries, one for each device/part of the split keyboard. For example, you will need one binary for the left half, and another binary for the right half.
Continue reading to see how to implement a “central” and a “peripheral” device using rumcake
.
Example
The following documentation will show an example for a split keyboard with a left and right half, no dongle.
The central device code will be placed in left.rs
, and the peripheral device code will be
placed in right.rs
.
For a similar full example of how to implement a split keyboard, check the template repo.
Central setup
The “central” device in a split keyboard setup defines the keyboard layout, communicates with the host device, and receives matrix events from other peripherals. There should only be one central device. If the split keyboard also uses extra features like backlighting or underglow, the central device will also be responsible for sending their related commands to the peripherals.
Typically, the central device could be a dongle (good for saving battery life), or one of the keyboard halves.
Required Cargo features for central device
You must compile a binary with the following rumcake
features:
split-central
- Feature flag for one of the available split drivers that you would like to use
Required code for central device
To set up the central device, you must add split_central(driver_setup_fn = <setup_fn>)
to your #[keyboard]
macro invocation,
and your keyboard must implement the CentralDevice
trait. Your CentralDevice
implementation should include type Layout = Self;
.
This will tell rumcake to redirect matrix events (received from other peripherals) to the layout, to be processed as keycodes.
The driver_setup_fn
must be an async function that has no parameters, and returns a type that implements the
CentralDeviceDriver
trait.
Lastly, you must set up the driver. To do this, you need to complete your driver_setup_fn
by constructing the driver.
You can check the API reference for your chosen driver for a set up
function or macro to make this process easier.
Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver in the #[keyboard]
macro.
Check the list of available split drivers for this information.
For example, with the SerialSplitDriver
struct, you can construct it like so:
Peripheral setup
The “peripheral” device in a split keyboard setup has a switch matrix, and sends matrix events to the central device. A split keyboard setup could have more than one peripheral. If the split keyboard also uses extra features, then all the peripherals should receive the related commands from the central device.
Required Cargo features for peripheral device
You must compile a binary with the following rumcake
features:
split-peripheral
- Feature flag for one of the available split drivers that you would like to use
Required code for peripheral device
To set up the peripheral device, you must add split_peripheral(driver_setup_fn = <setup_fn>)
to your #[keyboard]
macro invocation,
and your keyboard must implement the PeripheralDevice
trait. Your KeyboardMatrix
implementation (which should already be implemented)
should include type PeripheralDeviceType = Self
. This will tell rumcake to redirect matrix events to the peripheral device driver, to
be sent to the central device.
The driver_setup_fn
must be an async function that has no parameters, and returns a type that implements the
PeripheralDeviceDriver
trait.
Lastly, you must set up the driver. To do this, you need to complete your driver_setup_fn
by constructing the driver.
You can check the API reference for your chosen driver for a set up
function or macro to make this process easier.
Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver in the #[keyboard]
macro.
Check the list of available split drivers for this information.
For example, with the SerialSplitDriver
struct, you can construct it like so:
Central Device Without a Matrix (Dongle)
An example of a central device without a matrix is a dongle. If you would like
to implement such a device, you can add no_matrix
to your #[keyboard]
macro invocation.
Doing so will remove the need to implement KeyboardMatrix
, so you will only have to implement
KeyboardLayout
.
nRF-BLE Driver
If you are using an nRF5x MCU, and want to use BLE for split keyboard communication, there are additional changes you need to make it work.
For both central and peripheral devices, the BluetoothDevice
trait must be implemented:
BLUETOOTH_ADDRESS
can be whatever you want, as long as it is a valid “Random Static” bluetooth address.
See “Random Static Address” here: https://novelbits.io/bluetooth-address-privacy-ble/
You will also need to change the #[keyboard]
macro invocation to add driver_type = "nrf-ble"
.
This will change the requirements for the signature of driver_setup_fn
.
Now, your driver_setup_fn
will need to change it’s signature.
For central devices, it will need to return:
CentralDeviceDriver
implementor- A slice containing peripheral addresses to connect to
For peripheral devices, it will need to return:
PeripheralDeviceDriver
implementor- Address of the central device to connect to
The setup_nrf_ble_split_central!
and setup_nrf_ble_split_peripheral!
driver can be used to
implement your driver_setup_fn
.
To-do List
- Method of syncing backlight and underglow commands from central to peripherals on split keyboard setups
- Single device that can act as both a peripheral and central device
- Serial (half duplex) driver
- I2C driver
Available Drivers
Name | Feature Flag | Required Traits |
---|---|---|
Serial1 | N/A (available by default) | N/A |
nRF Bluetooth LE | nrf-ble | BluetoothDevice |
Footnotes
-
Compatible with any type that implements both
embedded_io_async::Read
andembedded_io_async::Write
. This includesembassy_nrf::buffered_uarte::BufferedUarte
(nRF UARTE) andembassy_stm32::usart::BufferedUart
(STM32 UART). ↩