Skip to main content

Keyboard Dongle

A bluetooth dongle can be added to any wireless keyboard running ZMK. The result is a split keyboard with the dongle as "central". There are a number of advantages to adding a dongle, but also some disadvantages:

Benefits:

  • For split keyboards, having the dongle for the "central" role results in improved battery life for the former central part, as it is now a peripheral.
  • It is easier to connect to the host device since dongles are typically connected via USB.

Disadvantages:

  • An extra board is needed (any BLE-capable board that ZMK supports will work).
  • The keyboard becomes unusable without the dongle.

Depending on how the dongle is used, there are some additional latency considerations to keep in mind. The addition of the dongle adds an extra "hop" for the former central, increasing its latency to that of a peripheral. The other parts are unchanged latency-wise. There is also a commonly occurring case where the peripherals benefit. Assuming the dongle is connected to USB and the former central would have been connected via bluetooth to the host if the dongle wasn't present:

  • The former central will have its latency increased by about 1ms from the extra USB "hop"
  • The other parts will have their average latency decreased by 6.5ms from the replacement of a BLE "hop" with a "USB" hop.

As a result, for this common use case the average latency of the keyboard decreases.

Adding a Dongle

The approach taken to adding a dongle is generally the same1. Whether your keyboard consists of a board and a shield or is just a board is irrelevant.

To add a dongle, you will create a simplified form of a new shield. The approach described below assumes the dongle will not have any keys assigned to itself, so it will not work for that scenario. If you want to understand the details of how it all works better, please read through the new shield guide.

As there are a very large number of possible devices that could be used as a dongle, you will be defining your dongle as a personal shield intended for your exclusive use.

Prior to adding a dongle to your keyboard, please test its functionality without a dongle. The below guide will assume that your keyboard is named my_keyboard, replace accordingly.

Dongle Folder

First, make sure that your zmk-config matches the folder structure found in the unified ZMK config template (extra files and folders are fine, but none should be missing).

Next, navigate to the zmk-config/boards/shields directory. Create a subdirectory called my_keyboard, if one doesn't already exist. Unless otherwise specified, the below files should all be placed in said folder.

Kconfig Files

Kconfig.shield

Make a file called Kconfig.shield, if one does not exist already. Add the following lines to it, replacing SHIELD_MY_KEYBOARD_DONGLE and my_keyboard_dongle according to your keyboard:

Kconfig.shield
# No whitespace after the comma or in the keyboard name!
config SHIELD_MY_KEYBOARD_DONGLE
def_bool $(shields_list_contains,my_keyboard_dongle)

Kconfig.defconfig

Make a file called Kconfig.defconfig, if one does not exist already. Add the following lines to it where SHIELD_MY_KEYBOARD_DONGLE should match the previous section:

Kconfig.defconfig
if SHIELD_MY_KEYBOARD_DONGLE

# Max 16 characters in keyboard name
config ZMK_KEYBOARD_NAME
default "My Board"

config ZMK_SPLIT_ROLE_CENTRAL
default y

config ZMK_SPLIT
default y

# Set this to the number of peripherals your dongle will have.
# For a unibody, this would be 1. If you have left and right halves, set it to 2, etc.
config ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS
default 1

# Set this to ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS + your desired number of BT profiles (default is 5)
config BT_MAX_CONN
default 6

# Set this to the same number as BT_MAX_CONN
config BT_MAX_PAIRED
default 6

endif

Dongle Overlay File

Create a file called my_keyboard_dongle.overlay (renamed as appropriate). This file will include your keyboard's matrix transform and physical layout, allowing it to map key press events from the peripherals to behaviors.

Add the following lines to the file you created, introducing a mock kscan for the dongle since it has no keys itself:

my_keyboard_dongle.overlay
#include <dt-bindings/zmk/matrix_transform.h>

/ {
chosen {
zmk,kscan = &mock_kscan;
};

mock_kscan: mock_kscan_0 {
compatible = "zmk,kscan-mock";
columns = <0>;
rows = <0>;
events = <0>;
};
};

You will now need to find and copy your keyboard's matrix transform into the my_keyboard_dongle.overlay file.

Matrix transform

Navigate to the directory defining your keyboard (in-tree keyboards found here, if your keyboard is a shield look under the shields subdirectory) and look through the devicetree files for nodes with compatible = "zmk,matrix-transform";. This should look something like this:

    default_transform: keymap_transform_0 {
compatible = "zmk,matrix-transform";
columns = <14>;
rows = <5>;

map = <
// Lots of RC(r,c) macros
>;
};

Copy this node into your my_keyboard_dongle.overlay file. It should be a child node of the root node (put it inside the / { ... };). If there are multiple matrix transformations, copy the one that matches your keyboard's layout.

Make a note of the label that the transform has, it will be used later. In the example the label is default_transform.

Physical layout

A full physical layout is necessary to allow your dongle to be used with ZMK Studio. If your keyboard is not Studio-ready or you have no interest in using ZMK Studio with your dongle, then this section is simplified significantly.

You will need to find your keyboard's physical layout, much like finding the matrix transform. Depending on your keyboard, the physical layout could be found in a variety of locations. Look for a node with compatible = "zmk,physical-layout";. There are three commonly found possibilities:

  • The physical layout is found in a file called my_keyboard-layouts.dtsi
  • The physical layout is found in the same file as the matrix transform and chosen node
  • The physical layout is imported from ZMK's shared layouts - #include <layouts/<layout_name>.dtsi> can be found at the top of one of the devicetree files.

Copy the file into your dongle's folder. Then import the file, assign the matrix transform to it, and select it in the chosen node:

#import "my_keyboard-layouts.dtsi"

&physical_layout0 {
transform = <&default_transform>;
};

/ {
chosen {
zmk,kscan = &mock_kscan;
zmk,physical-layout = &physical_layout0;
};
};

Make sure that the labels physical_layout0 and default_transform match those of the physical layout node defined in the file and the matrix transform respectively.

note

If there are multiple physical layouts in the file, you will need to copy over all of the remaining matrix transformations and assign them to their corresponding physical layout.

Building the Firmware

Add the appropriate lines to your build.yml file to build the firmware for your dongle. Also add some CMake arguments using cmake-args to the existing parts of your keyboard, turning them into peripherals for your dongle.2

include:
# -----------------------------------------
# Your other keyboard parts here
# -----------------------------------------
# Change the board appropriately, you can use any board
- board: nice_nano_v2
shield: my_keyboard_dongle
- board: nice_nano_v2
shield: settings_reset
# Add these cmake-args to the peripherals you wish to use with the dongle
- board: nice_nano_v2
shield: my_keyboard
cmake-args: -DCONFIG_ZMK_SPLIT=y -DCONFIG_ZMK_SPLIT_ROLE_CENTRAL=n

You can then flash the firmware to your device as detailed in our user setup and split keyboard pages.

warning

Before flashing your new firmware, you need to flash settings_reset firmware on all devices to ensure they can pair to each other.

To use your dongled keyboard with ZMK Studio, apply the instructions for building with Studio to the dongle.

If you ever want to "undongle" your keyboard, simply remove these CMake arguments and flash the resulting firmware (after a settings_reset).

Footnotes

  1. If you have a custom dongle that uses an onboard MCU, then you will need to take a slightly different approach that isn't currently documented.

  2. If you are building locally, you can append these flags to the end of the build command.