refactor: Move to Zephyr v4.1.0 Move to Zephyr v4.1.0, with various build/compilation fixes needed for basic use. refactor(tests): Move to native_sim for tests. feat(core): (Optionally) use Zephyr keyboard input devices Add ability to assign a keyboard `input` device to a physical layout, or use a chosen `zmk,matrix-input`. fix(pointing): Refactor for changes to input API Pass NULL user_data to input callbacks. fix(tests): Fix BLE test to account for Zephyr changes Handle additional read callback invocation once all matching characteristic have been read. fix(sensors): Initialize sensor data to 0 before fetching. Be sure we don't get back any uninitialized data by initializing the channel data to 0 before calling into the sensor API. refactor(input): Adjust split input to input API changes. Input callbacks now have a user_data parameter, adjust accordingly. chore(bluetooth): Minor cleanup of split BT code after refactor Small fixes and remove commented dead code left after the split refactor. refactor: Fix up BLE tests after Zephyr upgrade. Minor changes to snapshots based on newer Zephyr version. refactor(boards): Move to upstream xiao_ble board ID. Move to official upstream board definition for the Seeed XIAO BLE. refactor: Adjust metadata schema for HWMv2 board IDs w/ qualifiers Adjust our ZMK metadata to allow for board IDs that include qualifiers with slash delimeters. refactor!(boards): Move nice!nano to HWMv2, and proper revisioning Upgrade the nice!nano board to HWMv2, under the proper nicekeyboards vendor directory, and with proper revisions. Includes a breaking change to default the `2.0.0` version instead of the much older v1 (`1.0.0`). fix: Disable Nordic dt-bindings header checks. Disable the recently added Nordic dt-bindings header checks, which cause issues for our HID related headers. fix(studio): Correct `memset` usage. Use the correct memset call to clear our RPC memory. fix: Refactor for new Zephyr PM API Adjustments to our PM code to match Zephyr PM APIs. refactor(ble): Use correct BT opt for connectable. Adjust for upstream Zephyr BT API changes for advertising options. refactor(boards): Move MakerDiary M2 board to HWMv2. Run the HWMv2 script to convert the MakerDiary M2 board. fix(studio): Correct usage of thread analyzer API Fix up the RPC code that invoke the thread analyzer API to account for API changes. chore: Remove nanopb module override. Leverage nanopb version that's used by Zephyr. feat(core): mapper for magic bootloader values. To trigger bootloaders that use a magic value in RAM to trigger bootloader mode, add a mapping retained memory driver that maps write/read of boot mode values to a special magic value stored in the actually backing RAM. feat(behaviors): Add retention boot mode to reset. Support new generic Zephyr retention boot mode API in the reset behavior. feat: Add double tap to enter bootloader functionality Add ability to enter the bootloader if double tapping reset within the specified window. refactor(CI): Move to 4.1 container tags. Move to the new 4.1 tagged container, to ensure updated SDK, Python packages, etc. refactor(boards): Move nRFMicro to HWMv2 Refactor nRFMicro to HWMv2, using proper SoC, revisions, and variants (for flipped). Also move to devicetree setup of DCDC/HV DCDC. refactor(boards): Move QMK Proton-C to HWMv2 Move Proton-C to HWMv2 for use with Zephyr 4.1. chore(ci): Adjust core coverage for new board IDs. Use correct board IDs, with qualifiers, for our core coverage testing. refactor(boards): Move BDN9 to HWMv2 Move BDN9 to HWMv2, using the base `bdn9` ID, no longer including the `_rev2` suffix in the ID. refactor(boards): Move nice!60 to HWMv2 Migrate nice!60 to HWMv2. refactor: Adjust how we're searching/loading keymap files Use new post_boards_shields extension point for loading keymap files from board/shield directories. refactor(boards): Move planck rev6 to HWMv2. Move Planck board definition to HWMv2, including versioning tweaks. refactor(boards): Move OLKB Preonic to HWMv2 Move Preonic board definition to HWMv2 and remove `_rev3` variant suffix in favor of board versioning with `3.0.0` as the default. chore(deps): Pull in Zephyr optional group for nanopb. Ensure we enable nanopb by adding +optional group filter. fix(ci): Prevent slash characters in artifact names. Move to HWMv2 means board IDs often include slashes, so replace those with underscores when doing file uploads. fix(usb): Adjust Kconfig settings for USB. * Ensure USB isn't initialized automatically before we do, which can happen if USB CDC logging is used/enabled for a given board. * Adjust USB HID to initialize the USB class/interface before we enable the USB device itself. fix(display): Fix setting the small font for the mono theme. Adjust for modified mono theme init function to pass the small font. chore(ci): Fix changed board IDs for core coverage. Adjust board IDs for our core coverage after move to HWMv2 and board versioning consistently. * planck_rev6 -> planck * bdn9_rev2 -> bdn9 fix(underglow): Remove use of removed Kconfig WS2812 symbol refactor(boards): Move PW CKP boards to HWMv2 Migrate the bt60, bt65, and bt75 to HWMv2. refactor(boards): Move Puchi BLE to HWMv2 Migrate the Puchi BLE to HWMv2. refactor(boards): Migrate Ferris rev02 to HWMv2. Move Ferris rev02 to HMWv2, and remove the revision from the ID. refactor(boards): Move Pillbug to HWMv2 Migrate the MechWild PillBug board to HWMv2. refactor(boards): Migrate s40nc to HWMv2 Move the ShortyFortyNoCordy (s40nc) to HWMv2. refactor(boards): Move bluemicro840 board to HWMv2. Migrate bluemicro840 board to HWMv2, set up boot mode retention. fix(boards): Retore bootloader support on XIAO BLE. Set up necessary boot mode/retention to properly set GPREGRET to trigger Adafruit bootloader to run on the XIAO BLE. refactor(boards): Move Adv360 Pro to HWMv2. Migrate Adv360 Pro left/right to HWMv2. refactor(boards): Move Glove80 to HMWv2 Refactor the MoErgo Glove80 left/right to HWMv2. refactor(boards): Move Mikoto to HMWv2. Migrate Mikoto to HWMv2, with non-exact matching, tweaks to I2C selection to imply it for the 7.2.0 revision for the fuel gauge. refactor(boards): Move kbdfans Tofu65 2.0 to HMWv2 Move Tofu65 2.0 to HMWv2, with ID of just `tofu65`. refactor(boards): Remove dz60rgb board Remove dz60rgb, it's no longer readily available and we have other current stm32 reference designs for testing. refactor(boards): Move Corneish Zen to HMWv2 Move Corneish Zen to HMWv2, with IDs of `corneish_zen_left`/`corneish_zen_right`. refactor(boards): Migrate Corne-ish Zen status screen * refactor(boards): Add boot mode to the nice!nano using common dtsi * Add a new .dtsi for setting up nRF52 boot mode/retained memory settings * Adjust XIAO BLE to use the new include file * Add boot mode to to the nice!nano refactor(boards): Add boot mode support to nice!60 board Enable boot mode for nice!60 board. refactor(boards): Adjust Zephyr board metadata file locations Move the ZMK metadata files for upstream Zephyr boards to align with the HWMv2 directory structure that uses the vendor ID for the parent directory for a board directory. fix: Don't enable ZMK Display by default for a few shields By convention, avoid enabling ZMK Display by default on shields that may be built with under-resourced controllers (e.g. nRF52833 based ones). fix: Remove usage of renamed Kconfig from core coverage. Avoid using WS2812_LED_STRIP, since that Kconfig was renamed/split into SPI/GPIO/I2S symbols. refactor(boards): Adjust XIAO RP2040 override names, bootloader support Adjust the .conf/.overlay files to match the proper naming for the XIAO rp2040 board. Also add the necessary Kconfig/DTS bits for supporting bootloader using retained memory/boot mode retention. fix(display): Adjust stack sizes for display usage. Updated LVGL is bumping our stack size, so adjust the system work queue and dedicated display queue stack sizes as needed to account for this. feat(display): Add thread name to dedicated display queue. When thread names are enabled, pass a name to the dedicated display queue for better tracibility when using the thread analyzer. docs(blog): Add Zephyr upgrade post docs: Add bootloader integration page Add a dedicated page to outline steps to set up bootloader integration using the boot retention mechanism in newer Zephyr versions. fix(display): port nice!view display code * remove `lv_` prefix from old LVGL methods doc: Update local setup docs to use `west packages pip` Install Zephyr deps using the newer `west packages pip --install`. Signed-off-by: Peter Johanson <peter@peterjohanson.com> refactor(split): Adjust BT split code for newer Zephyr APIs. refactor(boards): Adjust upstream RP2040 boards for boot mode retention Add necessary DTS/Kconfig settings to upstream RP2040 boards so they can use the ZMK bootloader functionality using the boot mode retention infrastructure. docs: Update Zephyr docs links to 4.1.0 version. Update all links to the Zephyr docs to the 4.1.0 versions to match our Zephyr version in use. docs: Add a note about using CMake v3 for maximum compatibility. Some optional modules, like libmetal, which is used on nRF5340, specifically require CMake v3, so add a note in the native toolchain setup about this. feat(pointing): Handle INPUT_BTN_TOUCH codes for mouse buttons Translate INPUT_BTN_TOUCH input codes into button 0 press/release for HID layer. chore(pointing): Clean up some warning messages. Properly check return code from queue-ing messages, and fix up some type warnings in our logging calls. * Fix input event codes line numbers fix(studio): Properly serialize GATT RPC indications. fix(core): Set a system work queue stack size of 2048 by default We use a fair amount of stack even without BLE or RP2040, so default to 2048 by default everywhere, and constrained platforms can lowes this if they really need. refactor(core): Move away from deprecated DIS Kconfig symbols Use the correct Device Information Service Kconfig symbols for our model number and manufacturer. refactor: Move upstream Zephyr board overrides to extensions dirs Newer Zephyr supports "board extensions" to formally do what we've added in ourselves via some hacks, so move all our board overlay/config file overrides for upstream Zephyr boards into that correct structure. fix(boards): Add xiao_ble sd_partition label for nosd snippet compat Upstream xiao_ble uses different naming convention for the partition labels, so add an additional label for the SD range, so the existing nrf52840-nosd snippet will still work with the board. fix(core): Don't force CBPRINTF_NANO, for proper formatting. The nano CBPRINTF implementation lacks some padded formatting needed to ensure consistent formatting of BLE addresses, which we use to store keys as strings in a few places, so use the complete CBPRINTF by default now. fix(boards): Remove some references to old nice_nano_v2 board ID. The nice!nano board definition now properly uses versioning, so avoid referring to it with old `nice_nano_v2` board ID. fix(boards): Remove nano overlays for old nice_nano_v2 board ID. With board versioning in place, we can remove the unused `nice_nano_v2.overlay` files from shields. --------- Signed-off-by: Peter Johanson <peter@peterjohanson.com> Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com> Co-authored-by: Nicolas Munnich <munnich@lipn.univ-paris13.fr> Co-authored-by: snoyer <noyer.stephane@gmail.com>
17 KiB
title, sidebar_label
| title | sidebar_label |
|---|---|
| Devicetree Overview | Devicetree Overview |
ZMK makes heavy usage of a type of tree data structure known as devicetree. Devicetree is a declarative way of describing almost everything about a Zephyr device, from the definition of keymaps and configuration of behaviors all the way to the internal storage partitions and architecture of the board's MCU.
This page is an introduction to devicetree for ZMK users and designers. For further reading, refer to the devicetree spec and Zephyr's documentation.
Running Example
The following segment taken from a keymap will be used as a running example:
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
/ {
behaviors {
spc_ul: space_underscore {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
bindings = <&kp SPACE>, <&kp UNDERSCORE>;
mods = <(MOD_LSFT|MOD_RSFT)>;
};
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <&spc_ul &kp Z &kp M &kp K>;
};
};
};
It may be helpful to open this page twice and leave one copy open at this example.
Note also that Devicetree uses C-style comments, i.e. // ... for line comments and /* ... */ for block comments.
Structure
A devicetree node has the general structure (parts within [] being optional)
[label:] name {
[properties]
[child nodes]
};
The root node of the devicetree always has the name /, i.e. is written as
/ {
[child nodes]
};
It is also the only node which has the / character as a name. See the devicetree spec for permitted characters for node names.
After various preprocessing steps, all contents of the devicetree will be found within/under the root node. If one node is found within another node, we say that the first node is a child node of the second one. Similarly, one can also refer to a grandchild node, etc.
In the running example, behaviors and keymap are child nodes of the root node. space_underscore and default_layer are child nodes of behaviors and keymap respectively, making them both grandchild nodes of the root node.
Properties
What properties a node may have varies drastically. Of the standard properties, there are two which are of particularly relevant to users and designers: compatible and status. Additional standard properties may be found in the devicetree spec.
Property types
These are some of the property types you will see most often when working with ZMK. Zephyr's Devicetree bindings documentation provides more detailed information and a full list of types.
bool
True or false. To set the property to true, list it with no value. To set it to false, do not list it.
Example: property;
If a property has already been set to true and you need to override it to false, use the following command to delete the existing property:
/delete-property/ the-property-name;
int
A single integer surrounded by angle brackets. Also supports mathematical expressions.
Example: property = <42>;
string
Text surrounded by double quotes.
Example: property = "foo";
array
A list of integers surrounded by angle brackets and separated with spaces. Mathematical expressions can be used but must be surrounded by parenthesis.
Example: property = <1 2 3 4>;
Values can also be split into multiple blocks, e.g. property = <1 2>, <3 4>;
phandle
A single node reference surrounded by angle brackets. Phandles will be explained in more detail in a later section.
Example: property = <&label>
phandles
A list of node references surrounded by angle brackets. Phandles will be explained in more detail in a later section.
Example: property = <&label1 &label2 &label3>
phandle array
A list of node references and possibly numbers to associate with the node. Mathematical expressions can be used but must be surrounded by parenthesis. Phandles will be explained in more detail in a later section.
Example: property = <&none &mo 1>;
Values can also be split into multiple blocks, e.g. property = <&none>, <&mo 1>;
See the documentation for "phandle-array" in Zephyr's Devicetree bindings documentation for more details on how parameters are associated with nodes.
GPIO array
This is just a phandle array. The documentation lists this as a different type to make it clear which properties expect an array of GPIOs.
Each item in the array should be a label for a GPIO node (the names of which differ between hardware platforms) followed by an index and configuration flags. See Zephyr's GPIO documentation for a full list of flags. Phandles and labels will be explained in more detail in a later section.
Example:
some-gpios =
<&gpio0 0 GPIO_ACTIVE_HIGH>,
<&gpio0 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
;
path
A path to a node, either as a node reference or as a string. This will be explained in more detail in a later section.
Examples:
property = &label;
property = "/path/to/some/node";
Compatible
The most important property that a node has is generally the compatible property. This property is used to map code to nodes. There are some special cases, such as the node named chosen, where the node name is used rather than a compatible property.
In the running example, space_underscore has the property compatible = "zmk,behavior-mod-morph";. The ZMK's mod-morph behavior code acts on all nodes with compatible set to this value. The ZMK keymap code acts similarly for compatible = "zmk,keymap";.
The compatible property is also used to identify what additional properties a node may have. Any properties which are not one of the standard properties must be listed in a "devicetree bindings" file. These files will sometimes also include some additional information on the usage of the node.
ZMK keeps all of its devicetree bindings under the app/dts/bindings directory.
The bindings file for compatible = "zmk,behavior-mod-morph"; is app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml.
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Mod Morph Behavior
compatible: "zmk,behavior-mod-morph"
include: zero_param.yaml
properties:
bindings:
type: phandle-array
required: true
mods:
type: int
required: true
keep-mods:
type: int
required: false
The properties the node can have are listed under properties. Some additional properties are imported from zero_param.yaml. Bindings files are the authority on node properties, with our documentation of said properties sometimes omitting things like the #binding-cells property (imported from the previously mentioned file, describing the number of parameters that the behavior accepts). A full description of the bindings file syntax can be found in Zephyr's documentation.
Note that binding files can also specify properties for children, like the zmk,keymap.yaml bindings file specifying properties for layers in the keymap.
Status
The status property simply describes the status of a node. For ZMK users and designers, there are only two relevant values that this could be set to:
status = "disabled";The node is disabled. Code should not take effect or make use of the node, but it can still be referenced by other parts of the devicetree.status = "okay";The default setting when not explicitly stated. The node is treated as "active". This property is generally only explicitly stated when overwriting astatus = "disabled";.
How this property is used in practice will become more clear after the devicetree preprocessing section later on.
Labels and Phandles
In addition to names, nodes can also have labels. For the ZMK user/designer, labels are arguably more important than node names. Whereas node names are used within code to access individual nodes, labels are used to reference other nodes from within devicetree itself. Such a reference is called a phandle, and can be thought of as similar to a pointer in C.
In the running example, spc_ul is the label given to the node space_underscore. The bindings property of the default_layer node is a "phandle-array" - an array of references to other nodes1 . Its first element is &spc_ul - a phandle to the node with label spc_ul, i.e. space_underscore. &kp is another example of a phandle. It points to a node defined as below:
/ {
behaviors {
kp: key_press {
compatible = "zmk,behavior-key-press";
#binding-cells = <1>;
display-name = "Key Press";
};
};
};
This node is imported from a different file -- imports will be discussed later on. The &kp phandles found in the running example also show the concept of parameters being passed to phandles. In this case, Z, M, and K are passed as parameters.
When ZMK needs to trigger a behavior found at a location in the keymap's binding property, it uses the phandle to identify the behavior node which needs to be called. It then executes the code determined by the compatible property of said node, passing in parameters while doing so2 . Depending on the behavior, another behavior phandle may need to be triggered, in which case the same process is used to identify the node and thus the parts of code which need to be executed.
Essentially, each layer in a keymap consists of an array of phandles pointing to various behaviors (alongside parameters) that were defined elsewhere. If you do not need to define the behavior node yourself, that just means ZMK has already defined it for you.
Devicetree Preprocessing
Much of the complexity in dts files comes from preprocessing. The resulting devicetree after all preprocessing has finished can be inspected for both GitHub Actions and local builds. For reasons that will make more sense later, your keymap and most of your customisations will be found near the bottom of the file.
Preprocessing comes from two sources:
- The C preprocessor can be used within Devicetree Source (
dts) files. - Devicetree has its own system for merging together, overwriting, and even deleting nodes and properties.
C Preprocessor
An introduction to the C preprocessor is beyond the scope of this page. There are plenty of resources online for the unfamiliar reader to refer to.
However, some specific methods of how the C preprocessor is used in ZMK's devicetree files can be useful, to better understand how everything fits together.
The C preprocessor is used to import some nodes and other preprocessor definitions from other files. The lines
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
which are found at the top of the running example import the default behavior node definitions for ZMK, along with a list of preprocessor definitions. The parameters Z, M, and K (passed to the &kp phandle in the running example) are actually C preprocessor defines. For example, during preprocessing references to Z get turned into the number 0x07001D, which is the number that gets passed to the ZMK host device (e.g. your computer) for it to then re-interpret as the letter "z".
The C preprocessor often gets leveraged by ZMK power users to reduce repetition in their keymap files. An example of this is the macro-behavior convenience macro. ZMK designers will also come across the RC macro used for matrix transformations, and make use of convenience defines such as GPIO_ACTIVE_HIGH.
Devicetree Processing
A devicetree is almost always constructed from multiple files. These files are generally speaking:
.dtsifiles, which exist exclusively to be included via the C preprocessor (their contents get "pasted" at the location of the#includecommand) and are not used by the build sytem otherwise.- A
.dtsfile, which forms the "base" of the devicetree. A single one of these is always present when a devicetree is constructed. For ZMK, the.dtsfile contains the sections of the devicetree describing the board. This includes importing a number of.dtsifiles describing the specific SoC that the board uses. - Any number of
.overlayfiles. These files can come from various sources, such as shields or snippets. An overlay is applied to a.dtsfile by appending its contents to the end of the.dtsfile, i.e. it is placed at the bottom of the file. Multiple overlays are applied by doing so repeatedly in a particular order. Without going into the details of the exact order in which overlays are applied, it is enough to know that if you specify e.g.shield: corne_left nice_view_adapter nice_viewin yourbuild.yaml, then the overlays are applied left to right. - A single
.keymapfile. This file being included is ZMK-specific, and is treated as the "final".overlayfile, appended after all other overlays.
Merging and overwriting nodes
When a node appears multiple times in the devicetree (after the files are imported and merged together), it gets merged into a single node as a preprocessing step. For example:
/ {
mn_ex: my_example_node {
property1 = <0>;
property2 = <2>;
};
};
/ {
my_example_node {
property2 = <1>;
property3 = <4>;
};
example2 {
property;
};
};
The second appearance of my_example_node has priority, thus its property2 value will overwrite the first appearance. The two root nodes also get merged in the process. The resulting tree after processing would be
/ {
mn_ex: my_example_node {
property1 = <0>;
property2 = <1>;
property3 = <4>;
};
example2 {
property;
};
};
Labels do not get overwritten; a node can have multiple labels. Phandles can also be used to overwrite or add properties:
&mn_ex {
property4 = <2>;
};
The phandle approach is the recommended one, as one does not need to know the exact names of all the parent nodes with this approach. Crucially, when using phandles to overwrite or add properties, the phandle must not be located within the root node. It is instead placed outside of the tree entirely.
Special devicetree directives
Devicetree has some special directives that affect the tree. Relevant ones are:
- Nodes can be deleted with the /delete-node/ directive:
/delete-node/ &node_label;outside of the root node. - Properties can be deleted with the /delete-property/ directive:
/delete-property/ node-property;inside the relevant node. /omit-if-no-ref/causes a node to be omitted from the resulting devicetree if there are no references/phandles to the node:/omit-if-no-ref/ &node_label;
-
A phandle array by definition also includes metadata, i.e. parameters. Strictly speaking, a list of phandles without metadata has type
phandlesrather thanphandle-array. A property with a single phandle has typephandle. ↩︎ -
The number of parameters passed to the behavior code (and skipped over to find the next behavior phandle) is determined by the
#binding-cellsproperty mentioned above. ↩︎