Hardware Integration
This section of the documentation describes steps necessary to get ZMK running on a keyboard, including basic keyboard functionality as well as additional features such as encoders. Please see pages in the sidebar for guides and reference that describe different aspects of this integration.
The foundational elements needed to get a specific keyboard working with ZMK can be broken down into:
- A physical layout that describes the electrical and physical structure of the keyboard, referring to:
- A kscan driver, which most frequently uses
compatible = "zmk,kscan-gpio-matrix"
for GPIO matrix based keyboards, orcompatible = "zmk,kscan-gpio-direct"
for direct wires. - A matrix transform, which defines how the kscan row/column events are translated into logical "key positions".
- An optional description of physical key positions and sizes, in order to visualize the keyboard accurately in ZMK Studio.
- A kscan driver, which most frequently uses
- A keymap, which binds each key position to a behavior, e.g. key press, mod-tap, momentary layer, in a set of layers.
- Other, optional configuration items to support features such as encoders or lighting systems.
These core architectural elements are defined per-keyboard, and where they are defined depends on the specifics of how that keyboard is structured.
Boards & Shields
ZMK uses the Zephyr concepts of "boards" and "shields" to refer to different parts of a keyboard build, that in turn get combined during a firmware build. Also see the Zephyr documentation on boards and shields.
What is a "board"?
In ZMK, a board defines the PCB that includes the microcontroller unit (MCU). For keyboards, this is one of two options:
- Complete keyboard PCBs that include the MCU (e.g. the Planck or Preonic).
- Small MCU boards (e.g. the nice!nano or Seeed Studio Xiao RP2040) that expose pins and are designed to be combined with larger keyboard PCBs, or hand-wired to switches to create the final keyboard.
What is a "shield"?
In ZMK, a shield is a PCB or hardwired set of components that when combined with an MCU-only board, like the SparkFun Pro Micro RP2040 or nice!nano, results in a complete usable keyboard. Examples would be keyboard PCBs like the Kyria or Lily58. The shield is usually the big PCB containing all the keys.
Why not just "keyboard"?
"Composite" keyboards are keyboards which use a separate small PCB MCU module for its "brains". In such keyboards, the shield is a brainless shell containing all the keys, RGB LEDs, encoders etc. It typically maps all of these features to a standard pin footprint, such as the Pro Micro pinout.
To bring this brainless shield to life, you attach any MCU board matching the footprint. For instance, the nice!nano is pin-compatible with the SparkFun Pro Micro RP2040, so you can substitute either board onto the shield. But each board comes with its own features (MCU, flash, BLE, etc.) which must also be handled.
Therefore in ZMK, board and shield are considered two different (but related) entities so that it is easier to mix and match them, and they are combined during a ZMK build. This provides the modularity to be able to use composite keyboards with different compatible controllers.
Note that "self-contained" keyboards only have a single PCB which includes the "brains" (MCU) onboard. In ZMK, these have no shield, only a board.
Organization Overview
- Self-contained keyboards
- Composite keyboards
For a self-contained keyboard that includes the microprocessor, all of the above architecture components are included in the Zephyr board definition and no shield is defined. You can see an example for the Planck V6 board directory.
With this type of keyboard, the full ZMK definition for the keyboard exists in the <board_root>/boards/<arch>/<keyboard_name>
directory where <board_root>
is zmk/app
or a module root, e.g. zmk/app/boards/arm/planck/
.
In that directory you'll have the following files, where there can be multiples of files with <board_name>
s, corresponding to each keyboard part for split keyboards:
<keyboard_name>
├── Kconfig.board
├── Kconfig.defconfig
├── <board_name>_defconfig
├── <board_name>.dts
├── <keyboard_name>.keymap
├── board.cmake
└── <keyboard_name>.zmk.yml
These files include base Kconfig files:
- A
Kconfig.board
file that defines the toplevel Kconfig items for the board, including which SoC Kconfig setting it depends on. - A
Kconfig.defconfig
file that sets some initial defaults when building this keyboard. This usually includes:- Setting
ZMK_KEYBOARD_NAME
to a value, for the product name to be used for USB/BLE info, - Setting
ZMK_USB
and/orZMK_BLE
for the default values for which HID transport(s) to enable by default
- Setting
Configuration files that set the visible Kconfig symbols:
- A
<board_name>_defconfig
file that forces specific Kconfig settings that are specific to this hardware configuration. These are mostly SoC settings around the specific hardware configuration.
<board_name>.dts
which contains all the devicetree definitions1, including but not limited to:- An
#include
line that pulls in the specific microprocessor that is used, e.g.#include <st/f3/stm32f303Xc.dtsi>
, - Kscan, matrix transform and physical layout devicetree nodes as described above,
- A chosen node including
zmk,physical-layout
property among others, where each property references the nodes defined in the file.
- An
- A
<keyboard_name>.keymap
file that includes the default keymap for that keyboard. Users will be able to override this keymap in their user configs.
And other miscellaneous ones:
- A
board.cmake
file with CMake directives for how to flash to the device. - A
<keyboard_name>.zmk.yml
file containing metadata for the keyboard.
See Zephyr's board porting guide for information on creating a new board. Also see the new keyboard shield guide for information on parts of the devicetree specifically related to ZMK.
Keyboards that require an add-on board to operate are composite keyboards, where the ZMK integration pieces are placed in the shield definition for that keyboard. This allows users to swap in different boards that use the same interconnect (e.g. Pro Micro RP2040, or nice!nano) and build a firmware the matches their actual combination of physical components.
With this type of keyboard, the partial definition for the keyboard exists in the <board_root>/boards/shields/<keyboard_name>
directory where <board_root>
is zmk/app
or a module root, e.g. zmk/app/boards/shields/clueboard_california/
.
In that directory, you'll have the following files, where there can be multiple <shield_name>
s, corresponding to each keyboard part for split keyboards:
<keyboard_name>
├── Kconfig.shield
├── Kconfig.defconfig
├── <shield_name>.overlay
├── <keyboard_name>.keymap
└── <keyboard_name>.zmk.yml
These files include base Kconfig files:
- A
Kconfig.shield
that defines the toplevel Kconfig value for the shield, which uses a supplied utility to function to default the value based on the shield list, e.g.def_bool $(shields_list_contains,clueboard_california)
. - A
Kconfig.defconfig
file to set default values for settings likeZMK_KEYBOARD_NAME
- A
<shield_name>.overlay
file which is a devicetree overlay file1, containing definitions including but not limited to:- Kscan, matrix transform and physical layout devicetree nodes as described above, where the kscan node uses the interconnect nexus node aliases such as
&pro_micro
for GPIO pins. - A chosen node including at least the
zmk,physical-layout
property, referring to the defined node.
- Kscan, matrix transform and physical layout devicetree nodes as described above, where the kscan node uses the interconnect nexus node aliases such as
- A
<keyboard_name>.keymap
file that includes the default keymap for that keyboard. Users will be able to override this keymap in their user configs.
And other miscellaneous ones:
- A
<keyboard_name>.zmk.yml
file containing metadata for the keyboard.
See the new keyboard shield guide for documentation that walks you through creating these files to define a new composite keyboard.
Footnotes
-
Parts of these files can live in separate
.dtsi
files (typically in the same directory) that are then#include
d in the files, to reduce duplication or improve organization. For instance, a shared<keyboard_name>.dtsi
file is used for split keyboards that contains devicetree definitions that are shared across keyboard parts. ↩ ↩2