From c3582ae15a1fcd50f3c10141118019a143df5352 Mon Sep 17 00:00:00 2001 From: Nicolas Munnich Date: Wed, 2 Oct 2024 10:16:57 +0200 Subject: [PATCH] docs: added a page on dongles. Co-authored-by: rasmuskoit Co-authored-by: Cem Aksoylar --- .../hardware-integration/dongle.mdx | 302 ++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 303 insertions(+) create mode 100644 docs/docs/development/hardware-integration/dongle.mdx diff --git a/docs/docs/development/hardware-integration/dongle.mdx b/docs/docs/development/hardware-integration/dongle.mdx new file mode 100644 index 000000000000..451e4ee1224f --- /dev/null +++ b/docs/docs/development/hardware-integration/dongle.mdx @@ -0,0 +1,302 @@ +--- +title: Keyboard Dongle +sidebar_label: Keyboard Dongle +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +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: + +Benefits: + +- 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. +- It is easier to connect to the host device since dongles are typically connected via USB. + +Disadvantages: + +- An extra [board](../../faq.md#what-is-a-board) is needed (any BLE-capable board will work). +- The keyboard becomes unusable without the dongle. + +Depending on how the dongle is used, there are some additional [latency considerations](../../features/split-keyboards.md#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 same[^1]. +Whether your keyboard consists of a [board and a shield](index.mdx#boards--shields) or is just a board is irrelevant. + +To add a dongle, you will create a simplified form of a [new shield](new-shield.mdx). +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](new-shield.mdx). + +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](https://github.com/zmkfirmware/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 title="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 title="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: + +```dts title="my_keyboard_dongle.overlay" +#include + +/ { + 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](https://github.com/zmkfirmware/zmk/tree/main/app/boards)) and look through the [devicetree files](../../config/index.md) for nodes with `compatible = "zmk,matrix-transform";`. +This should look something like this: + +```dts + 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. Take care to put it under the root node (the `/ { ... };` node). +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](../../features/studio.md). +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 .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: + +```dts +#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. + +If there are multiple physical layouts in the file, you will need to copy over all of the remaining matrix transformations and assign them similarly. + + + +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. + +```dts +/ { + chosen { + zmk,kscan = &mock_kscan; + zmk,physical-layout = &physical_layout0; + }; + + physical_layout0: physical_layout_0 { + compatible = "zmk,physical-layout"; + display-name = "Default Layout"; + transform = <&default_transform>; + keys = <...>; // Long list of key positions + }; +}; +``` + +Make sure that the labels `physical_layout0` and `default_transform` match those of the physical layout node and the matrix transform respectively. + +If there are multiple physical layouts, you need only copy the one that applies to your keyboard. + + + +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. + +```dts +#include .dtsi> + +&physical_layout0 { + transform = <&default_transform>; +}; +``` + +In this case your node label will _not_ be `physical_layout0`, make sure to adjust it accordingly. + +Finally, select your physical layout in the chosen node. + +```dts +/ { + chosen { + zmk,kscan = &kscan0; + zmk,physical-layout = &physical_layout0; + }; +}; +``` + + + + + + +Add a barebones [physical layout](physical-layouts.md) without the `keys` property to your `my_keyboard_dongle.overlay` file. + +```dts +/ { + physical_layout0: physical_layout_0 { + compatible = "zmk,physical-layout"; + display-name = "Default Layout"; + transform = <&default_transform>; + }; +}; +``` + +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: + +```dts +/ { + chosen { + zmk,kscan = &mock_kscan; + zmk,physical-layout = &physical_layout0; // New item added + }; +}; +``` + + + + +## Building the Firmware + +:::warning +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. +::: + +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. + +```yaml +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 +``` + +If you ever want to "undongle" your keyboard, simply remove these CMake arguments and flash the resulting firmware (after a `settings_reset`). + +[^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. diff --git a/docs/sidebars.js b/docs/sidebars.js index c480a907213e..c0082867eb8a 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -134,6 +134,7 @@ module.exports = { "development/hardware-integration/physical-layouts", "development/hardware-integration/hardware-metadata-files", "development/hardware-integration/pinctrl", + "development/hardware-integration/dongle", "development/hardware-integration/shift-registers", "development/hardware-integration/encoders", "development/hardware-integration/soft-off-setup",