|
| 1 | +--- |
| 2 | +title: Keyboard Dongle |
| 3 | +sidebar_label: Keyboard Dongle |
| 4 | +--- |
| 5 | + |
| 6 | +import Tabs from "@theme/Tabs"; |
| 7 | +import TabItem from "@theme/TabItem"; |
| 8 | + |
| 9 | +A bluetooth dongle can be added to any keyboard running ZMK. The result is a [split keyboard](../../features/split-keyboards.md) with the dongle as ["central"](../../features/split-keyboards.md#central-and-peripheral-roles). There are a number of advantages to adding a dongle, but also some disadvantages: |
| 10 | + |
| 11 | +Benefits: |
| 12 | + |
| 13 | +- For split keyboards, having the dongle for the "central" role results in [improved battery life](/power-profiler) for the former central part, as it is now a peripheral. |
| 14 | +- It is easier to connect to the host device since dongles are typically connected via USB. |
| 15 | + |
| 16 | +Disadvantages: |
| 17 | + |
| 18 | +- An extra [board](../../faq.md#what-is-a-board) is needed (any BLE-capable board will work). |
| 19 | +- The keyboard becomes unusable without the dongle. |
| 20 | + |
| 21 | +Depending on how the dongle is used, there are some additional [latency considerations](../../features/split-keyboards.md#latency-considerations) to keep in mind. |
| 22 | +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. |
| 23 | +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: |
| 24 | + |
| 25 | +- The former central will have its latency increased by about 1ms from the extra USB "hop" |
| 26 | +- The other parts will have their average latency _decreased_ by 6.5ms from the replacement of a BLE "hop" with a "USB" hop. |
| 27 | + |
| 28 | +As a result, for this common use case the average latency of the keyboard decreases. |
| 29 | + |
| 30 | +## Adding a Dongle |
| 31 | + |
| 32 | +The approach taken to adding a dongle is _generally_ the same[^1]. |
| 33 | +Whether your keyboard consists of a [board and a shield](index.mdx#boards--shields) or is just a board is irrelevant. |
| 34 | + |
| 35 | +To add a dongle, you will create a simplified form of a [new shield](new-shield.mdx). |
| 36 | +The approach described below assumes the dongle will not have any keys assigned to itself, so it will not work for that scenario. |
| 37 | +If you want to understand the details of how it all works better, please read through the [new shield guide](new-shield.mdx). |
| 38 | + |
| 39 | +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. |
| 40 | + |
| 41 | +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. |
| 42 | + |
| 43 | +### Dongle Folder |
| 44 | + |
| 45 | +First, make sure that your `zmk-config` matches the folder structure found in the [unified ZMK config template](https://github.com/zmkfirmware/unified-zmk-config-template) (extra files and folders are fine, but none should be missing). |
| 46 | + |
| 47 | +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. |
| 48 | + |
| 49 | +### Kconfig Files |
| 50 | + |
| 51 | +#### Kconfig.shield |
| 52 | + |
| 53 | +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: |
| 54 | + |
| 55 | +```kconfig title="Kconfig.shield" |
| 56 | +# No whitespace after the comma or in the keyboard name! |
| 57 | +config SHIELD_MY_KEYBOARD_DONGLE |
| 58 | + def_bool $(shields_list_contains,my_keyboard_dongle) |
| 59 | +``` |
| 60 | + |
| 61 | +#### Kconfig.defconfig |
| 62 | + |
| 63 | +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: |
| 64 | + |
| 65 | +```kconfig title="Kconfig.defconfig" |
| 66 | +if SHIELD_MY_KEYBOARD_DONGLE |
| 67 | +
|
| 68 | +# Max 16 characters in keyboard name |
| 69 | +config ZMK_KEYBOARD_NAME |
| 70 | + default "My Board" |
| 71 | +
|
| 72 | +config ZMK_SPLIT_ROLE_CENTRAL |
| 73 | + default y |
| 74 | +
|
| 75 | +config ZMK_SPLIT |
| 76 | + default y |
| 77 | +
|
| 78 | +# Set this to the number of peripherals your dongle will have. |
| 79 | +# For a unibody, this would be 1. If you have left and right halves, set it to 2, etc. |
| 80 | +config ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS |
| 81 | + default 1 |
| 82 | +
|
| 83 | +# Set this to ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS + your desired number of BT profiles (default is 5) |
| 84 | +config BT_MAX_CONN |
| 85 | + default 6 |
| 86 | +
|
| 87 | +# Set this to the same number as BT_MAX_CONN |
| 88 | +config BT_MAX_PAIRED |
| 89 | + default 6 |
| 90 | +
|
| 91 | +endif |
| 92 | +``` |
| 93 | + |
| 94 | +### Dongle Overlay File |
| 95 | + |
| 96 | +Create a file called `my_keyboard_dongle.overlay` (renamed as appropriate). |
| 97 | +This file will include your keyboard's matrix transform and physical layout, allowing it to map key press events from the peripherals to behaviors. |
| 98 | + |
| 99 | +Add the following lines to the file you created: |
| 100 | + |
| 101 | +```dts title="my_keyboard_dongle.overlay" |
| 102 | +#include <dt-bindings/zmk/matrix_transform.h> |
| 103 | +
|
| 104 | +/ { |
| 105 | + chosen { |
| 106 | + zmk,kscan = &mock_kscan; |
| 107 | + }; |
| 108 | +
|
| 109 | + mock_kscan: mock_kscan_0 { |
| 110 | + compatible = "zmk,kscan-mock"; |
| 111 | + columns = <0>; |
| 112 | + rows = <0>; |
| 113 | + events = <0>; |
| 114 | + }; |
| 115 | +}; |
| 116 | +``` |
| 117 | + |
| 118 | +You will now need to find and copy your keyboard's matrix transform into the `my_keyboard_dongle.overlay` file. |
| 119 | + |
| 120 | +#### Matrix transform |
| 121 | + |
| 122 | +Navigate to the directory defining your keyboard (in-tree keyboards found [here](https://github.com/zmkfirmware/zmk/tree/main/app/boards), if your keyboard is a shield look under the `shields` subdirectory) and look through the [devicetree files](../../config/index.md) for nodes with `compatible = "zmk,matrix-transform";`. |
| 123 | +This should look something like this: |
| 124 | + |
| 125 | +```dts |
| 126 | + default_transform: keymap_transform_0 { |
| 127 | + compatible = "zmk,matrix-transform"; |
| 128 | + columns = <14>; |
| 129 | + rows = <5>; |
| 130 | +
|
| 131 | + map = < |
| 132 | + // Lots of RC(r,c) macros |
| 133 | + >; |
| 134 | + }; |
| 135 | +``` |
| 136 | + |
| 137 | +Copy this node into your `my_keyboard_dongle.overlay` file. It should be a child node of the root node (put it inside the `/ { ... };`). |
| 138 | +If there are multiple matrix transformations, copy the one that matches your keyboard's layout. |
| 139 | + |
| 140 | +Make a note of the label that the transform has, it will be used later. In the example the label is `default_transform`. |
| 141 | + |
| 142 | +#### Physical layout |
| 143 | + |
| 144 | +A full physical layout is necessary to allow your dongle to be used with [ZMK Studio](../../features/studio.md). |
| 145 | +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. |
| 146 | + |
| 147 | +<Tabs |
| 148 | + groupId="studio-ready" |
| 149 | + defaultValue="studio" |
| 150 | + values={[ |
| 151 | + { label: "Dongle with Studio", value: "studio" }, |
| 152 | + { label: "Dongle without Studio", value: "nostudio" } |
| 153 | + ]} |
| 154 | + > |
| 155 | +<TabItem value="studio"> |
| 156 | + |
| 157 | +You will need to find your keyboard's physical layout, much like finding the matrix transform. |
| 158 | +Depending on your keyboard, the physical layout could be found in a variety of locations. Look for a node with `compatible = "zmk,physical-layout";`. |
| 159 | +There are three commonly found possibilities: |
| 160 | + |
| 161 | +- The physical layout is found in a file called `my_keyboard-layouts.dtsi` |
| 162 | +- The physical layout is found in the same file as the matrix transform and chosen node |
| 163 | +- 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. |
| 164 | + |
| 165 | +<Tabs |
| 166 | + groupId="layout-locations" |
| 167 | + defaultValue="layouts" |
| 168 | + values={[ |
| 169 | + { label: "Layouts File", value: "layouts" }, |
| 170 | + { label: "Base File", value: "base" }, |
| 171 | + { label: "Imported", value: "imported" } |
| 172 | + ]} |
| 173 | + > |
| 174 | +<TabItem value="layouts"> |
| 175 | +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: |
| 176 | + |
| 177 | +```dts |
| 178 | +#import "my_keyboard-layouts.dtsi" |
| 179 | +
|
| 180 | +&physical_layout0 { |
| 181 | + transform = <&default_transform>; |
| 182 | +}; |
| 183 | +
|
| 184 | +/ { |
| 185 | + chosen { |
| 186 | + zmk,kscan = &mock_kscan; |
| 187 | + zmk,physical-layout = &physical_layout0; |
| 188 | + }; |
| 189 | +}; |
| 190 | +``` |
| 191 | + |
| 192 | +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. |
| 193 | + |
| 194 | +:::note |
| 195 | +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. |
| 196 | +::: |
| 197 | + |
| 198 | +</TabItem> |
| 199 | +<TabItem value="base"> |
| 200 | +Copy the physical layout node into your `my_keyboard_dongle.overlay` file. Make sure the matrix transform is assigned to it, and select it in the `chosen` node. |
| 201 | + |
| 202 | +```dts |
| 203 | +/ { |
| 204 | + chosen { |
| 205 | + zmk,kscan = &mock_kscan; |
| 206 | + zmk,physical-layout = &physical_layout0; |
| 207 | + }; |
| 208 | +
|
| 209 | + physical_layout0: physical_layout_0 { |
| 210 | + compatible = "zmk,physical-layout"; |
| 211 | + display-name = "Default Layout"; |
| 212 | + transform = <&default_transform>; |
| 213 | + keys = <...>; // Long list of key positions |
| 214 | + }; |
| 215 | +}; |
| 216 | +``` |
| 217 | + |
| 218 | +Make sure that the labels `physical_layout0` and `default_transform` match those of the physical layout node and the matrix transform respectively. |
| 219 | + |
| 220 | +:::note |
| 221 | +If there are multiple physical layouts, you need only copy the one that applies to your keyboard. |
| 222 | +::: |
| 223 | + |
| 224 | +</TabItem> |
| 225 | +<TabItem value="imported"> |
| 226 | +Include your desired layout at the top of the file. Then assign your matrix transform to it, taking care to match up the node labels correctly. |
| 227 | + |
| 228 | +```dts |
| 229 | +#include <layouts/<layout_name>.dtsi> |
| 230 | +
|
| 231 | +&physical_layout0 { |
| 232 | + transform = <&default_transform>; |
| 233 | +}; |
| 234 | +``` |
| 235 | + |
| 236 | +In this case your node label will _not_ be `physical_layout0`, make sure to adjust it accordingly. |
| 237 | + |
| 238 | +Finally, select your physical layout in the chosen node. |
| 239 | + |
| 240 | +```dts |
| 241 | +/ { |
| 242 | + chosen { |
| 243 | + zmk,kscan = &kscan0; |
| 244 | + zmk,physical-layout = &physical_layout0; |
| 245 | + }; |
| 246 | +}; |
| 247 | +``` |
| 248 | + |
| 249 | +</TabItem> |
| 250 | +</Tabs> |
| 251 | + |
| 252 | +</TabItem> |
| 253 | +<TabItem value="nostudio"> |
| 254 | +Add a barebones [physical layout](physical-layouts.md) without the `keys` property to your `my_keyboard_dongle.overlay` file. |
| 255 | + |
| 256 | +```dts |
| 257 | +/ { |
| 258 | + physical_layout0: physical_layout_0 { |
| 259 | + compatible = "zmk,physical-layout"; |
| 260 | + display-name = "Default Layout"; |
| 261 | + transform = <&default_transform>; |
| 262 | + }; |
| 263 | +}; |
| 264 | +``` |
| 265 | + |
| 266 | +Make sure that the `transform` property uses the same label as your matrix transform. Then select the physical layout in the same `chosen` node that was previously created: |
| 267 | + |
| 268 | +```dts |
| 269 | +/ { |
| 270 | + chosen { |
| 271 | + zmk,kscan = &mock_kscan; |
| 272 | + zmk,physical-layout = &physical_layout0; // New item added |
| 273 | + }; |
| 274 | +}; |
| 275 | +``` |
| 276 | + |
| 277 | +</TabItem> |
| 278 | +</Tabs> |
| 279 | + |
| 280 | +## Building the Firmware |
| 281 | + |
| 282 | +:::warning |
| 283 | +Before flashing your new firmware, you need to flash `settings_reset` [firmware](../../troubleshooting/connection-issues.mdx#acquiring-a-reset-uf2) on all devices to ensure they can pair to each other. |
| 284 | +::: |
| 285 | + |
| 286 | +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. |
| 287 | + |
| 288 | +```yaml |
| 289 | +include: |
| 290 | + # ----------------------------------------- |
| 291 | + # Your other keyboard parts here |
| 292 | + # ----------------------------------------- |
| 293 | + # Change the board appropriately, you can use any board |
| 294 | + - board: nice_nano_v2 |
| 295 | + shield: my_keyboard_dongle |
| 296 | + - board: nice_nano_v2 |
| 297 | + shield: settings_reset |
| 298 | + # Add these cmake-args to the peripherals you wish to use with the dongle |
| 299 | + - board: nice_nano_v2 |
| 300 | + shield: my_keyboard |
| 301 | + cmake-args: -DCONFIG_ZMK_SPLIT=y -DCONFIG_ZMK_SPLIT_ROLE_CENTRAL=n |
| 302 | +``` |
| 303 | +
|
| 304 | +To use your dongled keyboard with [ZMK Studio](../../features/studio.md), apply the instructions for [building with Studio](../../features/studio.md#building) to the dongle. |
| 305 | +
|
| 306 | +If you ever want to "undongle" your keyboard, simply remove these CMake arguments and flash the resulting firmware (after a `settings_reset`). |
| 307 | + |
| 308 | +[^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. |
0 commit comments