# Technical Documentation: zmkfirmware/zmk

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/zmkfirmware/zmk)

### Overview (`README.md`)

# Zephyr™ Mechanical Keyboard (ZMK) Firmware

[](https://zmk.dev/community/discord/invite)
[](https://github.com/zmkfirmware/zmk/actions)
[](CODE_OF_CONDUCT.md)

[ZMK Firmware](https://zmk.dev/) is an open source ([MIT](LICENSE)) keyboard firmware built on the [Zephyr™ Project](https://www.zephyrproject.org/) Real Time Operating System (RTOS). ZMK's goal is to provide a modern, wireless, and powerful firmware free of licensing issues.

Check out the website to learn more: https://zmk.dev/.

You can also come join our [ZMK Discord Server](https://zmk.dev/community/discord/invite).

To review features, check out the [feature overview](https://zmk.dev/docs/). ZMK is under active development, and new features are listed with the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. Please feel free to add 👍 to the issue description of any requests to upvote the feature.

---

### Overview (`docs/README.md`)

# Website

This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.

## License

The ZMK Documentation is licensed [CC-BY-NC-SA](http://creativecommons.org/licenses/by-nc-sa/4.0/).

### Installation

```sh
$ npm ci
```

### Local Development

```sh
$ npm start
```

This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.

### Build

```sh
$ npm build
```

This command generates static content into the `build` directory and can be served using any static contents hosting service.

---

### Overview (`app/tests/hold-tap/README.md`)

Refer to the pdf/open document "zmk-modtap-proposal.{pdf,odt}" in this directory for a visual representation of the numbered tests for hold-tap.

---

### Overview (`app/boards/keebio/bdn9/README.md`)

# Building ZMK for the BDN9

Some general notes/commands for building standard BDN9 layouts from the assembly documentation.

## Standard Build

```
west build -p -d build/bdn9 --board bdn9_rev2
```

## Encoder Notes

If you built your BDN9 with encoders, you'll need to change the following in your local BDN9 config or add them to the end of the file.

```
CONFIG_EC11=y
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
```

Then, you'll want to uncomment the necessary encoder lines in your `bdn9_rev2.keymap`:

```
&sensors {
     status = "okay";
     sensors = <&left_encoder &mid_encoder &right_encoder>;
};

&left_encoder { status = "okay"; };
&mid_encoder { status = "okay"; };
&right_encoder { status = "okay"; };
```

And then add the correct `sensor-bindings` array to each keymap layer, e.g.:

```
sensor-bindings = <&inc_dec_kp PG_UP PG_DN &inc_dec_kp M_VOLU M_VOLD &inc_dec_kp C_PREV C_NEXT>;
```

---

### Overview (`app/boards/kinesis/adv360pro/README.md`)

# Kinesis Advantage 360 Professional

This board definition provides upstream support for the [Kinesis Advantage 360 Professional](https://kinesis-ergo.com/keyboards/advantage360/)

Kinesis offer a specific [custom configuration](https://github.com/KinesisCorporation/Adv360-Pro-ZMK/) for the 360 Pro that references [a customised version of ZMK](https://github.com/ReFil/zmk/tree/adv360-z3.2-2) with Advantage 360 Pro specific functionality and changes over base ZMK. The Kinesis fork is regularly updated to bring the latest updates and changes from base ZMK however will not always be completely up to date, some features such as new keycodes will not be immediately available on the 360 Pro after they are implemented in base ZMK.

When using this board definition some of the more advanced features (the indicator RGB leds) will not work, and Kinesis cannot provide customer service for usage of base ZMK. Likewise the ZMK community cannot provide support for either the Kinesis keymap editor, nor any usage of the Kinesis custom fork.

---

### Overview (`app/boards/mechwild/s40nc/README.md`)

# S40NC



Shorty40NoCordy (S40NC) is a limited run 40% bluetooth keyboard originally made and sold by MechWild.

## Building S40NC ZMK firmware

```
west build -p -b s40nc
```

---

### Overview (`app/boards/moergo/glove80/readme.md`)

## MoErgo Glove80

This board definition provides ZMK support for the [MoErgo Glove80](https://www.moergo.com)
keyboard.

MoErgo additionally offers a customized version of ZMK which adds additional functionality such as
RGB status indicators, available on GitHub at [moergo-sc/zmk](https://github.com/moergo-sc/zmk). The
MoErgo customized ZMK fork is regularly updated to include the latest changes from mainline ZMK, but
will not always be completely up-to-date. MoErgo also offers an online layout configurator and
firmware builder application using the customized fork at [my.glove80.com](https://my.glove80.com).

While mainline ZMK is expected to work well with Glove80, MoErgo only provides support for use of
their customized fork. Likewise, the ZMK community cannot directly provide support for MoErgo's fork.

---

### Overview (`app/boards/nicekeyboards/nice60/README.md`)

# nice!60



The nice!60 is a hotswap 60% made by Nice Keyboards. https://nicekeyboards.com/nice-60

## Building nice!60 ZMK firmware

```
west build -p -b nice60//zmk
```

---

### Overview (`app/boards/pierrechevalier83/ferris/README.md`)

# Building ZMK for the Ferris 0.2

## Standard Build

```
west build -p -d build/ferris --board ferris
```

## Flashing

`west` can be used to flash the board directly. Press the reset button once, and run:

```
west flash -d build/ferris
```

---

### Overview (`app/boards/shields/a_dux/README.md`)

# A. Dux

Shield configuration for [Architeuthis Dux by Tapi][1] (aka A. Dux, A.D., "Giant Squid").

![Wireless Architeuthis Dux with nice!nano controllers][2]

This shield is an adaptation of the direct pin [Cradio shield by @davidphilipbarr][3].

## Cephalopoda

Check out the rest of Tapi's Cephalopoda collection of low profile split ergonomic mechanical keyboards at <https://github.com/tapioki/cephalopoda>.

[1]: https://github.com/tapioki/cephalopoda/tree/main/Architeuthis%20dux
[2]: https://media.discordapp.net/attachments/855822038287908864/866315666802081792/image0.jpg
[3]: https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields/cradio

---

### Overview (`app/boards/shields/bfo9000/README.md`)

# [BFO-9000](https://keeb.io/products/bfo-9000-keyboard-customizable-full-size-split-ortholinear)

Customizable full-size split ortholinear.

## Features

- Compatible with MX-compatible, Alps-compatible, and Kailh Low-Profile Choc switches.
- Breakoff pieces to allow for 4 to 6 rows and 7 to 9 columns.
- RGB LED connections

## Hardware Notes

[Included default keymap](http://www.keyboard-layout-editor.com/#/gists/51293c31afcd5f1765e8f413a46bfcf8)

---

### Overview (`app/boards/shields/clog/README.md`)

# The Clog

The Clog is a 34-key choc v1 split board by S'mores. This firmware works for both the [Clog][clog],
as well as the [Sephirette][sephirette], the MX version. You can purchase kits for both boards
at smoresboards.com.

[clog]: https://github.com/smores56/clog
[sephirette]: https://github.com/smores56/sephirette

---

### Overview (`app/boards/shields/clueboard_california/README.md`)

# [Clueboard California Macropad](https://clueboard.co/parts/clueboard-california-macropad-kit)

A small 10-key macropad in the shape of the US state of California.

## Features

- Per key LED.
- Proton-C only, including space for the piezo speaker.
- Direct GPIO wiring, no GPIO matrix at all.

## Hardware Notes

| Switch       | Switch Pin | LED Pin |
| ------------ | ---------- | ------- |
| shasta1      | a10        | a2      |
| modoc1       | a9         | b5      |
| mendocino1   | a0         | a1      |
| lassen1      | b8         | b4      |
| eldorado1    | b11        | b12     |
| big_sur1     | b9         | b10     |
| sierra1      | a8         | a15     |
| los_padres1  | a7         | a6      |
| mojave1      | b1         | b0      |
| joshua_tree1 | b2         | b3      |

---

### Overview (`app/boards/shields/cradio/README.md`)

# Cradio

Cradio is a firmware for a few 34 key keyboards, including Cradio, Hypergolic and Sweep.

## Pin arrangement

Some revisions of the aforementioned PCBs have slightly different pin arrangements compared to what's defined in [`cradio.dtsi`](./cradio.dtsi). If you need to swap a few keys for your particular PCB, you can easily reorder the `input-gpio` definition in your own keymap file (i.e. in `zmk-config/config/cradio.keymap`):

```dts
/* Adjusted Cradio pin arrangement */
/* The position of Q and B keys have been swapped */
&kscan0 {
    input-gpios
    = <&pro_micro  6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 18 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 19 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 20 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 21 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    , <&pro_micro  9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
    ;
};
```

This `&kscan0` block must be placed outside of any blocks surrounded by curly braces (`{...}`).

---

### Overview (`app/boards/shields/eek/README.md`)

A fixed split 36key-board with a typing angle of 90 degrees distributed by cbkbd

---

### Overview (`app/boards/shields/eternal_keypad/README.md`)

# Eternal Keypad

Eternal Keypad is an open-source input device for gaming that can be assembled for both right and left hand mouse users.

Firmware is described in the [Eternal Keypad](https://github.com/duckyb/eternal-keypad) repository [README](https://github.com/duckyb/eternal-keypad#firmware).

Two keymaps are included:

- eternal_keypad (default)
- eternal_keypad_lefty (for left handed users)

---

### Overview (`app/boards/shields/helix/README.md`)

#### Note to user:

- If desired, RGB underglow must be manually enabled before building and flashing. Check 'helix.conf' to do so.
- Peripheral RGB function is impaired until full support is implemented in the master branch.
- OLED displays are not currently included in this shield. This will be updated after OLED support is live.
- 'KANA' and 'EISUU' input is currently utilized under the 'LANG1' and 'LANG2' keycodes respectively.

---

Thanks to Nicell, KemoNine, petejohanson, TJ "Chormbo The Great", joelspadin/Rinh, Wofiel, Okke, innovaker,
and the rest of the ZMK contributors for their support in constructing this shield. I appreciate your assistance greatly.
This has been a valuable learning experience for me. May this contribution serve the community well.

---

### Overview (`app/boards/shields/leeloo/README.md`)

# Clickety Split | Leeloo v2



Keyboard Designer: [clicketysplit.ca](https://clicketysplit.ca)
GitHub: [ClicketySplit](https://github.com/ClicketySplit)
Hardware Supported: Pro Micro, Elite-C, and nice!nano v2

Leeloo v2 has been designed from scratch—again. Everything from the wiring schematic to its case. Leeloo v2 still keeps the column stagger that it's known for, along with its low profile design.

## Features/Differences from Leeloo v1

- Support for Kailh Low Profile Choc switches with 18mm x 18mm spacing.
  - A version for Kailh Box/MX switches with 19.05mm x 19.05mm spacing will be available in the future.
- All switch locations are socketed.
- Rotary encoder locations are socketed.
  - One of two locations on each side can be used for a rotary encoder.
- OLED Displays and nice!view Displays are natively supported, socketed, and no extra wiring is required.
- Support for per-switch RGB underglow.
- Better location for 110mAh or 700mAh batteries.
  - Different location for soldering battery leads.
- Support for Alps Alpine Micro On/off switches.

# Leeloo v1



## Features

- 4x6x5m Split Keyboard
- Support for both Low Profile Choc switches, and Box/MX switches; 19.05mm x 19.05mm spacing.
- 90% of the switches are socketed; with the exception to the rotary encoder positions.
- Support for Alps Alpine EC11 Rotary Encoders—one on each side, in one of three locations.
- Support for OLED Displays or nice!view Displays.
  - nice!view displays require a wire to be soldered from the CS Pin on nice!view display to P0.22 or D4 on the nice!nano.
- Support for both 110mAh or 700mAh batteries.
- Solder pads for battery leads.
- Support for Alps Alpine Micro On/off switches.

# Building Leeloo's ZMK Firmware

ZMK Firmware: [Introduction to ZMK](https://zmk.dev/docs/)
Installation: [Installing ZMK](https://zmk.dev/docs/user-setup)
Customization: [Customizing ZMK](https://zmk.dev/docs/customization)
Development Environment: [Basic Setup](https://zmk.dev/docs/development/setup)

Build commands for the default keymap of Leeloo v1:

```
west build -d build/left -p -b nice_nano -- -DSHIELD=leeloo_left
west build -d build/right -p -b nice_nano -- -DSHIELD=leeloo_right
```

Build commands for the default keymap of Leeloo v2:

```
west build -d build/left_v2 -p -b nice_nano -- -DSHIELD=leeloo_rev2_left
west build -d build/right_v2 -p -b nice_nano -- -DSHIELD=leeloo_rev2_right
```

Build commands for your custom keymap of Leeloo v1:

```
west build -d build/right -p -b nice_nano -- -DSHIELD=leeloo_right -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo/config"
west build -d build/left -p -b nice_nano -- -DSHIELD=leeloo_left -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo/config"
```

Build commands for your custom keymap of Leeloo v2:

```
west build -d build/right_v2 -p -b nice_nano -- -DSHIELD=leeloo_rev2_right -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo_v2/config"
west build -d build/left_v2 -p -b nice_nano -- -DSHIELD=leeloo_rev2_left -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo_v2/config"
```

## Building Leeloo's ZMK Firmware with nice!view Displays

There are a couple of files that need to be adjusted before the build commands can be run.

### Edit the leeloo[_rev2].keymap File

Near the top 3rd of the leeloo[_rev2].keymap file, locate the following code block:

```
//nice_view_spi: &spi0 {
//  cs-gpios = <&pro_micro 4 GPIO_ACTIVE_HIGH>;
//};
```

Remove the forward slashes to resemble the following:

```
nice_view_spi: &spi0 {
    cs-gpios = <&pro_micro 4 GPIO_ACTIVE_HIGH>;
};
```

Save your changes and close the file.

### Sample Build Commands for nice!view Displays

Build commands for the default keymap of Leeloo v1:

```
west build -d build/left -p -b nice_nano -- -DSHIELD="leeloo_left nice_view_adapter nice_view"
west build -d build/right -p -b nice_nano -- -DSHIELD="leeloo_right nice_view_adapter nice_view"
```

Build commands for the default keymap of Leeloo v2:

```
west build -d build/left_v2 -p -b nice_nano -- -DSHIELD="leeloo_rev2_left nice_view_adapter nice_view"
west build -d build/right_v2 -p -b nice_nano -- -DSHIELD="leeloo_rev2_right nice_view_adapter nice_view"
```

Build commands for your custom keymap of Leeloo v2:

```
west build -d build/left -p -b nice_nano -- -DSHIELD="leeloo_rev2_left nice_view_adapter nice_view" -DZMK_CONFIG="/workspaces/zmk-config/[yourName]/leeloo_v2/config"
west build -d build/right -p -b nice_nano -- -DSHIELD="leeloo_rev2_right nice_view_adapter nice_view" -DZMK_CONFIG="/workspaces/zmk-config/[yourName]/leeloo_v2/config"
```

# Support

If you have any questions with regards to Leeloo, please [Contact Us](https://clicketysplit.ca/pages/contact-us).

Clickety Split
For the love of split keyboards.

---

### Overview (`app/boards/shields/leeloo_micro/README.md`)

# Clickety Split | Leeloo-Micro



Keyboard Designer: [clicketysplit.ca](https://clicketysplit.ca)
GitHub: [ClicketySplit](https://github.com/ClicketySplit)
Hardware Supported: nice!nano v2, nice!view v1

Leeloo-Micro is a 3x5x5m derivative of Leeloo v2; inheriting the column stagger and modifiers row, yet, reducing the number of switches by removing the top row and outside columns. With Leeloo-Micro's inaugural release being wireless, it leverages nice!nanos and nice!views for its microcontrollers and displays.

## Features

- 3x5x5m Split Keyboard
- Support for Kailh Low Profile Choc switches with 18mm x 18mm spacing.
- All switch locations are socketed.
- Support for Alps Alpine EC11 Rotary Encoders—one on each side, in one of two locations.
  - Rotary encoder locations are socketed.
- nice!view Displays are inherently supported, socketed, and no extra wiring is required.
- Support for per-switch RGB underglow.
- Support for both 110mAh or 700mAh batteries.
- Support for Alps Alpine Micro On/off switches.

# Building Leeloo-Micro ZMK Firmware

ZMK Firmware: [Introduction to ZMK](https://zmk.dev/docs/)
Installation: [Installing ZMK](https://zmk.dev/docs/user-setup)
Customization: [Customizing ZMK](https://zmk.dev/docs/customization)
Development Environment: [Basic Setup](https://zmk.dev/docs/development/setup)

Build commands for the default keymap of Leeloo-Micro:

```
west build -d build/left -p -b nice_nano -- -DSHIELD=leeloo_micro_left
west build -d build/right -p -b nice_nano -- -DSHIELD=leeloo_micro_right
```

Build commands for your custom keymap of Leeloo-Micro:

```
west build -d build/right -p -b nice_nano -- -DSHIELD=leeloo_micro_right -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo_micro/config"
west build -d build/left -p -b nice_nano -- -DSHIELD=leeloo_micro_left -DZMK_CONFIG="C:/dev/zmk/[yourName]/leeloo_micro/config"
```

## Building Leeloo-Micro's ZMK Firmware with nice!view Displays

There are a couple of files that need to be adjusted before the build commands can be run.

### Edit the leeloo_micro.keymap File

Near the top 3rd of the leeloo_micro.keymap file, locate the following code block:

```
//nice_view_spi: &spi0 {
//  cs-gpios = <&pro_micro 4 GPIO_ACTIVE_HIGH>;
//};
```

Remove the forward slashes to resemble the following:

```
nice_view_spi: &spi0 {
    cs-gpios = <&pro_micro 4 GPIO_ACTIVE_HIGH>;
};
```

Save your changes and close the file.

### Sample Build Commands for nice!view Displays

Build commands for the default keymap of Leeloo-Micro:

```
west build -d build/left -p -b nice_nano -- -DSHIELD="leeloo_micro_left nice_view_adapter nice_view"
west build -d build/right -p -b nice_nano -- -DSHIELD="leeloo_micro_right nice_view_adapter nice_view"
```

Build commands for your custom keymap of Leeloo-Micro:

```
west build -d build/left -p -b nice_nano -- -DSHIELD="leeloo_micro_left nice_view_adapter nice_view" -DZMK_CONFIG="/workspaces/zmk-config/[yourName]/leeloo_micro/config"
west build -d build/right -p -b nice_nano -- -DSHIELD="leeloo_micro_right nice_view_adapter nice_view" -DZMK_CONFIG="/workspaces/zmk-config/[yourName]/leeloo_micro/config"
```

# Support

If you have any questions with regards to Leeloo-Micro, please [Contact Us](https://clicketysplit.ca/pages/contact-us).

Clickety Split
For the love of split keyboards.

---

### Overview (`app/boards/shields/m60/README.md`)

# [Makerdiary M60](https://wiki.makerdiary.com/m60)

A 60% ANSI keyboard designed and manufactured by Makerdiary.
http://makerdiary.com

## Features

- Per key RGB LED.
- Uses makerdiary M.2 nRF52840 module
- Matrix wiring

## Hardware Notes

https://wiki.makerdiary.com/m60/developer_guide/hardware/

---

### Overview (`app/boards/shields/microdox/README.md`)

# Microdox

Microdox is a 36 key split keyboard by Boardsource.

Two variants are defined for this shield – V1 and V2. The layout is exactly the same between the
two. Per [help documentation](https://www.boardsource.xyz/help/6129be4a9c85c6050be190d2), if you
purchased your PCB before April 2022, use `microdox_left`/`microdox_right`. Otherwise, use
`microdox_v2_left`/`microdox_v2_right`.

---

### Overview (`app/boards/shields/nibble/README.md`)

# Building ZMK for the Nibble

Some general notes/commands for building standard nibble layouts from the assembly documentation.

## LED Notes

If you built your nibble without the LEDs _and_ are using a nice!nano board, you'll need to change the following in your local nibble config or add them to the end of the file.

```
CONFIG_ZMK_RGB_UNDERGLOW=n
```

## Encoder Notes

If you built your nibble without an encoder, you'll need to change the following in your local nibble config or add them to the end of the file.

```
CONFIG_EC11=n
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=n
```

## OLED Builds

If using an OLED screen, you'll need to change the following in your local nibble config or add them to the end of the file.

```
CONFIG_ZMK_DISPLAY=y
```

---

### Overview (`app/boards/shields/nice_view/README.md`)

# nice!view

The nice!view is a low-power, high refresh rate display meant to replace I2C OLEDs traditionally used.

This shield requires that an `&nice_view_spi` labeled SPI bus is provided with _at least_ MOSI, SCK, and CS pins defined.

## Custom widget

The nice!view shield includes a custom vertical widget.

Profile indicators show the status of the first five BLE profiles using numbers from 1 to 5.
The number corresponding to the currently selected profile is drawn in a filled disk
and the circle outline around each profile number correspond to the following states:

- solid outline: connected
- dashed outline: not connected
- no outline: not bound

## Disable custom widget

To use the built-in ZMK widget instead of the custom nice!view one, add the following item to your `.conf` file:

```
CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN=y
CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_26=y
CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26=y
```

---

### Overview (`app/boards/shields/nice_view_adapter/README.md`)

# nice!view Adapter

This shield is used as an adapter between the nice!view and existing shields/boards that expose an I2C OLED header.

To use this shield, you should add this shield to your list of shields _before_ `nice_view`.

The nice!view will use the SDA/SCL pins of the OLED, and then the adapter expects a final pin to be "bodged" from your microcontroller to the nice!view CS pin. This adapter assumes that the CS pin bodged is the `&pro_micro 1` pin or "D1", which is the top left pin when looking at the front of the board. If you can't use this pin, you'll need to override the `cs-gpios` for the `&nice_view_spi` bus (in your `zmk-config` keymap for example) or you will want to define your own `&nice_view_spi` bus without using this adapter.

```
west build -b nice_nano -- -DSHIELD="lily58_left nice_view_adapter nice_view"
```

---

### Overview (`app/boards/shields/reviung34/README.md`)

# REVIUNG34

REVIUNG34 is a 33-34 key unibody split keyboard by [gtips](https://github.com/gtips). An in-stock version can be found at [Little Keyboards](https://www.littlekeyboards.com/products/reviung34-analyst-keyboard-kit).

By default, the 2x1u layout is used. To use to the 1x2u layout, add the following to your keymap:

```
/ {
    chosen {
        zmk,matrix-transform = &single_2u_transform;
    };
};
```

---

### Overview (`app/boards/shields/tg4x/README.md`)

# TG4x

Standard setup for the [TG4x](https://github.com/MythosMann/tg4x/) 40% keyboard.

## Board Revision and Layout Notes

This TG4x implementation is for...

- rev 2.1 of the board
- Split spacebar with 2.25U on the left and 2.75U on the right
- 2U right shift

---

### Overview (`app/boards/shields/tidbit/README.md`)

# TIDBIT Compatibility Notes

- The top-left and top-right encoders share the same pins. Install only one, and enable/include EITHER `encoder_1` OR `encoder_1_top_row` in your keymap; not both.
- `encoder_3` cannot be used at the same time as the OLED and/or HT16K33 modules, as it is wired to the same pins.
  - While the HT16K33 hardware is supported by Zephyr, functionality may not have been implemented in ZMK for it.
- `encoder_4` cannot be used at the same time as the TRRS jack, as it is wired to the same pins.

---

### Docs Intro (`docs/docs/intro.mdx`)

---
title: Introduction to ZMK
sidebar_label: Introduction
slug: /
---

import { Columns, Column } from "@site/src/components/columns";

ZMK Firmware is an open source (MIT) keyboard firmware built on the [Zephyr™ Project](https://zephyrproject.org/) Real Time Operating System (RTOS).
ZMK's goal is to provide a modern and powerful firmware that is designed for power-efficiency, flexibility, and broad hardware support.
ZMK is capable of being used for both wired and wireless input devices.

## Features

Below table lists major features/capabilities currently supported in ZMK, as well as ones that are currently under development and not planned.

<Columns>
  <Column className="full-width-table">

| Legend: | ✅ Supported | 🚧 Under Development | ❌ Not Planned |
| :------ | :----------- | :------------------- | -------------- |

  </Column>
</Columns>

<Columns>
  <Column className="full-width-table">

| Hardware                                                                                                      | Support |
| ------------------------------------------------------------------------------------------------------------- | :-----: |
| [Wireless Split Keyboards](features/split-keyboards.md)                                                       |   ✅    |
| Wired Split Keyboards                                                                                         |   🚧    |
| [Low Active Power Usage](/power-profiler)                                                                     |   ✅    |
| [Encoders](features/encoders.md)                                                                              |   ✅    |
| [LED-based Lighting](features/lighting.md)                                                                    |   ✅    |
| [Displays](features/displays.md)                                                                              |   🚧    |
| [Pointing Devices](features/pointing.md)                                                                      |   ✅    |
| Multitouch Touchpads (PTP)                                                                                    |   🚧    |
| [Low Power Sleep States](features/low-power-states.md)                                                        |   ✅    |
| [Low Power Mode (VCC Shutoff) for Peripherals](keymaps/behaviors/power.md)                                    |   ✅    |
| Improved Power Handling for Multiple Peripherals                                                              |   🚧    |
| [Battery Level Reporting](features/battery.md)                                                                |   ✅    |
| [Support for a Wide Range of 32-bit Microcontrollers](https://docs.zephyrproject.org/4.1.0/boards/index.html) |   ✅    |
| Support for AVR/8-bit Chips                                                                                   |   ❌    |

  </Column>
  <Column className="full-width-table">

| Connectivity                                                    | Support |
| --------------------------------------------------------------- | :-----: |
| Low-Latency BLE Support                                         |   ✅    |
| [Multi-Device BLE Connectivity](features/bluetooth.md#profiles) |   ✅    |
| [USB Connectivity](keymaps/behaviors/outputs.md)                |   ✅    |

| Keymap Features                                                                                                                                                     | Support |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----: |
| [User Configuration Repositories](user-setup.mdx)                                                                                                                   |   ✅    |
| [Keymaps and Layers](keymaps/index.mdx)                                                                                                                             |   ✅    |
| [Wide Range of Keycodes](keymaps/list-of-keycodes.mdx)                                                                                                              |   ✅    |
| [Flexible Behavior System](keymaps/behaviors/index.mdx)                                                                                                             |   ✅    |
| [Hold-Taps](keymaps/behaviors/hold-tap.mdx) (including [Mod-Tap](keymaps/behaviors/hold-tap.mdx#mod-tap) and [Layer-Tap](keymaps/behaviors/hold-tap.mdx#layer-tap)) |   ✅    |
| [Tap-Dances](keymaps/behaviors/tap-dance.mdx)                                                                                                                       |   ✅    |
| [Sticky (One Shot) Keys](keymaps/behaviors/sticky-key.md)                                                                                                           |   ✅    |
| [Combos](keymaps/combos.md)                                                                                                                                         |   ✅    |
| [Macros](keymaps/behaviors/macros.md)                                                                                                                               |   ✅    |
| [Mouse Keys](keymaps/behaviors/mouse-emulation.md)                                                                                                                  |   ✅    |
| [Realtime Keymap Updating](features/studio.md)                                                                                                                      |   🚧    |

  </Column>
</Columns>

## Code of Conduct

Please note that this project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/).
By participating in this project you agree to abide by its terms.

---

### Docs Config Index (`docs/docs/config/index.md`)

---
title: Configuration Overview
sidebar_label: Overview
---

ZMK has many configuration settings that can be changed to change the behavior of your keyboard. They are set in either Kconfig or Devicetree files.

This page describes the Kconfig and Devicetree file formats and how to change settings in them. See the other pages in the configuration section for a list of settings you can change.

:::note
All configuration is currently set at compile time. After changing any settings, you must build new firmware and flash it for the changes to apply.
:::

## Config File Locations

ZMK will search multiple folders for the config files described below. There are three primary locations it will search:

### User Config Folder

When building with a `zmk-config` folder, ZMK will search the `zmk-config/config` folder for the following config files, where `<name>` is the name of the shield if using a shield, or the name of the board otherwise:

- `<name>.conf` (Kconfig)
- `<name>.keymap` (Devicetree)

These files hold your personal settings for the keyboard. All files are optional. If present, they override any configuration set in the board or shield folders. Otherwise, the default configuration and/or keymap is used.

When using a [split keyboard](../features/split-keyboards.md), you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. If a shared config file exists, any left or right files will be ignored.

### Board Folder

ZMK will search for config files in:

- [`zmk/app/boards/<vendor>/<board>`](https://github.com/zmkfirmware/zmk/tree/main/app/boards)
- `zmk-config/boards/<vendor>/<board>`
- `<module>/boards/<vendor>/<board>`
- `zmk-config/config/boards/<vendor>/<board>` (For backwards compatibility only, do not use.)

...where `<vendor>` is the name of the creator or vendor, `<board>` is the name of the board and `<module>` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the board.

ZMK will search the board folder for the following config files _in addition_ to [Zephyr board-defining files](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#create-your-board-directory):

- `<board>.conf` (Kconfig)
- `<board>.keymap` (Devicetree, keyboards with onboard controllers only)

Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in board folders.

For more documentation on creating and configuring a new board, see [Zephyr's board porting guide](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#write-kconfig-files).

### Shield Folder

When building with a shield, ZMK will search for config files in:

- [`zmk/app/boards/shields/<shield>`](https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields)
- `zmk-config/boards/shields/<shield>`
- `<module>/boards/shields/<shield>`
- `zmk-config/config/boards/shields/<shield>` (For backwards compatibility only, do not use.)

...where `<shield>` is the name of the shield and `<module>` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the shield that the board is plugged into.

ZMK will search the shield folder for the following config files _in addition_ to [Zephyr shield-defining files](https://docs.zephyrproject.org/4.1.0/hardware/porting/shields.html#shield-porting-and-configuration):

- `<shield>.conf` (Kconfig)
- `<shield>.keymap` (Devicetree)

Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in shield folders.

For more documentation on creating and configuring a new shield, see [Zephyr's shield documentation](https://docs.zephyrproject.org/4.1.0/hardware/porting/shields.html) and [ZMK's new keyboard shield](../hardware-integration/new-shield.mdx) guide.

## Kconfig Files

Kconfig is used to configure global settings such as the keyboard name and enabling certain hardware devices. These typically have a `.conf` file extension and are text files containing `CONFIG_XYZ=value` assignments, with one setting per line.

Kconfig files look like this:

```ini
CONFIG_ZMK_SLEEP=y
CONFIG_EC11=y
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
```

Files ending with `_defconfig` use the same syntax as `.conf` files. They set the default configuration for the hardware, which is then overridden by anything in a `.conf` file.

The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. Note that options are _not_ prefixed with `CONFIG_` in these files.

See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/4.1.0/build/kconfig/index.html) for more details on Kconfig files.

:::tip

You can investigate the [final set of configuration settings](../troubleshooting/building-issues.md#configuration-setting-issues) that are in effect during a build to diagnose any issues with changing Kconfig settings.

:::

### KConfig Value Types

#### bool

Either `y` for yes or `n` for no.

Example: `CONFIG_FOO=y`

#### int

An integer.

Example: `CONFIG_FOO=42`

#### string

Text surrounded by double quotes.

Example: `CONFIG_FOO="foo"`

## Devicetree Files

Various Devicetree files are combined to build a tree that describes the hardware for a keyboard. They are also used to define keymaps. A full primer on Devicetree can be found in the [Devicetree Overview page](../development/devicetree.md) -- a shorter summary of some of the points can be found below.

### Devicetree Overview

Devicetree files use various file extensions. These indicate the purpose of the file, but they have no effect on how the file is processed. Common file extensions for Devicetree files include:

- `.dts`: The base hardware definition.
- `.overlay`: Adds to and/or overrides definitions in a `.dts` file.
- `.keymap`: Holds a keymap and user-specific hardware configuration.
- `.dtsi`: A file which is only intended to be `#include`d from another file.

Devicetree files look like this:

```dts
/ {
    chosen {
        zmk,kscan = &kscan0;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
    };
};
```

Devicetree properties apply to specific nodes in the tree instead of globally. The properties that can be set for each node are determined by `.yaml` files in ZMK in the various `dts/bindings` folders.

See [Zephyr's Devicetree guide](https://docs.zephyrproject.org/4.1.0/build/dts/index.html) for more details on Devicetree files.

:::tip

You can investigate the [final combined Devicetree](../troubleshooting/building-issues.md#devicetree-related-issues) produced during a build to diagnose any issues with changing Devicetree nodes.

:::

### Changing Devicetree Properties

Since Devicetree properties are set for specific nodes in the tree, you will first need to find the node you want to configure. You will typically need to
search through the `.dts` file for your board, `.overlay` file for your shield, or a `.dtsi` file included by those files using an `#include` statement.

A Devicetree node looks like this:

```dts
kscan0: kscan {
    compatible = "zmk,kscan-gpio-matrix";
    // more properties and/or nodes...
};
```

The part before the colon, `kscan0`, is a label. This is optional, and it provides a shortcut to allow changing the properties of the node. The part after the colon, `kscan`, is the node's name. The values inside the curly braces are the node's properties.

The `compatible` property indicates what type of node it is. Search this documentation for the text inside the quotes to see which properties the node
supports. You can also search ZMK for a file whose name is the value of the `compatible` property with a `.yaml` file extension.

To set a property, see below for examples for common property types, or see [Zephyr's Devicetree documentation](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#writing-property-values) for more details on the syntax for properties.

To change a property for an existing node, first find the node you want to change and find its label. Next, outside of any other node, write an ampersand (`&`)
followed by the node's label, an opening curly brace (`{`), one or more new property values, a closing curly brace (`}`), and a semicolon (`;`).

For example, to adjust the debouncing of the `zmk,kscan-gpio-matrix` node shown above, you could add this to your keymap:

```dts
&kscan0 {
    debounce-press-ms = <0>;
};
```

If the node you want to edit doesn't have a label, you can also write a new tree and it will be merged with the existing tree, overriding any properties. Adding this to your keymap would be equivalent to the previous example.

```dts
/ {
    kscan {
        debounce-press-ms = <0>;
    };
};
```

---

### Docs Hardware Integration Index (`docs/docs/hardware-integration/index.mdx`)

---
title: Hardware Integration
sidebar_label: Overview
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

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](physical-layouts.md) that describes the electrical and physical structure of the keyboard, referring to:
  - A [kscan driver](../config/kscan.md), which most frequently uses `compatible = "zmk,kscan-gpio-matrix"` for GPIO matrix based keyboards, or `compatible = "zmk,kscan-gpio-direct"` for direct wires.
  - A [matrix transform](../config/layout.md), which defines how the kscan row/column events are translated into logical "key positions".
  - An [optional description](physical-layouts.md#optional-keys-property) of physical key positions and sizes, in order to visualize the keyboard accurately in [ZMK Studio](../features/studio.md).
- A [keymap](../keymaps/index.mdx), 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](https://docs.zephyrproject.org/4.1.0/glossary.html#term-board) and [shields](https://docs.zephyrproject.org/4.1.0/hardware/porting/shields.html).

### 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](../hardware.mdx#composite) are keyboards which use a separate small PCB MCU module for its "brains".
In such keyboards, the [shield](#what-is-a-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](#what-is-a-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](../hardware.mdx#onboard) only have a single PCB which includes the "brains" (MCU) onboard.
In ZMK, these have no shield, only a board.

## Organization Overview

<Tabs
queryString="keyboard-type"
defaultValue="self-contained"
values={[
{label: 'Self-contained keyboards', value: 'self-contained'},
{label: 'Composite keyboards', value: 'composite'},
]}>

<TabItem value="self-contained">

For a [self-contained keyboard](../hardware.mdx#onboard) 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](https://github.com/zmkfirmware/zmk/tree/main/app/boards/olkb/planck) board directory.

With this type of keyboard, the full ZMK definition for the keyboard exists in the `<board_root>/boards/<vendor or designer>/<keyboard_name>` directory where `<board_root>` is `zmk/app` or a [module](../features/modules.mdx) root, e.g. `zmk/app/boards/olkb/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](../features/split-keyboards.md):

```
<keyboard_name>
├── Kconfig.defconfig
├── Kconfig.<board_name>
├── <board_name>_defconfig
├── <board_name>.dts
├── <keyboard_name>.keymap
├── board.cmake
├── board.yml
└── <keyboard_name>.zmk.yml
```

These files include [base Kconfig files](https://docs.zephyrproject.org/4.1.0/build/kconfig/index.html):

- A `Kconfig.<board_name>` file that defines the toplevel [Kconfig](https://docs.zephyrproject.org/4.1.0/build/kconfig/index.html) items for the board, including selecting the corresponding SoC Kconfig setting.
- A `Kconfig.defconfig` file that sets some initial defaults when building this keyboard. This usually includes:
  - Setting [`ZMK_KEYBOARD_NAME`](../config/system.md#general) to a value, for the product name to be used for USB/BLE info

[Configuration files](../config/index.md#kconfig-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 tend to be settings enabling various drivers or features, e.g. GPIO, USB support, or memory settings for ZMK Studio.

[Devicetree files](../config/index.md#devicetree-files):

- `<board_name>.dts` which contains all the devicetree definitions[^1], 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](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes) node including `zmk,physical-layout` property among others, where each property references the nodes defined in the file.
- 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 [`board.yml`](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#write-your-board-yaml) file containing information about the board and variants.
- A `<keyboard_name>.zmk.yml` file containing [metadata](hardware-metadata-files.md) for the keyboard.

See Zephyr's [board porting guide](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html) for information on creating a new board.
See also our [new board guide](new-board.md) for information on creating a ZMK-compatible board variant.

[^1]:
    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.

</TabItem>
<TabItem value="composite">

Keyboards that require an add-on board to operate are [composite keyboards](../hardware.mdx#composite), 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](../features/modules.mdx) 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](../features/split-keyboards.md):

```
<keyboard_name>
├── Kconfig.shield
├── Kconfig.defconfig
├── <shield_name>.overlay
├── <keyboard_name>.keymap
└── <keyboard_name>.zmk.yml
```

These files include [base Kconfig files](new-shield.mdx#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 like `ZMK_KEYBOARD_NAME`

[Devicetree files](../config/index.md#devicetree-files):

- A `<shield_name>.overlay` file which is a devicetree overlay file[^1], 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](https://docs.zephyrproject.org/4.1.0/hardware/porting/shields.html#gpio-nexus-nodes) aliases such as `&pro_micro` for GPIO pins.
  - A [chosen](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes) node including at least the `zmk,physical-layout` property, referring to the defined node.
- 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](hardware-metadata-files.md) for the keyboard.

See the [new keyboard shield guide](new-shield.mdx) for documentation that walks you through creating these files to define a new composite keyboard.

</TabItem>
</Tabs>

---

### Docs Keymaps Index (`docs/docs/keymaps/index.mdx`)

---
title: Keymaps & Behaviors
sidebar_label: Keymaps
---

import KeymapExample from "../keymap-example.md";

ZMK uses a declarative approach to keymaps, using devicetree syntax to configure them in a `<keyboard>.keymap` file.
This declarative configuration defines the key mappings, behaviors used within them and the configuration of certain features.
These keymaps can then be updated dynamically/at runtime using the [ZMK Studio](../features/studio.md) feature,
which allows keymap modifications over USB or BLE.

## Stock and User Keymaps

All keyboard definitions (complete boards or shields) include the _default_ keymap for that keyboard,
so ZMK can produce a "stock" firmware for that keyboard without any further modifications.
When users complete the [user setup](../user-setup.mdx), the stock `.keymap` file is copied to the
user config directory, which can be used to [customize](../customization.md) the keymap to each user's liking.

## Behaviors

ZMK implements the concept of "behaviors", which can be bound to a certain key position, sensor (encoder),
or layer, to perform certain actions when events occur for that binding (e.g. when a certain key position
is pressed or released, or an encoder triggers a rotation event).

For example, the simplest behavior in ZMK is the "key press" behavior, which responds to key position
(a certain spot on the keyboard), and when that position is pressed, send a keycode to the host, and
when the key position is released, updates the host to notify of the keycode being released.

For the full set of possible behaviors, see the [overview page for behaviors](behaviors/index.mdx).

## Layers

Like many mechanical keyboard firmwares, ZMK keymaps are composed of a collection of layers, with a
minimum of at least one layer that is the default, usually near the bottom of the "layer stack". Each layer
in ZMK contains a set of bindings that bind a certain behavior to a certain key position in that layer.

|                                                                                                                |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| _A simplified diagram showing three layers. The layout of each layer is the same (they all contain four keys), but the behavior bindings within each layer can be different._ |

All layers are assigned and referred to by a natural number, with the base layer being layer `0`. It is common to [use the C preprocessor to "name" layers](behaviors/layers.md#defines-to-refer-to-layers), making them more legible.

The default layer (the base layer with index 0) is always enabled. Certain bound behaviors may enable/disable additional layers.

When a key location is pressed/released, the _highest-valued currently active_ layer is used. The press/release event is sent to the behavior bound at that position in said layer, for it to perform whatever actions it wants to in reaction to the event. The behavior can choose to "consume" the event, or "pass it along" and let the next highest-valued active layer _also_ get the event (whose behavior may continue "passing it along").

Note that the _activation_ order isn't relevant for determining the priority of active layers, it is determined _only_ by the definition order.

:::tip
If you wish to use multiple base layers (with a [toggle](behaviors/layers.md#toggle-layer)), e.g. one for QWERTY and another for Colemak layouts, you will want these layers to have the lowest value possible. In other words, one should be layer `0`, and the other should be layer `1`. This allows other momentary layers activated on top of them to work with both.
:::

## Behavior Bindings

Binding a behavior at a certain key position may include up to two extra parameters that are used to
alter the behavior when that specific key position is activated/deactivated. For example, when binding
the "key press" (`kp`) behavior at a certain key position, you must specify _which_ keycode should
be used for that key position.

```dts
&kp A
```

In this case, the `A` is actually a define for the raw HID keycode, to make keymaps easier to read and write.

For example of a binding that uses two parameters, you can see how "mod-tap" (`mt`) is bound:

```dts
&mt LSHIFT D
```

Here, the first parameter is the set of modifiers that should be used for the "hold" behavior, and the second
parameter is the keycode that should be sent when triggering the "tap" behavior.

## Keymap File

A keymap file is composed of several sections, that together make up a valid devicetree file for describing the keymap and its layers.

### Includes

The devicetree files are actually preprocessed before being finally leveraged by Zephyr. This allows using standard C defines to create meaningful placeholders
for what would otherwise be cryptic integer keycodes, etc. This also allows bringing in _other_ devicetree nodes from separate files.

The top two lines of most keymaps should include:

```dts
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
```

The first defines the nodes for all the available behaviors in ZMK, which will be referenced in the behavior bindings. This is how bindings like `&kp` can reference the key press behavior defined with an anchor name of `kp`.

The second include brings in the defines for all the keycodes (e.g. `A`, `N1`, `C_PLAY`) and the modifiers (e.g. `LSHIFT`) used for various behavior bindings.

### Root Devicetree Node

All the remaining keymap nodes will be nested inside of the root devicetree node, like so:

```dts
/ {
    // Everything else goes here!
};
```

### Keymap Node

Nested under the devicetree root, is the keymap node. The node _name_ itself is not critical, but the node **MUST** have a property
`compatible = "zmk,keymap"` in order to be used by ZMK.

```dts
    keymap {
        compatible = "zmk,keymap";

        // Layer nodes go here!
    };
```

### Layers

Each layer of your keymap will be nested under the keymap node. Here is an example of a layer in a 6-key macropad.

```dts
    keymap {
        compatible = "zmk,keymap";

        default_layer { // Layer 0
            display-name = "Base";
// ----------------------------------------------
// |     Z     |     M     |     K     |
// |     A     |     B     |     C     |
            bindings = <
                &kp Z    &kp M    &kp K
                &kp A    &kp B    &kp C
            >;
        };
    };
```

Each layer should have:

1. A `bindings` property that will be a list of [behavior bindings](behaviors/index.mdx), one for each key position for the keyboard.
1. (Optional) A `sensor-bindings` property that will be a list of behavior bindings for each sensor on the keyboard. (Currently, only encoders are supported as sensor hardware, but in the future devices like trackpoints would be supported the same way)
1. (Optional) A `display-name` property that is a string used by certain features, such as ZMK Studio and the layer status display widget.

### Multiple Layers

Layers are numbered in the order that they appear in keymap node - the first layer is `0`, the second layer is `1`, etc.

Here is an example of a trio of layers for a simple 6-key macropad:

<KeymapExample />

:::note
Even if layer `1` was to be activated after `2`, layer `2` would still have priority as it is higher valued. Behaviors such as [To Layer (`&to`)](behaviors/layers.md#to-layer) can be used to enable one layer _and disable all other non-default layers_, though.
:::

### Complete Example

Some examples of complete keymaps for a keyboard are:

- [`corne.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/corne/corne.keymap)
- [`kyria.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/kyria.keymap)
- [`lily58.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/lily58/lily58.keymap)

:::tip
Every keyboard comes with a "default keymap". For additional examples, the [ZMK tree's `app/boards` folder](https://github.com/zmkfirmware/zmk/blob/main/app/boards) can be browsed.
:::

---

### Docs Troubleshooting Index (`docs/docs/troubleshooting/index.mdx`)

---
title: Troubleshooting
sidebar_label: Troubleshooting
---

import DocCardList from "@theme/DocCardList";

The following pages provide suggestions for common errors that may occur while setting up or using devices running ZMK. If the information provided is insufficient to resolve an issue, feel free to seek out additional help from the [ZMK Discord](https://zmk.dev/community/discord/invite).

<DocCardList />

---

### Docs Hardware Integration Bootloader Index (`docs/docs/hardware-integration/bootloader/index.mdx`)

---
title: Bootloader Integration
sidebar_label: Bootloader
---

:::info
The information on this page is only relevant for **boards**, not **shields**.
:::

The `&bootloader` behavior requires properly set up [boot mode](https://docs.zephyrproject.org/4.1.0/services/retention/index.html#boot-mode) support to function properly. The behavior operates by setting the boot mode, resetting, and then relies on an SoC/bootloader specific early init hook to enter the bootloader when the boot mode is found to have been set.

Most of the SoCs actively supported by ZMK rely on a generic retained memory driver to store the boot mode between restarts, and additional configuration is required when using a second stage bootloader like the [Adafruit nRF52 Bootloader](https://github.com/adafruit/Adafruit_nRF52_Bootloader/) or [tinyuf2](https://github.com/adafruit/tinyuf2).

## Magic Value Bootloaders

Most "second stage" bootloaders will enter bootloader mode on startup when a specific magic value is found in a specific reserved location in memory. For those bootloaders, an extra mapping layer is used to map the Zephyr "bootloader mode" retained value to the magic value expected by the bootloader.

The following bootloaders of this type are supported, see those pages for details on the additional configuration needed:

- [Adafruit nRF52](./adafruit-nrf52.mdx)
- [TinyUF2](./tinyuf2.mdx)
- [SAMD21 UF2](./samd21-uf2.mdx)

## Jump-To Bootloaders

Several SoCs use bootloaders that can be directly jumped to from early init code in the firmware. For these situations, the only setup required is a a [retained mem](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/retained_mem.html) instance that can retain the set boot mode after the reset, in order for the early initailization code to check the value and then jump to the bootloader.

The following bootloaders of this type are supported, see those pages for details on the additional configuration needed:

- [RP2040/RP2350](./rp2.mdx)
- [STM32](./stm32.mdx)

---

### Docs Hardware Integration Lighting Index (`docs/docs/hardware-integration/lighting/index.mdx`)

---
title: Lighting
sidebar_label: Lighting
---

import DocCardList from "@theme/DocCardList";

The following pages detail adding lighting support to ZMK keyboards, which currently supports two lighting systems: underglow and backlight.

:::warning

Although the naming of the systems might imply it, which system you use typically does _not_ depend on the physical location of the LEDs.
Instead, you should use the one that supports the LED hardware type that your keyboard has.

:::

<DocCardList />

---

### Docs Keymaps Behaviors Index (`docs/docs/keymaps/behaviors/index.mdx`)

---
title: Behavior Overview
sidebar_label: Overview
---

# Behaviors Overview

"Behaviors" are bindings that are assigned to and triggered by key positions on keymap layers, sensors (like an encoder) or combos. They describe what happens e.g. when a certain key position is pressed or released, or an encoder triggers a rotation event. They can also be recursively invoked by other behaviors, such as macros.

Below is a summary of pre-defined behavior bindings and user-definable behaviors available in ZMK, with references to documentation pages describing them.

## Key Press Behaviors

| Binding       | Behavior                                      | Description                                                                                                                                                                                                                                       |
| ------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `&kp`         | [Key Press](key-press.md)                     | Send keycodes to the connected host when a key is pressed                                                                                                                                                                                         |
| `&mt`         | [Mod Tap](hold-tap.mdx#mod-tap)               | Sends a different key press depending on whether a key is held or tapped                                                                                                                                                                          |
| `&kt`         | [Key Toggle](key-toggle.md)                   | Toggles the press of a key. If the key is not currently pressed, key toggle will press it, holding it until the key toggle is pressed again or the key is released in some other way. If the key is currently pressed, key toggle will release it |
| `&sk`         | [Sticky Key](sticky-key.md)                   | Stays pressed until another key is pressed, then is released. It is often used for modifier keys like shift, which allows typing capital letters without holding it down                                                                          |
| `&gresc`      | [Grave Escape](mod-morph.md#behavior-binding) | Sends Grave Accent `` ` `` keycode if shift or GUI is held, sends Escape keycode otherwise                                                                                                                                                        |
| `&caps_word`  | [Caps Word](caps-word.md)                     | Behaves similar to caps lock, but automatically deactivates when any key not in a continue list is pressed, or if the caps word key is pressed again                                                                                              |
| `&key_repeat` | [Key Repeat](key-repeat.md)                   | Sends again whatever keycode was last sent                                                                                                                                                                                                        |

## Miscellaneous Behaviors

| Binding  | Behavior                           | Description                                                                                                                           |
| -------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `&trans` | [Transparent](misc.md#transparent) | Passes the key press down to the next active layer in the stack for processing                                                        |
| `&none`  | [None](misc.md#none)               | Swallows and stops the key press, no keycode will be sent nor will the key press be passed down to the next active layer in the stack |

## Layer Navigation Behaviors

| Binding | Behavior                                     | Description                                                                                              |
| ------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `&mo`   | [Momentary Layer](layers.md#momentary-layer) | Enables a layer while a key is pressed                                                                   |
| `&lt`   | [Layer-tap](layers.md#layer-tap)             | Enables a layer when a key is held, and outputs a key press when the key is only tapped for a short time |
| `&to`   | [To Layer](layers.md#to-layer)               | Enables a layer and disables all other layers except the default layer                                   |
| `&tog`  | [Toggle Layer](layers.md#toggle-layer)       | Enables a layer until the layer is manually disabled                                                     |
| `&sl`   | [Sticky Layer](sticky-layer.md)              | Activates a layer until another key is pressed, then deactivates it                                      |

## Mouse Emulation Behaviors

| Binding | Behavior                                                    | Description                     |
| ------- | ----------------------------------------------------------- | ------------------------------- |
| `&mkp`  | [Mouse Button Press](mouse-emulation.md#mouse-button-press) | Emulates pressing mouse buttons |
| `&mmv`  | [Mouse Move](mouse-emulation.md#mouse-move)                 | Emulates mouse movement         |
| `&msc`  | [Mouse Scroll](mouse-emulation.md#mouse-scroll)             | Emulates mouse scrolling        |

## Reset Behaviors

| Binding       | Behavior                                | Description                                                                              |
| ------------- | --------------------------------------- | ---------------------------------------------------------------------------------------- |
| `&sys_reset`  | [Reset](reset.md#reset)                 | Resets the keyboard and re-runs the firmware flashed to the device                       |
| `&bootloader` | [Bootloader](reset.md#bootloader-reset) | Resets the keyboard and puts it into bootloader mode, allowing you to flash new firmware |

## Output Selection Behaviors

| Binding | Behavior                                                 | Description                                                                                        |
| ------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `&bt`   | [Bluetooth](bluetooth.md#bluetooth-behavior)             | Completes a bluetooth action given on press, for example switching between devices                 |
| `&out`  | [Output Selection](outputs.md#output-selection-behavior) | Allows selecting whether output is sent to the USB or bluetooth connection when both are connected |

## Lighting Behaviors

| Binding   | Behavior                                       | Description                                                                  |
| --------- | ---------------------------------------------- | ---------------------------------------------------------------------------- |
| `&rgb_ug` | [RGB Underglow](underglow.md#behavior-binding) | Controls the RGB underglow, usually placed underneath the keyboard           |
| `&bl`     | [Backlight](backlight.md#behavior-binding)     | Controls the keyboard backlighting, usually placed through or under switches |

## Power Management Behaviors

| Binding      | Behavior                                      | Description                                                     |
| ------------ | --------------------------------------------- | --------------------------------------------------------------- |
| `&ext_power` | [Power management](power.md#behavior-binding) | Allows enabling or disabling the VCC power output to save power |
| `&soft_off`  | [Soft off](soft-off.md#behavior-binding)      | Turns the keyboard off.                                         |

## ZMK Studio Behaviors

| Binding          | Behavior                                               | Description                                               |
| ---------------- | ------------------------------------------------------ | --------------------------------------------------------- |
| `&studio_unlock` | [ZMK Studio Unlock](studio-unlock.md#behavior-binding) | Unlocks the device so that ZMK Studio UI can make changes |

## User-Defined Behaviors

| Behavior                            | Description                                                                                         |
| ----------------------------------- | --------------------------------------------------------------------------------------------------- |
| [Macros](macros.md)                 | Allows configuring a list of other behaviors to invoke when the key is pressed and/or released      |
| [Hold-Tap](hold-tap.mdx)            | Invokes different behaviors depending on key press duration or interrupting keys.                   |
| [Tap Dance](tap-dance.mdx)          | Invokes different behaviors corresponding to how many times a key is pressed                        |
| [Mod-Morph](mod-morph.md)           | Invokes different behaviors depending on whether a specified modifier is held during a key press    |
| [Sensor Rotation](sensor-rotate.md) | Invokes different behaviors depending on whether a sensor is rotated clockwise or counter-clockwise |

---

### Docs Keymaps Input Processors Index (`docs/docs/keymaps/input-processors/index.md`)

---
title: Input Processor Overview
sidebar_label: Overview
---

## Input Processors Overview

"Input processors" are small pieces of functionality that process and optionally modify events generated from emulated and physical pointing devices. Processors can do things like scaling movement values to make them larger or smaller for detailed work, swapping the event types to turn movements into scroll events, or temporarily enabling an extra layer while the pointer is in use.

## Usage

For information on using input processors with a given pointing device, see [input processor usage](usage.md).

## Available Processors

Below is a summary of pre-defined input processors and user-definable input processors available in ZMK, with references to documentation pages describing them.

### Pre-Defined Processors

A set of predefined input processors is available by adding the following at the top of your keymap/overlay file:

```dts
#include <input/processors.dtsi>
```

Once included, you can use the following:

| Binding                    | Processor                                                    | Description                                                              |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `&zip_xy_scaler`           | [XY Scaler](scaler.md#pre-defined-instances)                 | Scale the X/Y input events using a multiplier and divisor                |
| `&zip_x_scaler`            | [X Scaler](scaler.md#pre-defined-instances)                  | Scale the X input events using a multiplier and divisor                  |
| `&zip_y_scaler`            | [Y Scaler](scaler.md#pre-defined-instances)                  | Scale the Y input events using a multiplier and divisor                  |
| `&zip_scroll_scaler`       | [Scroll Scaler](scaler.md#pre-defined-instances)             | Scale wheel/horizontal wheel input events using a multiplier and divisor |
| `&zip_xy_transform`        | [XY Transform](transformer.md#pre-defined-instances)         | Transform X/Y values, e.g. inverting or swapping                         |
| `&zip_scroll_transform`    | [Scroll Transform](transformer.md#pre-defined-instances)     | Transform wheel/horizontal wheel values, e.g. inverting or swapping      |
| `&zip_xy_to_scroll_mapper` | [XY To Scroll Mapper](code-mapper.md#pre-defined-instances)  | Map X/Y values to scroll wheel/horizontal wheel events                   |
| `&zip_xy_swap_mapper`      | [XY Swap Mapper](code-mapper.md#pre-defined-instances)       | Swap X/Y values                                                          |
| `&zip_temp_layer`          | [Temporary Layer](temp-layer.md#pre-defined-instances)       | Temporarily enable a layer during pointer use                            |
| `&zip_button_behaviors`    | [Mouse Button Behaviors](behaviors.md#pre-defined-instances) | Trigger behaviors when certain mouse buttons are pressed                 |

### User-Defined Processors

Several of the input processors that have predefined instances, e.g. `&zip_xy_scaler` or `&zip_xy_to_scroll_mapper` can also have new instances created with custom properties around which input codes to scale, or which codes to map, etc.

| Compatible                        | Processor                                               | Description                                               |
| --------------------------------- | ------------------------------------------------------- | --------------------------------------------------------- |
| `zmk,input-processor-scaler`      | [Scaler](scaler.md#user-defined-instances)              | Scale value of input events                               |
| `zmk,input-processor-transform`   | [Transform](transformer.md#user-defined-instances)      | Perform various transforms like inverting values          |
| `zmk,input-processor-code-mapper` | [Code Mapper](code-mapper.md#user-defined-instances)    | Map one event code to another type                        |
| `zmk,input-processor-behaviors`   | [Behaviors](behaviors.md#user-defined-instances)        | Trigger behaviors for certain matching input events       |
| `zmk,input-processor-temp-layer`  | [Temporary layer](temp-layer.md#user-defined-instances) | Temporarily enable a layer when input events are received |

## External Processors

Much like behaviors, custom input processors can also be added to [external modules](../../features/modules.mdx) to allow complete control of the processing operation. See [`input_processor.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/drivers/input_processor.h) for the definition of the driver API.

---

### Docs Development Local Toolchain Setup Index (`docs/docs/development/local-toolchain/setup/index.md`)

---
title: Getting Started
sidebar_label: Getting Started
---

:::tip
We recommend reading through the setup process before following it step by step,
to ensure that you are happy with installing the required dependencies.
:::

## Environment Setup

There are two ways to set up the ZMK development environment:

- [Docker or Podman](container.mdx): \
  A self-contained development environment. It uses the same
  [Docker image which is used by the GitHub action](https://github.com/zmkfirmware/zmk-docker)
  for local development. Beyond the benefits of
  [dev/prod parity](https://12factor.net/dev-prod-parity), this approach may be
  easier to set up for some operating systems. No toolchain or dependencies are
  necessary when using a container; the image has the toolchain installed and
  set up to use.

- [Native](native.mdx): \
  This uses your operating system directly. Usually runs slightly faster than
  the container approach, and can be preferable for users who already have the
  dependencies on their system.

Please see the [container](container.mdx) or [native](native.mdx) instructions
to continue the setup.

---

### Package.Json (`docs/package.json`)

{
  "name": "docs",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "docusaurus start --host 0.0.0.0",
    "build": "docusaurus build",
    "serve": "docusaurus serve",
    "swizzle": "docusaurus swizzle",
    "deploy": "docusaurus deploy",
    "clear": "docusaurus clear",
    "lint": "eslint",
    "prettier:check": "prettier --check .",
    "prettier:format": "prettier --write .",
    "typecheck": "tsc"
  },
  "dependencies": {
    "@docusaurus/core": "^3.10.2",
    "@docusaurus/preset-classic": "^3.10.2",
    "@docusaurus/theme-mermaid": "^3.10.2",
    "@fortawesome/fontawesome-svg-core": "^7.3.1",
    "@fortawesome/free-solid-svg-icons": "^7.3.1",
    "@fortawesome/react-fontawesome": "^3.5.0",
    "@mdx-js/react": "^3.1.0",
    "classnames": "^2.2.6",
    "js-yaml": "^5.3.0",
    "react": "^19.2.8",
    "react-async": "^10.0.1",
    "react-copy-to-clipboard": "^5.0.3",
    "react-dom": "^19.2.8",
    "react-toastify": "^11.1.0",
    "tree-sitter-devicetree": "^0.12.1",
    "web-tree-sitter": "^0.24.3"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@docusaurus/module-type-aliases": "^3.10.2",
    "@docusaurus/tsconfig": "^3.10.2",
    "@docusaurus/types": "^3.10.2",
    "@eslint/js": "^9.39.5",
    "@types/glob": "^8.1.0",
    "@types/js-yaml": "^4.0.5",
    "@types/react": "^19.2.18",
    "@types/react-helmet": "^6.1.6",
    "@types/react-router-dom": "^5.1.7",
    "eslint": "^9.39.2",
    "eslint-plugin-mdx": "^3.8.1",
    "eslint-plugin-react": "^7.37.5",
    "globals": "^17.11.0",
    "json-schema-to-typescript": "^15.0.3",
    "mustache": "^4.2.0",
    "null-loader": "^4.0.0",
    "prebuild-webpack-plugin": "^1.1.1",
    "prettier": "^3.9.6",
    "string-replace-loader": "^3.3.0",
    "typescript": "^5.9.3",
    "typescript-eslint": "^8.67.0",
    "webpack": "^5.109.2"
  }
}

---

### Tsconfig.Json (`docs/tsconfig.json`)

{
  "extends": "@docusaurus/tsconfig",
  "include": ["src/"],
  "compilerOptions": {
    "types": ["node", "@docusaurus/theme-classic"],
    "moduleResolution": "Bundler",
    "module": "esnext",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "strict": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "target": "ES6",
    "lib": ["ES2022", "DOM", "DOM.Iterable"]
  }
}

---

### Blog 2020 05 24 Wip (`docs/blog/2020-05-24-wip.md`)

---
title: WIP
authors: petejohanson
tags: [keyboards, firmware, oss, ble]
---

This blog is a work-in-progress as I work on basic docs + blog on this nascent keyboard firmware.

As is, there are more features _missing_ from ZMK than features it has. As always with pre-alpha software,
if something breaks, you get to keep both halves! (especially if it is a split KB)

---

### Blog 2020 08 12 Zmk Sotf 1 (`docs/blog/2020-08-12-zmk-sotf-1.md`)

---
title: "ZMK State Of The Firmware #1"
authors: petejohanson
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the first ZMK "State Of The Firmware"!

With interest and Discord activity growing, it seemed important to lay out the progress made recently, current major bugs/showstoppers, and planned next steps.



## Recent Activity

There's been lots of various activity in ZMK land!

- [Nicell](https://github.com/Nicell) (nice!nano creator) contributed initial [RGB Underglow](/docs/features/lighting#rgb-underglow) ([#64](https://github.com/zmkfirmware/zmk/pull/64)) support to ZMK.
- Tons of [documentation](/docs) work.
- Refactoring ([#73](https://github.com/zmkfirmware/zmk/pull/73), [#74](https://github.com/zmkfirmware/zmk/pull/74)) of [keymaps](/docs/keymaps) to make them simpler for users.
- Mod-Tap Behavior (docs coming!) is much improved ([#69](https://github.com/zmkfirmware/zmk/pull/69)) and usable now.
- An initial `setup.sh` script was created, allowing users to quickly bootstrap a "user config" setup and push it to GitHub, where GitHub Actions will build the firmware for you.
- Corne shield ([#80](https://github.com/zmkfirmware/zmk/pull/80)) shield definition was added.
- Initial [encoder](/docs/features/encoders) support ([#61](https://github.com/zmkfirmware/zmk/pull/61)) was added.

## Bugs and Showstoppers

Despite the flurry of activity, there are still some serious bugs and show stoppers that potential ZMK users should be aware of:

- [Bluetooth Related](https://github.com/zmkfirmware/zmk/issues/58) - There are several key bugs and fixes needed, including one complete show stopper:
  - <del>Fully working split wireless is not working. In particular, both split halves can properly pair, but once they do so, pairing with the _central_ host will not work. Workaround: You can currently have both halves pair, and use USB on the central side (Left side right now for Kyria, Corne, Lily58) and receive HID events over USB.</del> - Fixed in <a href="https://github.com/zmkfirmware/zmk/commit/8b61beb2bbc62f754db670ad77266f84edde041d">8b61beb</a>.
  - BT bond information is not currently stored to the devices, so after powering off or restarting your device, users need to re-pair
- USB - There is one important USB related bug which is a showstopper:
  - <del>The Zephyr USB stack does not have a built in queue for endpoint data being written. As a result, HID events sent by ZMK are sometimes <a href="https://github.com/zmkfirmware/zmk/issues/84">dropped, or lost</a>.</del> - Fixed by <a href="https://github.com/careyk007">careyk007</a> in <a href="https://github.com/zmkfirmware/zmk/pull/93">#93</a>.

## Next Steps

There's plenty of places to go next! To help keep folks in the loop for what's next, I've created a [Core Functionality](https://github.com/zmkfirmware/zmk/projects/1) project/kanban board in GitHub, where users should be able to get some visibility into items being focused on.

Of course, at the top of that list currently is the above bugs/showstoppers, and then from there, I hope to:

- Work on power management.
- Improve our documentation on various aspects of the system, mostly around:
  - End user documentation for understanding how to use ZMK, better installation docs, etc.
  - Developer focused documentation, so interested contributors can start building out more behaviors and ZMK functionality.
- Implement true "hold" detection, useful for several behaviors such as Mod-Tap and Layer-Tap.
- Hopefully acquire a proper and official USB Product ID for use for the project.
- Fun things that come up along the way!

## Thanks!

A big thanks for everyone who has shown interest in the project, tested things, asked questions, and contributed PRs ([Nicell](https://github.com/Nicell), [CrossR](https://github.com/CrossR), [careyk007](https://github.com/careyk007)).

---

### Blog 2020 09 21 Zmk Sotf 2 (`docs/blog/2020-09-21-zmk-sotf-2.md`)

---
title: "ZMK State Of The Firmware #2"
authors: petejohanson
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the second ZMK "State Of The Firmware" (SOTF)!

This update will cover all the major activity since [SOTF #1](/blog/2020/08/12/zmk-sotf-1), preparations for the upcoming
Hacktoberfest activity, and a current open call for community feedback on a ZMK mascot.



## Recent Activity

So much going on in ZMK!

- Added a new generic [Hold Tap behavior](https://zmk.dev/docs/behaviors/hold-tap)
  in [#146](https://github.com/zmkfirmware/zmk/pull/146) which now powers mod-tap, layer-tap, etc. - [okke-formsma]
- [BLE profile/connection management](https://zmk.dev/docs/behaviors/bluetooth)
  in [#133](https://github.com/zmkfirmware/zmk/pull/133) - [petejohanson]
- Integration tests were added to automate testing of behaviors in [#131](https://github.com/zmkfirmware/zmk/pull/131) by [BrainWart] & [petejohanson]
- [Toggle layer behavior](https://zmk.dev/docs/behaviors/layers#toggle-layer), e.g. `&tog LOWER`, in
  [#98](https://github.com/zmkfirmware/zmk/pull/98) - [BrainWart]
- Key fix for dropped press/release over HID [#93](https://github.com/zmkfirmware/zmk/pull/93)/[#96](https://github.com/zmkfirmware/zmk/pull/96) - [careyk007](https://github.com/careyk007) & [petejohanson]
- Code formatting standardized using `clang-format` in [#183](https://github.com/zmkfirmware/zmk/pull/183) - [petejohanson]
- [Bootloader reset behavior](https://zmk.dev/docs/behaviors/reset#bootloader-reset), e.g. `&bootloader`, in [#116](https://github.com/zmkfirmware/zmk/pull/116) - [petejohanson]
- Various bug fixes and documentation

## New Shields

- QAZ in [#130](https://github.com/zmkfirmware/zmk/pull/130) - [tominabox1](https://github.com/tominabox1)
- Iris in [#151](https://github.com/zmkfirmware/zmk/pull/151) - [kurtis-lew](https://github.com/kurtis-lew)
- RoMac 2.1 in [#122](https://github.com/zmkfirmware/zmk/pull/122) - [bmcgavin](https://github.com/bmcgavin)
- Sofle in [#118](https://github.com/zmkfirmware/zmk/pull/118) - [CrossR](https://github.com/CrossR)
- splitreus62 in [#92](https://github.com/zmkfirmware/zmk/pull/92) - [Na-Cly](https://github.com/Na-Cly)

## New Boards

- DZ60RGB rev1 in [#166](https://github.com/zmkfirmware/zmk/pull/166) - [Nicell]
- nrfMicro in [#101](https://github.com/zmkfirmware/zmk/pull/101) - [okke-formsma]
- BlueMicro840 [#91](https://github.com/zmkfirmware/zmk/pull/91) - [Na-Cly](https://github.com/Na-Cly)

## Hacktoberfest Preparation

[Hacktoberfest](https://hacktoberfest.digitalocean.com/) is a yearly celebration of open source,
which encourages participation in OSS, especially from new contributors.

The ZMK contributors have been busy preparing for folks to join in on the fun by contributing to
ZMK!

- There is now a basic [Contributing Guide](https://github.com/zmkfirmware/zmk/blob/main/CONTRIBUTING.md) to help newcomers get oriented, and get up to speed.
- The [`Hacktoberfest`](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3AHacktoberfest)
  issue label will help participants discover good issues to work on.
  (The existing [`good first issue`](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label also helps with this)

We're looking forward to the launch of Hacktoberfest!

## Mascot Selection Feedback

The ZMK project would like to settle on a mascot! We're soliciting community feedback as part of
the process before a final mascot is selected.

The current mascots up for consideration are:

- Griffin
- Peregrine Falcon
- Zapata Wren
- Zorro (south american fox)

If you're interested in helping with the decision, head over to [Issue #195](https://github.com/zmkfirmware/zmk/issues/195) and add a reaction!

## Coming Soon!

There still lots of activity in ZMK, and plenty of exciting upcoming changes.

- Improved modifier infrastructure, including "shifted keycodes" - [okke-formsma]
- Battery percentage reporting over BLE - [Nicell]
- Complete defines for HID keycodes/usage IDs - [innovaker](https://github.com/innovaker)
- Additional core BLE connection/bond management work - [petejohanson]
- Improved power management - [petejohanson], [Nicell]
- One shot mod/layer behaviors - [okke-formsma]

## Statistics

Some statistics of interest for ZMK:

- GitHub
  - 115 Closed PRs
  - 64 Stars
  - 48 Forks
- Discord Chat
  - 186 total registered
- Website (last 30 days)
  - 7.4K page views
  - 474 new users

## Thanks!

Thanks again to the numerous contributors and users who have made working on ZMK such a pleasure!

[okke-formsma]: https://github.com/okke-formsma
[nicell]: https://github.com/Nicell
[petejohanson]: https://github.com/petejohanson
[brainwart]: https://github.com/BrainWart

---

### Blog 2020 10 03 Bootloader Fix (`docs/blog/2020-10-03-bootloader-fix.md`)

---
title: Fixing the Mysterious Broken Bootloader
authors: nickwinans
tags: [bootloader, keyboards, firmware, oss, ble]
---

Recently I was able to fix the "stuck in the bootloader" issue in
[#322](https://github.com/zmkfirmware/zmk/pull/322) that had been plaguing us
for quite some time. I want to go over what the issue was, how the issue was
diagnosed, and how it was fixed.



## Background

What exactly is the "stuck in the bootloader" issue? Seemingly randomly, users'
keyboards would suddenly stop working and when they would reset their keyboard
they would get put into the bootloader instead of back into the firmware. This
would require the user to re-flash the firmware again to get into the firmware.
That wouldn't be so bad except for the fact that once this occurs, every reset
would require the user to re-flash the firmware again. The only way to really
fix this issue was to re-flash the bootloader itself, which is a huge pain.

Going into this, all we knew was that this issue was most likely introduced
somewhere in the [#133](https://github.com/zmkfirmware/zmk/pull/133), which
added Bluetooth profile management. We've had quite a few attempts at trying to
recreate the issue, but we never were able to get it to happen consistently.

## Diagnosing the issue

This issue had been happening sporadically for the past month, and I finally
decided to dig in to see what was going on. We started in the Discord and
discussed what was common between all of the people who have experienced this
issue. Everyone who had this issue reported that they did quite a bit of profile
switching. This lined up with the possible connection to the Bluetooth profile
management pull request.

### Pinpointing the cause

I had a hunch that this was related to the settings system. The settings system
is used by profile Bluetooth switching, and the settings system works directly
with the system flash. Based on this hunch, I tried spamming the RGB underglow
cycle behavior on my main keyboard. Sure enough after a couple minutes, I got
stuck in the bootloader. I was even able to reproduce it again.

This was an important discovery for two reasons. First, I was able to recreate
the issue consistently, which meant I could set up logging and more closely
monitor what the board was doing. Second, this more or less proved that it was
specifically the settings system at fault. Both Bluetooth profile switching and
RGB underglow cycling trigger it, and the one common piece is they save their
state to settings.

### Settings system overview

To understand what's going wrong, we first need to understand how the settings
system works. Here's a diagram to explain the flash space that the settings
system holds for our nRF52840 based boards (nice!nano, nRFMicro, BlueMicro).



The settings flash space lives at the end of the flash of the chip. In this case
it starts at `0xF8000` and is `0x8000` bytes long, which is 32KB in more
comprehensible units. Then due to the chip's architecture, this flash space is
broken into pages, which are `0x1000` bytes in size (4KB).

The backend that carries out the settings save and read operation in ZMK is
called NVS. NVS calls these pages sectors. Due to how flash works, you can't
write to the same bytes multiple times without erasing them first, and to erase
bytes, you need to erase the entire sector of flash. This means when NVS writes
to the settings flash if there's no erased space available for the new value, it
will need to erase a sector.

### Logging discoveries

So first I enabled logging of the NVS module by adding
`CONFIG_NVS_LOG_LEVEL_DBG=y` to my `.conf` file. I repeated the same test of
spamming RGB underglow effect cycle and the resulting logs I got were this:

```log
[00:00:00.000,671] <inf> fs_nvs: 8 Sectors of 4096 bytes
[00:00:00.000,671] <inf> fs_nvs: alloc wra: 3, f70
[00:00:00.000,671] <inf> fs_nvs: data wra: 3, f40
// A bunch of effect cycle spam
[00:02:34.781,188] <dbg> fs_nvs: Erasing flash at fd000, len 4096
// A bunch more effect cycle spam
[00:06:42.219,970] <dbg> fs_nvs: Erasing flash at ff000, len 4096
// A bunch more effect cycle spam
// KABOOM - bootloader issue
```

So at start up, we can see that the 8 sectors of 4KB are found by NVS properly,
however, I wasn't sure what the second and third lines meant, but we'll get back
to that. Nonetheless the next two logs from NVS showed erasing the sector at
`0xFD000` and then erasing the `0xFF000` sector.



It's really odd that the third to last sector and the last sector are erased,
and then shortly after the bootloader issue is hit. I really had no explanation
for this behavior.

### Reaching out to Zephyr

At this point, I nor anyone else working on the ZMK project knew enough about
NVS to explain what was going on here. [Pete
Johanson](https://github.com/petejohanson), project founder, reached out on the
Zephyr Project's Slack (ZMK is built on top of Zephyr if you weren't aware).
Justin B and Laczen assisted by first explaining that those `alloc wra` and
`data wra` logs from earlier are showing what data NVS found at startup.

More specifically, `data wra` should be `0` when it first starts up on a clean
flash. As we can see from my earlier logging on a clean flash I was instead
getting `f40`. NVS is finding data in our settings sectors when they should be
blank! We were then given the advice to double check our bootloader.

### The Adafruit nRF52 Bootloader

Most of the boards the contributors of ZMK use have the [Adafruit nRF52
Bootloader](https://github.com/adafruit/Adafruit_nRF52_Bootloader), which allows
for extremely easy flashing by dragging and dropping `.uf2` files onto the board
as a USB drive. Every bootloader takes up a portion of the flash, and in the
README explains that the first `0x26000` is reserved for the bootloader with the
nRF52840, and we've properly allocated that.

However, there isn't a full explanation of the flash allocation of the
bootloader in the README. There's a possibility that the bootloader is using
part of the same flash area we're using. I reached out on the Adafruit Discord,
and [Dan Halbert](https://github.com/dhalbert) pointed me towards the [linker
map](https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/linker/nrf52840.ld)
of the nRF52840. Let's take a look.

```linker-script
FLASH (rx) : ORIGIN = 0xF4000, LENGTH = 0xFE000-0xF4000-2048 /* 38 KB */

BOOTLOADER_CONFIG (r): ORIGIN = 0xFE000 - 2048, LENGTH = 2048

/** Location of mbr params page in flash. */
MBR_PARAMS_PAGE (rw) : ORIGIN = 0xFE000, LENGTH = 0x1000

/** Location of bootloader setting in flash. */
BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
```

Here's a diagram to show this a bit better.



We've found the issue! As you can see from the red bar (representing our
settings flash area), we've put the settings flash area _right on top_ of the
Adafruit bootloader's flash space. Oops!

This also shines some light on why NVS erased `0xFD000` and `0xFF000` sectors.
It's possible there was no flash written to `0xFD000` because the bootloader
didn't use up all of that space it has, and then there possibly weren't any
bootloader settings set yet, so `0xFF000` could be used and erased by NVS too.

After erasing `0xFF000`, NVS probably next erased a rather important part of the
bootloader that resulted in this issue at hand. In my opinion, we're pretty
lucky that it didn't delete an even more vital part of the bootloader. At least
we could still get to it, so that we could re-flash the bootloader easily!

## The solution

Now that we've found the issue, we can pretty easily fix this. We'll need to
move the settings flash area back so that it doesn't overlap with the
bootloader. First we calculate the size of the of flash area the bootloader is using.

```linker-script
0x100000 (end of flash) - 0x0F4000 (start of bootloader) = 0xC000 (48KB)
```

So the bootloader is using the last 48KB of the flash, this means all we need to
do is shift back the settings area and code space `0xC000` bytes. We'll apply
this to all of the `.dts` files for the boards that were affected by this issue.

```diff
        code_partition: partition@26000 {
-           reg = <0x00026000 0x000d2000>;
+           reg = <0x00026000 0x000c6000>;
        };


-       storage_partition: partition@f8000 {
+       storage_partition: partition@ec000 {
-           reg = <0x000f8000 0x00008000>;
+           reg = <0x000ec000 0x00008000>;
        };
```

And with those changes, we should no longer run into this issue! In the process
of these changes, we lost 48KB of space for application code, but we're only
using around 20% of it anyways. 🎉

## Article Updates

- 12/2023: Removed the deprecated `label` property from code snippets.

---

### Blog 2020 11 09 Zmk Sotf 3 (`docs/blog/2020-11-09-zmk-sotf-3.md`)

---
title: "ZMK State Of The Firmware #3"
authors: petejohanson
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the third ZMK "State Of The Firmware" (SOTF)!

This update will cover all the major activity since [SOTF #2](/blog/2020/09/21/zmk-sotf-2). This edition comes a bit later than planned, but the amount of features and changes will hopefully make it worth it!



## Recent Activity

Here's a summary of the various major changes since last time, broken down by theme:

### Keymaps/Behaviors

Tons of activity related to keymaps, so we'll go into more detail this time.

#### Codes Overhaul

[innovaker] _completely_ overhauled the set of available codes for keymaps, and simultaneously has created
_beautiful_ [documentation](/docs/keymaps/list-of-keycodes) to help users visualize the codes, and also understand if they are supported on their particular operating system.

This also laid the foundation for the other keymap related changes that are now available.

#### Modified (Shifted) Codes

[okke-formsma] added the ability to apply modifiers to a code, e.g.:

```dts
&kp LC(C)
```

which sends `Control + c` when pressed. This feature is often used on smaller keyboards to achieve "shifted keycodes", e.g. `LS(N1)` to send a `!`.
To make this easier, in addition to all the normal codes, we now have defines for common shifted codes, e.g. `EXCL` for `!`, `AT` for `@`, etc.

To learn more, check out the [Modifiers](/docs/keymaps/modifiers) documentation.

#### Simplified Key Press Behavior

In previous versions of ZMK, users needed to be careful to select between the `&kp` and `&cp` behaviors in their keymaps, depending on
whether the particular keycode they wanted to send was in the "HID consumer page" or not. Forcing users to understand the difference and get
this right was awkward and error prone.

[petejohanson] and [innovaker] have reduced this complexity. Users can now simply use `&kp` with all available codes and ZMK will
handle sending the right events to the connected host.

### Power Management

Several important power management features have been added to ZMK, helping save power for many use cases.

#### BLE Battery Level Reporting

[Nicell] added the necessary [driver](https://github.com/zmkfirmware/zmk/pull/293) and [core code](https://github.com/zmkfirmware/zmk/pull/306) to send BLE battery level notifications to hosts that support displaying them. Testing seems to show this works with Windows and GNOME, but macOS does not display the battery info.

#### External Power Control

[megamind4089] added a new [driver](https://github.com/zmkfirmware/zmk/pull/242) and [behavior](https://github.com/zmkfirmware/zmk/pull/258) to allow users to toggle (on/off) the external power supplied by boards such as the nRFMicro
and nice!nano that have specialized hardware for this purpose.

With this change, you can add

```dts
&ext_power EP_TOG
```

to toggle (on/off) the power to external hardware like RGB underglow or OLEDs. Check out the [external power control](/docs/keymaps/behaviors/power#external-power-control) docs for more info.

#### Deep Sleep

[petejohanson] has contributed the initial [deep sleep](https://github.com/zmkfirmware/zmk/pull/211) support to ZMK. This work also
included some automatic power savings by switching to PORT events on the nRF52 chips, which reduces the idle power draw, even without deep sleep. Deep sleep is currently not turned on by default, but will be soon.

### Miscellaneous

#### Output Selection

[joelspadin] added [output selection](/docs/keymaps/behaviors/outputs) to allow selecting whether to send output over USB or BLE if both are connected. This should now help avoid having "double keypresses" when your keyboard is plugged into a host.

#### Bootloader Corruption Fix

[Nicell] has already [blogged about this](/blog/2020/10/03/bootloader-fix), but for those that missed it, a _major_, and incredibly difficult to pin down bug involving corruption of the bootloader on devices using the Adafruit nRF52 bootloader has been fixed by [Nicell]. If you've encountered this bug, flashing the latest firmware should prevent it from reoccurring. Unfortunately, due to the nature of this fix, you will need to re-pair your keyboard with your hosts, as the fix involves changing where settings are stored in the flash of the controller.

#### Official USB Product ID

[petejohanson] has gotten an official [USB product ID](https://github.com/openmoko/openmoko-usb-oui/pull/15) assigned to the ZMK Firmware. For anyone looking to uniquely identify a USB device running the ZMK Firmware, you can match on:

- Vendor ID: `0x1d50`
- Product ID: `0x615e`

We are incredibly grateful that Openmoko Inc., in the wake of discontinuing the openmoko projects, has made this an option for OSS projects.

#### Development: Remote Docker Container Integration

[idan](https://github.com/idan) contributed [VSCode devcontainer integration](https://github.com/zmkfirmware/zmk/pull/209) to make it easier for developers to build and develop ZMK without having to do complicated local toolchain setup and configuration. This also opens up some amazing future flexibility for things like [GitHub Codespaces](https://twitter.com/ZMKFirmware/status/1315760616779505678?s=20).

There's some follow up tweaks necessary for better supporting using this with user config repositories, which will be available soon.

## New Shields

- Reviung41 in [#297](https://github.com/zmkfirmware/zmk/pull/297) - [Nicell]
- Boardsource 3x4 in [#296](https://github.com/zmkfirmware/zmk/pull/296) - [neegool](https://github.com/neegool)
- NIBBLE in [#292](https://github.com/zmkfirmware/zmk/pull/292) - [jaygreco](https://github.com/jaygreco)
- Microdox in [#245](https://github.com/zmkfirmware/zmk/pull/245) - [careyk007](https://github.com/careyk007)
- MakerDiary M60 in [#233](https://github.com/zmkfirmware/zmk/pull/233) - [megamind4089]
- TGX4 in [#226](https://github.com/zmkfirmware/zmk/pull/226) - [mubeenkhan94](https://github.com/mubeenkhan94)
- Quefrency V1 in [#216](https://github.com/zmkfirmware/zmk/pull/216) - [noar-t](https://github.com/noar-t)
- Cradio in [#224](https://github.com/zmkfirmware/zmk/pull/224) - [davidphilipbarr](https://github.com/davidphilipbarr)
- Romac+ in [#198](https://github.com/zmkfirmware/zmk/pull/198) - [reizero00](https://github.com/reizero00)

## New Boards

- MakerDiary nRF52840 M.2 Module in [#233](https://github.com/zmkfirmware/zmk/pull/233) - [megamind4089]

## Testing

There has been an _amazing_ amount of testing from various users as we develop new features. In particular, we'd like to give a shout out to [tominabox1](https://github.com/tominabox1) who has been tireless in providing detailed and thorough testing of
changes as they are being developed.

## Coming Soon!

Some items listed in the last coming soon section are still under active development.

- OLED work, including battery and USB/BLE connection status - [petejohanson]
- One shot mod/layer behaviors - [okke-formsma]
- A power profiler page for the website, to help users estimate their battery life for a given keyboard - [Nicell]
- A keymap converter to automatically update keymaps to the new codes and use of `&kp` everywhere - [joelspadin]

## Statistics

Some statistics of interest for ZMK:

- GitHub (lifetime stats)
  - 210 Closed PRs
  - 116 Stars
  - 101 Forks
- Discord Chat
  - 363 total registered
- Website (last 30 days)
  - 8.5K page views
  - 766 new users

## Thanks!

Thanks again to the numerous contributors and users who have made working on ZMK such a pleasure!

[okke-formsma]: https://github.com/okke-formsma
[nicell]: https://github.com/Nicell
[petejohanson]: https://github.com/petejohanson
[brainwart]: https://github.com/BrainWart
[innovaker]: https://github.com/innovaker
[megamind4089]: https://github.com/megamind4089
[joelspadin]: https://github.com/joelspadin

---

### Blog 2021 01 27 Zmk Sotf 4 (`docs/blog/2021-01-27-zmk-sotf-4.md`)

---
title: "ZMK State Of The Firmware #4"
authors: petejohanson
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the fourth ZMK "State Of The Firmware" (SOTF)!

This update will cover all the major activity since [SOTF #3](/blog/2020/11/09/zmk-sotf-3).



## Recent Activity

Here's a summary of the various major changes since last time, broken down by theme:

### Keymaps/Behaviors

Since last time, there have been several new powerful keymap features and behaviors added, including one of the most asked for features, combos!

#### Combos

The initial [combos](/docs/keymaps/combos) work has landed! The amazing [okke-formsma] has once again delivered another powerful feature for ZMK. Combos are "position based", and are configured in a new toplevel node next to they `keymap` node in user's keymap files.

An example, that would send the `ESC` keycode when pressing both the first and second positions on your keyboard:

```dts
/ {
    combos {
        compatible = "zmk,combos";
        combo_esc {
            timeout-ms = <50>;
            key-positions = <0 1>;
            bindings = <&kp ESC>;
        };
    };
};
```

:::note
Combos currently are "global", and not scoped to a given active layer. There is future planned work to allow enabling a certain combo for only certain active layers.
:::

#### Sticky Keys (One-Shot Mods/Layers) Behavior

[okke-formsma] also contributed the initial "sticky keys" behavior, which can be used for functionality sometimes called "one shot mods" or "one shot layers". In your keymap, this would like like:

```dts
&sk LEFT_CONTROL
```

for a sticky key/modifier, or:

```dts
&sl NAV
```

for a sticky layer.

#### `&to` Layer Behavior

[mcrosson] contributed the new [`&to`](/docs/keymaps/behaviors/layers#to-layer) layer related behavior. This can be used to completely replace the active layer with a new one.

This is most frequently used when using multiple core base layers with different layouts, e.g. QWERTY and DVORAK, to switch between them.

#### Grave Escape Behavior

[okke-formsma] added an implementation of the "Grave Escape" behavior, developing a more generic "mod-morph" behavior to do so. Adding

```dts
&gresc
```

to your keymap will send `ESC` when pressed on its own, but will send `` ` `` when pressed with a GUI or Shift modifier held.

#### RGB Underglow Color Selection

[mcrosson] updated the [RGB Underglow behavior](/docs/keymaps/behaviors/underglow) to allow [binding an explicit color selection](/docs/keymaps/behaviors/underglow#examples) to a key position.

#### Keymap Upgrader

[joelspadin] completed the [Keymap Upgrader](/keymap-upgrader) which can be used to update your keymap to using the latest supported codes, and move away from the old deprecated codes.

If you've made keymap customizations, please make sure to run your keymaps through the upgrader, since the old deprecated codes will be removed in a future version of ZMK.

### Displays

There has been lots of work to get display support complete enough for use by end users. Although not quite ready for prime time, it is incredibly close, and we are looking forward to having the last few items completed and the feature documented!

#### Idle Blanking

[petejohanson] added idle blanking for displays, which ensures they will go blank, and into low power mode, after a short period of inactivity from the user. This ensures we avoid burn-in for OLEDs, and helps improve battery life.

#### Battery and Output Widgets

[petejohanson] implemented the first two complete, dynamic "widgets" for the displays for ZMK, adding a small battery indicator, which includes charging status, and a small output indicator, showing the currently active output (USB or BLE). When using BLE, the indicator also shows the active profile slot, as well as if the profile slot is open, awaiting connection from the paired host, or is actively connected to the host for that profile slot.

#### Highest Layer Display

[mcrosson] has contributed the next display widget, showing the highest active layer in the keymap. [petejohanson] then added a small follow up to allow layers in keymaps to add a `name` property to each layer, e.g. `name = "Nav";` and have that name be displayed in the widget instead of the numeric layer number.

#### WPM

New contributor [allymparker](https://github.com/allymparker) added our fourth widget, a words-per-minute display! This widget work also included creating the core state logic for tracking the WPM.

For now, this widget is only working on the central side of split keyboards.

### Miscellaneous

#### Zephyr 2.4

[innovaker] is at it again with some crucial core fixes, helping prepare and test the upgrade of ZMK to Zephyr 2.4. The updated Zephyr release brings with it some key BLE stability fixes, as well as various other core improvements that improve ZMK. This was a huge undertaking!

#### BLE Deadlock Fixes

[petejohanson] was heads down diagnosing and fixing a deadlock issue on BLE that was frustrating and plaguing many users. After finally pinpointing the underlying root cause, he developed a fix and roped in many testers on Discord to help stress test things before merging.

#### Central/Peripheral Selection

Previously overriding the selection of left as central, and right as peripheral for wireless splits required making local edits to the configuration files, and maintaining them in a ZMK fork.

[petejohanson] updated [the config files](https://github.com/zmkfirmware/zmk/pull/510) to allow users to override this in their `<board>_left.conf`/`<board>_right.conf` files in their user repos.

#### Improved Docker Containers

As part of the Zephyr 2.4. prep work, [innovaker], along with lots of testing and input from [mcrosson], developed a brand new pair of [Docker images](https://github.com/zmkfirmware/zmk-docker) which is now published to Docker Hub as [zmkfirmware/zmk-build-arm](https://hub.docker.com/repository/docker/zmkfirmware/zmk-build-arm) and [zmkfirmware/zmk-dev-arm](https://hub.docker.com/repository/docker/zmkfirmware/zmk-build-arm).

The previously blogged VSCode + Docker integration, as well as our GH Action build automation was all moved over to the new images.

#### Settings Debounce

[nicell] contributed settings debounce work, to help avoid unnecessary extra writes to flash when making various changes that should be saved,
such as the active BLE profile, external VCC on/off, etc.

## New Shields

- Jorne & Jian in [#331](https://github.com/zmkfirmware/zmk/pull/331) - [krikun98](https://github.com/krikun98)
- tidbit in [#424](https://github.com/zmkfirmware/zmk/pull/424) - [mcrosson](https://github.com/mcrosson)
- Helix in [#429](https://github.com/zmkfirmware/zmk/pull/429) - [KingCoinless](https://github.com/KingCoinless)
- BFO-9000 in [#472](https://github.com/zmkfirmware/zmk/pull/472) - [pbz](https://github.com/pbz)
- CRBN in [#493](https://github.com/zmkfirmware/zmk/pull/483) - [ReFil](https://github.com/ReFil)
- Eek in [#529](https://github.com/zmkfirmware/zmk/pull/529) - [MangoIV](https://github.com/MangoIV)

## New Boards

- BDN9 Rev2 in [#557](https://github.com/zmkfirmware/zmk/pull/557) - [petejohanson]

## Sponsorship

Since it's inception, quite a few users have inquired whether they could sponsor any of the contributors involved in ZMK. Although we are not intending to directly fund any individual contributors for their work on ZMK, there _is_ good that can come from folks sponsoring ZMK.

You can see the full discussion on [#497](https://github.com/zmkfirmware/zmk/issues/497), but some items that are being considered with sponsorship funds:

- Hiring a designer to complete the logo/mascot work.
- Creating stickers to send as thank-yous to first time contributors.
- Hosting costs for GitHub Pro.
- Other hosting costs, e.g. Docker Hub.

For anyone looking to contribute, you can find the [ZMK Firmware project](https://opencollective.com/zmkfirmware) is now set up on [Open Collective](https://opencollective.com).

## Coming Soon!

Some items listed in the last coming soon section are still under active development.

- A power profiler page for the website, to help users estimate their battery life for a given keyboard - [Nicell]
- Behavior "locality", allowing improved split usage for things like `&sys_reset`, and controlling external power and RGB underglow for both sides - [petejohanson]
- More modular approach to external boards/shields, custom code, user keymaps, etc.
- More shields and boards

## Statistics

Some statistics of interest for ZMK:

- GitHub (lifetime stats)
  - 389 Closed PRs
  - 199 Stars
  - 163 Forks
- Discord Chat
  - 702 total registered
- Website (last 30 days)
  - 11.5K page views
  - 1K new users

## Thanks!

Thanks again to the numerous contributors, testers, and users who have made working on ZMK such a pleasure!

[okke-formsma]: https://github.com/okke-formsma
[mcrosson]: https://github.com/mcrosson
[nicell]: https://github.com/Nicell
[petejohanson]: https://github.com/petejohanson
[innovaker]: https://github.com/innovaker
[joelspadin]: https://github.com/joelspadin

## Article Updates

- 12/2023: The `label` property for keymap layers was renamed to `display-name`.

---

### Blog 2021 07 17 Zephyr 2 5 (`docs/blog/2021-07-17-zephyr-2-5.md`)

---
title: "Zephyr 2.5 Update"
authors: petejohanson
tags: [firmware, zephyr, core]
---

I'm happy to announce that we have completed the [work](https://github.com/zmkfirmware/zmk/pull/736/) to upgrade ZMK to [Zephyr 2.5](https://docs.zephyrproject.org/2.5.0/releases/release-notes-2.5.html)!



A big part of this work was some _major_ refactors and improvements by [innovaker] to our [zmk-docker](https://github.com/zmkfirmware/zmk-docker/) Docker image and GH Actions automation.

- Faster build times with improved caching.
- Integration tests which automatically verify new images.
- PRs to the repo now build properly and run the tests as well.
- Build images for multiple target architectures, e.g. `zmk-build-riscv64`, all in parallel.
- Nightly builds to be sure we're pulling in the latest OS/package updates, to ensure we keep our images up to date, address any reported vulnerabilities, etc.
- Faster upgrade paths for future Zephyr SDK and Zephyr versions.

In addition, [petejohanson] did the upgrade work to adjust ZMK for the Zephyr changes.

- Updated to newer devicetree/driver Zephyr API
- Adjustment for Zephyr pinmux changes
- Fixes for power management, LVGL, and formatter changes

## Getting The Changes

Use the following steps to update to the latest tooling in order to properly use the new ZMK changes:

### User Config Repositories Using GitHub Actions

Existing user config repositories using Github Actions to build will pull down Zephyr 2.5 automatically,
and should work, fine as is. However, to upgrade to the newer Docker image, you should:

- Open `.github/workflows/build.yml` in your editor/IDE
- Change `zmkfirmware/zmk-build-arm:2.4` to `zmkfirmware/zmk-build-arm:2.5` wherever it is found

:::note

If you created your user config repository a while ago, you may find that your `build.yml` file instead references
a `zephyr-west-action-arm` custom GitHub Action instead. In this case, the upgrade is not as direct. We suggest that
instead you [re-create your config repository](/docs/user-setup) to get an updated setup using the new automation
approach.

:::

### VS Code & Docker (Dev Container)

If you build locally using VS Code & Docker then:

- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- reload the project
- if you are prompted to rebuild the remote container, click `Rebuild`
- otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.

Once the container has rebuilt, VS Code will be running the 2.5 Docker image.

### Local Host Development

The following steps will get you building ZMK locally against Zephyr 2.5:

- Run the updated [toolchain installation](/docs/development/local-toolchain/setup) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- run `west update` to pull the updated Zephyr version and its dependencies

From there, you should be ready to build as normal!

## Thanks!

Thanks again to [innovaker] for all the hard work, and to all the testers who have helped verify ZMK functionality on the newer Zephyr version.

[petejohanson]: https://github.com/petejohanson
[innovaker]: https://github.com/innovaker

---

### Blog 2022 03 08 Zephyr 3 0 Upgrade Prep (`docs/blog/2022-03-08-zephyr-3-0-upgrade-prep.md`)

---
title: "Zephyr 3.0 Update Preparation"
authors: petejohanson
tags: [firmware, zephyr, core]
---

As preparation for completing the [work](https://github.com/zmkfirmware/zmk/pull/1143) to upgrade ZMK to [Zephyr 3.0](https://docs.zephyrproject.org/3.0.0/releases/release-notes-3.0.html), users with user config repositories who wish to avoid future build failures with their GitHub Actions workflows can take steps to adjust
their repositories now.



GitHub Actions needs to use our latest Docker image to ensure continued compatibility with the ZMK codebase on Zephyr 3.0 (and beyond). You should:

- Open `.github/workflows/build.yml` in your editor/IDE
- Change `zmkfirmware/zmk-build-arm:2.5` to `zmkfirmware/zmk-build-arm:stable` wherever it is found

Once the changes are committed and pushed, the build will run as expected.

A future blog post will outline the complete Zephyr 3.0 changes once that work is finalized.

:::note

If you created your user config repository a while ago, you may find that your `build.yml` file instead references
a `zephyr-west-action-arm` custom GitHub Action instead. In this case, the upgrade is not as direct. We suggest that
instead you [re-create your config repository](/docs/user-setup) to get an updated setup using the new automation
approach.

:::

---

### Blog 2022 04 02 Zephyr 3 0 (`docs/blog/2022-04-02-zephyr-3-0.md`)

---
title: "Zephyr 3.0 Update"
authors: petejohanson
tags: [firmware, zephyr, core]
---

I'm happy to announce that we have completed the [work](https://github.com/zmkfirmware/zmk/pull/1143) to upgrade ZMK to [Zephyr 3.0](https://docs.zephyrproject.org/3.0.0/releases/release-notes-3.0.html)!



[petejohanson] did the upgrade work to adjust ZMK for the Zephyr changes.

- Moving to Zephyr's UF2 build integration that was submitted upstream by [petejohanson]
- Additional `color-mapping` property needed for ws2812 LED strep devicetree nodes
- Zephyr core API changes, including delayed work, USB/HID
- Adjust for pinctrl changes on stm32
- Fixes for power management and log formatter changes

## Getting The Changes

Use the following steps to update to the latest tooling in order to properly use the new ZMK changes:

### User Config Repositories Using GitHub Actions

Existing user config repositories using Github Actions to build will pull down Zephyr 3.0 automatically, however to build properly, the repository needs to be updated to use the `stable` Docker image tag for the build:

- Open `.github/workflows/build.yml` in your editor/IDE
- Change `zmkfirmware/zmk-build-arm:2.5` to `zmkfirmware/zmk-build-arm:stable` wherever it is found
- Locate and delete the lines for the DTS output step, which is no longer needed:

  ```yaml
  - name: ${{ steps.variables.outputs.display-name }} DTS File
    if: ${{ always() }}
    run: |
      if [ -f "build/zephyr/${{ matrix.board }}.pre.tmp" ]; then cat -n build/zephyr/${{ matrix.board }}.pre.tmp; fi
      if [ -f "build/zephyr/zephyr.dts" ]; then cat -n build/zephyr/zephyr.dts; fi
  ```

:::note

If you created your user config repository a while ago, you may find that your `build.yml` file instead references
a `zephyr-west-action-arm` custom GitHub Action instead. In this case, the upgrade is not as direct. We suggest that
instead you [re-create your config repository](/docs/user-setup) to get an updated setup using the new automation
approach.

:::

### VS Code & Docker (Dev Container)

If you build locally using VS Code & Docker then:

- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- reload the project
- if you are prompted to rebuild the remote container, click `Rebuild`
- otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.

Once the container has rebuilt, VS Code will be running the 3.0 Docker image.

### Local Host Development

The following steps will get you building ZMK locally against Zephyr 3.0:

- Run the updated [toolchain installation](/docs/development/local-toolchain/setup) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- run `west update` to pull the updated Zephyr version and its dependencies

From there, you should be ready to build as normal!

## Board/Shield Changes

The following changes have [already been completed](https://github.com/zmkfirmware/zmk/pull/1143/commits) for all boards/shields in ZMK `main` branch. For existing or new PRs, or out of tree boards, the following changes are necessary to properly work with the latest changes.

### RGB Underglow

Zephyr's WS2812 `led_strip` driver added a new required property. When adding [underglow](/docs/features/lighting#adding-rgb-underglow-support-to-a-keyboard) to a board, you now must also add the additional include `#include <dt-bindings/led/led.h>` at the top of your devicetree file, and add a `color-mapping` property like:

```dts
led_strip: ws2812@0 {
  compatible = "worldsemi,ws2812-spi";

  /* SPI */
  reg = <0>; /* ignored, but necessary for SPI bindings */
  spi-max-frequency = <4000000>;

  /* WS2812 */
  chain-length = <10>; /* number of LEDs */
  spi-one-frame = <0x70>;
  spi-zero-frame = <0x40>;
  color-mapping = <LED_COLOR_ID_GREEN
                   LED_COLOR_ID_RED
                   LED_COLOR_ID_BLUE>;
};
```

:::note

Standard WS2812 LEDs use a wire protocol where the bits for the colors green, red, and blue values are sent in that order.
If your board/shield uses LEDs that require the data sent in a different order, the `color-mapping` property ordering should be changed to match.

:::

### Display Selection

Zephyr moved to using a `chosen` node named `zephyr,display` to select the display device to be used with LVGL, the underlying display library we use.

For example, for a shield with:

```dts
&pro_micro_i2c {
  status = "okay";

  oled: ssd1306@3c {
    compatible = "solomon,ssd1306fb";
    reg = <0x3c>;
    width = <128>;
    height = <32>;
    segment-offset = <0>;
    page-offset = <0>;
    display-offset = <0>;
    multiplex-ratio = <31>;
    com-invdir;
    segment-remap;
    com-sequential;
    prechargep = <0x22>;
  };
};
```

You would add a `chosen` node like:

```dts
/ {
  chosen {
    zephyr,display = &oled;
  };
};
```

### USB Logging

Zephyr unified the way the console/logging device is selected, removing the hacks that special-cased the USB CDC ACM output.
Now, the CDC ACM device is configured in the devicetree as well. To ensure that USB logging properly works with custom board definitions,
two sections of the `<board>.dts` file need updating.

Underneath the USB device, add the CDC ACM node:

```dts
&usbd {
  status = "okay";
  cdc_acm_uart: cdc_acm_uart {
    compatible = "zephyr,cdc-acm-uart";
  };
};
```

Then, an additional `chosen` node (near the top of the file) will mark the CDC ACM device as the console:

```dts
/ {
  chosen {
    ...
    zephyr,console = &cdc_acm_uart;
  };
  ...
};
```

### UF2 Builds

Previously, to get ZMK to build a UF2 image to flash to a given board required adding a `CMakeLists.txt` file that added a custom post build command.
Now, the only thing necessary to have Zephyr build a UF2 is to add the following to your `<board>_defconfig` file:

```ini
CONFIG_BUILD_OUTPUT_UF2=y
```

If updating an existing board, be sure to remove the previous `CMakeLists.txt` file to avoid generating the UF2 twice during a `west build`.

For more details on the implementation, see [zephyr#31066](https://github.com/zephyrproject-rtos/zephyr/pull/31066).

### STM32 Clock Configuration

Clock configuration moved to devicetree as well, out of the Kconfig files. Here is a sample config for a board that uses the HSI for the PLL source:

```dts
&clk_hsi {
  status = "okay";
};

&pll {
  prediv = <1>;
  mul = <12>;
  clocks = <&clk_hsi>;
  status = "okay";
};

&rcc {
  clocks = <&pll>;
  clock-frequency = <DT_FREQ_M(72)>;
  ahb-prescaler = <1>;
  apb1-prescaler = <2>;
};
```

After adding the nodes, be sure to remove the clock/PLL related configuration from the `<board>_defconfig` file.

## Seeeduino XIAO

The Seeed(uino) XIAO has gained in popularity for use on smaller boards, and gained more traction with the release of the new XIAO BLE board,
powered by the popular nRF52840 SoC. As part of the 3.0 update, we've also more fully integrated the XIAO and XIAO BLE to make it easier to
build keyboard (shields) using either controller.

## Future Hardware

One of the exciting items that's one step closer as part of this work is [support for Raspberry Pi Pico/RP2040](https://github.com/zmkfirmware/zmk/issues/1085).
With Zephyr 3.0 merged, this start the process for getting those controllers/chips supported by ZMK. Follow the issue to keep track of progress.
This will also enable us to support the XIAO compatible Adafruit Qt Py RP2040 and XIAO RP2040.

## Thanks!

Thanks to all the testers who have helped verify ZMK functionality on the newer Zephyr version.

[petejohanson]: https://github.com/petejohanson

## Article Updates

- 12/2023: Removed the deprecated `label` property from code snippets.

---

### Blog 2022 04 10 Zmk Sotf 5 (`docs/blog/2022-04-10-zmk-sotf-5.md`)

---
title: "ZMK State Of The Firmware #5"
authors: petejohanson
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the fifth ZMK "State Of The Firmware" (SOTF)!

This update will cover all the major activity since [SOTF #4](/blog/2021/01/27/zmk-sotf-4). That was over a year ago, so lots to cover!



## Recent Activity

Here's a summary of the various major changes since last time, broken down by theme:

### Keymaps/Behaviors

Since last time, there have been several new powerful keymap features and behaviors added, including several asked for features, such as tap-dance and macros.

#### Caps Word

[petejohanson] added the [caps word](/docs/keymaps/behaviors/caps-word) behavior, i.e. `&caps_word`, in [#823](https://github.com/zmkfirmware/zmk/pull/823) that allows toggling a mode where all all alpha characters are sent
to the host capitalized until a non-alpha, non-"continue list" keycode is sent. This can be useful for typing things like `CONFIG_ENABLE_CAPS_WORD` without having to hold down shift. This is similar in spirit to using the caps lock key, but with the added benefit of turning itself off automatically.

#### Key Repeat

[petejohanson] added the new [key repeat](/docs/keymaps/behaviors/key-repeat) behavior in [#1034](https://github.com/zmkfirmware/zmk/pull/1034) to allow repeating the last sent key-press again, including any modifiers that were applied to that key press. It can be added to your keymap using the simple `&key_repeat` reference.

#### Macros

[petejohanson], taking heavy inspiration on the initial work from [okke-formsma], added [macro support](/docs/keymaps/behaviors/macros) in [#1168](https://github.com/zmkfirmware/zmk/pull/1166). Several [common patterns](/docs/keymaps/behaviors/macros#common-patterns) are documented, but one example, changing the underglow color as you activate/deactivate a layer, looks like:

```dts
ZMK_MACRO(layer_color_macro,
  wait-ms = <0>;
  tap-ms = <0>;
  bindings
      = <&macro_press &mo 1>
      , <&macro_tap &rgb_ug RGB_COLOR_HSB(128,100,100)>
      , <&macro_pause_for_release>
      , <&macro_release &mo 1>
      , <&macro_tap &rgb_ug RGB_COLOR_HSB(300,100,50)>;
)
```

#### Tap Dance

[kurtis-lew] worked diligently to add the [tap-dance behavior](/docs/keymaps/behaviors/tap-dance) in [#1139](https://github.com/zmkfirmware/zmk/pull/1139), allowing different behaviors to be invoked based on the number of times
a user taps a single key in their keymap, e.g.

```dts
/ {
    behaviors {
        td0: tap_dance_0 {
            compatible = "zmk,behavior-tap-dance";
            #binding-cells = <0>;
            tapping-term-ms = <200>;
            bindings = <&kp N1>, <&kp N2>, <&kp N3>;
        };
    };

    keymap {
        compatible = "zmk,keymap";

        default_layer {
            bindings = <
                &td0
            >;
        };
    };
};
```

#### Conditional Layers

[bcat] added [conditional layers](/docs/keymaps/conditional-layers) in [#830](https://github.com/zmkfirmware/zmk/pull/830) as a generalized version of the common "adjust layer" pattern on smaller keyboards.

Example:

```dts
/ {
    conditional_layers {
        compatible = "zmk,conditional-layers";
        tri_layer {
            if-layers = <1 2>;
            then-layer = <3>;
        };
    };
};
```

#### Combos

[mcrosson] added the [layer specific combos](https://zmk.dev/docs/keymaps/combos#configuration) in [#661](https://github.com/zmkfirmware/zmk/pull/661), so users can make certain combos only triggerable when the layers set for the combo are active.

This is used by the [ZMK implementation](https://github.com/artseyio/zmk-artsey) of [ARTSEY](https://artsey.io/) extensively.

#### Sticky Keys

[okke-formsma] updated [sticky keys](/docs/keymaps/behaviors/sticky-key) in [#1122](https://github.com/zmkfirmware/zmk/pull/1122) to add the `ignore-modifiers;` property; when set, sticky keys won't release when other modifiers are pressed. This allows you to combine sticky modifiers, which is popularly used with ["callum-style mods"](https://github.com/callum-oakley/qmk_firmware/tree/master/users/callum#oneshot-modifiers).

#### Hold-Tap Improvements

[jmding8](https://github.com/jmding8) added an additional [positional hold-tap configuration](https://zmk.dev/docs/keymaps/behaviors/hold-tap#positional-hold-tap-and-hold-trigger-key-positions) in [#835](https://github.com/zmkfirmware/zmk/pull/835) to help certain sequences produce the expected results.

[jmding8](https://github.com/jmding8) also added an additional [hold-tap flavor: `tap-unless-interrupted`](https://zmk.dev/docs/keymaps/behaviors/hold-tap#flavors) in [#1018](https://github.com/zmkfirmware/zmk/pull/1018) which works very well with the new positional hold-tap config.

[okke-formsma] implemented [`retro-tap` hold-tap property](https://zmk.dev/docs/keymaps/behaviors/hold-tap#retro-tap) in [#667](https://github.com/zmkfirmware/zmk/pull/667)

[okke-formsma] _also_ added [`quick-tap-ms` hold-tap property](https://zmk.dev/docs/keymaps/behaviors/hold-tap#quick-tap-ms) in [#655](https://github.com/zmkfirmware/zmk/pull/655)

### Apple Device Compatibility Improvements

#### Pairing

[petejohanson] did some sleuthing and fixed a long standing problem with inconsistent pairing with macOS in [#946]](https://github.com/zmkfirmware/zmk/pull/946). With the changes, macOS more reliably pairs with ZMK devices.

#### Consumer (Media) Codes

Another persistent bug that Apple users experienced was related to crashes and problems with keyboard configurations, that was traced to an issue with ZMK's HID usage that was fixed by [petejohanson] in [#726](https://github.com/zmkfirmware/zmk/pull/726).

### Debounce Enhancements

[joelspadin] applied some major enhancements to our [debouncing](/docs/features/debouncing) approach to allow fine grained control of our debouncing in [#888](https://github.com/zmkfirmware/zmk/pull/888), including allowing [eager debouncing](/docs/features/debouncing#eager-debouncing) which can reduce key press latency.

### Split Improvements

#### Behavior Locality

The long awaited locality enhancement was finally merged by [petejohanson] in [#547](https://github.com/zmkfirmware/zmk/pull/547), allowing more fine grained control of where certain behaviors are invoked. Some key improvements thanks to the changes:

- [RGB Underglow](/docs/features/lighting#rgb-underglow) behaviors now run globally, so enabling/disabling RGB, changing the color, animation, etc. applies to both sides of a split properly.
- [Reset](/docs/keymaps/behaviors/reset#reset)/[Bootloader](/docs/keymaps/behaviors/reset#bootloader-reset) behaviors now run wherever the key was pressed. For example, adding a `&bootloader` reference to the peripheral side of a split will now put that side of the split into the bootloader when pressed.

#### Split Connections

[petejohanson] also added fixes to improve split re-connection for certain scenarios in [#984](https://github.com/zmkfirmware/zmk/pull/984), helping ensure splits properly connect when one side or the other is reset.

### Hardware Support

#### Backlight

[bortoz](https://github.com/bortoz) added [single color backlight support](/docs/features/lighting#backlight) in [#904](https://github.com/zmkfirmware/zmk/pull/904) for those keyboards that have it as an alternative to RGB underglow.

#### E-Paper Display (EPD) Driver

[petejohanson] worked with [LOWPROKB](https://github.com/LOWPROKB) to add support for the E-Paper Displays (EPD) in [#895](https://github.com/zmkfirmware/zmk/pull/895) used in keyboards like the Corne-ish Zen.

#### nRF VDDH Battery Sensing

[joelspadin] added a new sensor driver to support battery charge calculation by sensing voltage on the VDDH pin on nRF52 chips in [#750](https://github.com/zmkfirmware/zmk/pull/750), which is particularly useful for designs
using "high voltage mode" with that SoC.

### Miscellaneous

#### Documentation

[dxmh] and [caksoylar](https://github.com/caksoylar) have joined the ZMK organization to help with documentation, and have been doing an amazing job adding new docs, and leading reviewing docs related PRs to free other contributors up to focus on other areas. It's been an incredible addition to ZMK!

#### NKRO Support

[petejohanson]'s work on the HID foundation also included adding support for full NKRO HID in [#726](https://github.com/zmkfirmware/zmk/pull/726) that can be enabled by adding the following to your `.conf` file for your config:

```ini
CONFIG_ZMK_HID_REPORT_TYPE_NKRO=y
```

#### Power Profiler

It's been live for a while, but [nicell] added an amazing [power profiler](/power-profiler) in [#312](https://github.com/zmkfirmware/zmk/pull/312) to allow users to estimate their battery life for various hardware configurations.

#### Min/Max Underglow Brightness

[malinges](https://github.com/malinges) added support for configuring min/max underglow brightness in [#944](https://github.com/zmkfirmware/zmk/pull/944) by setting the values in your `.conf` file as percentages of full:

```ini
CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN=20
CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX=80
```

This can be useful to be sure that lowering brightness doesn't set the brightness to zero, and raising the brightness doesn't consume too much power.

#### Zephyr 3.0

[petejohanson] helped prepare and test the upgrade of ZMK to Zephyr 3.0 in [#1143](https://github.com/zmkfirmware/zmk/pull/1143). The updated Zephyr release brings with it some key BLE stability fixes, as well as various other core improvements that improve ZMK. This was a huge undertaking!

## New Shields

- Contra in [#633](https://github.com/zmkfirmware/zmk/pull/633) - [iangus](https://github.com/iangus)
- Naked60 in [#681](https://github.com/zmkfirmware/zmk/pull/681) - [xiushak](https://github.com/xiushak)
- Murphpad in [#806](https://github.com/zmkfirmware/zmk/pull/806) - [kylemccreery](https://github.com/kylemccreery)
- A. Dux in [#951](https://github.com/zmkfirmware/zmk/pull/951) - [dxmh]
- Bat43 in [#956](https://github.com/zmkfirmware/zmk/pull/956) - [dnaq](https://github.com/dnaq)
- Zodiark in [#959](https://github.com/zmkfirmware/zmk/pull/959) - [Aleblazer](https://github.com/Aleblazer)
- Osprette in [#974](https://github.com/zmkfirmware/zmk/pull/974) - [smores56](https://github.com/smores56)
- Knob Goblin in [#990](https://github.com/zmkfirmware/zmk/pull/990) - [lucasuyezu](https://github.com/lucasuyezu)
- Redox in [#1002](https://github.com/zmkfirmware/zmk/pull/1002) - [toddmok](https://github.com/toddmok)
- Elephant42 in [#1009](https://github.com/zmkfirmware/zmk/pull/1009) - [filoxo](https://github.com/filoxo)
- Chalice in [#1022](https://github.com/zmkfirmware/zmk/pull/1022) - [joshajohnson](https://github.com/joshajohnson)
- Boardsource 5x12 in [#1027](https://github.com/zmkfirmware/zmk/pull/1027) - [fsargent](https://github.com/fsargent)
- Jiran in [#1048](https://github.com/zmkfirmware/zmk/pull/1048) - [krikun98](https://github.com/krikun98)
- keeb.io Fourier in [#1056](https://github.com/zmkfirmware/zmk/pull/1056) - [TheButlah](https://github.com/TheButlah)
- Lotus58 in [#1090](https://github.com/zmkfirmware/zmk/pull/1090) - [nettema](https://github.com/nettema)
- Clog in [#1092](https://github.com/zmkfirmware/zmk/pull/1092) - [smores56](https://github.com/smores56)
- Kyria rev2 in [#1112](https://github.com/zmkfirmware/zmk/pull/1112) - [petejohanson]
- Leeloo in [#1165](https://github.com/zmkfirmware/zmk/pull/1165) - [ClicketySplit](https://github.com/ClicketySplit)
- 2% Milk in [#1135](https://github.com/zmkfirmware/zmk/pull/1135) - [kurtis-lew]

## New Boards

- Ferris rev02 in [#642](https://github.com/zmkfirmware/zmk/pull/642) - [petejohanson]
- nice!60 in [#810](https://github.com/zmkfirmware/zmk/pull/810) - [nicell]
- nice!nano v2 in [#867](https://github.com/zmkfirmware/zmk/pull/867) - [nicell]
- Mikoto 520 in [#985](https://github.com/zmkfirmware/zmk/pull/985) - [mrninhvn](https://github.com/mrninhvn)
- S40NC in [#1021](https://github.com/zmkfirmware/zmk/pull/1021) - [kylemccreery](https://github.com/kylemccreery)
- BT60 in [#1029](https://github.com/zmkfirmware/zmk/pull/1029) - [ReFil](https://github.com/ReFil)
- Seeeduino XIAO BLE (as part of the Zephyr 3.0 work) in [#1143](https://github.com/zmkfirmware/zmk/pull/1143) - [petejohanson]

## Board/Shield Metadata

[nicell] and [petejohanson] worked together in [#883](https://github.com/zmkfirmware/zmk/pull/883) to settle on a [metadata format](/docs/hardware-integration/hardware-metadata-files) that is used to document every board and shield. This now drives automatic generation of our [supported hardware](/docs/hardware) page and our
more nuanced GH Actions automation for testing changes to ZMK.

## Coming Soon!

Some items listed in the last coming soon section are still under active development.

- RP2040 support
- Peripheral rotary encoder support
- Caps/Scroll/Num Lock LED support
- Mouse Keys
- Wired split support
- More modular approach to external boards/shields, custom code, user keymaps, etc.
- More shields and boards

## Statistics

Some statistics of interest for ZMK:

- GitHub (lifetime stats)
  - 105 Contributors
  - 791 Closed PRs
  - 849 Stars
  - 832 Forks
- Discord Chat
  - 3430 total registered
- Website (last 30 days)
  - 35.9K page views
  - 3.29K new users

## Thanks!

As we approach the two year birthday for ZMK, I am reminded of how far we have come in such a short time, in large part thanks to the _amazing_ community that has grown around it. I am so grateful to have so many contributors, testers, and user believing in the project and helping make it a joy to work on.

[okke-formsma]: https://github.com/okke-formsma
[mcrosson]: https://github.com/mcrosson
[nicell]: https://github.com/Nicell
[petejohanson]: https://github.com/petejohanson
[kurtis-lew]: https://github.com/kurtis-lew
[joelspadin]: https://github.com/joelspadin
[bcat]: https://github.com/bcat
[dxmh]: https://github.com/dxmh

## Article Updates

- 12/2023: Removed the deprecated `label` property from code snippets.

---

### Blog 2022 04 21 Zmk 2yo (`docs/blog/2022-04-21-zmk-2yo.md`)

---
title: "ZMK's Second Birthday"
authors: petejohanson
tags: [keyboards, firmware, oss]
---

Two years ago, today, I minted the first ever commit for ZMK:



```
commit 85c8be89dea8f7a00e8efb06d38e2b32f3459935
Author: Pete Johanson <peter@peterjohanson.com>
Date:   Tue Apr 21 16:20:34 2020 -0400

    Initial work.

 .gitignore     |  1 +
 .gitmodules    |  3 +++
 CMakeLists.txt | 40 +++++++++++++++++++++++++++++++++++++++
 src/main.c     | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/zmk_lib.h  |  7 +++++++
 zephyr-rust    |  1 +
 6 files changed, 112 insertions(+)
```

I will never forget that commit. Not because of the code it contained (please don't look, it's not worth it!), but for what it started.

Working on ZMK has given me the opportunity to reconnect with old friends ([@brixmeister](https://twitter.com/brixmeister) was my Gentoo mentor/sponsor when I became a contributor there on my first ever OSS project, and is a current active Zephyr RTOS contributor!), make new ones, and learn so much from the amazing mechanical keyboard community.

## First Keyboard

But I'm getting ahead of myself! Back to early ZMK. I present you the first ZMK keyboard:



That first "keyboard" taught me a _lot_. It forced me to dust off my long forgotten, rudimentary electronics knowledge, and gave me my first taste of really combining the physical/tangible with code in a way that years of doing backend API development never had.

I was _hooked_.

## Zephyr RTOS

Early in my brainstorming, I knew I needed a foundation to build upon that would get me "a lot for free." I evaluated several different real-time operating systems (RTOSes) and happened upon [Zephyr](https://zephyrproject.org/). It immediately ticked all the boxes I wanted:

- Robust, open source Bluetooth stack, supporting multiple SoCs. At the time, I was trying out stm32wb thanks to some interest among keyboard designers, but I also so there were other compelling choices that might be a good fit.
- An open source, non-copyleft license. I am a firm believer in F/OSS, and wanted to use a license that was as unrestricted as possible.
- Had a lot of core APIs available, so I could focus on the keyboard functionality, not the plumbing. I love tinkering, but I wanted to focus my time on the interesting bits, not infrastructure.

I'm really happy with the choice, it has served us incredibly well the past two years.

## Real Keyboard

At some point, somehow, [innovaker] introduced me to [nicell] who graciously sent me a few of the early pre-production nice!nano controllers, which I was able to get running on my Kyria. Doing so required the first split code, as well as lots of general improvements.



The day I was finally able to type on a wireless, split keyboard running ZMK was deeply momentous for me!

## Onward and Upward

We've come a long way since then, with our [supported hardware](/docs/hardware), [features](/docs/keymaps) and [behaviors](/docs/keymaps/behaviors/key-press) growing regularly.

ZMK powered keyboards are now available in group buys and in stock at various vendors; compatible controllers have been used in a wide range of builds to empower our users to free themselves from their USB/TRRS cables and move about untethered.

This progress is only possible thanks to all of the contributors who've joined me in the vision for a wireless-first world. I am so grateful for everyone who has given their time to contribute code, answer questions on our Discord server, write more documentation, and especially all the users who have trusted us to make their input devices work.

I can't wait to see what we can accomplish together in the next two years.

[innovaker]: https://github.com/innovaker
[nicell]: https://github.com/Nicell

---

### Blog 2023 04 06 Zephyr 3 2 (`docs/blog/2023-04-06-zephyr-3-2.md`)

---
title: "Zephyr 3.2 Update"
authors: petejohanson
tags: [firmware, zephyr, core]
---

I'm happy to announce that we have completed the [work](https://github.com/zmkfirmware/zmk/pull/1499) to upgrade ZMK to [Zephyr 3.2](https://docs.zephyrproject.org/3.2.0/releases/release-notes-3.2.html)!



[petejohanson] did the upgrade work to adjust ZMK for the Zephyr changes, with help from [Nicell] on the LVGL pieces.

- Upgrade to LVGL 8.x API, and move to the new Kconfig settings.
- Tons of RP2040 work.
- Zephyr core API changes, including DTS `label` use changes.
- Move to [pinctrl](https://docs.zephyrproject.org/3.2.0/hardware/pinctrl/index.html) Zephyr subsystem.

## Getting The Changes

Use the following steps to update to the latest tooling in order to properly use the new ZMK changes:

### User Config Repositories Using GitHub Actions

Existing user config repositories using Github Actions to build will pull down Zephyr 3.2 automatically, however if you created your user config a while ago, you may need to update it to reference our shared build configuration to leverage the correct Docker image.

1.  Replace the contents of your `.github/workflows/build.yml` with:

    ```yaml
    on: [push, pull_request, workflow_dispatch]

    jobs:
      build:
        uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@main
    ```

1.  If it doesn't exist already, add a new file to your repository named `build.yaml`:

    ```yaml
    # This file generates the GitHub Actions matrix
    # For simple board + shield combinations, add them
    # to the top level board and shield arrays, for more
    # control, add individual board + shield combinations to
    # the `include` property, e.g:
    #
    # board: [ "nice_nano_v2" ]
    # shield: [ "corne_left", "corne_right" ]
    # include:
    #   - board: bdn9_rev2
    #   - board: nice_nano_v2
    #     shield: reviung41
    #
    ---
    ```

and then update it as appropriate to build the right shields/boards for your configuration.

### Upgrade a manual script

If you have a custom GitHub Actions workflow you need to maintain for some reason, you can update the workflow to use the `stable` Docker image tag for the build:

- Open `.github/workflows/build.yml` in your editor/IDE
- Change `zmkfirmware/zmk-build-arm:2.5` to `zmkfirmware/zmk-build-arm:stable` wherever it is found
- Locate and delete the lines for the DTS output step, which is no longer needed:

  ```yaml
  - name: ${{ steps.variables.outputs.display-name }} DTS File
    if: ${{ always() }}
    run: |
      if [ -f "build/zephyr/${{ matrix.board }}.pre.tmp" ]; then cat -n build/zephyr/${{ matrix.board }}.pre.tmp; fi
      if [ -f "build/zephyr/zephyr.dts" ]; then cat -n build/zephyr/zephyr.dts; fi
  ```

### VS Code & Docker (Dev Container)

If you build locally using VS Code & Docker then:

- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- reload the project
- if you are prompted to rebuild the remote container, click `Rebuild`
- otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.

Once the container has rebuilt, VS Code will be running the 3.2 Docker image.

### Local Host Development

The following steps will get you building ZMK locally against Zephyr 3.2:

- Run the updated [toolchain installation](/docs/development/local-toolchain/setup) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- Install the latest version of `west` by running `pip3 install --user --update west`.
- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- run `west update` to pull the updated Zephyr version and its dependencies

From there, you should be ready to build as normal!

## Known Issues

A few testers have reported inconsistent issues with bluetooth connections on Windows after upgrading, which can be resolved by re-pairing your keyboard by:

1. Remove the device from Windows.
1. Clear the profile on your keyboard that is associated with the Windows device by triggering `&bt BT_CLR` on your keymap while that profile is active.
1. Restart Windows.
1. Re-connect Windows to your keyboard.

## Windows Battery Reporting Fix

Zephyr 3.2 introduced [a new Kconfig setting](https://github.com/zephyrproject-rtos/zephyr/pull/48929) that can be used to work around a bug in Windows related to battery reporting. Check out our [bluetooth config](/docs/config/bluetooth) for the full details. The key new configuration that can be set if using Windows is:

```ini
CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n
```

## Keymap Changes

Due to conflicts with new devicetree node labels added for Zephyr's [reset system](https://docs.zephyrproject.org/3.2.0/hardware/peripherals/reset.html), the `&reset` behavior has been renamed to `&sys_reset`.

All of the in-tree keymaps have been fixed, but you may encounter build failures about duplicate names, requiring you rename the behavior reference in your keymap. Use the [Keymap Upgrader](/keymap-upgrader) and this will get fixed for you automatically.

## Board/Shield Changes

The following changes have [already been completed](https://github.com/zmkfirmware/zmk/pull/1499/commits) for all boards/shields in ZMK `main` branch. For existing or new PRs, or out of tree boards, the following changes are necessary to properly work with the latest changes.

### Move to `pinctrl` driver

Before this change, setting up the details of pins to use them for peripherals like SPI, I2C, etc. was a mix of platform specific driver code. Zephyr has moved to the newer `pinctrl` system to unify the handling of pin configuration, with additional flexibility for things like low power modes for those pins, etc.

#### Board specific shield overlays

The main area this affects existing shields is those with board specific overrides, e.g. `<shield>/boards/seeeduino_xiao_ble.overlay`, that sets up additional components on custom buses, e.g. addressable RGB LEDs leveraging the SPI MOSI pin.

#### nRF52 Pin Assignments

Previously in ZMK, we relied on per-driver devicetree source properties to set the alternate pin functions for things like SPI or I2C. For example, here is the I2C bus setup as it was previously on the `nice_nano` board:

```dts
&i2c0 {
    compatible = "nordic,nrf-twi";
    sda-pin = <17>;
    scl-pin = <20>;
};
```

With the move to the `pinctrl` system, this setup now look like:

```dts
 &i2c0 {
    compatible = "nordic,nrf-twi";
    pinctrl-0 = <&i2c0_default>;
    pinctrl-1 = <&i2c0_sleep>;
    pinctrl-names = "default", "sleep";
 };
```

which references the `pinctrl` configuration:

```dts
&pinctrl {
       i2c0_default: i2c0_default {
               group1 {
                       psels = <NRF_PSEL(TWIM_SDA, 0, 17)>,
                               <NRF_PSEL(TWIM_SCL, 0, 20)>;
               };
       };

       i2c0_sleep: i2c0_sleep {
               group1 {
                       psels = <NRF_PSEL(TWIM_SDA, 0, 17)>,
                               <NRF_PSEL(TWIM_SCL, 0, 20)>;
                       low-power-enable;
               };
       };
};
```

Although slightly more _verbose_ this allows pin configuration infrastructure to be re-used, specify other modes, like sleep, etc. in a standard way across architectures.

#### Out of Tree Boards/Shields

All of the in-tree boards and shields have been upgraded, but if you maintain/use an out-of-tree board or shield that uses the converted boards and overrides pins for various buses, you may need to switch to `pinctrl` to match ZMK's new approach.

The approach is the following when updating a _board_:

1. Add an entry `CONFIG_PINCTRL=y` to the `<board>_defconfig` file in the board directory.
1. Add a new file with the naming convention `<board>-pinctrl.dtsi` to your board directory.
1. In the new file, add your `pinctrl` entries that set up different pin control configurations for whatever peripherals/buses are needed. Here's the nice!nano file as an example:

   ```dts
   /*
   * Copyright (c) 2022 The ZMK Contributors
   * SPDX-License-Identifier: MIT
   */

   &pinctrl {
           uart0_default: uart0_default {
                   group1 {
                           psels = <NRF_PSEL(UART_RX, 0, 8)>;
                           bias-pull-up;
                   };
                   group2 {
                           psels = <NRF_PSEL(UART_TX, 0, 6)>;
                   };
           };

           uart0_sleep: uart0_sleep {
                   group1 {
                           psels = <NRF_PSEL(UART_RX, 0, 8)>,
                                   <NRF_PSEL(UART_TX, 0, 6)>;
                           low-power-enable;
                   };
           };

           i2c0_default: i2c0_default {
                   group1 {
                           psels = <NRF_PSEL(TWIM_SDA, 0, 17)>,
                                   <NRF_PSEL(TWIM_SCL, 0, 20)>;
                   };
           };

           i2c0_sleep: i2c0_sleep {
                   group1 {
                           psels = <NRF_PSEL(TWIM_SDA, 0, 17)>,
                                   <NRF_PSEL(TWIM_SCL, 0, 20)>;
                           low-power-enable;
                   };
           };
   };
   ```

1. From the main `<board>.dts` file, add an `#include "<board>-pinctrl.dtsi"` to have the C-preprocessor combine the files.
1. Update the various peripheral nodes to use the new `pinctrl` configurations. For example, the following old configuration:

   ```dts
   &i2c0 {
       compatible = "nordic,nrf-twi";
       sda-pin = <15>;
       scl-pin = <17>;
   };
   ```

   would be changed to:

   ```dts
   &i2c0 {
       compatible = "nordic,nrf-twi";
       pinctrl-0 = <&i2c0_default>;
       pinctrl-1 = <&i2c0_sleep>;
       pinctrl-names = "default", "sleep";
   };
   ```

Because `pinctrl` configuration is very dependent on the specific target SoC, you will rarely need to consider it for a shield overlay that leverages a pro micro or XIAO abstraction. As noted, you're more likely to need to fix up pinctrl settings is using a board specific shield overlay, e.g. `<shield>/boards/<board>.overlay` to set things up.

### LVGL Kconfig changes.

With the update to LVGL 8.x, Zephyr now leverages an upstream Kconfig file for most LVGL settings. Due to this, the naming for many existing configs has been adjusted. For any configs moved upstream, the naming mostly involves a prefix change from `LVGL_` to the shorter `LV_`. For any that are still Zephyr specific configs, they are now prefixed with `LV_Z_` prefix.

If you maintain or use an out of tree board/shield with a display, the following will need to be changed in your Kconfig files:

- `LVGL_VDB_SIZE` -> `LV_Z_VDB_SIZE`
- `LVGL_DPI` -> `LV_DPI_DEF`
- `LVGL_BITS_PER_PIXEL` -> `LV_Z_BITS_PER_PIXEL`

Other than those specific examples, most other Kconfig values can simply change the `LVGL_` prefix to `LV_`.

## Raspberry Pi Pico/RP2040 Support

This Zephyr update allows ZMK to support the new(-ish) RP2040 SoC found in the Raspberry Pi Pico.

:::note

ZMK does _not_ support wired split communication yet, so RP2040 is only usable for non-split keyboards. To follow progress on wired splits, see [#1117](https://github.com/zmkfirmware/zmk/pull/1117).

:::

### Supported Controllers

The following RP2040 powered controllers have board definitions for folks to test:

- Raspberry Pi Pico (`rpi_pico`)
- SparkFun Pro Micro RP2040 (`sparkfun_pro_micro_rp2040`)
- Adafruit Keyboar/KB2040 (`adafruit_kb2040`)
- Seeeduino XIAO RP2040 (`seeeduino_xiao_rp2040`)
- Adafruit Qt PY RP2040 (`adafruit_qt_py_rp2040`)
- BoardSource blok (`boardsource_blok`)
- Elite-Pi (compatible with the `sparkfun_pro_micro_rp2040` board)

## Upcoming Changes

### Display re-init

Zephyr's improved [power domain](https://docs.zephyrproject.org/3.2.0/services/pm/power_domain.html#pm-power-domain) support is a foundation
upon which we can provide a proper fix for the [longstanding display re-init bug](https://github.com/zmkfirmware/zmk/issues/674) which has prevented
ZMK from formally supporting our display code.

There is work still remaining to fully leverage the power domain system within ZMK to fix the bug, but upgrading Zephyr is the first necessary step.

## Thanks!

Thanks to all the testers who have helped verify ZMK functionality on the newer Zephyr version.

[petejohanson]: https://github.com/petejohanson
[nicell]: https://github.com/Nicell

---

### Blog 2023 06 18 Encoder Refactors (`docs/blog/2023-06-18-encoder-refactors.md`)

---
title: "Major Encoder Refactor"
authors: petejohanson
tags: [firmware, zephyr, sensors, encoders]
---

Today, we merged a significant change to the low level sensor code that is used to support encoders. In particular,
this paves the way for completing the work on supporting split peripheral sensors/encoders, and other future sensors
like pointing devices.



As part of the work, backwards compatibility for existing shields has been retained, but only for a grace period to allow out-of-tree shields to move to the new approach for encoders.

Special thanks to [joelspadin] for the _thorough_ code review and testing throughout the development of the refactor.

## Summary of Changes

The following items have been merged:

1. Split configuration of hardware details, and behavior configuration to allow more flexible functionality of sensors/encoders, in particular linear encoders that lack detents/"clicks" as they rotate.
2. Support for upstream Zephyr sensor drivers, including the NRFX QDEC driver that can be used on nRF52 based keyboards.
3. Sensor data handling changes that pave the way for split sensor handling easily.

## Configuration Changes

The major changes to configuration in the devicetree files relates to how the number of steps/triggers for a given encoder are set. In particular, the number of pulses/steps for a given encoder is configured first, allowing ZMK to determine the exact angular degrees of change that is represented by a single pulse on the data lines to that encoder.

Once that angular degrees mapping is completed, now independently there is a configuration setting to control how many triggers of the behavior in the keymap should occur for each full rotation of the sensor. Another way to think of this is "how many degrees of rotation results in a triggering of the sensor behavior in your keymap layer".

Splitting these two parts of the encoder configuration allows greater flexibility, and fine grained control of encoder behavior for linear encoders that don't have fixed detents.

### Old Configuration

Previously, an encoder configuration looked like:

```dts
    left_encoder: encoder_left {
        compatible = "alps,ec11";
        a-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
        b-gpios = <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
        resolution = <4>;
    };
```

Here, the `resolution` property was used to indicate how many encoder pulses should trigger the sensor behavior one time. Next, the encoder is selected in the sensors node:

```dts
    sensors {
        compatible = "zmk,keymap-sensors";
        sensors = <&left_encoder &right_encoder>;
    };
```

That was the entirety of the configuration for encoders.

### New Configuration

```dts
    left_encoder: encoder_left {
        compatible = "alps,ec11";
        a-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
        b-gpios = <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
        steps = <80>;
    };
```

Here, the `steps` property is now used to indicate how many encoder pulses there are in a single complete rotation of the encoder. Next, the encoder is selected in the sensors node as before, but an additional configuration is used to indicate how many times the encoder should trigger the behavior in your keymap per rotation:

```dts
    sensors {
        compatible = "zmk,keymap-sensors";
        sensors = <&left_encoder &right_encoder>;
        triggers-per-rotation = <20>;
    };
```

For tactile encoders that have detents, the `triggers-per-rotation` would match the number of detents on the encoder. For linear encoders, the value can be chosen to suit your needs.

## Zephyr Sensor Drivers

The configuration changes bring ZMK's code in line with how upstream Zephyr sensor drivers handle rotations. This has the added advantage of allowing us to leverage other sensor drivers. On Nordic MCUs, like nRF52840, the NRFX QDEC driver can be used, for example:

```dts
&pinctrl {
    qdec_default: qdec_default {
        group1 {
            psels = <NRF_PSEL(QDEC_A, 1, 11)>,
                    <NRF_PSEL(QDEC_B, 1, 10)>;
            bias-pull-up;
        };
    };
};

// Set up the QDEC hardware based driver and give it the same label as the deleted node.
encoder: &qdec0 {
    status = "okay";
    led-pre = <0>;
    steps = <80>;
    pinctrl-0 = <&qdec_default>;
    pinctrl-names = "default";
};
```

The NRFX QDEC driver has the advantage of supporting optical encoders as well, and although it polls, it does so in hardware without waking the MCU core; initial basic power profiling is promising.

## Split Sensor/Encoder Support

In addition to the refactors for splitting the configuration, the changes merged included refactors designed to simplify and move forward with the long outstanding feature of supporting encoders on the peripheral side of split keyboards. That work is planned as a follow up.

## Deprecation

The old configuration will be supported for a period of one month, and then removed, giving users a grace period to complete the migration to the new separated configuration.

[petejohanson]: https://github.com/petejohanson
[joelspadin]: https://github.com/joelspadin

## Article Updates

- 12/2023: Removed the deprecated `label` property from code snippets.

---

### Blog 2023 10 05 Zmk Sotf 6 (`docs/blog/2023-10-05-zmk-sotf-6.md`)

---
title: "ZMK State Of The Firmware #6"
authors: caksoylar
tags: [SOTF, keyboards, firmware, oss, ble]
---

Welcome to the sixth ZMK "State Of The Firmware" (SOTF)!

This update will cover all the major activity since [SOTF #5](/blog/2022/04/10/zmk-sotf-5). That was over a year ago (again!), so there are many new exciting features and plenty of improvements to cover!



## Recent Activity

Here's a summary of the various major changes since last time, broken down by theme:

### Keymaps/Behaviors

#### Hold-tap improvements

[andrewjrae] added the [`require-prior-idle-ms` property](/docs/keymaps/behaviors/hold-tap#require-prior-idle-ms) to the hold-tap behavior in [#1187](https://github.com/zmkfirmware/zmk/pull/1187) and [#1387](https://github.com/zmkfirmware/zmk/pull/1387), which prevents the hold behavior from triggering if it hasn't been a certain duration since the last key press. This is a useful feature to prevent accidental hold activations during quick typing and made its way into many keymaps! The same property was added to [combos](/docs/keymaps/combos#configuration) as well to help prevent false combo activations.

Note that an earlier iteration of this feature was supported with the `global-quick-tap` property, which did not allow customizing the timeout and used the value of `quick-tap-ms` for it. This property is now deprecated and users are encouraged to use `require-prior-idle-ms` instead.

[urob] added the [`hold-trigger-on-release` property](/docs/keymaps/behaviors/hold-tap#positional-hold-tap-and-hold-trigger-key-positions) in [#1423](https://github.com/zmkfirmware/zmk/pull/1423). This significantly increases the usefulness of positional constraints on hold-taps, since it allows combining multiple holds such as different modifiers for home row mods usage.

#### Masking mods in mod-morphs

[aumuell](https://github.com/aumuell), [vrinek](https://github.com/vrinek) and [urob] contributed to improving the behavior of [mod-morphs](/docs/keymaps/behaviors/mod-morph) by masking the triggering modifiers and added `keep-mods` property in [#1412](https://github.com/zmkfirmware/zmk/pull/1412). This unlocks more use cases for mod-morphs, since you are no longer constrained to emitting keycodes that work well with the triggering modifier keycodes.

As an example, you can now define a mod-morph that swaps `;` and `:` so that the former is the shifted version of the latter, which wasn't previously possible:

```dts
        col_semi: colon_semicolon {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp COLON>, <&kp SEMI>;
            mods = <(MOD_LSFT|MOD_RSFT)>;
        };
```

#### Parameterized macros

[petejohanson] added [macros that can be parameterized](/docs/keymaps/behaviors/macros#parameterized-macros) with one or two parameters in [#1232](https://github.com/zmkfirmware/zmk/pull/1232). This allows users to define macros in a more modular way and is a nice quality-of-life improvement.

As a simple example, you could define a macro that puts any keycode provided between double quotes as below, then use it like `&ql A` in your keymap:

```dts
        ql: quoted_letter {
            #binding-cells = <1>;
            compatible = "zmk,behavior-macro-one-param";
            bindings =
                <&kp DQT>,
                <&macro_param_1to1 &kp MACRO_PLACEHOLDER>,
                <&kp DQT>;
        };
```

Please see the documentation page linked above for usage and more examples.

#### Arbitrary behaviors on encoder rotation

[nickconway](https://github.com/nickconway) and [petejohanson] added [sensor rotation behaviors](/docs/keymaps/behaviors/sensor-rotate) to allow invoking arbitrary behaviors from encoders [#1758](https://github.com/zmkfirmware/zmk/pull/1758). Previously encoder rotations could only invoke the key-press behavior `&kp` through the `&inc_dec_kp` binding, whereas now you can define new sensor rotation behaviors to invoke others.

(Note that currently behaviors that have "locality" such as `&rgb_ug` do not work as expected via encoder rotation bindings in split keyboards, due to issue [#1494](https://github.com/zmkfirmware/zmk/issues/1494).)

#### Pre-releasing already pressed keys

[andrewjrae] contributed a tweak to emitting keycodes in [#1828](https://github.com/zmkfirmware/zmk/pull/1828), where rolling multiple keys that involve the same keycode now releases the keycode before sending a press event again. While this might sound like a technical distinction, it leads to more correct behavior when quickly typing sequences like `+=` and makes the [key repeat behavior](/docs/keymaps/behaviors/key-repeat) work properly when it is pressed before the previous key is released.

#### Key toggle behavior

[cgoates](https://github.com/cgoates) added the [key toggle behavior](/docs/keymaps/behaviors/key-toggle) in [#1278](https://github.com/zmkfirmware/zmk/pull/1278), which can be used via its `&kt` binding to toggle the state of a keycode between pressed and released.

#### Apple Globe key

[ReFil] added support for the `C_AC_NEXT_KEYBOARD_LAYOUT_SELECT` keycode with alias `GLOBE` which acts as the Globe key in macOS and iOS in [#1938](https://github.com/zmkfirmware/zmk/pull/1938). Note that this keycode doesn't exactly behave like a Globe key that is present on an Apple keyboard and its limitations are documented in [this comment](https://github.com/zmkfirmware/zmk/pull/1938#issuecomment-1744579039) thanks to testing by [SethMilliken](https://github.com/SethMilliken). These limitations will be noted in the official [keycodes documentation](/docs/keymaps/list-of-keycodes) shortly.

#### Bug fixes and other improvements

[petejohanson], [andrewjrae] and [okke-formsma] tracked down and fixed an issue causing stuck keys when there are combos on key positions involving hold-tap behaviors in [#1411](https://github.com/zmkfirmware/zmk/pull/1411). This was an elusive bug that took a lot of effort from the community to nail down and fix!

[nguyendown](https://github.com/nguyendown) and [joelspadin] tracked down and fixed a couple issues causing stuck keys with [sticky keys](/docs/keymaps/behaviors/sticky-key) in [#1586](https://github.com/zmkfirmware/zmk/pull/1586), [#1745](https://github.com/zmkfirmware/zmk/pull/1745).

[okke-formsma] fixed an issue allowing tap dances to be invoked by combos in [#1518](https://github.com/zmkfirmware/zmk/pull/1518).

[petejohanson] tweaked the caps word behavior to ignore modifiers in [#1330](https://github.com/zmkfirmware/zmk/pull/1330).

[HelloThisIsFlo](https://github.com/HelloThisIsFlo) documented a bug with combos involving overlapping keys and different timeouts, produced a reproducing unit test, then proceeded to fix it in [#1945](https://github.com/zmkfirmware/zmk/pull/1945).

### Bluetooth and Split Improvements

#### Multiple peripherals

[xudongzheng] contributed to add support for more than one peripheral per keyboard in [#836](https://github.com/zmkfirmware/zmk/pull/836). This allows setups such as split keyboards with more than two halves, or enable a BLE-based "dongle mode" via a third device running ZMK that can stay connected to a computer via USB.

Note that documentation is still lacking for utilizing more than one peripheral and there will potentially be future changes in the build system to allow for more seamless configuration.

#### Pairing passkey requirement

[petejohanson] added [the option to require passkey input](/docs/config/bluetooth) while pairing to new devices in [#1822](https://github.com/zmkfirmware/zmk/pull/1822). Enabling this will require you to enter a six digit passcode via the number keys on your keymap and press enter when pairing to a new device, enhancing security during the pairing procedure.

#### Split keyboard improvements

[petejohanson] contributed a fix to release held keys on peripheral disconnect [#1340](https://github.com/zmkfirmware/zmk/pull/1340), which makes scenarios where a split disconnects unexpectedly less painful.

[petejohanson] also improved [the `settings_reset` shield](/docs/troubleshooting/connection-issues#split-keyboard-parts-unable-to-pair) by making it clear bonds more reliably, and allow it to build for all boards in [#1879](https://github.com/zmkfirmware/zmk/pull/1879).

[petejohanson] and [xudongzheng] contributed additional split connectivity improvements, via using directed advertising in [#1913](https://github.com/zmkfirmware/zmk/pull/1913) and improving the robustness of central scanning in [#1912](https://github.com/zmkfirmware/zmk/pull/1912).

### Hardware Support

#### Encoders

[petejohanson] contributed a major refactor of encoder (and more generally sensor) functionality in [#1039](https://github.com/zmkfirmware/zmk/pull/1039). While the documentation for these changes are still in progress, check out the [dedicated blog post](/blog/2023/06/18/encoder-refactors) for more details.

This refactor paved way to implementing a long-awaited feature, encoder support in peripheral halves of split keyboards! Building upon the work by [stephen](https://github.com/stephen) in [#728](https://github.com/zmkfirmware/zmk/pull/728), [petejohanson] implemented support in [#1841](https://github.com/zmkfirmware/zmk/pull/1841).

#### Direct GPIO driver

[joelspadin] extended the comprehensive debouncing framework used for matrix scan driver to the [direct GPIO driver](/docs/config/kscan#direct-gpio-driver) in [#1288](https://github.com/zmkfirmware/zmk/pull/1288).

[kurtis-lew] added toggle mode support for direct GPIO driver in [#1305](https://github.com/zmkfirmware/zmk/pull/1305). This allows for adding toggle switches to a keyboard, by properly reading their initial state on boot and making sure the power use is efficient.

#### IO peripheral drivers

[petejohanson] added support for the 595 shift register commonly used with smaller controllers like Seeeduino Xiaos, in [#1325](https://github.com/zmkfirmware/zmk/pull/1325).

[zhiayang](https://github.com/zhiayang) added the driver for the MAX7318 GPIO expander in [#1295](https://github.com/zmkfirmware/zmk/pull/1295).

#### Underglow auto-off options

[ReFil] added two [new RGB auto off options](/docs/config/lighting#rgb-underglow), one using an idle timeout and the other USB status in [#1010](https://github.com/zmkfirmware/zmk/pull/1010).

#### nice!view support

[nicell] added support for nice!view, a memory display optimized for low power use in [#1462](https://github.com/zmkfirmware/zmk/pull/1462).
He also contributed a custom vertically-oriented status screen that is automatically enabled when the `nice_view` shield is used in [#1768](https://github.com/zmkfirmware/zmk/pull/1768), since the default status screen has a horizontal orientation.
Please see the instructions in the [nice!view README](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/nice_view/README.md) if you would like to restore the stock status screen.

#### E-paper display initialization

[petejohanson] contributed EPD initialization improvements in [#1098](https://github.com/zmkfirmware/zmk/pull/1098), which makes the keyboards using slow refresh displays such as the Corne-ish Zen much more responsive during initial boot.

#### Xiao BLE improvements

Various improvements were made for the Seeeduino Xiao BLE board in [#1293](https://github.com/zmkfirmware/zmk/pull/1293), [d0176f36](https://github.com/zmkfirmware/zmk/commit/d0176f36), [#1545](https://github.com/zmkfirmware/zmk/pull/1545) and [#1927](https://github.com/zmkfirmware/zmk/pull/1927) by [petejohanson] and [caksoylar], enabling features necessary for ZMK and improving its power use.

### Zephyr 3.2 Upgrade

[petejohanson] once again contributed the massive work necessary for upgrading ZMK to Zephyr 3.2 in [#1499](https://github.com/zmkfirmware/zmk/pull/1499), with review help from [joelspadin] and testing by the community. This Zephyr release brings with it upgrades to the display library LVGL, adds official support for the RP2040 controllers and many internal refactors to help future development.
Check out the [dedicated blog post](/blog/2023/04/06/zephyr-3-2) for more details!

### Documentation

#### Configuration docs

[joelspadin], through a massive amount of work in [#722](https://github.com/zmkfirmware/zmk/pull/722), contributed a whole new section to the documentation: [configuration](/docs/config)! It enumerates the configuration options for each ZMK feature that might be relevant to users in dedicated pages, making it a very handy reference.

In addition, the [overview page](/docs/config) presents an overview of how configuration works in Zephyr in the context of ZMK, in terms of devicetree files (like the keymap files or shield overlays), and Kconfig ones (like the `.conf` files). It is very helpful in de-mystifying what the various files do and what syntax is expected in them.

#### New behavior guide

For users or future contributors that might want to dive into writing their own ZMK behaviors, [kurtis-lew] wrote a useful [guide on how to create new behaviors](/docs/development/new-behavior) in [#1268](https://github.com/zmkfirmware/zmk/pull/1268).

#### Tap dance and hold-tap documentation improvements

[kurtis-lew] also improved the documentation for these two behaviors in [#1298](https://github.com/zmkfirmware/zmk/pull/1298), by updating the diagrams to better clarify how their timings work and adding examples for scenarios that are frequently asked by users.

#### Battery sensor documentation

[joelspadin] also added documentation for setting up battery sensors, typically required for new boards, in [#868](https://github.com/zmkfirmware/zmk/pull/868).

#### Shield interconnects

[petejohanson] updated the [new shield guide](/docs/hardware-integration/new-shield) for non-Pro Micro interconnects including Xiao, Arduino Uno and Blackpill in [#1607](https://github.com/zmkfirmware/zmk/pull/1607).

#### Bluetooth feature page

[petejohanson] and [caksoylar] added a new [Bluetooth feature page](/docs/features/bluetooth) as part of [#1499](https://github.com/zmkfirmware/zmk/pull/1499) and in [#1818](https://github.com/zmkfirmware/zmk/pull/1499), detailing ZMK's Bluetooth implementation and troubleshooting for common problems.

In addition to the specific contributions listed above, various improvements and fixes to documentation are made by many users from the community, including but not limited to [kurtis-lew], [joelspadin], [filterpaper], [byran.tech](https://github.com/byran.tech), [dxmh] and [caksoylar]. These contributions are are all very appreciated!

### Miscellaneous

#### Reusable GitHub build workflow

[elagil](https://github.com/elagil) helped switch the build workflow used by the [user config repos](/docs/user-setup) to a reusable one in [#1183](https://github.com/zmkfirmware/zmk/pull/1183) and it was further tweaked by [filterpaper] in [#1258](https://github.com/zmkfirmware/zmk/pull/1258). This allows any changes in the workflow to be propagated automatically to users, rather than requiring them to make the updates. The build workflow can be customized by the users [using input parameters](https://github.com/zmkfirmware/zmk/blob/main/.github/workflows/build-user-config.yml#L5) if desired.

#### Pre-commit hooks

[joelspadin] added various [pre-commit](https://pre-commit.com/) hooks and added checks to the repo to run them for each commit in [#1651](https://github.com/zmkfirmware/zmk/pull/1651). These hooks and resulting updates standardize formatting across devicetree and other source files, reducing busywork on both contributors and reviewers.

#### Zephyr usage and other refactors

[joelspadin] also contributed a few refactor PRs such as [#1269](https://github.com/zmkfirmware/zmk/pull/1269), [#1255](https://github.com/zmkfirmware/zmk/pull/1255) and [#1803](https://github.com/zmkfirmware/zmk/pull/1803), generally improving code quality and bringing the codebase in line with the latest Zephyr conventions.

[petejohanson] refactored the drivers structure to bring it in line with the current Zephyr conventions for out-of-tree drivers in [#1919](https://github.com/zmkfirmware/zmk/pull/1919).

#### Updated USB polling interval default

USB HID polling interval now defaults to 1 ms, i.e. a 1000Hz polling rate, thanks to [joelspadin]'s tweak in [#1271](https://github.com/zmkfirmware/zmk/pull/1271).

#### Additional display config options

[caksoylar] added a couple configuration options for displays, including a setting to invert display colors in [#1754](https://github.com/zmkfirmware/zmk/pull/1754) and an option to display the battery percentage for the stock status screen in [#1563](https://github.com/zmkfirmware/zmk/pull/1563).

## New Shields

- Eternal keypad [#1136](https://github.com/zmkfirmware/zmk/pull/1136) - [halcyonCorsair](https://github.com/halcyonCorsair)
- nullbits SNAP [#1319](https://github.com/zmkfirmware/zmk/pull/1319) - [jaygreco](https://github.com/jaygreco)
- Aurora Sweep [#1504](https://github.com/zmkfirmware/zmk/pull/1504), Corne [#1520](https://github.com/zmkfirmware/zmk/pull/1520), Lily58 [#1553](https://github.com/zmkfirmware/zmk/pull/1553), Sofle [#1864](https://github.com/zmkfirmware/zmk/pull/1864), and Helix [#1873](https://github.com/zmkfirmware/zmk/pull/1873) - [petejohanson]
- ZMK Uno shield [#1576](https://github.com/zmkfirmware/zmk/pull/1576) - [petejohanson]
- Waterfowl [#1554](https://github.com/zmkfirmware/zmk/pull/1554) - [JW2586](https://github.com/JW2586)
- Kyria Rev 3 [#1627](https://github.com/zmkfirmware/zmk/pull/1627) - [petejohanson]
- Leeloo v2 and Leeloo-Micro [#1762](https://github.com/zmkfirmware/zmk/pull/1762) - [ClicketySplit](https://github.com/ClicketySplit)
- Spaceboards Pancake [#1400](https://github.com/zmkfirmware/zmk/pull/1400) - [jasonhazel](https://github.com/jasonhazel)
- Reviung5 [#1548](https://github.com/zmkfirmware/zmk/pull/1548) - [zblesk](https://github.com/zblesk)

## New Boards

- RP2040 boards, including Sparkfun Pro Micro, Adafruit KB2040 and Seeeduino Xiao RP2040 were added as part of the Zephyr 3.2 upgrade in [#1499](https://github.com/zmkfirmware/zmk/pull/1499) - [petejohanson]
- Puchi BLE [#1445](https://github.com/zmkfirmware/zmk/pull/1445) - [BenRoe](https://github.com/BenRoe)
- nRFMicro 1.3/1.4 (nRF52833) [#912](https://github.com/zmkfirmware/zmk/pull/912) - [pashutk](https://github.com/pashutk)
- nRF5340 DK [#1562](https://github.com/zmkfirmware/zmk/pull/1562) - [joelspadin]
- PillBug [#1530](https://github.com/zmkfirmware/zmk/pull/1530) - [kylemccreery](https://github.com/kylemccreery)
- Preonic Rev 3 [#1575](https://github.com/zmkfirmware/zmk/pull/1575) - [jeromeOlivier](https://github.com/jeromeOlivier)
- Corne-ish Zen v2 [#1498](https://github.com/zmkfirmware/zmk/pull/1498) and v1 [#1593](https://github.com/zmkfirmware/zmk/pull/1593) - [LOWPROKB](https://github.com/LOWPROKB) and [caksoylar]
- Polarity Works CKP family of boards [#1547](https://github.com/zmkfirmware/zmk/pull/1547) - [ReFil]

## Coming Soon!

Some items listed in the last coming soon section are still under active development and other new exciting items are in progress:

- Automatic/simple BLE profile management
- Soft off support for turning the keyboard "off" through firmware
- Improved automatic power management for devices with multiple peripherals, e.g. OLED displays and RGB LEDs
- Caps/Scroll/Num Lock LED support
- Mouse keys
- Wired split support
- More modular approach to external boards/shields, custom code, user keymaps, etc.
- More shields and boards

## Statistics

Some statistics of interest for ZMK:

- GitHub (lifetime stats)
  - 166 Contributors
  - 1256 Closed PRs
  - 1883 Stars
  - 1949 Forks
- Discord Chat
  - 8055 total registered (130% up from last SOTF!)
- Website (last 30 days)
  - 52K page views
  - 4.7K new users

## Sponsorship

While ZMK is an open source project that uses the permissive MIT license, below are opportunities for anyone who would like to show their support to the project financially.

### Open Collective

The ZMK project has an [Open Collective sponsorship](https://opencollective.com/zmkfirmware) that has been going for two and a half years.
This fund helps pay for project costs like domain registration or development of hardware such as the [ZMK Uno shield](https://github.com/zmkfirmware/zmk-uno).
Note that donations to this fund do _not_ pay for the work of any individual contributor directly.

### Contributor sponsorships

Project creator and lead Pete Johanson has a [GitHub sponsorship](https://github.com/sponsors/petejohanson) set up that you can contribute to, in order to directly support his time and efforts in developing and maintaining ZMK.
He has also been traveling full time while focusing on ZMK and keyboard hardware design for more than a year now!
If you are curious, you can check out [his blog post](https://petejohanson.dev/blog/new-journey-2022) on deciding to embark on this adventure, in addition to his thoughts on contributor vs. project sponsorship, and sustainability of open source projects in general.

## Thanks!

As the first person to author a State Of The Firmware post besides Pete, I'd like to take the opportunity to thank him for his efforts on leading and developing ZMK, along with fostering a great community of contributors and users around it.

Also a big thank you to contributors that submit patches and perform reviews, testers that help validate changes, and users that take time out of their day to help out folks with ZMK usage on [Discord channels](https://zmk.dev/community/discord/invite), GitHub issues and other communities.

[okke-formsma]: https://github.com/okke-formsma
[andrewjrae]: https://github.com/andrewjrae
[xudongzheng]: https://github.com/xudongzheng
[nicell]: https://github.com/Nicell
[petejohanson]: https://github.com/petejohanson
[kurtis-lew]: https://github.com/kurtis-lew
[joelspadin]: https://github.com/joelspadin
[dxmh]: https://github.com/dxmh
[caksoylar]: https://github.com/caksoylar
[urob]: https://github.com/urob
[filterpaper]: https://github.com/filterpaper
[ReFil]: https://github.com/ReFil

## Article Updates

- 12/2023: Removed the deprecated `label` property from code snippets.

---

### Blog 2023 11 09 Keymap Editor (`docs/blog/2023-11-09-keymap-editor.mdx`)

---
title: "Community Spotlight Series #1: Keymap Editor"
authors: caksoylar
tags: [keyboards, firmware, community]
---

import ThemedImage from "@theme/ThemedImage";

This blog post is the first in a series of posts where we highlight projects within the ZMK ecosystem that we think are cool and that the users might benefit from knowing about them. We are starting the series with a big one, [Keymap Editor](http://nickcoutsos.github.io/keymap-editor) by [Nick Coutsos](https://github.com/nickcoutsos)!

{/* truncate */}

<ThemedImage
  alt="Shows a screenshot of the Keymap Editor application featuring a graphical layout of the Corne Keyboard with a keymap loaded from the nickcoutsos/keymap-editor-demo-crkbd GitHub repository."
  sources={{
    light:
      require("./assets/2023-11-09-keymap-editor/editor-screenshot-light.png")
        .default,
    dark: require("./assets/2023-11-09-keymap-editor/editor-screenshot-dark.png")
      .default,
  }}
/>

In the rest of the post we leave it to Nick himself to introduce the project, detail his goals and motivation in developing such a tool, and talk about the future of the project. Stay tuned for future installments in the series!

## What is Keymap Editor?

_[Keymap Editor]_ is a web based graphical editor for ZMK keymaps. It provides a visual way to manage the contents of your keymap and if nothing else offers two critical features:

1. Automatic formatting of the keymap file, so that bindings arrays remain readable
2. Searchable behaviors, keycodes, commands, etc, so you won't have to remember if it's `LCTL` or `LCTRL` (I just had to double check myself and I guessed wrong, apparently)

## What can Keymap Editor do?

- Render [devicetree keymaps](/docs/keymaps) using pre-defined, auto-generated, or side-loadable keyboard layouts
- Integrate with a GitHub repo to streamline firmware builds, or FileSystem/Clipboard if you'd still rather build locally
- Edit [combos](/docs/keymaps/combos), [behaviors](/docs/keymaps/behaviors/key-press), [macros](/docs/keymaps/behaviors/macros), [conditional layers](/docs/keymaps/conditional-layers) and [rotary encoder bindings](/docs/keymaps/behaviors/sensor-rotate)
- Manage references: moving a layer or renaming a behavior will look for references throughout your keymap and update them.

But check back regularly, because I update pretty often. A recent significant achievement was enabling [parameterized macros](/docs/keymaps/behaviors/macros#parameterized-macros) and tying it in with my existing parameter type resolution so, yeah, you can finally create that reusable macro combining bluetooth profile selection with RGB backlight colour. Or use it for an actual useful thing, even. _(See also: [Using Parameterized Macros in Keymap Editor](https://github.com/nickcoutsos/keymap-editor/wiki/Using-Parameterized-Macros-in-Keymap-Editor))_

My goals are, broadly:

- **Treat code as a first-class entity:** as long as ZMK keymaps are described in devicetree code then an editor needs to produce readable devicetree code.
- **Flexibly support ZMK features:** use of any ZMK keymap feature should theoretically be achievable within the app. In some cases this can mean more initial setup _(See also: [my thoughts on implementing "autoshift"](https://github.com/nickcoutsos/keymap-editor/wiki/Autoshift-using-ZMK-behaviors))_ but having that foundation makes its easier to add shortcuts and niceties &mdash; something I do quite often now.
- **Don't get in the way of not-yet-supported features:** If a new ZMK feature is released and the app isn't able to add it natively, you can always edit your keymap file directly. While the app may not _recognize_ the new features, further changes through the app should not break your keymap.

## History of Keymap Editor

When I started writing Keymap Editor I had a handwired Dactyl variant running QMK. Manually editing keymap code was fine, but keeping things readable was important to me, and automating that was the best way to ensure consistency. Programmatically modifying source code was beyond me at the time so the first version persisted keymap data in JSON and spat out formatted versions of both the JSON and C keymaps.

After switching to ZMK I added a few more features, I guess as a pandemic project, and then gradually migrated from generating a templated keymap file to manipulating devicetree syntax directly, and that has made a big difference in adding new ZMK features.

## Why am I doing this?

It started out as a useful tool for me. I shared it with the ZMK community and gained a little traction, and then apparently quite a bit of traction &mdash; turns out it's useful for a lot of people.

I'm a software developer because I enjoy building things. Much of my day-to-day work isn't user facing, so seeing how helpful the keymap editor has been for people in the ZMK community is a big motivator to keep improving it.

## Future plans

### Runtime updates

Streamlining the keymap update process is probably top of mind for most users, but that involves a really big _firmware_ feature, and I'm the wrong person to tackle it.

That said, once there's a protocol I would _absolutely_ be down to integrate it as an additional keymap source. Being able to pull data directly from the keyboard should unlock a lot of possibilities and ease some of the constraints imposed by using devicetree code as a medium.

### Simplifying behavior use

I think a lot of people would like to see the concept of behaviors abstracted away for new users and to prompt them with

- _"When the key is tapped..."_,
- _"When the key is held..."_,
- _"When the key is double-tapped..."_ and so on.

Users who are less familiar with ZMK's behaviors and how they are composed may find these prompts to be more intuitive, and their answers could be mapped to an appropriate combination of behaviors managed internally by an editor.

### Uh, what else?

This has been long enough already, if you're looking for a feature I haven't mentioned don't assume I won't add it. Feel free to make feature requests on the GitHub repo, and I'd be happy to discuss it!

## About Me And My Keebs

I like computers and write software. Many in this field enjoy using mechanical
keyboards for their feel or aesthetics, but what piqued my interest was the
Dactyl keyboard. I think, ergonomics aside, I'm more interested in the DIY/maker
aspect than the collecting of keyboards and switches.

So [I made a Dactyl](https://github.com/nickcoutsos/dactyl-flatpacked/), and
then [I made another Dactyl](https://github.com/nickcoutsos/dactyl-deskmount/)
and I made a third Dactyl that isn't interesting enough to photograph, but now
I'm using ZMK so I left room for 18650 cells.

That last Dactyl (with MX browns and a cheap blank XDA keycap set) serves me
well the eight or so hours a day I'll spend at my desk, but I also spend a good
deal of time computing on my couch where I'll use... my Macbook's built-in
keyboard.

In case that's not surprising enough I'll leave you with this: despite all of
the work and testing I've put into the keymap editor project, I've only updated
an actual keymap once in the last year.

Thank you and good night.

## More information

- [Keymap Editor Wiki](https://github.com/nickcoutsos/keymap-editor/wiki)
- [Keymap Editor Discussions](https://github.com/nickcoutsos/keymap-editor/discussions)
- (YouTube video) [Ben Frain's overview of the Keymap Editor](https://www.youtube.com/watch?v=Vy7IoQAe3oU)

[Keymap Editor]: http://nickcoutsos.github.io/keymap-editor

---

### Blog 2023 12 17 Nodefree Config (`docs/blog/2023-12-17-nodefree-config.md`)

---
title: "Community Spotlight Series #2: Node-free Config"
authors: caksoylar
tags: [keyboards, firmware, community]
---

This blog continues our series of posts where we highlight projects within the ZMK ecosystem
that we think are interesting and that the users might benefit from knowing about them. You might
be aware that ZMK configurations in the [Devicetree format](/docs/config#devicetree-files)
use the [C preprocessor](https://en.wikipedia.org/wiki/C_preprocessor) so that directives like
`#define RAISE 2` or `#include <behaviors.dtsi>` can be used in them. In this installment we are
highlighting the [`zmk-nodefree-config` project](https://github.com/urob/zmk-nodefree-config)
by [urob](https://github.com/urob) that contains helper methods that utilizes this fact
for users who prefer editing and maintaining their ZMK config directly using the Devicetree
syntax format.



In the rest of the post we leave it to urob to introduce and explain the motivations of the
project, and various ways it can be used to help maintain ZMK keymaps. Stay tuned for future
installments in the series!

## Overview

Loosely speaking the _nodefree_ repo -- more on the name later -- is a
collection of helper functions that simplify configuring keymap files. Unlike
the graphical keymap editor covered in the [previous spotlight
post](https://zmk.dev/blog/2023/11/09/keymap-editor), it is aimed at users who
edit and maintain directly the source code of their keymap files.

The provided helpers fall into roughly one of three categories:

1. Helpers that eliminate boilerplate, reduce the complexity of keymaps, and improve readability.
2. Helpers that improve portability of "position-based" properties such as combos.
3. Helpers that define international and other unicode characters.

The reminder of this post details each of these three categories.

## Eliminating Boilerplate

In ZMK, keymaps are configured using so-called _Devicetree_ files. Devicetree files
define a collection of nested _nodes_, whereas each node in turn specifies a variety of
_properties_ through which one can customize the keymap.

For example, the following snippet sets up a
[mod-morph](https://zmk.dev/docs/keymaps/behaviors/mod-morph) behavior that sends <kbd>.</kbd>
("dot") when pressed by itself and sends <kbd>:</kbd> ("colon") when shifted:

```dts {6-7} showLineNumbers
/ {
    behaviors {
        dot_colon: dot_colon_behavior {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp DOT>, <&kp COLON>;
            mods = <(MOD_LSFT|MOD_RSFT)>;
        };
    };
};
```

Adding this snippet to the keymap will create a new node `dot_colon_behavior`
(nested underneath the `behaviors` and root `/` nodes), and assigns it four
properties (`compatible`, `#binding-cells`, etc). Here, the crucial properties are `bindings`
and `mods`, which spell out the actual functionality of the new behavior. The rest
of the snippet (including the nested node-structure) is boilerplate.

The idea of the _nodefree_ repo is to use C preprocessor macros to improve
readability by eliminating as much boilerplate as possible. Besides hiding
redundant behavior properties from the user, it also automatically creates and
nests all required behavior nodes, making for a "node-free" and less
error-prone user experience (hence the name of the repo).

For example, using `ZMK_BEHAVIOR`, one of the repo's helper functions, the
above snippet simplifies to:

```dts showLineNumbers
ZMK_BEHAVIOR(dot_colon, mod_morph,
    bindings = <&kp DOT>, <&kp COLON>;
    mods = <(MOD_LSFT|MOD_RSFT)>;
)
```

For complex keymap files, the gains from eliminating boilerplate can be
enormous. To provide a benchmark, consider my [personal
config](https://github.com/urob/zmk-config), which uses the _nodefree_ repo to
create various behaviors, set up combos, and add layers to the keymap. Without
the _nodefree_ helpers, the total size of my keymap would have been 41 kB. Using
the helper macros, the actual size is instead reduced to a more sane 12 kB.[^1]

[^1]:
    To compute the impact on file size, I ran `pcpp
--passthru-unfound-includes` on the `base.keymap` file, comparing two
    variants. First, I ran the pre-processor on the actual file. Second, I ran
    it on a version where I commented out all the _nodefree_ headers,
    preventing any of the helper functions from getting expanded. The
    difference isolates precisely the size gains from eliminating boilerplate,
    which in my ZMK config are especially large due to a vast number of
    behaviors used to add various Unicode characters to my keymap.

## Simplifying "Position-based" Behaviors

In ZMK, there are several features that are position-based. As of today, these
are [combos](/docs/keymaps/combos) and [positional
hold-taps](/docs/keymaps/behaviors/hold-tap#positional-hold-tap-and-hold-trigger-key-positions),
with behaviors like the ["Swapper"](https://github.com/zmkfirmware/zmk/pull/1366) and [Leader
key](https://github.com/zmkfirmware/zmk/pull/1380) currently
developed by [Nick Conway](https://github.com/nickconway) in pull requests also utilizing them.

Configuring these behaviors involves lots of key counting, which can be
cumbersome and error-prone, especially on larger keyboards. It also reduces the
portability of configuration files across keyboards with different layouts.

To facilitate configuring position-based behaviors, the _nodefree_ repo comes
with a community-maintained library of "key-position labels" for a variety of
popular layouts. The idea is to provide a standardized naming convention that
is consistent across different keyboards. For instance, the labels for a 36-key
layout are as follows:

```
    ╭─────────────────────┬─────────────────────╮
    │ LT4 LT3 LT2 LT1 LT0 │ RT0 RT1 RT2 RT3 RT4 │
    │ LM4 LM3 LM2 LM1 LM0 │ RM0 RM1 RM2 RM3 RM4 │
    │ LB4 LB3 LB2 LB1 LB0 │ RB0 RB1 RB2 RB3 RB4 │
    ╰───────╮ LH2 LH1 LH0 │ RH0 RH1 RH2 ╭───────╯
            ╰─────────────┴─────────────╯
```

The labels are all of the following form:

- `L/R` for **L**eft/**R**ight side
- `T/M/B/H` for **T**op/**M**iddle/**B**ottom and t**H**umb row.
- `0/1/2/3/4` for the finger position, counting from the inside to the outside

The library currently contains definitions for 17 physical
layouts, ranging from the tiny [Osprette](https://github.com/smores56/osprette) to the large-ish
[Glove80](https://www.moergo.com/collections/glove80-keyboards).
While some of these layouts contain more keys than others, the idea behind the
library is that keys that for all practical purposes are in the "same" location
share the same label. That is, the 3 rows containing the alpha keys are
always labeled `T/M/B` with `LM1` and `RM1` defining the home position of
the index fingers. For larger boards, the numbers row is always labeled
`N`. For even larger boards, the function key row and the row below `B` are
labeled `C` and `F` (mnemonics for **C**eiling and **F**loor), etc.

Besides sparing the user from counting keys, the library also makes it easy to
port an entire, say, combo configuration from one keyboard to the next by simply
switching layout headers.

## Unicode and International Keycodes

The final category of helpers is targeted at people who wish to type international characters
without switching the input language of their operation system. To do so, the repo comes with
helper functions that can be used to define Unicode behaviors.

In addition, the repo also ships with a community-maintained library of
language-files that define Unicode behaviors for all relevant characters in a
given language. For instance, after loading the German language file, one can
add `&de_ae` to the keymap, which will send <kbd>ä</kbd>/<kbd>Ä</kbd> when pressed or shifted.

## About Me

My path to ZMK and programmable keyboards started in the early pandemic, when I
built a [Katana60](https://geekhack.org/index.php?topic=88719.0) and learned
how to touch-type Colemak. Soon after I purchased a Planck, which turned out
to be the real gateway drug for me.

Committed to making the best out of the Planck's 48 keys, I have since
discovered my love for tinkering with tiny layouts and finding new ways of
[squeezing out](https://xkcd.com/2583/) a bit more ergonomics. Along the way, I
also made the switch from QMK to ZMK, whose "object-oriented" approach to
behaviors I found more appealing for complex keymaps.[^2]

[^2]:
    I am using the term object-oriented somewhat loosely here. What I mean by
    that is the differentiation between abstract behavior classes (such as
    hold-taps) and specific behavior instances that are added to the keymap.
    Allowing to set up multiple, reusable instances of each behavior has been a
    _huge_ time-saver compared to QMK's more limited behavior settings that are
    either global or key-specific.

These days I mostly type on a Corne-ish Zen and are waiting for the day when I
will finally put together the
[Hypergolic](https://github.com/davidphilipbarr/hypergolic) that's been sitting
on my desk for months. My current keymap is designed for 34 keys, making
liberal use of combos and [timerless homerow
mods](https://github.com/urob/zmk-config#timeless-homerow-mods) to make up for
a lack of keys.

---

### Blog 2024 01 05 Zmk Tools (`docs/blog/2024-01-05-zmk-tools.md`)

---
title: "Community Spotlight Series #3: ZMK Tools and ZMK Locale Generator"
authors: caksoylar
tags: [keyboards, firmware, community]
---

This blog continues our series of posts where we highlight projects within the ZMK ecosystem
that we think are interesting and that the users might benefit from knowing about them.

In this installment, we are highlighting two projects (and a bonus one!) from [Joel Spadin](https://github.com/joelspadin),
a member of the core ZMK team.



The first one is [ZMK Tools](#zmk-tools), a handy Visual Studio Code extension to ease working with ZMK configurations, and the second is [ZMK Locale Generator](#zmk-locale-generator), a tool to help users that use non-US English keyboard locales in their operating systems.

In the rest of the post we leave it to Joel to introduce and explain the motivations of his ZMK-related projects.
Stay tuned for future installments in the series!

## ZMK Tools

[ZMK Tools](https://github.com/joelspadin/zmk-tools) is an extension for [Visual Studio Code](https://code.visualstudio.com) that helps with editing a ZMK user config repo or a fork of ZMK. I originally created it to add some code completion in `.keymap` files, but then I realized that with the web version of VS Code, I could also let you set up a user config repo and build firmware, much like the user setup script, except without downloading a single thing.

### User Config Setup in Browser

Here is how you can use ZMK Tools to get started with writing a ZMK keymap entirely within your browser. More detailed instructions can be found on the [ZMK Tools README](https://github.com/joelspadin/zmk-tools/blob/main/README.md).

1. Open the [ZMK config template repo](https://github.com/zmkfirmware/unified-zmk-config-template) on GitHub.
2. Click the **Use this template** button and follow the instructions to create your own repo.
   - If you don't see this button, make sure you're signed in to GitHub first.
   - You can name the repo anything you want, but "zmk-config" is the conventional name.
3. From the GitHub page for your new repo, press <kbd>.</kbd> (period) and it will re-open the repo in github.dev.
4. Press <kbd>Ctrl + P</kbd> and enter the following to install the ZMK Tools extension:
   ```
   ext install spadin.zmk-tools
   ```
5. Press <kbd>Ctrl + Shift + P</kbd> and run the **ZMK: Add Keyboard** command.
6. Follow the prompts to select a keyboard. ZMK Tools will copy the default keymap for that keyboard if you don't already have one, and it will automatically add it to your `build.yaml` file so GitHub will build it for you.

You can then edit your `.keymap` and `.conf` files. Once you're done:

1. Click the **Source Control** tab on the side bar.
2. Hover over the header for the **Changes** list and click the `+` (Stage All Changes) button.
3. Write a commit message and click **Commit & Push** to push your changes to GitHub.

GitHub will start building the new firmware. To check the results:

1. Use your browser's back button to go back to your repo's GitHub page.
2. Click the **Actions** tab at the top of the page.
3. Click the latest build (it should show the commit message you entered earlier). If it's still in progress, wait for it to finish.
4. If the build was successful, go to the **Artifacts** section and click **firmware** to download the firmware. If it failed, check the error and go back to github.dev to fix it.

### Keymap Code Completion

ZMK Tools also provides some basic code completion in `.keymap` files. It will suggest any of ZMK's built-in behaviors inside `bindings` and `sensor-bindings` properties, and it will automatically add the necessary headers.

For example, with the cursor at the end of line 6 in the following keymap...

```dts {6} showLineNumbers
/ {
    keymap {
        compatible = "zmk,keymap";
        default_layer {
            bindings = <
                &
            >;
        };
    };
};
```

...it will suggest things such as `&kp`, `&mo`, etc., and upon entering one, it will recognize that `#include <behaviors.dtsi>` is missing and add it to the top of the keymap:

```dts {1} showLineNumbers
#include <behaviors.dtsi>
/ {
    keymap {
        compatible = "zmk,keymap";
        default_layer {
            bindings = <
                &kp
            >;
        };
    };
};
```

Press space after `&kp`, and it will suggest all of ZMK's key codes. Upon entering one, it will again recognize that `#include <dt-bindings/zmk/keys.h>` is missing and add it too:

```dts {2} showLineNumbers
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
/ {
    keymap {
        compatible = "zmk,keymap";
        default_layer {
            bindings = <
                &kp A
            >;
        };
    };
};
```

This can be very helpful for making sure you spelled key codes correctly and included all the correct headers.

### Future Work

Unfortunately, all the code completion info currently comes from a config file baked into the extension, so it won't pick up any custom behaviors or key code aliases you've defined. I'd like to make that work eventually, but it's a much more difficult problem to solve.

ZMK Tools will discover all the boards/shields from both ZMK and your user config repo. With some recent changes in ZMK to allow pulling in features from other Zephyr modules, it's now possible to use board/shields defined in other repos, but ZMK Tools doesn't know about this yet. I'd like to support this too, but making it work in the web version of the extension will be challenging.

## ZMK Locale Generator

ZMK's key codes follow the [HID specification](https://www.usb.org/hid), and many key codes indicate the _position_ of a key on US keyboard layout, not the key's function. If your operating system is set to a different keyboard locale, then the character each key types won't necessarily line up with the key code name. For example, on a German "QWERTZ" layout, `&kp Y` will type <kbd>Z</kbd> and `&kp Z` will type <kbd>Y</kbd>, so you have to write your layout as if it were QWERTY instead. Other layouts can be even more confusing!

[ZMK Locale Generator](https://github.com/joelspadin/zmk-locale-generator) is another tool I made to help with this. It reads [CLDR keyboard layouts](https://cldr.unicode.org/index/charts/keyboards) and generates `#define`s to alias key codes to names that make sense in other locales. To use it, first go to the [latest release](https://github.com/joelspadin/zmk-locale-generator/releases/latest) and download the header that matches the locale you use. Next, copy it into the same folder as your keymap and `#include` it:

```dts
#include "keys_de.h"

/ {
    ...
};
```

If you open the header file in a text editor, you'll see that it contains many of the standard ZMK key codes, except they are prefixed by the locale code. Depending on the locale, it may also define key codes for special characters specific to that locale, e.g. `DE_A_UMLAUT` for "ä" and `DE_SZ` for "ß". If you use these in your keymap, then ZMK will send the correct key codes to type those characters.

```dts
#include "keys_de.h"

/ {
    keymap {
        compatible = "zmk,keymap";
        default_layer {
            bindings = <
                &kp DE_Q &kp DE_W &kp DE_E &kp DE_R &kp DE_T &kp DE_Z ...
            >;
        };
    }
};
```

I should note that, as a native English speaker and typer, I don't use any of this myself! I just saw that many people were asking for help with this, and I realized I could automate a solution. If you find something that isn't generated correctly, please [file an issue](https://github.com/joelspadin/zmk-locale-generator/issues) or PR a fix on GitHub.

## Keyboard Latency Testing

The last project I want to mention is a tool for testing keyboard latency. It requires only a Raspberry Pi, an optocoupler IC, a resistor, and some wire. If you've ever wondered how ZMK's latency compares to other keyboards, you can [check the results here](https://github.com/joelspadin/keyboard-latency-tester/blob/main/results/chart.ipynb)!

I don't have a very large collection of keyboards though, so the data is pretty limited so far. If you want to try it on your own keyboard, see the instructions on the [keyboard latency tester README](https://github.com/joelspadin/keyboard-latency-tester), and please send me a PR with your results!

## About Me

I got a degree in electrical engineering but promptly became a software engineer instead. I still like tinkering with electronics though, so I discovered ZMK when I was making wireless macropad with a nice!nano, and I became a regular contributor after that. I use mostly larger keyboards with standard layouts and rarely use anything more complicated than momentary layers, so I've mostly focused on improving core features and tooling.

The keyboards I regularly use are a Ducky One 2 TKL that I leave at work, a Freebird TKL[^1], a custom [wireless numpad](https://github.com/joelspadin/NumBLE), and a Yamaha CP4.

[^1] Running QMK, but I have designs to make a wireless PCB for it someday...

---

### Blog 2024 02 09 Zephyr 3 5 (`docs/blog/2024-02-09-zephyr-3-5.md`)

---
title: "Zephyr 3.5 Update"
authors: petejohanson
tags: [firmware, zephyr, core]
---

I'm happy to announce that we have completed the [work](https://github.com/zmkfirmware/zmk/pull/1995) to upgrade ZMK to [Zephyr 3.5](https://docs.zephyrproject.org/3.5.0/releases/release-notes-3.5.html)!



[petejohanson] did the upgrade work to adjust ZMK for the Zephyr changes:

- Add `west flash` support to all UF2 capable boards.
- Adjust for LVGL DTS/Kconfig changes
- Zephyr core API changes, including `CONTAINER_OF` work API changes, init priority/callback, and others

## Getting The Changes

Use the following steps to update to the latest tooling in order to properly use the new ZMK changes:

### User Config Repositories Using GitHub Actions

Existing user config repositories using Github Actions to build will pull down Zephyr 3.5 automatically, however if you created your user config a while ago, you may need to update it to reference our shared build configuration to leverage the correct Docker image.

1.  Replace the contents of your `.github/workflows/build.yml` with:

    ```yaml
    on: [push, pull_request, workflow_dispatch]

    jobs:
      build:
        uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@main
    ```

1.  If it doesn't exist already, add a new file to your repository named `build.yaml`:

    ```yaml
    # This file generates the GitHub Actions matrix
    # For simple board + shield combinations, add them
    # to the top level board and shield arrays, for more
    # control, add individual board + shield combinations to
    # the `include` property, e.g:
    #
    # board: [ "nice_nano_v2" ]
    # shield: [ "corne_left", "corne_right" ]
    # include:
    #   - board: bdn9_rev2
    #   - board: nice_nano_v2
    #     shield: reviung41
    #
    ---
    ```

and then update it as appropriate to build the right shields/boards for your configuration.

### VS Code & Docker (Dev Container)

If you build locally using VS Code & Docker then:

- Pull the latest ZMK `main` with `git pull` for your ZMK checkout
- Reload the project
- If you are prompted to rebuild the remote container, click `Rebuild`
- Otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.

Once the container has rebuilt, VS Code will be running the 3.5 Docker image.

### Local Host Development

The following steps will get you building ZMK locally against Zephyr 3.5:

- Run the updated [toolchain installation](/docs/development/local-toolchain/setup) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- Install the latest version of `west` by running `pip3 install --user --update west`.
- Pull the latest ZMK `main` with `git pull` for your ZMK checkout
- Run `west update` to pull the updated Zephyr version and its dependencies

From there, you should be ready to build as normal!

## Board/Shield Changes

The following changes have [already been completed](https://github.com/zmkfirmware/zmk/pull/1995/commits) for all boards/shields in ZMK `main` branch. For existing or new PRs, or out of tree boards, the following changes are necessary to properly work with the latest changes.

### West Flash Support

If you have a custom board for a target that has a UF2 supporting bootloader, you can easily add support for
flashing via `west flash`. Note that using `west flash` isn't mandatory, it is merely a convenient way to automate copying to the mass storage device, which you can continue to do manually.
To add support, add a line to your board's `board.cmake` file like so:

```
include(${ZEPHYR_BASE}/boards/common/uf2.board.cmake)
```

### LVGL DTS/Kconfig Changes

Two items were changed for LVGL use for displays that may need adjusting for custom shields:

#### DPI Kconfig Rename

The old `LV_Z_DPI` Kconfig symbol was promoted to a Kconfig in upstream LVGL, and is now named `LV_DPI_DEF`. You
will need to replace this symbol in your board/shield's `Kconfig.defconfig` file.

#### SSD1306 OLED Inverse Refactor

Inverting black/white pixels has moved out of the Kconfig system and into a new DTS property. If you have a custom
shield that uses an SSD1306, you should:

- Remove any override for the `SSD1306_REVERSE_MODE` from your Kconfig files.
- Add the new `inversion-on;` boolean property to the SSD1306 node in your devicetree file.

### Maxim max17048 Sensor Driver

Upstream Zephyr has added a driver for the max17048 fuel gauge, but using the new [fuel gauge API](https://docs.zephyrproject.org/3.5.0/hardware/peripherals/fuel_gauge.html) that ZMK
does not yet consume. To avoid a conflict with the new upstream and keep our existing sensor driver, our driver has been renamed to be namespaced with a ZMK prefix. The following changes are needed for any boards using the driver:

- Change the `compatible` value for the node to be `zmk,maxim-17048`, e.g. `compatible = "zmk,maxim-max17048";`.
- If enabling the driver explicitly via Kconfig, rename `MAX17048` to the new `ZMK_MAX17048` in your `Kconfig.defconfig` or `<board>_defconfig` files.

## Upcoming Changes

Moving to Zephyr 3.5 will unblock several exciting efforts that were dependent on that Zephyr release.

### BLE Stability Improvements

Many users have reported various BLE issues with some hardware combinations, including challenges with updated
Intel drivers, and macOS general stability problems. The Zephyr 3.5 release includes many fixes for the BT host and controller portions that, combined with some small upcoming ZMK changes, have been reported to completely resolve previous issues. Further focused testing will immediately commence to fully verify the ZMK changes before
making them the default.

If you'd like to test those changes, enable `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN=y` for your builds.

### Pointer Integration

The Zephyr 3.5 release includes a new [input subsystem](https://docs.zephyrproject.org/3.5.0/services/input/index.html) that we will be leveraging for our upcoming pointer support. The open PR for that work is now unblocked and further testing and code review will begin to work on getting that feature integrated into ZMK as well.

### Power Domains

Several power domain related changes are now available as well, which were a necessity for continued work on the improved peripheral power handling that's planned to supersede the existing "VCC cutoff" code that currently exists but causes problems for builds that include multiple powered peripherals like Displays + RGB.

## Thanks!

Thanks to all the testers who have helped verify ZMK functionality on the newer Zephyr version.

[petejohanson]: https://github.com/petejohanson

---

### Blog 2024 11 11 Zmk Studio Mvp Ga (`docs/blog/2024-11-11-zmk-studio-mvp-ga.mdx`)

---
title: "ZMK Studio MVP General Availability"
authors: petejohanson
tags: [firmware, studio, core]
---

import Metadata from "@site/src/data/hardware-metadata.json";

import ThemedImage from "@theme/ThemedImage";

import studioApp from "./assets/2024-11-11-zmk-studio-mvp-ga/zmk-studio-screenshot.png";
import studioAppDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/zmk-studio-screenshot-dark-mode.png";

import niceAndTyperactive from "./assets/2024-11-11-zmk-studio-mvp-ga/niceandtyperactive.png";
import niceAndTyperactiveDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/niceandtyperactive-dark-mode.png";

import kinesis from "./assets/2024-11-11-zmk-studio-mvp-ga/kinesis.png";
import kinesisDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/kinesis-dark-mode.png";

import cannonKeys from "./assets/2024-11-11-zmk-studio-mvp-ga/cannonkeys.png";
import cannonKeysDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/cannonkeys-dark-mode.png";

import keychron from "./assets/2024-11-11-zmk-studio-mvp-ga/keychron.png";
import keychronDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/keychron-dark-mode.png";

import littleKeyboards from "./assets/2024-11-11-zmk-studio-mvp-ga/littlekeyboards.avif";
import littleKeyboardsDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/littlekeyboards-dark-mode.avif";

import keebmaker from "./assets/2024-11-11-zmk-studio-mvp-ga/keebmaker.png";
import keebmakerDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/keebmaker-dark-mode.png";

import keebio from "./assets/2024-11-11-zmk-studio-mvp-ga/keebio.avif";

import mode from "./assets/2024-11-11-zmk-studio-mvp-ga/mode.png";
import modeDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/mode-dark-mode.png";

import phaseByte from "./assets/2024-11-11-zmk-studio-mvp-ga/phasebyte.png";

import deskHero from "./assets/2024-11-11-zmk-studio-mvp-ga/deskhero.webp";
import deskHeroDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/deskhero-dark-mode.webp";

import mechlovin from "./assets/2024-11-11-zmk-studio-mvp-ga/mechlovin.png";
import mechlovinDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/mechlovin-dark-mode.png";

import splitkb from "./assets/2024-11-11-zmk-studio-mvp-ga/splitkb.png";
import splitkbDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/splitkb-dark-mode.png";

import keycapsss from "./assets/2024-11-11-zmk-studio-mvp-ga/keycapsss.png";
import keycapsssDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/keycapsss-dark-mode.png";

import mekibo from "./assets/2024-11-11-zmk-studio-mvp-ga/mekibo.png";
import mekiboDarkMode from "./assets/2024-11-11-zmk-studio-mvp-ga/mekibo-dark-mode.png";

I'm delighted to announce that after many months of work, the ZMK Studio Minimum Viable Product (MVP) has reached General Availability (GA) status. I'm very excited with the feature set and foundation we've gotten into this first release, and equally excited for the upcoming work to add even more features.

ZMK Studio provides runtime keymap functionality for ZMK powered devices, allowing you to make changes without the need to flash new firmware. It is a combination of major firmware enhancements paired with a new frontend application for interacting with your keyboard.

The app is available for compatible browsers, and as an installable desktop application for Linux, macOS, and Windows.

<ThemedImage
  alt="ZMK Studio Application"
  sources={{
    light: studioApp,
    dark: studioAppDarkMode,
  }}
/>

{/* truncate */}

## Gratitude

I'm extremely grateful for everyone who has helped make this release possible, which includes testers, code reviewers/contributors, including [Nick-Munnich], [joelspadin], and [caksoylar], as well as the collection of vendors who have directly sponsored my work on the project, allowing it to come to fruition _much_ faster than it would have taken me to do in my spare time.

### Vendor Sponsors

The following sponsors were instrumental in making ZMK Studio possible by sponsoring my work on the firmware and frontend application:

{(function() {
const sponsors = [
{
level: "Platinum",
vendors: [
{
name: "nice!keyboards / typeractive",
img: niceAndTyperactive,
darkModeImg: niceAndTyperactiveDarkMode,
url: "https://typeractive.xyz/",
},
{
name: "Kinesis",
img: kinesis,
darkModeImg: kinesisDarkMode,
url: "https://kinesis-ergo.com/",
},
],
},
{
level: "Gold+",
vendors: [
{
name: "CannonKeys",
img: cannonKeys,
darkModeImg: cannonKeysDarkMode,
url: "https://cannonkeys.com/",
},
{
name: "Keychron",
img: keychron,
darkModeImg: keychronDarkMode,
url: "https://keychron.com/",
},
],
},
{
level: "Gold",
vendors: [
{
name: "Little Keyboards",
img: littleKeyboards,
darkModeImg: littleKeyboardsDarkMode,
url: "https://littlekeyboards.com/",
},
{
name: "Keebmaker",
img: keebmaker,
darkModeImg: keebmakerDarkMode,
url: "https://keebmaker.com/",
},
],
},
{
level: "Silver",
vendors: [
{
name: "keeb.io",
img: keebio,
url: "https://keeb.io/",
},
{
name: "Mode Designs",
img: mode,
darkModeImg: modeDarkMode,
url: "https://modedesigns.com/",
},
],
},
{
level: "Bronze",
vendors: [
{
name: "deskhero",
img: deskHero,
darkModeImg: deskHeroDarkMode,
url: "https://deskhero.ca/",
},
{
name: "PhaseByte",
img: phaseByte,
url: "https://phasebyte.com/",
},
{
name: "Mechlovin'",
img: mechlovin,
darkModeImg: mechlovinDarkMode,
url: "https://mechlovin.studio/",
},
],
},
{
level: "Additional",
vendors: [
{
name: "splitkb.com",
img: splitkb,
darkModeImg: splitkbDarkMode,
url: "https://splitkb.com/",
},
{
name: "keycapsss",
img: keycapsss,
darkModeImg: keycapsssDarkMode,
url: "https://keycapsss.com/",
},
{
name: "mekibo",
img: mekibo,
darkModeImg: mekiboDarkMode,
url: "https://mekibo.com/",
},
],
},
];

return sponsors.map(g => {
return (

<div key={g.level}>
  <h4>{g.level} Level</h4>
  <div
    style={{
      display: "grid",
      gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
      placeItems: "center",
      gap: "2em 2em",
    }}
  >
    {g.vendors.map((v) => {
      return (
        <a href={v.url} key={v.url}>
          <ThemedImage
            alt={v.name}
            sources={{
              light: v.img,
              dark: v.darkModeImg || v.img,
            }}
          />
        </a>
      );
    })}
  </div>
</div>
); }); }())}

## Using ZMK Studio

:::note

For keyboard maintainers, additional changes are needed to add metadata about the keyboard's physical layouts in order to use ZMK Studio. See the documentation on [physical layouts](/docs/hardware-integration/physical-layouts#optional-keys-property) for more information.
:::

To use ZMK Studio, you need to have a firmware for your keyboard with the feature enabled, as well as a small keymap change to add an unlock key. See [Building with ZMK Studio](/docs/features/studio#building) and [ZMK Studio keymap changes](/docs/features/studio#keymap-changes) for more details.

See [Accessing ZMK Studio](/docs/features/studio#accessing-zmk-studio) for more details on the browser and desktop options for using the ZMK Studio application.
Note: Depending on your browser, operating system, and app vs website selection, you may be limited to connecting over USB. See the previous link for details.

## In-Tree Supported Keyboards

The following in-tree keyboards have been updated to support ZMK Studio.
If your keyboard is supported by an external module/config, check with the maintainer to see
if support has been added.

{(function() {
return (

<ul>
  {Metadata.filter((m) => (m.features || []).includes("studio")).map((m) => {
    return <li key={m.id}>{m.name}</li>;
  })}
</ul>
); }())}

## Next Steps

The ZMK Studio MVP is truly that, a "minimum viable product". It establishes the core firmware functionality and frontend application infrastructure in order to validate the technology, prove the approaches, and get initial core features into user's hands as early as possible.

There are many ZMK features that are not yet possible to update in ZMK Studio. For a better understanding of where ZMK Studio is today, and where we are headed, please have a look at the [ZMK Studio capabilities](/docs/features/studio#capabilities) table.

[petejohanson]: https://github.com/petejohanson
[caksoylar]: https://github.com/caksoylar
[Nick-Munnich]: https://github.com/Nick-Munnich
[joelspadin]: https://github.com/joelspadin

---

### Blog 2025 03 20 Contributor Sync 1 (`docs/blog/2025-03-20-contributor-sync-1.md`)

---
title: Contributor Sync February 2025
authors: nmunnich
tags: [sync, keyboards, firmware, oss, ble]
---

On the 26th of February some of the ZMK contributors got together for a contributor sync. In the interest of transparency, it was decided that meeting notes from the sync would be published on the ZMK webpage.



The following contributors were able to attend the sync:

- [petejohanson]
- [caksoylar]
- [nmunnich]

[joelspadin] was also able to comment on the meeting notes after the meeting. The notes have been adjusted accordingly.

## Meeting Notes

### Meeting Purpose

- Purpose of meeting is to sync up, discuss blockers, talk about big picture work.
- Meeting notes should be published on the webpage, while the meetings themselves are private.

### Pete's Summary

- Starting a new job Mon. 2025-03-03. Reduced time for ZMK, but more freedom to work on what Pete wants/ZMK needs.
- Two main ZMK-contracts need to be finished:
  - Pointer work. Basically good to go, just needs 2721 to be tested and verified as as fix. Then ZMK release v0.2 will happen. (This has already happened at time of publishing these notes)
  - Wired split work. Separated into parts:
    - UART full duplex, PR 2766. What's there seems very solid, barring some final touchups. Should have v0.2 released prior to merging this. (This has already happened at time of publishing these notes)
    - Half-duplex with a single wire. To be a separate PR, but framework should be good for when that happens. To work on after UART full duplex is merged.
    - Runtime dynamic switching between wired and wireless split using a GPIO as sense pin for wired.
- After contracts finish, look at versioning again. Better documentation, defaults, workflow improvements, etc.
- More Studio work is also on the cards. Studio work will continue, but slowly.
  - Hoping for help from community around UI/UX, in particular clarity around saving edited keymaps would be good.
  - Want to add runtime property modification next, allowing for e.g. changing hold-tap flavors in Studio.
- Investigate zmk-cli a bit; Pete has not investigated this enough and wants to get a solid overview of it.

### Nick's summary:

- Trying to make combo PR feasible (allows combo triggers inside behaviors, enabling e.g. tap-only combos)
  - Adding `behavior_state_changed` event has a precursor (see 573, 532)
    - Requires deleting ZMK_BEHAVIOR_TRANSPARENT
    - Requires some refactoring/investigating of sensors/encoders, as they rely on ZMK_BEHAVIOR_TRANSPARENT
- Locking PR, needs review. Same with 2811.
- RP2040-Zero board definition matches upstream, acts as a temporary board definition until we update Zephyr (introduced to Zephyr in 4.1)
- Parameterised mod-morph and tap-dance
  - Question: Do we move hold-tap over to the same system?
    - Plan after discussion is to add a second hold-tap `compatible` and move over to it, to avoid breaking existing keymaps.
- Config index page and "Customising ZMK" docs overhaul, making them more visible and easier to understand.

### Cem's summary:

- Keymap recipes has been shelved for the time being. Prefer to have better examples in the behavior pages themselves, e.g. timeless homerow mods as an example under hold-tap.
- Hold-tap page needs overhauling. (This has already happened at time of publishing these notes)
- New behavior guide needs overhauling and should be aimed towards modules.
- 2758 needs a review and merge. (This has already happened at time of publishing these notes)

### Other Discussion:

#### ZMK-CLI:

- It is likely time to rip the bandaid off and switch over to it, getting rid of the old setup script.
- CLI has some flaws that may need improvement (e.g. around modules), but is already much better than the existing script.
- New shield guide should be adjusted to take advantage of ZMK-CLI's templates.
- Joel requests issues be opened talking about points to improve on.

#### zmk-locales:

- Should be moved under zmkfirmware organisation
  - Should do the same for zmk-locales-generator, and maybe figure out a way to set up a GH action that makes it send PRs to zmk-locales.
    - Move first, make these adjustments afterwards
  - Documenting them in list of keycodes page with a locale selector would be ideal, but complex
    - Refer people to headers directly in the docs for the time being
- Runtime locale changing would be preferred, but would also be a lot of work.
  - zmk-locales could be viewed as a (semi-permanent) bandaid until such a time

#### Modules:

- Need to find the right balance between overloading contributers and overfragmentation
- ZMK module collection https://github.com/nmunnich/zmk-module-collection seems like a good way to go forward
  - Main concerns:
    - Need to handle breaking changes elegantly, informing users _which_ module is to blame for the new version not working
    - Need to have tests for modules (can assume keyboard works if it builds, what about other stuff?)
    - Maintainability
      - Hopefully have more volunteers from the community to look at modules and verify them, should be much easier than reviewing PRs
      - Automated tests
      - Add an "experimental" group tag for unverified modules
  - Despite concerns, plenty of upsides. Seems like the best way forwards, and it is worth putting more time into.
  - Some upsides:
    - Very easy for users to add modules, particularly ones that depend on other modules
    - Automated testing and notification for module owners
    - Better discoverability
    - Versioning parity between the collection and ZMK itself to allow for easy upgrades
  - Likely will want to introduce "RC" releases, to allow module owners the time to fix any issues with their module before an actual release
  - Likely will only accept keyboards and drivers initially, until better tooling and systems are in place, given they are easier to validate

#### Minor notes:

- Documenting BLE tests would be useful
- Need new display and lighting systems at some point
- Zephyr upgrade was not discussed in this meeting
- Would be good to give some extra priority to PRs from new contributors, to help motivate them.
- Would be nice to have another SOTF at some point. Doing a full commit overview is unfeasible at this point, but touching on the major issues seems doable.

[petejohanson]: https://github.com/petejohanson
[joelspadin]: https://github.com/joelspadin
[caksoylar]: https://github.com/caksoylar
[nmunnich]: https://github.com/nmunnich

---

### Blog 2025 06 20 Pinned Zmk (`docs/blog/2025-06-20-pinned-zmk.md`)

---
title: "Pin your ZMK version"
authors: nmunnich
tags: [keyboards, firmware, oss, versioning]
---

ZMK is preparing for a big breaking change. As such, users should consider pinning their ZMK version. Read on to find out more about our versioning system and how you can pin your ZMK version.



## ZMK Versioning -- The 'what' and the 'why'

Around 6 months ago, ZMK began using versioning. Prior to this point, all ZMK users ran their firmware off of `main`. This meant that users would always get the latest and greatest changes whenever they built new firmware, but on occasion there would be breaking changes in `main` that resulted in user confusion.

Avoiding this user confusion became a limiting factor when it came to making changes. [Zephyr](https://www.zephyrproject.org/), the RTOS that ZMK is based off of, made some breaking changes in version 3.6. Hence ZMK has been stuck on Zephyr 3.5 for over 1.5 years, while Zephyr has already moved on to version 4.1. Asking when ZMK will upgrade is a frequent question we get asked, as the new version comes with plenty of improvements and drivers for newer, exciting hardware.

With versioning now in place, users can pin their ZMK configuration to a particular version. This prevents sudden breaking changes from happening without user opt-in and frees ZMK contributors to make breaking changes more frequently, as is necessary.

### ZMK's Versioning System

ZMK follows the [Semantic Versioning](https://semver.org/) standard. Versions are `vX.Y.Z`. ZMK uses release branches and tags: Every major or minor version bump, a new branch and tag are created. The tag points to the latest patch version. For example, when releasing `v0.2.0` the branch `v0.2-branch` and tag `v0.2` were created. When patches are made, such as for `v0.2.1`, the branch and tag are updated to point to `v0.2.1` instead of `v0.2.0`. This way, users can receive non-breaking security and bug fixes without needing to bump their version.

Whenever ZMK releases a major version with breaking changes, a proper changelog will be provided detailing the major changes from the previous version, along with clear instructions on how to upgrade and fix any breaking changes.

## To Pin or Not To Pin

For casual users of ZMK, pinning your version is recommended. Unpinned ZMK configurations are mainly suitable for those more heavily involved or interested in ZMK development. Moving forward, if you are on the `main` branch of ZMK you will be considered a (highly valued) tester, rather than a user.

To ensure that you still update your ZMK version regularly and get the latest and greatest features that interest you, I would recommend setting up notifications about new releases. You can do so on the [GitHub repository](https://github.com/zmkfirmware/zmk):




## How To Pin Your ZMK Version

:::info
If you created your ZMK config on or after `2025-07-03`, then you are already pinned to what was the most recent ZMK version at the time of creation.
:::

To pin your ZMK version, you need to edit two files:

### West Manifest

You need to edit the `config/west.yml` file in your `zmk-config` repository. The `config` folder also contains your keymaps, and is located next to the `build.yaml` file defining which keyboards are built in the first place.

Your `west.yml` should look something like this (example without [modules](/docs/features/modules) shown for simplicity):

```yaml {8} showLineNumbers
manifest:
  remotes:
    - name: zmkfirmware
      url-base: https://github.com/zmkfirmware
  projects:
    - name: zmk
      remote: zmkfirmware
      revision: main
      import: app/west.yml
  self:
    path: config
```

Simply edit the line `revision` to point to the release tag instead of `main`. At the time of writing, the latest release is `v0.2`, making your `west.yml` look something like this:

```yaml {8} showLineNumbers
manifest:
  remotes:
    - name: zmkfirmware
      url-base: https://github.com/zmkfirmware
  projects:
    - name: zmk
      remote: zmkfirmware
      revision: v0.2
      import: app/west.yml
  self:
    path: config
```

See that `main` has changed to `v0.2` on the 8th line.

### GitHub Action

You need to edit `.github/workflows/build.yml` in your `zmk-config`. Again, we are simply replacing `main` with the latest release tag (`v0.2` at time of writing):

```yaml {6} showLineNumbers
name: Build ZMK firmware
on: [push, pull_request, workflow_dispatch]

jobs:
  build:
    uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@main
```

Your file's contents should look identical to the above. Replace with the below:

```yaml {6} showLineNumbers
name: Build ZMK firmware
on: [push, pull_request, workflow_dispatch]

jobs:
  build:
    uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@v0.2
```

The change occurred on the last line.

### Version Bumping

If in the future you wish to upgrade your ZMK's version, simply change the tag in both of these files. For example, change from `v0.1` to `v0.2`.

[nmunnich]: https://github.com/nmunnich

---

### Blog 2025 12 09 Zephyr 4 1 (`docs/blog/2025-12-09-zephyr-4-1.md`)

---
title: "Zephyr 4.1 Update"
authors: nmunnich
tags: [firmware, zephyr, core]
---

We're happy to announce that after a long wait, ZMK's `main` branch is now running [Zephyr 4.1](https://docs.zephyrproject.org/latest/releases/release-notes-4.1.html)!



:::note

The following changes have been made since this blog post was originally published:

- The [move to `zmk` variants by default](https://github.com/zmkfirmware/zmk/pull/3145) for ZMK boards was documented.

:::

Zephyr 4.1 is a large leap forward from our previous version of 3.5, featuring:

- Support for lots of new SoCs, boards, and shields, such as the WCH CH32V003, the Raspberry Pi Pico 2, and [many many more](https://docs.zephyrproject.org/4.1.0/boards/index.html#boards).
- Hardware Model V2 (HWMv2), providing better support for SoCs which have multiple cores on the same chip, such as the nRF5340.
- Lots of new drivers for chips such as the nPM1300.

This was a very large undertaking, and a big congratulations and thanks to [petejohanson] is due for all of his hard work in making this possible.

After we have verified functionality, ironed out any major bugs, and given any third party module maintainers time to update their code, we will be releasing ZMK `v0.4` as the first version to include this Zephyr version update.

**All** out-of-tree keyboards will need to be updated to use HWMv2. If you maintain such a keyboard, you can find instructions on doing so [below](#moving-to-hwmv2).

## Switching To the Previous Release

Some readers may be coming here because the above changes have _broken_ their builds. ZMK uses a formal release process that allows users to build against a specific release, instead of following the unstable/development version found in the `main` branch. However, since this is a relatively new process, many users may still be tracking `main`.

For all users, except those willing to accept periodic breaks they need to track down, we highly recommend [pinning your ZMK version](./2025-06-20-pinned-zmk.md). Doing so will allow you to avoid any issues related to the Zephyr upgrade, and allow you to choose when to upgrade to a future ZMK release when you are ready.

## Getting The Changes

### User Config Repositories Using GitHub Actions

Existing user repositories that have `revision: main` in their `west.yml` will receive the changes automatically when rebuilding.

Any user repositories created on or after `2025-07-03` are currently pinned to the most recent ZMK release. You will need to change over to `main` to get these changes immediately, or wait for `v0.4` and upgrade your version then.
See the recent [blog post on pinning ZMK versions](./2025-06-20-pinned-zmk.md) for more information.

Likewise, if you are currently on `main` but do not wish to upgrade yet, please pin your ZMK version to `v0.3` by following the instructions in said blog post.

### VS Code & Docker (Dev Container)

If you build locally using VS Code & Docker then:

- Pull the latest ZMK `main` with `git pull` for your ZMK checkout
- Reload the project
- If you are prompted to rebuild the remote container, click `Rebuild`
- Otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.

Once the container has rebuilt, VS Code will be running the 4.1 Docker image.

### Local Host Development

The following steps will get you building ZMK locally against Zephyr 4.1:

- Run the updated [toolchain installation](/docs/development/local-toolchain/setup) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- Install the latest version of `west` by running `pip3 install --upgrade west` in a virtual environment, or add the `--user` flag if `west` is installed globally: `pip3 install --user --upgrade west`.
- Pull the latest ZMK `main` with `git pull` for your ZMK checkout
- Run `west update` to pull the updated Zephyr version and its dependencies
- Run `west packages pip --install` to update required Python packages

From there, you should be ready to build as normal!

## ZMK Board Variant

The new Zephyr version includes a [standard mechanism for extending boards](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#board-extensions) found in Zephyr (like the Seeeduino XIAO, or Raspberry Pi Pico) with new variants that include additional configuration/defaults that are relevant to the application. This also means the original "stock" board can still be used as is, if consumers don't want to use those customizations for any reason.

As a result, all board definitions found in the ZMK tree now must be used with a `zmk` variant. For example, the previous board ID of `adv360pro_left` would now be `adv360pro_left/nrf52840/zmk`, which can be shortened to `adv360pro_left//zmk` since there's only one SoC on the board.

## Board Revisions

As part of this change, ZMK is now using board/shield revisions, rather than duplicate board/shield definitions. This means that instead of having e.g. `nice_nano`, and `nice_nano_v2`, we only have `nice_nano`, which by default points to the `2.0.0` revision. To point to the original Nice!Nano V1, you would need to use `nice_nano@1.0.0` where you would have previously used `nice_nano`. Of course, you could also put `nice_nano@2.0.0` if you wished to make that explicit, instead of merely replacing `nice_nano_v2` with `nice_nano`. Some boards, such as the `nrfmicro`, also have additional _board qualifiers_ such as the choice between multiple SoCs. Board qualifiers must always be specified, and do not have defaults. See [Zephyr's overview](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#board-terminology) for more information on board qualifiers. The below table provides an overview of some of the differences in in-tree boards we have in ZMK, and how they are selected in the new build system. The shorthand shows the minimum needed to build with a specific board, taking into account defaults.

- nice!nano (`nice_nano`)
  - `nice_nano` -> `nice_nano@1.0.0//zmk` (short: `nice_nano@1//zmk`)
  - `nice_nano_v2` -> `nice_nano@2.0.0//zmk` (short: `nice_nano//zmk`)
- nRFMicro (`nrfmicro/nrf52840`)
  - `nrfmicro_11` -> `nrfmicro@1.1.0/nrf52840/zmk` (short: `nrfmicro@1.1/nrf52840/zmk`)
  - `nrfmicro_11_flipped` -> `nrfmicro@1.1.0/nrf52840/flipped_zmk` (short: `nrfmicro@1.1/nrf52840/flipped_zmk`)
  - `nrfmicro_13` -> `nrfmicro@1.3.0/nrf52840/zmk` (short: `nrfmicro/nrf52840/zmk`)
  - `nrfmicro_13_52833` -> `nrfmicro@1.3.0/nrf52833/zmk` (short: `nrfmicro/nrf52833/zmk`)
- Mikoto (`mikoto`)
  - `mikoto` -> `mikoto@5.20.0//zmk` (short: `mikoto//zmk`)
  - `mikoto@6.1` -> `mikoto@6.1.0//zmk` (short: `mikoto@6//zmk`)
  - `mikoto@7.2` -> `mikoto@7.2.0//zmk` (short: `mikoto@7//zmk`)
- XIAO RP2040 (`xiao_rp2040`)
  - `seeeduino_xiao_rp2040` -> `xiao_rp2040//zmk`
- XIAO nRF52840/BLE (`xiao_ble`)
  - `seeeduino_xiao_ble` -> `xiao_ble//zmk`
- BT60 (`bt60`)
  - `bt60_v1` -> `bt60@1.0.0//zmk`
  - `bt60_v2` -> `bt60@2.0.0//zmk`
  - `bt60_hs` -> `bt60_hs//zmk`
- Planck (`planck`)
  - `planck_rev6` -> `planck//zmk`
- BDN9 (`bdn9`)
  - `bdn9_rev2` -> `bdn9//zmk`
- Ferris Rev2 (`ferris`)
  - `ferris_rev02` -> `ferris@2.0.0//zmk` (short: `ferris//zmk`)
- Corne-ish Zen (`corneish_zen`)
  - `corneish_zen_v2_left` -> `corneish_zen_left@2.0.0//zmk` (short: `corneish_zen_left//zmk`)
  - `corneish_zen_v2_right` -> `corneish_zen_right@2.0.0//zmk` (short: `corneish_zen_right//zmk`)
  - `corneish_zen_v1_left` -> `corneish_zen_left@1.0.0//zmk` (short: `corneish_zen_left@1//zmk`)
  - `corneish_zen_v1_right` -> `corneish_zen_right@1.0.0//zmk` (short: `corneish_zen_right@1//zmk`)

The boards above are those which have changed in ZMK's tree, with the addition of the very popular XIAO series. For other boards in Zephyr's tree, please refer to the Zephyr documentation or source files directly.

## Moving To HWMv2

The move to HWMv2 has already been completed for all boards in ZMK's `main` branch. For out-of-tree boards, those need to be converted using either an automated script provided by the Zephyr project, or manually.

:::note

This _only_ applies to boards. Shields do not need any changes to account for the move to HWMv2.

:::

### Board Upgrade Script

The Zephyr project provides a script to automate updating a board to HWMv2. To run the script, you'll need to have a local [development setup](/docs/development/local-toolchain/setup/) ready to use. You'll need to ensure you've updated to the new ZMK version and run `west update` to ensure you've got the new Zephyr version with the script available.

:::note

The board upgrade script does _not_ work well with split designs. If upgrading a split keyboard board definition, you'll need to update it by hand.

:::

The following parameters are relevant for out-of-tree boards:

- `--board-root </the/path/to/the/module/` -- the full path to the module directory that contains a `boards/` directory, e.g. `/home/peter/git/my-zmk-module/`.
- `-b <board_id>` -- The board ID to update, e.g. `tenbit`.
- `-v <vendor_id>` -- The vendor for the board, this should be a vendor ID, or designer nickname.
- `-g <group_id>` -- The name of the group directory under which to place the new board files. Typically this will match the vendor ID.
- `-s <soc_id>` -- The SoC identifier, e.g. `nrf52840`, `rp2040`, `stm32f411xe`.

For example:

```sh
$ python3 zmk/zephyr/scripts/utils/board_v1_to_v2.py \
    --board-root my-zmk-module -b my_board \
    -v my_company -g my_group -s nrf52840
```

### Migrating an Out-Of-Tree Board Manually

The following steps can be completed manually if you encounter issues with the upgrade script, or don't have a local development setup available.

#### Vendor Directory

Boards no longer need to live in a parent directory named after the architecture of the board (.e.g `boards/arm`), and should instead be placed in a vendor/designer named directory (e.g. `boards/my_company`).

#### Write a `board.yml`

In your board's folder, next to other files such as `<your_board>.dts`, add a file called `board.yml`. This file should have the following structure:

```yml title="board.yml"
board:
  name: <board-name>
  vendor: <board-vendor>
  revision:
    format: <major.minor.patch|letter|number|custom>
    default: <default-revision-value>
    exact: <true|false>
    revisions:
    - name: <revA>
    - name: <revB>
      ...
  socs:
  - name: <soc-1>
    variants:
    - name: <variant-1>
    - name: <variant-2>
      variants:
      - name: <sub-variant-2-1>
        ...
  - name: <soc-2>
    ...
```

In the above:

- `<board-name>` is the name of the board as specified when selecting a build target, such as `nice_nano`.
- `<vendor-name>` is the name of the board's vendor, such as `nicekeyboards`. If you are an individual, rather than acting as an organization, please use your name/online id/similar (e.g. `zhiayang` in the case of the `mikoto`). This value should match the vendor directory name that the board definition folder is placed in the previous section.
- `revision` defines any board revisions. See [Zephyr's overview](https://docs.zephyrproject.org/4.1.0/hardware/porting/board_porting.html#multiple-board-revisions) for more information on board revisions. If your board does not have any revisions, you can omit this section.
- `socs` lists all SoCs that your board could have, e.g. `nrf52840` or `stm32f072xb`. If your board only has one SoC available and no variants, then the SoC can be omitted when selecting a build target, but must still be specified in this file. For an understanding of SoC variants, refer to the Zephyr documentation.

If you need to define multiple boards in the same `board.yml`, such as for a split keyboard, you can do so like this:

```yml
boards:
 - name: <board_name_1>
   ...
 - name: <board_name_2>
   ...
```

#### Revision adjustments

If, as a side effect of adding revisions, you renamed the board (e.g. `ferris_rev02` -> `ferris`), you should adjust the other places where the board name was previously -- `<board>.zmk.yml` and `<board>.yaml`. You may also need to rearrange/consolidate other Kconfig flags and devicetree nodes. See [the Zephyr documentation](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html#multiple-board-revisions) for more details.

#### Adjust Kconfig files

##### `Kconfig.<board>`

Previously, your board folder will have had a file named `Kconfig.board`. This should be renamed to `Kconfig.<board>`, where `<board>` is the board name given in `board.yml`. The contents of this file will previously look something like this:

```title="Kconfig.board"
config BOARD_FERRIS
    bool "Ferris rev 0.2"
    depends on SOC_STM32F072XB
```

Remove the `bool` and change the `depends on` to a `select`:

```title="Kconfig.ferris"
config BOARD_FERRIS
    select SOC_STM32F072XB
```

If you had multiple boards specified for different SoCs, you should consolidate them to one:

```title="Kconfig.nrfmicro"
config BOARD_NRFMICRO
    select SOC_NRF52840_QIAA if BOARD_NRFMICRO_NRF52840
    select SOC_NRF52840_QIAA if BOARD_NRFMICRO_NRF52840_FLIPPED
    select SOC_NRF52833_QIAA if BOARD_NRFMICRO_NRF52833
```

##### `<board>_defconfig`

Previously, this file was used to select the board and SOC with Kconfig flags. All such selections should be removed from this file. For example, all of the below flags should be removed:

```
CONFIG_SOC_SERIES_NRF52X=y
CONFIG_SOC_NRF52833_QIAA=y
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_<BOARDNAME>=y
CONFIG_SOC_SERIES_STM32F0X=y
CONFIG_SOC_STM32F072XB=y
```

#### DeviceTree changes

For most boards, aside from rearranging due to moving to revisions, there should be no changes necessary to the devicetree nodes. However, if your board makes use of upstream Zephyr drivers, these may have been renamed (e.g. Ferris' `microchip,mcp230xx` has been changed to `microchip,mcp23017`).

## General Board/Shield Changes

A few other changes, unrelated to the HWMv2 move, may impact out-of-tree boards/shields:

### Bootloader Setup

With the version bump, the previous method to enable `&bootloader` has been disabled. Instead, ZMK is introducing _boot retention_, which as a side effect also enables `&bootloader` for SoCs which previously didn't work with said behavior, such as the STM32F072. To set up boot retention for your board, please read through [the dedicated page](/docs/hardware-integration/bootloader).

### nRF52840 NFC Pins as GPIO

If your board or shield is using either of the nRF52840 NFC pins, as is often done with the XIAO nRF52840, you'll need to perform an additional update.

#### Remove deprecated Kconfig symbol

Previously, using those pins required enabling `CONFIG_NFCT_PINS_AS_GPIOS=y` in some Kconfig file. That Kconfig symbol has been removed, so remove any use of that Kconfig symbol from your board/shield.

#### Set up NFC GPIO devicetree

The following should be added to the board or shield's devicetree, e.g. in `<board>.dtsi` or in a board specific shield overlay file like `<my_shield>/boards/xiao_ble.overlay`:

```dts
&uicr {
        nfct-pins-as-gpios;
};
```

### nRF52840 DC/DC Modes

For boards with the necessary additional hardware to enable DC/DC modes for the reg0 and/or reg1 power stages, the configuration to enable DC/DC has also moved out of Kconfig and into devicetree.

#### Remove Kconfig Settings

Usually, the DC/DC modes were enabled in the board's `Kconfig.defconfig` file, looking like:

```Kconfig
config BOARD_ENABLE_DCDC
    bool "Enable DCDC mode"
    select SOC_DCDC_NRF52X
    default y
    depends on (BOARD_MY_BOARD)

config BOARD_ENABLE_DCDC_HV
    bool "High voltage DCDC converter"
    select SOC_DCDC_NRF52X_HV
    default n
    depends on (BOARD_MY_BOARD)
```

Remove the lines from the file that look like above, or remove the `Kconfig.defconfig` file entirely if that is the only content contained therein.

#### Add DC/DC setup to devicetree

The DC/DC mode is now enabled for the `&reg0` and `&reg1` devicetree nodes, depending on which stage you want to use in that mode.

For a high voltage board, where the necessary inductor is connected to the `DCCH` pin, enable the following in the board's `.dts` file:

```dts
&reg0 {
    status = "okay";
};
```

For both high voltage and non-HV boards, where the necessary inductor is connected to the `DCC` pin, enable the following in the board's `.dts` file:

```dts
&reg1 {
    regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};
```

### RP2040 Board Adjustments

A few small tweaks are required for custom/out-of-tree RP2040 based boards:

#### Clock control

RP2040 boards now require clock control enabled to use several peripherals, including USB.

The following should be added to the board's `<board>_defconfig` file:

```dts
CONFIG_CLOCK_CONTROL=y
```

#### Base devicetree changes

The location for the base set of devicetree these boards need to include has changed. In the board's `<board>.dts` file, replace:

```dts
#include <rpi_pico/rp2040.dtsi>
```

with

```dts
#include <raspberrypi/rpi_pico/rp2040.dtsi>
```

Next, any fixed clock node needs to be removed:

```dts
    xtal_clk: xtal-clk {
        compatible = "fixed-clock";
        clock-frequency = <12000000>;
        #clock-cells = <0>;
    };
```

And the following added, to set up the core device hardware properly:

```dts
&timer {
    status = "okay";
};

&rtc {
    clocks = <&clocks RPI_PICO_CLKID_CLK_RTC>;
    status = "okay";
};

&vreg {
    regulator-always-on;
    regulator-allowed-modes = <REGULATOR_RPI_PICO_MODE_NORMAL>;
};
```

Lastly, an additional property must be added to the `chosen` node to supplement the existing properties there:

```dts
/ {
    chosen {
        ...
        zephyr,flash-controller = &ssi;
        ...
    };
};
```

### LED Strip Kconfig Changes

If your board or shield uses RGB underglow, the following Kconfig flag which was previously enabled should now be removed:

```
CONFIG_WS2812_STRIP=y
```

If this is the only SPI device your shield uses, also remove the Kconfig flag enabling SPI (assuming it is present). It will be automatically enabled.

### Cirque Pinnacle Input Driver

Upstream Zephyr now contains a driver for the popular small Cirque Pinnacle trackpads. To transition to the new upstream driver, instead of the [out-of-tree module](https://github.com/petejohanson/cirque-input-module), some small adjustments are needed.

#### Remove module references

Often, the out-of-tree module is referenced from the `west.yml` in user's repos. The entry pointing to the module should be removed from your `projects` list there. If building locally, be sure you are not adding the module directory to the `ZMK_EXTRA_MODULES` CMake parameter.

#### Devicetree changes

The properties for the upstream driver can be found [here](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/input/cirque%2Cpinnacle-i2c.html#dtbinding-cirque-pinnacle-i2c). The following changes are required when migrating:

- The `dr-gpios` property in out-of-tree module is named `data-ready-gpios`, so renaming the property is required.
- Instead of an opt-out `no-taps` property to disable taps, you can enable primary taps with `primary-tap-enable`.
- The `sleep` property is now named `sleep-mode-enable`.
- Use `invert-x`/`invert-y` instead of `x-invert`/`y-invert`.
- Use `swap-xy` instead of `rotate-90`.

## Other Changes

LVGL was updated to 9.3.0, which comes with breaking API changes. If you are using custom widgets or displays from a module, these will likely need fixing. See the [LVGL changelog](https://docs.lvgl.io/master/CHANGELOG.html#v9-3-0-3-june-2025) for details.

## Board Extensions

Zephyr has formalized the concept of "board extensions", allowing modules/applications to extend boards that are defined elsewhere. If using a board from the upstream Zephyr project that ZMK hasn't yet extended with default settings, e.g. enabling the `CONFIG_ZMK_USB` symbol, users can add their own extensions to their modules under the `<board_root>/boards/extensions/<board_dir_name>/` directory. See https://github.com/zmkfirmware/zmk/tree/main/app/boards/extensions for the extensions that ZMK has added.

## Zephyr Upgrade Instructions

Should you encounter any other issues with custom or out-of-tree Zephyr code, consider consulting the following Zephyr upgrade guides:

- [3.7 Upgrade](https://docs.zephyrproject.org/4.1.0/releases/migration-guide-3.7.html)
- [4.1 Upgrade](https://docs.zephyrproject.org/4.1.0/releases/migration-guide-4.1.html)

## Thanks!

Thanks to all the testers who have helped verify ZMK functionality on the newer Zephyr version.

## Future

Once a ZMK version based on Zephyr 4.1 is released, we'll be working towards updating Zephyr even further, to try to catch up with the latest actual Zephyr release. This will likely mean a jump to the upcoming Zephyr 4.3. As part of that work, some other semi-disruptive changes will be required, including:

- Removing ZMK's use of the deprecated, and now removed, `kscan` APIs in favor of the newer matrix input API. ZMK already supports the matrix input API, but has not yet converted our existing drivers to that API. Advanced users looking to test can try leveraging the upstream Zephyr drivers today to test that functionality, but that is _not_ officially supported and is likely to have bugs or untested side effects.
- Move the new USB stack, that includes better High-Speed USB support.

[nmunnich]: https://github.com/nmunnich
[petejohanson]: https://github.com/petejohanson

---

### Blog 2026 07 17 Shield Wizard (`docs/blog/2026-07-17-shield-wizard.mdx`)

---
title: "Community Spotlight Series #4: Shield Wizard"
authors: nmunnich
tags: [keyboards, firmware, community]
---

import ThemedImage from "@theme/ThemedImage";

This blog continues our series of posts where we highlight projects within the ZMK ecosystem
that we think are interesting and that users might benefit from knowing about.
In this installment, we present a project by [Genteure](https://github.com/Genteure) to help users setup ZMK for custom keyboards.

{/* truncate */}

Onboarding a custom keyboard to ZMK has always been a challenge. Designers not only need to understand and write devicetree and Kconfig, they also need to be extremely careful while doing so. A typo somewhere, or an extra whitespace could easily lead to a cryptic error message or even firmware which builds and flashes normally, but results in a malfunctioning keyboard. Shield wizard promises to make this task easier for many designers, particularly those new to keyboard design.

As usual, in the rest of the post we leave it to [Genteure] to introduce and explain the history and motivations of the Shield Wizard project.

## Shield Wizard for ZMK

[Shield Wizard for ZMK](https://shield-wizard.genteure.com) is a web based graphical editor for setting up ZMK firmware for custom keyboards. It enables users to go from zero to compiled firmware, all within the browser, without the need to manually edit files or install tools locally (not even git). It provides an easy, foolproof, and visual way for new users to get started with ZMK, while also speeding up the workflow for more experienced users.

<ThemedImage
  alt="Screenshot of Shield Wizard for ZMK"
  sources={{
    light: require("./assets/2027-07-17-shield-wizard/shield-wizard.png")
      .default,
    dark: require("./assets/2027-07-17-shield-wizard/shield-wizard-dark.png")
      .default,
  }}
/>

### Features

Shield Wizard for ZMK creates more or less a conventional zmk-config repository with the standard GitHub Actions workflow for compiling firmware. While there are some opinionated decisions, the generated files work exactly as you'd expect from a handwritten zmk-config.

You can edit keyboard layouts visually in Shield Wizard, or import layouts from many common formats. The generated configuration is ready for ZMK Studio, and it also works with Keymap Editor out of the box.

Shield Wizard for ZMK can host a temporary Git repository for you, so you can import directly into your GitHub account using a link. You don't even need to have git installed locally. Downloading the generated files as a ZIP archive is also supported.

The generated configuration is pinned to the latest stable ZMK version (v0.3 at the time of writing) to avoid unexpected breaking changes.

Shield Wizard for ZMK covers the most common ZMK features used in DIY keyboards, including:

- Unibody and split keyboards with up to 5 split parts
- The most common controller boards, including nRF52840-based and RP2040-based boards
- Matrix, direct, and Charlieplex wiring, as well as a mix of all three
- 595 shift registers
- Rotary encoders
- A variety of SPI/I2C peripherals, including:
  - SSD1306 and nice!view displays
  - WS2812 RGB LEDs
- Selected external modules, including:
  - PMW3610 using the module by [badjeff]
  - Cirque trackpads using the module by [petejohanson]

New SPI/I2C peripherals and external modules can be added upon request. [Shield Wizard for ZMK is open source](https://github.com/Genteure/zmk-wizard) under the same MIT license as ZMK. Contributions are welcome too!

## The Making of Shield Wizard

ZMK arguably has a steeper learning curve than most other software projects. The main configuration languages, Devicetree and Kconfig, are niche and quite different from other common configuration languages.
Once you get the hang of them, they are intuitive, powerful, and flexible, but the learning curve can be a bit overwhelming for new users.

While hanging around and helping people in the ZMK Discord, I noticed that many of them were struggling with the initial setup. Their keyboard designs were not complicated, but they were having a hard time getting the firmware to compile, more often than not, because of small mistakes somewhere in the configuration files.

Shield Wizard was partially inspired by kbfirmware.com, which was a QMK-based firmware builder for custom keyboards. It's very outdated now and no longer maintained, but it was clearly very useful, judging by comments on Reddit and elsewhere.

So I started experimenting with a web based graphical editor for ZMK, and Shield Wizard for ZMK was born.

<figure style={{ maxWidth: "560px", margin: "1.5rem auto" }}>
  <img
    src={
      require("./assets/2027-07-17-shield-wizard/early-shield-wizard.png")
        .default
    }
    alt="First version of Shield Wizard for ZMK"
  />
  <figcaption>_Screenshot of the first iteration of Shield Wizard_</figcaption>
</figure>

### Laying Out the Goals

The main goals I had in mind were:

1. Help avoid common pitfalls and mistakes.
2. Be intuitive, easy to understand and use. Low barrier to entry for new users.
3. Be sustainable and maintainable, architected for the long term.
4. Be conventional and standard, not cause headaches elsewhere in the ecosystem.

Goal 1 is the easiest to define. It mostly just means we don't allow users to create invalid configurations.

For goal 2, one specific feature I wanted was hosted Git repositories for importing into GitHub. This way, people don't have to install git locally or use the command line. This is especially helpful for non-technical users, who may not be familiar with these tools.

For goal 3, I wanted to make sure I could provide Shield Wizard for ZMK as a free service for as long as possible. The project should be hostable anywhere, and not rely on some heavy backend service or a specific hosting provider. It should be hosted as cheaply as possible, with flexible hosting options if moving to a different provider is needed.

For goal 4, I wanted to avoid situations like kbfirmware and QMK, where kbfirmware, though based on QMK, diverged enough to become its own thing.

So how could we provide hosted Git repositories while keeping the project architecture simple and lean?

### The Git Server

The obvious approach to providing hosted Git repositories is to use a self-hosted Git server like Gitea, GitLab, or Forgejo.
We would have to run a Git server. The HTTP service would call the Git service's API to create new repositories, write files to a local path, and call `git` commands to commit and push the changes. A backend daemon would have to run on a timer to clean up old repositories, and we would use a reverse proxy to route requests to the Git server and API endpoints.

That's a lot of moving parts. It would require paying for an actual compute server (e.g. a VPS), and it would be a lot of work to maintain and keep secure. I wanted to avoid that.

What if we implemented a simple integrated Git server ourselves? Enter [the Git "dumb" transfer protocol](https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols).

There are two main ways to transfer Git repositories over HTTP: the "smart" and the "dumb" transfer protocols. Without going into too much technical detail, the "smart" protocol is more efficient and allows for `git push` but requires two-way communication in a custom syntax. The "dumb" protocol is less efficient but simpler, and works with just a static file server. Many Git services reject requests using the "dumb" protocol, but practically all Git clients, including the `git` command line tool and the GitHub repository importer, will happily fetch a repository from another server using the "dumb" protocol.

Shield Wizard implements a simple Git repository builder that only supports loose objects (without Git packfiles) and a single Git commit. This is just enough for Git clients to recognize it as a valid Git repository and fetch it using the "dumb" protocol. Internally, the Git repository is packed into a `.tar.gz` archive and stored in key-value (KV) storage with a unique identifier as the key. While it is not as efficient as a full Git server with the "smart" protocol and packfiles (a few dozen requests per fetch compared to 1 request per fetch), the architectural simplicity is a worthwhile tradeoff.

### The Stack

[Astro](https://astro.build/) is the main framework for the project. It provides just enough abstraction to allow flexible hosting options, while also sharing code between the frontend and backend.
The frontend was originally written in SolidJS, but it was later rewritten in Vue due to a better selection of libraries and a more mature ecosystem.

At the time of writing, the project is hosted on Cloudflare Workers (free tier) with Workers KV (also free tier) for storing the generated Git repositories. With minimal changes, it can be hosted anywhere (serverless environments or traditional servers), and the storage backend can be virtually anything - a KV store, object storage, a database, or even the local filesystem.

## Future Plans and Ideas

Looking ahead, my more immediate plans include the usual UI/UX improvements, bug fixes, adding support for new peripherals and modules, and general maintenance. As new ZMK versions come out, I'll upgrade the pinned version and add support for any new features that come with them.

I'd also like to upgrade the Git repository builder to use packfiles at some point, which could make fetches slightly more efficient.

Something I've considered is supporting GitHub login so users could fetch and edit an existing repository instead of creating a new one each time. I've noticed some users coming back to make revisions, and being able to update an existing repository would be very convenient.

I've also thought about a generalized build settings editor to edit `build.yaml`, `.conf` files, and other configuration outside of board and shield definitions, but that might be better suited as a separate project.

## About Me

I'm relatively new to the ZMK community compared to other folks. It all started with me wanting to dip my toes into DIY electronics a while back, and I ended up sticking around in the ZMK community.

Having a programmable, customizable keyboard is a lot of fun! My main ZMK keyboard right now is a 34 key split, [Swoop by jimmerricks](https://github.com/jimmerricks/swoop). I still use traditional keyboards sometimes.

Thank you for reading, and I hope you enjoy using ZMK and Shield Wizard!

[badjeff]: https://github.com/badjeff
[petejohanson]: https://github.com/petejohanson
[Genteure]: https://github.com/Genteure

---

### Blog Authors.Yml (`docs/blog/authors.yml`)

petejohanson:
  name: Pete Johanson
  title: Project Creator
  url: https://gitlab.com/petejohanson
  image_url: https://www.gravatar.com/avatar/2001ceff7e9dc753cf96fcb2e6f41110

nickwinans:
  name: Nick Winans
  title: Contributor
  url: https://github.com/Nicell
  image_url: https://avatars1.githubusercontent.com/u/9439650

caksoylar:
  name: Cem Aksoylar
  title: Documentation maintainer
  url: https://github.com/caksoylar
  image_url: https://avatars.githubusercontent.com/u/7876996

nmunnich:
  name: Nicolas Munnich
  title: Documentation maintainer
  url: https://github.com/nmunnich
  image_url: https://avatars.githubusercontent.com/u/98408764

---

### Docs Customization (`docs/docs/customization.md`)

---
title: Customizing ZMK/zmk-config folders
sidebar_label: Customizing ZMK
---

After verifying you can successfully flash the default firmware, you will probably want to begin customizing your keymap and other keyboard options.
[In the initial setup tutorial](user-setup.mdx), you created a Github repository called `zmk-config`. This repository is a discrete filesystem which works
with the main `zmk` firmware repository to build your desired firmware. The main advantage of a discrete configuration folder is ensuring that the
working components of ZMK are kept separate from your personal keyboard settings, reducing the amount of file manipulation in the configuration process.
This makes flashing ZMK to your keyboard much easier, especially because you don't need to keep an up-to-date copy of zmk on your computer at all times.

By default, the `zmk-config` folder should contain two files:

- `<keyboard>.conf`
- `<keyboard>.keymap`

However, your config folder can also be modified to include a `boards/` directory for keymaps and configurations for multiple boards/shields
outside of the default keyboard setting definitions.

## Configuration Changes

The setup script creates a `config/<keyboard>.conf` file that allows you to add additional configuration options to
control what features and options are built into your firmware. Opening that file with your text editor will allow you to see the
various config settings that can be commented/uncommented to modify how your firmware is built.

Refer to the [Configuration](/docs/config) documentation for more details on this file.

## Keymap

Once you have the basic user config completed, you can find the keymap file in `config/<keyboard>.keymap` and customize from there.
Refer to the [Keymaps](keymaps/index.mdx) documentation to learn more.

## Publishing

After making any changes you want, you should commit the changes and then push them to GitHub. That will trigger a new
GitHub Actions job to build your firmware which you can download once it completes.

:::note
If you need to, a review of [Learn The Basics Of Git In Under 10 Minutes](https://www.freecodecamp.org/news/learn-the-basics-of-git-in-under-10-minutes-da548267cc91/) will help you get these steps right.
:::

:::note
It is also possible to build firmware locally on your computer by following the [toolchain setup](development/local-toolchain/setup/index.md) and
[building instructions](development/local-toolchain/build-flash.mdx), which includes pointers to
[building using your `zmk-config` folder](development/local-toolchain/build-flash.mdx#building-from-zmk-config-folder).
:::

## Flashing Your Changes

For normal keyboards, follow the same flashing instructions as before to flash your updated firmware.

For [split keyboards](features/split-keyboards.md#building-and-flashing-firmware), only the central (left) side will need to be reflashed if you are just updating your keymap.
More troubleshooting information for split keyboards can be found [here](troubleshooting/connection-issues.mdx#split-keyboard-parts-unable-to-pair).

## Building Additional Keyboards

You can build additional keyboards with GitHub actions by appending them to `build.yaml` in your `zmk-config` folder. For instance assume that we have set up a Corne shield with nice!nano during [initial setup](user-setup.mdx) and we want to add a Lily58 shield with nice!nano v2. The following is an example `build.yaml` file that would accomplish that:

```yaml
include:
  - board: nice_nano@1//zmk
    shield: corne_left
  - board: nice_nano@1//zmk
    shield: corne_right
  - board: nice_nano//zmk
    shield: lily58_left
  - board: nice_nano//zmk
    shield: lily58_right
```

In addition to updating `build.yaml`, Lily58's shield files should also be added into the `config` sub-folder inside `zmk-config` together with your Corne files, e.g.:

```
corne.conf
corne.keymap
lily58.conf
lily58.keymap
```

---

### Docs Faq (`docs/docs/faq.md`)

---
title: FAQs
sidebar_label: FAQs
---

### Why Zephyr™?

As a best-in-class RTOS, Zephyr™ brings many [benefits](https://www.zephyrproject.org/benefits) to ZMK, such as:

- A _single_ platform [supporting](https://docs.zephyrproject.org/4.1.0/boards/index.html) many architectures, processors and boards.
- Optimization for low-powered, small memory footprint devices.
- Powerful hardware abstraction and configuration using [DeviceTree](https://docs.zephyrproject.org/4.1.0/build/dts/index.html) and [Kconfig](https://docs.zephyrproject.org/4.1.0/build/kconfig/index.html).
- A BLE stack that periodically obtains [qualification](https://docs.zephyrproject.org/4.1.0/connectivity/bluetooth/bluetooth-qual.html) listings, making it easier for final products to obtain qualification from the Bluetooth® SIG.
- Multi-processor support, which is critical for power efficiency in upcoming MCUs.
- Permissive licensing with its Apache 2.0 open source [license](https://www.apache.org/licenses/LICENSE-2.0).
- A buzzing developer [community](https://github.com/zephyrproject-rtos/zephyr) including many leading [embedded technology](https://www.zephyrproject.org/project-members) companies.
- Long term support (LTS) with security updates.

### Why yet another keyboard firmware?

That’s an excellent question! There are already great keyboard firmwares available, but ZMK has some advantages:

- Zephyr™
  - See [Why Zephyr™?](#why-zephyr)
- Licensing
  - Just like other open source firmware, ZMK is all about the free and the sharing. However, some other projects use the GPL license which prevents integration of libraries and drivers whose licenses are not GPL-compatible (such as some embedded BLE drivers). ZMK uses the permissive [MIT](https://github.com/zmkfirmware/zmk/blob/main/LICENSE) license which doesn’t have this limitation.
- Wireless First
  - ZMK is designed for the future, and we believe the future is wireless. So power efficiency plays a critical role in every design decision, just like in Zephyr™.

The ZMK contributors firmly believe that a keyboard firmware built on Zephyr™ will provide more long term benefits.

### What license does ZMK use?

ZMK uses the MIT [license](https://github.com/zmkfirmware/zmk/blob/main/LICENSE).

### What hardware/platforms does ZMK support?

ZMK has the potential to run on any platform supported by Zephyr™. However, it’s impractical for the ZMK contributors to test all possible hardware.

The Zephyr™ [documentation](https://docs.zephyrproject.org/4.1.0/boards/index.html) describes which hardware is currently natively supported by the Zephyr™ platform. _Similar documentation covering which keyboards have been integrated into ZMK is currently being planned._

### Does ZMK compile for AVR?

Sorry, Zephyr™ only supports 32-bit and 64-bit platforms.

### How do I get started?

ZMK is still in its infancy, so there’s a learning curve involved. But if you’d like to try it out, please check out the development [documentation](/docs) and the other FAQs. Please keep in mind that the project team is still small, so our support capability is limited whilst we focus on development. But we’ll try our best! Interested developers are also very welcome to contribute!

### What are _boards_ and _shields_? Why not just "keyboard"?

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.
This provides the modularity to be able to use composite keyboards with different compatible controllers.
Please see the [explainer on boards & shields](hardware-integration/index.mdx#boards--shields) for more details.

### Does ZMK support wired split?

Currently, ZMK only supports wireless [split keyboards](features/split-keyboards.md). Experimental wired split support for some specific hardware designs is available for advanced users to test.

### How is the latency?

The latency of ZMK is comparable to other firmware offerings. ZMK is equipped with a variety of scanning methods and [debounce algorithms](features/debouncing.md) that can affect the final measured latency. [This video](https://www.youtube.com/watch?v=jWL4nU-vtWs) shows a latency comparison of ZMK and other keyboard firmwares.

### Any chance for 2.4GHz dongle implementation?

At this time, there are no current plans to implement 2.4GHz dongle mode. This is because utilizing Nordic's proprietary 2.4GHz low level protocols requires use of the Nordic Connect SDK, which is licensed with a more restrictive license than ZMK's MIT license. However, it is possible to [create a dongle](hardware-integration/dongle.mdx) for your keyboard that runs ZMK and communicates between parts using BLE (with encryption). This results in a 3.75ms average theoretical latency from the protocol itself.

### What bootloader does ZMK use?

ZMK isn’t designed for any particular bootloader, and supports flashing different boards with different flash utilities (e.g. OpenOCD, nrfjprog, etc.). So if you have any difficulties, please let us know on [Discord](https://zmk.dev/community/discord/invite)!

### Can I contribute?

Of course! Please use the developer [documentation](/docs) to get started!

### I have an idea! What should I do?

Please join us on [Discord](https://zmk.dev/community/discord/invite) and discuss it with us!

### I want to add a new keyboard! What should I do?

The exact process for the management of all the possible hardware is still being finalized, but any developer looking to contribute new keyboard definitions should chat with us on [Discord](https://zmk.dev/community/discord/invite) to get started.

### Does ZMK have a Code of Conduct?

Yes, it does have a [Code of Conduct](https://github.com/zmkfirmware/zmk/blob/main/CODE_OF_CONDUCT.md)! Please give it a read!

### What does “ZMK” mean?

ZMK was originally coined as a quasi-acronym of “Zephyr Mechanical Keyboard” and also taking inspiration from the amazing keyboard firmware projects, TMK and QMK.

### Is ZMK related to TMK or QMK?

No. But inspired by, of course!

### Who created ZMK?

ZMK was created by Pete Johanson. It is developed and maintained by the open source community.

---

### Docs Hardware (`docs/docs/hardware.mdx`)

---
title: Supported Hardware
sidebar_label: Supported Hardware
---

import HardwareList from "@site/src/components/hardware-list";
import Metadata from "@site/src/data/hardware-metadata.json";

import Heading from "@theme/Heading";

import { groupedMetadata } from "@site/src/components/hardware-utils";

export const toc = [
  {
    value: "Composite Keyboards",
    id: "composite",
    level: 2,
  },
  ...Object.values(groupedMetadata(Metadata).interconnects)
    .filter((ic) => ic.interconnect !== undefined)
    .map(({ interconnect }) => ({
      value: `${interconnect.name} Interconnect`,
      id: interconnect.id,
      level: 3,
    })),
  {
    value: "Onboard Controller Keyboards",
    id: "onboard",
    level: 2,
  },
  {
    value: "Other Hardware",
    id: "other-hardware",
    level: 2,
  },
];

With the solid technical foundation of Zephyr™ RTOS, ZMK can support a wide variety of hardware targets,
including but not limited to Nordic nRF52, Raspberry Pi RP2040/RP2350, most ST STM32 MCUs, and Microchip SAMD21.
ZMK has the potential to run on any hardware supported by Zephyr™, such as those on
the [Zephyr™ supported boards](https://docs.zephyrproject.org/4.1.0/boards/index.html) page, though you may need to do some additional work to configure them for ZMK.
That being said, there are specific boards that have been tested and pre-configured by the ZMK contributors, denoted by the `zmk` board variants below.

Designing a new keyboard? Check out the [Hardware Integration](hardware-integration/index.mdx) section
for more information on how to configure ZMK to run on your custom hardware.

:::info[Boards, Board Variants, and Shields]

ZMK uses the Zephyr concepts of "boards" and "shields" to refer to different parts of a keyboard build that are then combined during a firmware build.
Please see the [explainer on boards & shields](hardware-integration/index.mdx#boards--shields) for more details.

Zephyr boards come with minimal configuration. ZMK board variants add the necessary configuration to make the board usable out of the box with ZMK.

:::

<HardwareList items={Metadata} />

{/* prettier-ignore */}
<Heading as="h2" id="other-hardware">Other Hardware</Heading>

In addition to the basic keyboard functionality, there is also support for additional keyboard hardware such as encoders, RGB underglow, backlight and displays.
Please see pages under the "Features" header in the sidebar for details.

ZMK doesn't support boards with AVR 8-bit processors, such as the ATmega32U4, because Zephyr™ only
supports 32-bit and 64-bit platforms. As a result, controllers like the SparkFun Pro Micro, Elite-C,
and Arduino Uno Rev3 are **NOT** supported by ZMK.

---

### Docs Keymap Example (`docs/docs/keymap-example.md`)

```dts
    keymap {
        compatible = "zmk,keymap";

        default_layer { // Layer 0
            display-name = "Base";
// ----------------------------------------------
// |      Z      |        M       |      K      |
// |    &mo 1    |   LEFT SHIFT   |    &mo 2    |
            bindings = <
                &kp Z    &kp M       &kp K
                &mo 1    &kp LSHIFT  &mo 2
            >;
        };
        abc { // Layer 1
            display-name = "ABC";
// ----------------------------------------------
// |      A      |       B       |       C      |
// |    &trans   |    &trans     |     &trans   |
            bindings = <
                &kp A     &kp B     &kp C
                &trans    &trans    &trans
            >;
        };
        xyz { // Layer 2
            display-name = "XYZ";
// ----------------------------------------------
// |        X       |      Y       |      Z     |
// |   LEFT CTRL    |  LEFT ALT    |   &trans   |
            bindings = <
                &kp X        &kp Y       &kp Z
                &kp LCTRL    &kp LALT    &trans
            >;
        };
    };
```

---

### Docs User Setup (`docs/docs/user-setup.mdx`)

---
title: Installing ZMK
sidebar_label: Installing ZMK
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

export const OsTabs = (props) => (
  <Tabs
    groupId="operating-system"
    queryString
    defaultValue="linux"
    values={[
      { label: "Linux", value: "linux" },
      { label: "Windows", value: "win" },
      { label: "macOS", value: "mac" },
    ]}
  >
    {/* eslint-disable-next-line */}
    {props.children}
  </Tabs>
);

Unlike other keyboard firmwares, ZMK Firmware has been built from the ground up to allow users to manage
their own keyboard configurations, including keymaps, specific hardware details, etc. all outside of the
core ZMK Firmware source repository.

In addition to this, most users will not need to install any complicated toolchains or tools to build ZMK. GitHub Actions is used instead to automatically build the user's configured firmware for them.

:::info[Something Isn't Working?]

If you run into any issues when following these instructions, please [file an issue on GitHub](https://github.com/zmkfirmware/zmk-cli/issues) or [join us on Discord](https://zmk.dev/community/discord/invite) and let us know what went wrong so we can improve ZMK CLI and this guide.

:::

## Summary

The following steps can be taken to obtain an installable firmware image for your device, without the need
to install any compiler or specialized toolchain. This is possible by leveraging [GitHub Actions](https://github.com/features/actions)
to build your firmware for you in the cloud, which you can then download and flash to your device.

Following the steps in this guide, you will:

1. Install ZMK CLI, a command line tool for setting up ZMK.
1. Create a new repository in GitHub that will contain your user config.
1. Use ZMK CLI to clone the repo to your computer so you can edit the files.
1. Select a keyboard to use and update the `build.yaml` file so GitHub will build firmware for that keyboard.
1. Add your own `.keymap` and `.conf` files to customize your keymap and enable/disable other ZMK features.
1. Commit and push the updates to trigger a new GitHub Action run to build the firmware.

## Prerequisites

You will need to install a few tools before you can get started.

Many instructions in this guide use commands that need to be run in a terminal application. On most operating systems, the program is simply named "Terminal".

On Windows, get [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701) from the Microsoft Store if it isn't already installed.

### Install Git

Open a terminal and run the following command. If Git is already installed, it will print out a version number.

```sh
git --version
```

If it prints an error instead, install Git from https://git-scm.com/install/. Close and reopen your terminal and run the `git --version` command again to check if it installed correctly.

:::note
This guide will have you run several more `git` commands later. If you aren't familiar with Git, check out [Learn The Basics Of Git In Under 10 Minutes](https://www.freecodecamp.org/news/learn-the-basics-of-git-in-under-10-minutes-da548267cc91/) to help you get started.
:::

### Set Up a GitHub Account

You will need a GitHub account. [Create an account](https://github.com/signup) if you don't have one yet.

Next, you will need to authenticate your local Git installation with GitHub. There are several ways to do this, but the following two are the simplest. Pick whichever seems easiest to you:

#### Authenticate with GitHub CLI

Install [GitHub CLI](https://cli.github.com/) and run the following command in a terminal. Follow the instructions it gives you, and it will open GitHub in a web browser for you to log in.

```sh
gh auth login
```

#### Authenticate with a Personal Access Token

Create a [personal access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) and make sure to create it with the "workflow" scope option selected.

When running `git` commands later in this guide, Git will ask for your username and password. Enter your GitHub username, but **do not enter your password**. Instead, paste your access token where it asks for the password. You won't be able to see the token again after you create it, so keep the page open until you've done this and/or copy it to a secure location. If you lose the token, simply create another one.

### Install uv

[uv](https://github.com/astral-sh/uv) is a Python package manager which can install ZMK CLI and a supported version of Python if necessary. The instructions for installing uv differ depending on your operating system. Select the tab for your OS below.

If installation fails, see [https://docs.astral.sh/uv/getting-started/installation/](https://docs.astral.sh/uv/getting-started/installation/) for more detailed instructions and alternative installation methods.

<details>
  <summary>Other package managers</summary>

ZMK CLI can also be installed with other Python package managers such as [pipx](https://pipx.pypa.io/stable/). If you have a different package manager you prefer, install the ["zmk" package](https://pypi.org/project/zmk/) and skip to [Config Repo Setup](#config-repo-setup).

</details>

<OsTabs>
<TabItem value="linux">

Open a terminal and run the following command:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

If you get an error like "curl: command not found", try using wget:

```sh
wget -qO- https://astral.sh/uv/install.sh | sh
```

If that doesn't work either, install [curl](https://curl.se/) and try again. For example, on Ubuntu and other Debian-based Linux distributions, run:

```sh
sudo apt update
sudo apt install curl
curl -LsSf https://astral.sh/uv/install.sh | sh
```

If the install script prints the following, close and reopen your terminal.

```
To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
```

</TabItem>
<TabItem value="win">

Open a terminal and run the following command:

```sh
winget install --id astral-sh.uv -e --source winget
```

If this prints the text that is highlighted below, close and reopen your terminal.

```
// highlight-next-line
Path environment variable modified; restart your shell to use the new value.
Command line alias added: "uvx"
Command line alias added: "uv"
Command line alias added: "uvm"
Successfully installed
```

</TabItem>
<TabItem value="mac">

Open a terminal and run the following command:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

If the install script prints the following, close and reopen your terminal.

```
To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
```

</TabItem>
</OsTabs>

### Install ZMK CLI

Finally, we'll install ZMK CLI. Open a terminal and run:

```sh
uv tool install zmk
```

<details>
  <summary>I get an "executable already exists" error</summary>

Earlier versions of this guide used pipx. If you get an error like this...

```
error: Executable already exists: zmk.exe (use `--force` to overwrite)
```

...run the following commands to remove the pipx version and reinstall it with uv:

```sh
python3 -m pipx uninstall zmk
uv tool install zmk
```

</details>

This will likely print a message like ``warning: `/home/username/.local/bin` is not on your PATH``. To fix that, run:

```sh
uv tool update-shell
```

Close and reopen your terminal to apply the changes, then run the following command. It should print a version number if everything installed correctly.

```sh
zmk --version
```

## Config Repo Setup

Now that ZMK CLI is installed, you can use it to initialize a repository.

The `zmk init` command walks you through creating a GitHub repository, then clones it to your computer so you can edit it.

<details>
  <summary>I already cloned my repo</summary>

If you have already created a repo and cloned it to your computer, for example with the old ZMK setup script, you do not need to do this. Instead, run the following command to point ZMK CLI to your repo, replacing `/path/to/zmk-config` with the actual path to the repo folder.

```sh
zmk config user.home "/path/to/zmk-config"
```

Then, [skip to the next step](#add-a-keyboard).

</details>

Open a terminal and use the `cd` command to move to a folder where you'd like to place the ZMK files (it will create a new folder in this location), then run `zmk init`. For example:

```sh
cd ~/Documents
zmk init
```

Follow the instructions it gives you. If you already have a ZMK config repo on GitHub, select "Clone an existing repo" and enter its URL when prompted, for example:

```
Repository URL: https://github.com/myusername/zmk-config
```

Otherwise, select "Create a new ZMK config repo", and follow the instructions to create a new repo.

After this finishes, you will have a copy of the repo stored on your computer. All `zmk` commands will run on this repo (unless the working directory is inside a different repo). If you ever forget where the repo is located, you can run `zmk cd` to find it.

### Add a Keyboard

To start building firmware for a new keyboard, run the following command:

```sh
zmk keyboard add
```

Follow the instructions to select a keyboard (and controller board if necessary). It will add the keyboard to your repo's `build.yaml` file so GitHub knows to build it, and it will create `config/<keyboard>.keymap` and `config/<keyboard>.conf` files for you to edit.

<details>
  <summary>My keyboard isn't listed</summary>

If ZMK doesn't support your keyboard by default, you have two options:

**Add a Module**

ZMK can be extended with [modules](features/modules.mdx) to add new keyboards. If someone has already created a module for your keyboard, run the following command to add it, replacing `<url>` with the URL of the Git repository for the module:

```sh
zmk module add <url>
```

For example, `zmk module add https://github.com/cannonkeys/zmk-cannonkeys-keyboards` would add support for CannonKeys keyboards.

Run `zmk keyboard add` again, and the new keyboards should appear in the list.

**Create It Yourself**

If nobody has created a module for your keyboard yet, or if it is your own custom design, you will need to set it up yourself. Your config repo is already set up as a module, so you can define new keyboards there. See ZMK's [new keyboard shield guide](hardware-integration/new-shield.mdx) for more information.

ZMK CLI can also help generate some of the boilerplate for defining a new keyboard:

```sh
zmk keyboard new
```

</details>

### Edit Your Keymap and Configuration

See the [customizing ZMK](customization.md) documentation for instructions on editing your keymap and configuring ZMK settings.

As a shortcut to open your keymap in a text editor, you can run the following command, replacing `<keyboard>` with the ID of the keyboard you chose earlier:

```sh
zmk code <keyboard>
```

If you have an editor like [VS Code](https://code.visualstudio.com/) that can open a folder, you can also run the command without a keyboard ID to open your config repo directly:

```sh
zmk code
```

The first time you run this command, it will try to identify which text editors you have installed and ask you which one to use. If you want to change this later, change the [`core.editor`](zmk-cli.mdx#coreeditor) setting.

### Commit and Push to GitHub

After you've changed your keymap to your liking, you need to commit and push your changes.

Run the following commands to go to the repo directory, mark all your changed files to be added to the commit, create the commit, and push it to GitHub.

```sh
zmk cd
git add .
git commit -m "Initial commit"
git push
```

The text after `-m` is the "commit message", which gives a name to the commit. This is your first commit, so we're calling it "initial commit" here, but you can name it whatever you like. When you make more commits later, it's a good idea to use the commit message to write a description of the changes you made.

:::warning

If the `git push` command asks you for a username and password, enter your GitHub username, but **do not enter your password**. Instead, paste your access token where it asks for the password. (Go back to the [GitHub account setup instructions](#set-up-a-github-account) if you need to create a token.)

:::

## Install the Firmware

### Download the Archive

Once you push a commit, GitHub will automatically run the action to build your keyboard firmware files. Run the following command to open the GitHub Actions page in your browser:

```sh
zmk download
```

Or, you can manually open your GitHub repository in your browser and click the "Actions" tab at the top.



Once you have loaded the Actions tab, select the top build from the list. If the build was successful, the right side panel will include
a link to download the `firmware` upload:



:::info[Fixing build errors]

If you see a red X instead of a link to download the firmware, the build failed, likely due to an error in your repo. See the [troubleshooting page](./troubleshooting/building-issues.md) for help fixing the error. After making changes, [commit and push](#commit-and-push-to-github) again and GitHub will start a new build.

:::

Once downloaded, extract the zip and you can verify it should contain one or more `.uf2` or `.hex` files, which contain the firmware you need to write to your keyboard.

### Flash UF2 Files

If your firmware contains a `.uf2` file, then you flash the new firmware by putting the keyboard into bootloader mode and copying the file directly to it in file explorer.

First, put your keyboard into bootloader mode by double clicking the reset button (either on the MCU board itself, or the one that is part of your keyboard). The controller should appear in your OS as a new USB storage device.

Once this happens, copy the correct UF2 file (e.g. left or right if working on a split), and paste it onto the root of that USB mass
storage device. Once the flash is complete, the controller should unmount the USB storage, automatically restart and load your newly
flashed firmware. It is recommended that you test your keyboard works over USB first to rule out hardware issues, before trying to
connect to it wirelessly.

:::warning[Split keyboards]

For split keyboards, only the central half (typically the left side) will send keyboard outputs over USB or advertise to other devices
over bluetooth. Peripheral half will only send keystrokes to the central once they are paired and connected. For this reason it is
recommended to test the left half of a split keyboard first.
Please refer to [split keyboards documentation](features/split-keyboards.md) for more information.

:::

### Flash Hex Files

If your firmware contains a `.hex` file, then you will use DFU to flash the firmware. [QMK Toolbox](https://qmk.fm/toolbox) is an easy-to-use tool with support for DFU flashing.

Open QMK Toolbox, click the "Open" button, and select the correct `.hex` file (e.g. left or right if working on a split). Consult the documentation for your keyboard to figure out how to enter bootloader or DFU mode (often this is done by pressing a pair of buttons on the keyboard or by holding a specific key as you plug in the USB cable), and once QMK Toolbox detects the keyboard, click "Flash" to flash the firmware.

## Wirelessly Connecting Your Keyboard

ZMK will automatically advertise itself as connectable if it is not currently connected to a device. You should be able to see your keyboard from the bluetooth scanning view of your computer or phone / tablet. It is reported by some users that the connections with Android / iOS devices are generally smoother than with laptops, so if you have trouble connecting, you could try to connect from your phone or tablet first to eliminate any potential hardware issues with bluetooth receivers.

ZMK supports multiple BLE “profiles”, which allows you to connect to and switch among multiple devices. Please refer to the [Bluetooth behavior](keymaps/behaviors/bluetooth.md) section for detailed explanations on how to use them. If you don't make use of the mentioned behaviors you will have issues pairing your keyboard to other devices.

### Connecting Split Keyboard Halves

For split keyboards, after flashing each half individually you can connect them together by resetting them at the same time. Within a few seconds of resetting, both halves should automatically connect to each other.
Please refer to [the pairing section in the split keyboards documentation](features/split-keyboards.md#pairing-for-wireless-split-keyboards) for more information.

:::note

If you have issues connecting the halves, make sure that both sides are getting powered properly through USB or batteries, then follow the
[recommended troubleshooting procedure](troubleshooting/connection-issues.mdx#split-keyboard-parts-unable-to-pair). This is typically necessary if you
swapped firmware sides between controllers, like flashing left side firmware to the same controller after flashing the right, or vice versa.

:::

---

### Docs Zmk Cli (`docs/docs/zmk-cli.mdx`)

---
title: ZMK CLI
sidebar_label: ZMK CLI
---

ZMK CLI is a command line tool which helps with setting up a ZMK config repo and automates some common tasks such as adding new keyboards to your repository.

See the [ZMK installation guide](user-setup.mdx#prerequisites) for instructions on installing ZMK CLI.

The instructions below contain commands that need to be run in a terminal program. On most operating systems, the program is simply named "Terminal".

On Windows, get [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701) from the Microsoft Store if it isn't already installed.

## Updating ZMK CLI

To update to the latest version of ZMK, run the following command in a terminal:

```sh
uv tool upgrade zmk
```

## Using ZMK CLI

All ZMK CLI commands start with `zmk`. Run `zmk --help` for general usage instructions. For help with a specific subcommand, add `--help` after the subcommand, e.g. `zmk init --help`.

### Initialize a Repository

The `zmk init` command walks you through creating a GitHub repository, then clones it to your computer so you can edit it. See the [ZMK installation guide](user-setup.mdx#config-repo-setup) for more details on this command.

### Keyboard Management

#### Add a keyboard

To start building firmware for a new keyboard, run `zmk keyboard add`. Follow the instructions to select a keyboard (and controller board if necessary), and it will add it to the list of firmware to build and copy a default keymap into your repo.

You can then run `zmk code <keyboard>` to open the keymap in a text editor.

This command reads from a local copy of ZMK to determine the supported keyboards. If the keyboard you want to use isn't listed, try running `zmk update` to update the local copy to the latest version of ZMK. If it still isn't listed, you may be able to find a [Zephyr module](#module-management) that provides it, or you may need to [create it yourself](#create-a-new-keyboard).

#### Remove a keyboard

To remove a keyboard from the build, run `zmk keyboard remove` and select the item to remove. For a split keyboard, you will need to run this twice and remove the left and right sides.

This simply removes a keyboard from the `build.yaml` file. It does not delete any `.keymap` or `.conf` files.

#### List supported keyboards

Run `zmk keyboard list` to print a list of supported keyboard hardware.

#### Create a new keyboard

If ZMK doesn't support your keyboard yet, you can run `zmk keyboard new` to create a new keyboard from a template.

This won't walk you through all of the details of adding support for a new keyboard, but it will generate most of the boilerplate for you. See the [New Keyboard Shield](hardware-integration/new-shield.mdx) guide for how to finish writing the keyboard files.

### Module Management

[Modules](features/modules.mdx) can add support for new keyboards, behaviors, and other features to ZMK. Use the `zmk module` command to install modules into your repo:

```sh
zmk module add        # Add a module
zmk module add <url>  # Add a module given the URL of the repo
zmk module remove     # Remove an installed module
zmk module list       # List the installed modules
zmk update            # Update the local copies of ZMK and modules to their latest versions
```

:::note

ZMK CLI stores a copy of ZMK and any modules in a `.zmk` folder so it can read board and shield information from them. This folder is only used by ZMK CLI and is not necessary for building firmware, therefore ZMK CLI will automatically add it to your repo's `.gitignore` file.

:::

### Edit Keymap and Config Files

The `zmk code` command will open ZMK files in a text editor:

```sh
zmk code                    # Open the repo directory in an editor
zmk code <keyboard>         # Open <keyboard>.keymap in an editor
zmk code --conf <keyboard>  # Open <keyboard>.conf in an editor
zmk code --build            # Open build.yaml in an editor
```

The first time you run this command, it will ask you which editor you want to use. If you want to change this choice later or use an editor that wasn't listed, see the [core.editor](#coreeditor) and [code.explorer](#coreexplorer) settings.

### Push Changes to GitHub

Run `zmk cd` to go to the repo directory. From here, you can run `git` commands manage the repo.

For example, after adding a keyboard to your repo and editing its keymap, you can run the following commands to push your changes to GitHub and trigger a firmware build:

```sh
git add .
git commit
git push
```

You will need to [authenticate with GitHub](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#authenticating-with-the-command-line) before the `git push` command will work. The easiest way to do this is to install the [GitHub CLI](https://cli.github.com/) and run

```sh
gh auth login
```

but you can also use a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). If using an access token, make sure you create it with the "workflow" scope option selected.

### Download Firmware from GitHub

After pushing changes, GitHub will automatically build the firmware for you. Run `zmk download` (or `zmk dl` for short) to open the GitHub actions page in your browser.

From this page, you can click on a build (the latest is at the top) to view its status. If the build succeeded, you can download the firmware from the "Artifacts" section at the bottom of the build summary page.

### Configuration

The `zmk config` command manages settings for ZMK CLI:

```sh
zmk config                 # List all settings
zmk config <name>          # Print the value of the setting <name>
zmk config <name> <value>  # Set <name> to <value>
zmk config --unset <name>  # Remove the setting <name>
```

By default, these settings are stored in a file in your user profile directory. Run `zmk config --path` to get the location of this file. You can change where the settings file is stored by setting a `ZMK_CLI_CONFIG` environment variable to the new path to use, or by adding a `--config-file=<path>` argument when running `zmk`.

Other commands use the following settings:

#### user.home

The path to the repository to use whenever `zmk` is run and the working directory is not inside a ZMK config repository.

For example, to point ZMK CLI to an existing repo at `~/Documents/zmk-config`, run:

```sh
zmk config user.home ~/Documents/zmk-config
```

#### core.editor

Command line for a text editor to use with the `zmk code` command.

For example, so set Visual Studio Code as the editor and make it always open a new window, run:

```sh
zmk config core.editor "code --new-window"
```

#### core.explorer

Command line for a file explorer to use with the `zmk code` command when opening a directory.

If this setting is not set, the `core.editor` tool will be run instead. Set this setting when using a text editor that does not support opening directories.

---

### Docs Config Battery (`docs/docs/config/battery.md`)

---
title: Battery Level
sidebar_label: Battery Level
---

See the [battery level feature page](../features/battery.md) for more details on configuring a battery sensor.

See [Configuration Overview](index.md) for instructions on how to change these settings.

### Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                               | Type | Description                                            | Default |
| ------------------------------------ | ---- | ------------------------------------------------------ | ------- |
| `CONFIG_ZMK_BATTERY_REPORTING`       | bool | Enables/disables all battery level detection/reporting | n       |
| `CONFIG_ZMK_BATTERY_REPORT_INTERVAL` | int  | Battery level report interval in seconds               | 60      |

:::note[Default setting]

While `CONFIG_ZMK_BATTERY_REPORTING` is disabled by default it is implied by `CONFIG_ZMK_BLE`, thus any board with BLE enabled will have this automatically enabled unless explicitly overridden.

:::

:::note[BLE reporting on MacOS]

On macOS the BLE battery reporting packets can cause the computer to wakeup from sleep. To prevent this, the battery _reporting_ service can be disabled by setting `CONFIG_BT_BAS=n`. This setting is independent of battery _monitoring_, for instance the battery level can still be indicated on a display.

:::

### Peripheral Battery Monitoring

You can [configure ZMK to allow support for peripheral battery monitoring over BLE](split.md) (e.g. when having a split keyboard with two independent and wirelessly connected sides).
If you want to report the battery levels of both sides of a split keyboard, you should have both `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY` and `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING` set to `y`.

:::note[Displaying both battery levels on your host]

Host support for multiple battery levels is undefined. It appears that in most of the cases only the main battery is being reported. In order to correctly display all the battery values, you probably need a special application or script.

:::

### Devicetree

Applies to: [`/chosen` node](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes)

| Property      | Type | Description                                   |
| ------------- | ---- | --------------------------------------------- |
| `zmk,battery` | path | The node for the battery sensor driver to use |

## Battery Voltage Divider Sensor

Driver for reading the voltage of a battery using an ADC connected to a voltage divider.

### Devicetree

Applies to: `compatible = "zmk,battery-voltage-divider"`

See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/iio/afe/voltage-divider.html).

## nRF VDDH Battery Sensor

Driver for reading the voltage of a battery using a Nordic nRF52's VDDH pin.

### Devicetree

Applies to: `compatible = "zmk,battery-nrf-vddh"`

Definition file: [zmk/app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/sensor/zmk%2Cbattery-nrf-vddh.yaml)

This driver has no configuration.

---

### Docs Config Behaviors (`docs/docs/config/behaviors.md`)

---
title: Behavior Configuration
sidebar_label: Behaviors
---

Some behaviors have properties to adjust how they behave. These can also be used as templates to create custom behaviors when none of the built-in behaviors do what you want.

See [Configuration Overview](index.md) for instructions on how to change these settings.

See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/app/dts/behaviors) folder for all default behaviors.

## Common

### Kconfig

| Config                            | Type | Description                                                                          | Default |
| --------------------------------- | ---- | ------------------------------------------------------------------------------------ | ------- |
| `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` | int  | Maximum number of behaviors to allow queueing from a macro or other complex behavior | 64      |

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/behavior-metadata.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/behavior-metadata.yaml)

| Property       | Type   | Description                                                                      | Default |
| -------------- | ------ | -------------------------------------------------------------------------------- | ------- |
| `display-name` | string | Name of the layer, for use with a display or [ZMK Studio](../features/studio.md) |         |

## Caps Word

Creates a custom behavior that behaves similar to a caps lock but deactivates when any key not in a continue list is pressed.

See the [caps word behavior](../keymaps/behaviors/caps-word.md) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-caps-word.yaml)

Applies to: `compatible = "zmk,behavior-caps-word"`

| Property         | Type  | Description                                                                          | Default                         |
| ---------------- | ----- | ------------------------------------------------------------------------------------ | ------------------------------- |
| `#binding-cells` | int   | Must be `<0>`                                                                        |                                 |
| `continue-list`  | array | List of [keycodes](/docs/keymaps/list-of-keycodes) which do not deactivate caps lock | `<UNDERSCORE BACKSPACE DELETE>` |
| `mods`           | int   | A bit field of modifiers to apply                                                    | `<MOD_LSFT>`                    |

`continue-list` is treated as if it always includes alphanumeric characters (A-Z, 0-9).

See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers.

You can use the following nodes to tweak the default behaviors:

| Node         | Behavior                                       |
| ------------ | ---------------------------------------------- |
| `&caps_word` | [Caps Word](../keymaps/behaviors/caps-word.md) |

## Hold-Tap

Creates a custom behavior that triggers one behavior when a key is held or a different one when the key is tapped.

See the [hold-tap behavior](../keymaps/behaviors/hold-tap.mdx) documentation for more details and examples.

### Kconfig

| Config                                             | Type | Description                                                                                  | Default |
| -------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD`            | int  | Maximum number of simultaneous held hold-taps                                                | 10      |
| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS` | int  | Maximum number of system events to capture while deferring a hold or tap decision resolution | 40      |

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-hold-tap.yaml)

Applies to: `compatible = "zmk,behavior-hold-tap"`

| Property                      | Type     | Description                                                                                                   | Default            |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- | ------------------ |
| `#binding-cells`              | int      | Must be `<2>`                                                                                                 |                    |
| `bindings`                    | phandles | A list of two behaviors (without parameters): one for hold and one for tap                                    |                    |
| `flavor`                      | string   | Adjusts how the behavior chooses between hold and tap                                                         | `"hold-preferred"` |
| `tapping-term-ms`             | int      | How long in milliseconds the key must be held to trigger a hold                                               |                    |
| `quick-tap-ms`                | int      | Tap twice within this period (in milliseconds) to trigger a tap, even when held                               | -1 (disabled)      |
| `require-prior-idle-ms`       | int      | Triggers a tap immediately if any non-modifier key was pressed within `require-prior-idle-ms` of the hold-tap | -1 (disabled)      |
| `retro-tap`                   | bool     | Triggers the tap behavior on release if no other key was pressed during a hold                                | false              |
| `hold-while-undecided`        | bool     | Triggers the hold behavior immediately on press and releases before a tap                                     | false              |
| `hold-while-undecided-linger` | bool     | Continues to hold the hold behavior until after the tap is released                                           | false              |
| `hold-trigger-key-positions`  | array    | If set, pressing the hold-tap and then any key position _not_ in the list triggers a tap                      |                    |
| `hold-trigger-on-release`     | bool     | If set, delays the evaluation of `hold-trigger-key-positions` until key release                               | false              |

This behavior forwards the first parameter it receives to the parameter of the first behavior specified in `bindings`, and second parameter to the parameter of the second behavior.

The `flavor` property may be one of:

- `"hold-preferred"`
- `"balanced"`
- `"tap-preferred"`
- `"tap-unless-interrupted"`

See the [hold-tap behavior documentation](../keymaps/behaviors/hold-tap.mdx) for an explanation of each flavor.

`hold-trigger-key-positions` is an array of zero-based key position indices.

You can use the following nodes to tweak the default behaviors:

| Node  | Behavior                                              |
| ----- | ----------------------------------------------------- |
| `&lt` | [Layer-tap](../keymaps/behaviors/layers.md#layer-tap) |
| `&mt` | [Mod-tap](../keymaps/behaviors/hold-tap.mdx#mod-tap)  |

## Key Repeat

Creates a custom behavior that repeats the whatever key code was last sent.

See the [key repeat behavior](../keymaps/behaviors/key-repeat.md) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-key-repeat.yaml)

Applies to: `compatible = "zmk,behavior-key-repeat"`

| Property         | Type  | Description                      | Default           |
| ---------------- | ----- | -------------------------------- | ----------------- |
| `#binding-cells` | int   | Must be `<0>`                    |                   |
| `usage-pages`    | array | List of HID usage pages to track | `<HID_USAGE_KEY>` |

For the `usage-pages` property, use the `HID_USAGE_*` defines from [dt-bindings/zmk/hid_usage_pages.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/hid_usage_pages.h).

You can use the following nodes to tweak the default behaviors:

| Node          | Behavior                                         |
| ------------- | ------------------------------------------------ |
| `&key_repeat` | [Key repeat](../keymaps/behaviors/key-repeat.md) |

## Key Toggle

Creates a custom behavior that toggles a key code on, off, or switches between the two states.

See the [key toggle behavior](../keymaps/behaviors/key-toggle.md) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-key-toggle.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-key-toggle.yaml)

Applies to: `compatible = "zmk,behavior-key-toggle"`

| Property         | Type | Description                    | Default |
| ---------------- | ---- | ------------------------------ | ------- |
| `#binding-cells` | int  | Must be `<1>`                  |         |
| `toggle-mode`    |      | One of `on`, `off`, and `flip` | `flip`  |

You can use the following node to tweak the default behavior:

| Node  | Behavior                                         |
| ----- | ------------------------------------------------ |
| `&kt` | [Key toggle](../keymaps/behaviors/key-toggle.md) |

## Momentary Layer

Creates a custom behavior that toggles a layer on when pressed, and off when released.

See the [momentary layer behavior](../keymaps/behaviors/layers.md#momentary-layer) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-momentary-layer.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-momentary-layer.yaml)

Applies to: `compatible = "zmk,behavior-momentary-layer"`

| Property         | Type | Description                                                     | Default |
| ---------------- | ---- | --------------------------------------------------------------- | ------- |
| `#binding-cells` | int  | Must be `<1>`                                                   |         |
| `locking`        | bool | Whether the behavior can lock and unlock layers in the on state | false   |

You can use the following node to tweak the default behavior:

| Node  | Behavior                                                          |
| ----- | ----------------------------------------------------------------- |
| `&mo` | [Momentary Layer](../keymaps/behaviors/layers.md#momentary-layer) |

## Layer Toggle

Creates a custom behavior that toggles a layer on, off, or switches between the two states.

See the [layer toggle behavior](../keymaps/behaviors/layers.md#toggle-layer) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-layer-toggle.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-layer-toggle.yaml)

Applies to: `compatible = "zmk,behavior-layer-toggle"`

| Property         | Type | Description                                                     | Default |
| ---------------- | ---- | --------------------------------------------------------------- | ------- |
| `#binding-cells` | int  | Must be `<1>`                                                   |         |
| `toggle-mode`    |      | One of `on`, `off`, and `flip`                                  | `flip`  |
| `locking`        | bool | Whether the behavior can lock and unlock layers in the on state | false   |

You can use the following node to tweak the default behavior:

| Node   | Behavior                                                    |
| ------ | ----------------------------------------------------------- |
| `&tog` | [Layer toggle](../keymaps/behaviors/layers.md#toggle-layer) |

## To Layer

Creates a custom behavior that toggles a layer on and toggles all other layers off, barring the default layer.

See the [to layer behavior](../keymaps/behaviors/layers.md#to-layer) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-to-layer.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-to-layer.yaml)

Applies to: `compatible = "zmk,behavior-to-layer"`

| Property         | Type | Description                                                     | Default |
| ---------------- | ---- | --------------------------------------------------------------- | ------- |
| `#binding-cells` | int  | Must be `<1>`                                                   |         |
| `locking`        | bool | Whether the behavior can lock and unlock layers in the on state | false   |

You can use the following node to tweak the default behavior:

| Node  | Behavior                                            |
| ----- | --------------------------------------------------- |
| `&to` | [To Layer](../keymaps/behaviors/layers.md#to-layer) |

## Macro

Creates a custom behavior which triggers a sequence of other behaviors.

See the [macro behavior](../keymaps/behaviors/macros.md) documentation for more details and examples.

### Kconfig

| Config                             | Type | Description                            | Default |
| ---------------------------------- | ---- | -------------------------------------- | ------- |
| `CONFIG_ZMK_MACRO_DEFAULT_WAIT_MS` | int  | Default value for `wait-ms` in macros. | 15      |
| `CONFIG_ZMK_MACRO_DEFAULT_TAP_MS`  | int  | Default value for `tap-ms` in macros.  | 30      |

### Devicetree

Definition files:

- [zmk/app/dts/bindings/behaviors/zmk,behavior-macro.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-macro.yaml)
- [zmk/app/dts/bindings/behaviors/zmk,behavior-macro-one-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-macro-one-param.yaml)
- [zmk/app/dts/bindings/behaviors/zmk,behavior-macro-two-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-macro-two-param.yaml)

| Property         | Type          | Description                                                                                                                                                                                          | Default                            |
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `compatible`     | string        | Macro type, **must be _one_ of**:<ul><li>`"zmk,behavior-macro"`</li><li>`"zmk,behavior-macro-one-param"`</li><li>`"zmk,behavior-macro-two-param"`</li></ul>                                          |                                    |
| `#binding-cells` | int           | Must be <ul><li>`<0>` if `compatible = "zmk,behavior-macro"`</li><li>`<1>` if `compatible = "zmk,behavior-macro-one-param"`</li><li>`<2>` if `compatible = "zmk,behavior-macro-two-param"`</li></ul> |                                    |
| `bindings`       | phandle array | List of behaviors to trigger                                                                                                                                                                         |                                    |
| `wait-ms`        | int           | The default time to wait (in milliseconds) before triggering the next behavior.                                                                                                                      | `CONFIG_ZMK_MACRO_DEFAULT_WAIT_MS` |
| `tap-ms`         | int           | The default time to wait (in milliseconds) between the press and release events of a tapped behavior.                                                                                                | `CONFIG_ZMK_MACRO_DEFAULT_TAP_MS`  |

With `compatible = "zmk,behavior-macro-one-param"` or `compatible = "zmk,behavior-macro-two-param"`, this behavior forwards the parameters it receives according to the `&macro_param_*` control behaviors noted below.

### Macro Control Behaviors

The following macro-specific behaviors can be added at any point in the `bindings` list to change how the macro triggers subsequent behaviors.

| Behavior                   | Description                                                                                                          |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `&macro_tap`               | Switches to tap mode                                                                                                 |
| `&macro_press`             | Switches to press mode                                                                                               |
| `&macro_release`           | Switches to release mode                                                                                             |
| `&macro_pause_for_release` | Pauses the macro until the macro key itself is released                                                              |
| `&macro_wait_time TIME`    | Changes the time to wait (in milliseconds) before triggering the next behavior.                                      |
| `&macro_tap_time TIME`     | Changes the time to wait (in milliseconds) between the press and release events of a tapped behavior.                |
| `&macro_param_1to1`        | Forward the first parameter received by the macro to the first parameter of the next (non-macro control) behavior.   |
| `&macro_param_1to2`        | Forward the first parameter received by the macro to the second parameter of the next (non-macro control) behavior.  |
| `&macro_param_2to1`        | Forward the second parameter received by the macro to the first parameter of the next (non-macro control) behavior.  |
| `&macro_param_2to2`        | Forward the second parameter received by the macro to the second parameter of the next (non-macro control) behavior. |

## Mod-Morph

Creates a custom behavior that triggers one of two behaviors depending on whether certain modifiers are held.

See the [mod-morph behavior](../keymaps/behaviors/mod-morph.md) documentation for more details and examples.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml)

Applies to: `compatible = "zmk,behavior-mod-morph"`

| Property         | Type          | Description                                                                       |
| ---------------- | ------------- | --------------------------------------------------------------------------------- |
| `#binding-cells` | int           | Must be `<0>`                                                                     |
| `bindings`       | phandle array | A list of two behaviors: one for normal press and one for mod morphed press       |
| `mods`           | int           | A bit field of modifiers. The morph behavior is used if any of these are pressed. |

See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers.

You can use the following nodes to tweak the default behaviors:

| Node     | Behavior                                          |
| -------- | ------------------------------------------------- |
| `&gresc` | [Grave escape](../keymaps/behaviors/mod-morph.md) |

## Sensor Rotation

Creates a custom behavior which sends a tap of other behaviors when a sensor is rotated.
Has two variants: with `compatible = "zmk,behavior-sensor-rotate"` it accepts no parameters when used, whereas with `compatible = "zmk,behavior-sensor-rotate-var"` it accepts two parameters.

See the [sensor rotation behavior](../keymaps/behaviors/sensor-rotate.md) documentation for more details and examples.

### Devicetree

Definition files:

- [zmk/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-sensor-rotate.yaml)
- [zmk/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-var.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-sensor-rotate-var.yaml)

Applies to: `compatible = "zmk,behavior-sensor-rotate"`

| Property                | Type     | Description                                                                                            | Default |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------ | ------- |
| `#sensor-binding-cells` | int      | Must be `<0>`                                                                                          |         |
| `bindings`              | phandles | A list of two behaviors to trigger for each rotation direction, must _include_ any behavior parameters |         |
| `tap-ms`                | int      | The tap duration (between press and release events) in milliseconds for behaviors in `bindings`        | 5       |

Applies to: `compatible = "zmk,behavior-sensor-rotate-var"`

| Property                | Type          | Description                                                                                            | Default |
| ----------------------- | ------------- | ------------------------------------------------------------------------------------------------------ | ------- |
| `#sensor-binding-cells` | int           | Must be `<2>`                                                                                          |         |
| `bindings`              | phandle array | A list of two behaviors to trigger for each rotation direction, must _exclude_ any behavior parameters |         |
| `tap-ms`                | int           | The tap duration (between press and release events) in milliseconds for behaviors in `bindings`        | 5       |

With `compatible = "zmk,behavior-sensor-rotate-var"`, this behavior forwards the first parameter it receives to the parameter of the first behavior specified in `bindings`, and second parameter to the parameter of the second behavior.

## Sticky Key

Creates a custom behavior that triggers a behavior and keeps it pressed it until another key is pressed and released.

See the [sticky key behavior](../keymaps/behaviors/sticky-key.md) and [sticky layer behavior](../keymaps/behaviors/sticky-layer.md) documentation for more details and examples.

### Kconfig

| Config                                    | Type | Description                                     | Default |
| ----------------------------------------- | ---- | ----------------------------------------------- | ------- |
| `CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD` | int  | Maximum number of simultaneous held sticky keys | 10      |

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-sticky-key.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-sticky-key.yaml)

Applies to: `compatible = "zmk,behavior-sticky-key"`

| Property           | Type     | Description                                                              | Default |
| ------------------ | -------- | ------------------------------------------------------------------------ | ------- |
| `#binding-cells`   | int      | Must be `<1>`                                                            |         |
| `bindings`         | phandles | A behavior (without parameters) to trigger                               |         |
| `release-after-ms` | int      | Releases the key after this many milliseconds if no other key is pressed | 1000    |
| `quick-release`    | bool     | Release the sticky key on the next key press instead of release          | false   |
| `lazy`             | bool     | Wait until the next key press to activate the sticky key behavior        | false   |
| `ignore-modifiers` | bool     | If enabled, pressing a modifier key does not cancel the sticky key       | true    |

This behavior forwards the one parameter it receives to the parameter of the behavior specified in `bindings`.

You can use the following nodes to tweak the default behaviors:

| Node  | Behavior                                             |
| ----- | ---------------------------------------------------- |
| `&sk` | [Sticky key](../keymaps/behaviors/sticky-key.md)     |
| `&sl` | [Sticky layer](../keymaps/behaviors/sticky-layer.md) |

## Tap Dance

Creates a custom behavior that triggers a different behavior corresponding to the number of times the key is tapped.

See the [tap dance behavior](../keymaps/behaviors/tap-dance.mdx) documentation for more details and examples.

### Kconfig

| Config                                   | Type | Description                                    | Default |
| ---------------------------------------- | ---- | ---------------------------------------------- | ------- |
| `CONFIG_ZMK_BEHAVIOR_TAP_DANCE_MAX_HELD` | int  | Maximum number of simultaneous held tap-dances | 10      |

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml)

Applies to: `compatible = "zmk,behavior-tap-dance"`

| Property          | Type          | Description                                                                                  | Default |
| ----------------- | ------------- | -------------------------------------------------------------------------------------------- | ------- |
| `#binding-cells`  | int           | Must be `<0>`                                                                                |         |
| `bindings`        | phandle array | A list of behaviors from which to select                                                     |         |
| `tapping-term-ms` | int           | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200     |

## Two Axis Input

This behavior is part of the core [pointing devices](../features/pointing.md) feature, and is used to generate X/Y and scroll input events. It is the underlying behavior used for the mouse [move](../keymaps/behaviors/mouse-emulation.md#mouse-move) and [scroll](../keymaps/behaviors/mouse-emulation.md#mouse-scroll) behaviors.

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-input-two-axis.yaml)

Applies to: `compatible = "zmk,behavior-input-two-axis"`

| Property                | Type | Description                                                                                                                                                                                   | Default |
| ----------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `#binding-cells`        | int  | Must be `<1>`                                                                                                                                                                                 |         |
| `x-input-code`          | int  | The [relative event code](https://github.com/zmkfirmware/zephyr/blob/v4.1.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L258) for generated input events for the X-axis. |         |
| `y-input-code`          | int  | The [relative event code](https://github.com/zmkfirmware/zephyr/blob/v4.1.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L258) for generated input events for the Y-axis. |         |
| `trigger-period-ms`     | int  | How many milliseconds between generated input events based on the current speed/direction.                                                                                                    | 16      |
| `delay-ms`              | int  | How many milliseconds to delay any processing or event generation when first pressed.                                                                                                         | 0       |
| `time-to-max-speed-ms`  | int  | How many milliseconds it takes to accelerate to the curren max speed.                                                                                                                         | 0       |
| `acceleration-exponent` | int  | The acceleration exponent to apply: `0` - uniform speed, `1` - uniform acceleration, `2` - linear acceleration                                                                                | 1       |

---

### Docs Config Bluetooth (`docs/docs/config/bluetooth.md`)

---
title: Bluetooth Configuration
sidebar_label: Bluetooth
---

See the [bluetooth feature page](../features/bluetooth.md) for more details on the general Bluetooth functionality in ZMK.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

| Option                                 | Type | Description                                                                                                                                                                                                        | Default |
| -------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
| `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN`     | bool | Enables a combination of settings that are planned to be default in future versions of ZMK to improve connection stability. Currently this only disables 2M PHY support.                                           | n       |
| `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`      | bool | Enables a combination of settings that are planned to be officially supported in the future. This includes enabling BT Secure Connection passkey entry, and allows overwrite of keys from previously paired hosts. | n       |
| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`.                                                                                                       | n       |
| `CONFIG_ZMK_BLE_PASSKEY_ENTRY`         | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.)                                                                      | n       |
| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION`  | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications.                                                                                            | y       |
| `CONFIG_BT_DEVICE_APPEARANCE`          | int  | Bluetooth device [appearance value](https://bluetooth.com/specifications/assigned-numbers) (should be converted from hexadecimal to decimal).                                                                      | 961     |

---

### Docs Config Bootloader (`docs/docs/config/bootloader.md`)

---
title: Bootloader Integration Configuration
sidebar_label: Bootloader Integration
---

These are general settings that control the various bootloader integration features of ZMK.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition file: [zmk/app/src/boot/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/boot/Kconfig)

### Double Tap To Bootloader

Some SoCs, like RP2040 or the various STM32 offerings, require holding a certain "boot button" down to enter the bootloader, on a hardware level. To make it easier to enter the bootloader on those platforms, ZMK integrates an optional feature to allow entering the bootloader by simply double tapping reset within the configured timeout period.

| Config                                     | Type | Description                                                         | Default                     |
| ------------------------------------------ | ---- | ------------------------------------------------------------------- | --------------------------- |
| `CONFIG_ZMK_DBL_TAP_BOOTLOADER`            | bool | Enable the double-tap to enter bootloader functionality             | y if STM32 or RP2040/RP2350 |
| `CONFIG_ZMK_DBL_TAP_BOOTLOADER_TIMEOUT_MS` | int  | Duration (in ms) to wait for a second reset to enter the bootloader | 500                         |

### STM32 nBOOT_SEL Option Byte Setup

Some newer STM32 series SoCs, in particular stm32c0 and stm32g0, enable the `nBOOT_SEL` bit of the option bytes by default. This bit prevents entering the system ROM bootloader by holding the BOOT0 pin/button during a reset/startup.

To ensure the BOOT button on keyboard and controllers using these SoCs works as expected after being flashed with ZMK, we check the `nBOOT_SEL` bit on startup and clear it if it is set. Should you _not_ want that functionality, for some reason, this can be disabled.

| Config                                    | Type | Description                           | Default                 |
| ----------------------------------------- | ---- | ------------------------------------- | ----------------------- |
| `CONFIG_ZMK_BOOT_STM32_ENFORCE_NBOOT_SEL` | bool | Ensure the `nBOOT_SEL` bit is not set | y if STM32CO or STM32G0 |

### Bootmode Magic Value Mapper

Some target SoCs may use the bootmode magic value mapper for [bootloader integration](docs/hardware-integration/bootloader/index.mdx). When doing so, the following configurations are used:

| Config                                                           | Type | Description                                                                           | Default |
| ---------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_BOOTMODE_BOOTLOADER_MAGIC_VALUE`                     | hex  | The magic value to place into retained memory when the bootloader boot mode is set    | none    |
| `CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_TINYUF2`        | bool | Used to default the bootloader magic value for the tinyuf2 bootloader                 | false   |
| `CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_ADAFRUIT_BOSSA` | bool | Used to default the bootloader magic value for the Adafruit BOSSA (SAMD21) bootloader | false   |
| `CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_ADAFRUIT_NRF52` | bool | Used to default the bootloader magic value for the Adafruit nRF52 bootloader          | false   |

---

### Docs Config Combos (`docs/docs/config/combos.md`)

---
title: Combo Configuration
sidebar_label: Combos
---

See the [Combos informational page](../keymaps/combos.md) for more details and examples.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                                | Type | Description                                                  | Default |
| ------------------------------------- | ---- | ------------------------------------------------------------ | ------- |
| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int  | Maximum number of combos that can be active at the same time | 4       |

## Devicetree

Applies to: `compatible = "zmk,combos"`

Definition file: [zmk/app/dts/bindings/zmk,combos.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ccombos.yaml)

The `zmk,combos` node itself has no properties. It should have one child node per combo.

Each child node can have the following properties:

| Property                | Type          | Description                                                                                                                               | Default       |
| ----------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `bindings`              | phandle-array | A [behavior](../keymaps/index.mdx#behaviors) to run when the combo is triggered                                                           |               |
| `key-positions`         | array         | A list of key position indices for the keys which should trigger the combo                                                                |               |
| `timeout-ms`            | int           | All the keys in `key-positions` must be pressed within this time in milliseconds to trigger the combo                                     | 50            |
| `require-prior-idle-ms` | int           | If any non-modifier key is pressed within `require-prior-idle-ms` before a key in the combo, the key will not be considered for the combo | -1 (disabled) |
| `slow-release`          | bool          | Releases the combo when all keys are released instead of when any key is released                                                         | false         |
| `layers`                | array         | A list of layers on which the combo may be triggered. Omission of this property allows all layers.                                        |               |

---

### Docs Config Displays (`docs/docs/config/displays.md`)

---
title: Display Configuration
sidebar_label: Displays
---

See the [displays feature page](../features/displays.md) for more details.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition files:

- [zmk/app/src/display/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/display/Kconfig)
- [zmk/app/src/display/widgets/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/display/widgets/Kconfig)

| Config                                             | Type | Description                                                    | Default      |
| -------------------------------------------------- | ---- | -------------------------------------------------------------- | ------------ |
| `CONFIG_ZMK_DISPLAY`                               | bool | Enable support for displays                                    | n            |
| `CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE`                 | bool | Blank display on idle                                          | y if SSD1306 |
| `CONFIG_ZMK_DISPLAY_TICK_PERIOD_MS`                | int  | Period (in ms) between display task execution                  | 10           |
| `CONFIG_ZMK_DISPLAY_INVERT`                        | bool | Invert display colors from black-on-white to white-on-black    | n            |
| `CONFIG_ZMK_WIDGET_LAYER_STATUS`                   | bool | Enable a widget to show the highest, active layer              | y            |
| `CONFIG_ZMK_WIDGET_BATTERY_STATUS`                 | bool | Enable a widget to show battery charge information             | y            |
| `CONFIG_ZMK_WIDGET_BATTERY_STATUS_SHOW_PERCENTAGE` | bool | If battery widget is enabled, show percentage instead of icons | n            |
| `CONFIG_ZMK_WIDGET_OUTPUT_STATUS`                  | bool | Enable a widget to show the current output (USB/BLE)           | y            |
| `CONFIG_ZMK_WIDGET_WPM_STATUS`                     | bool | Enable a widget to show words per minute                       | n            |

Note that `CONFIG_ZMK_DISPLAY_INVERT` setting might not work as expected with custom status screens that utilize images.

If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set.

| Config                                      | Description                    |
| ------------------------------------------- | ------------------------------ |
| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | Use the built-in status screen |
| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM`   | Use a custom status screen     |

If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set.

| Config                                    | Description                               |
| ----------------------------------------- | ----------------------------------------- |
| `CONFIG_ZMK_DISPLAY_WORK_QUEUE_SYSTEM`    | Use the system main thread for UI updates |
| `CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED` | Use a dedicated thread for UI updates     |

Using a dedicated thread requires more memory but prevents displays with slow updates (e.g. E-paper) from delaying key scanning and other processes. If enabled, the following options configure the thread:

| Config                                           | Type | Description                  | Default |
| ------------------------------------------------ | ---- | ---------------------------- | ------- |
| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_STACK_SIZE` | int  | Stack size for the UI thread | 2048    |
| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY`   | int  | Priority for the UI thread   | 5       |

You must also configure the driver for your display. ZMK provides the following display drivers:

- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/display/Kconfig.il0323)

Zephyr provides several display drivers as well. Search for the name of your display in [Zephyr's Kconfig options](https://docs.zephyrproject.org/4.1.0/kconfig.html) documentation.

## Devicetree

See the Devicetree bindings for your display. Here are the bindings for common displays:

- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/display/gooddisplay%2Cil0323.yaml)
- [SSD1306 (i2c)](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/display/solomon,ssd1306fb-i2c.html)
- [SSD1306 (spi)](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/display/solomon,ssd1306fb-spi.html)

A full list of drivers provided by Zephyr can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings.html).

### Chosen nodes

Applies to: [`/chosen` node](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes)

| Property          | Type | Description                                                                                              |
| ----------------- | ---- | -------------------------------------------------------------------------------------------------------- |
| `zephyr,display`  | path | The display device to use.                                                                               |
| `zmk,display-led` | path | The LED device to use for on/off blanking, if the hardware requires it. Can be a PWM or GPIO LED device. |

---

### Docs Config Encoders (`docs/docs/config/encoders.md`)

---
title: Encoder Configuration
sidebar_label: Encoders
---

See the [Encoders informational page](../features/encoders.md) for more details, including instructions for adding encoder support to a board.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## EC11 Encoders

### Kconfig

Definition file: [zmk/app/module/drivers/sensor/ec11/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/sensor/ec11/Kconfig)

| Config                          | Type | Description                      | Default |
| ------------------------------- | ---- | -------------------------------- | ------- |
| `CONFIG_EC11`                   | bool | Enable EC11 encoders             | n       |
| `CONFIG_EC11_THREAD_PRIORITY`   | int  | Priority of the encoder thread   | 10      |
| `CONFIG_EC11_THREAD_STACK_SIZE` | int  | Stack size of the encoder thread | 1024    |

If `CONFIG_EC11` is enabled, exactly one of the following options must be set to `y`:

| Config                              | Type | Description                                     |
| ----------------------------------- | ---- | ----------------------------------------------- |
| `CONFIG_EC11_TRIGGER_NONE`          | bool | No trigger (encoders are disabled)              |
| `CONFIG_EC11_TRIGGER_GLOBAL_THREAD` | bool | Process encoder interrupts on the global thread |
| `CONFIG_EC11_TRIGGER_OWN_THREAD`    | bool | Process encoder interrupts on their own thread  |

### Devicetree

#### Keymap sensor config

For shields/boards that export a `sensors` node configuration label, both global and per-sensor settings can be set by overriding the properties there.

To override the general settings, update them on the exported `sensors` node, e.g.:

```
&sensors {
    triggers-per-rotation = <18>;
};
```

Per sensor overrides can be added with ordered nested nodes with the correct overrides, e.g.:

```
&sensors {
    left_config {
        triggers-per-rotation = <18>;
    };

    right_config {
        triggers-per-rotation = <24>;
    };
};
```

The names of the child nodes are not important, and are applied in order to the sensors listed in the `sensors` property of the sensors node.

Applies to the node and child nodes of: `compatible = "zmk,keymap-sensors"`

Definition file: [zmk/app/drivers/zephyr/dts/bindings/zmk,keymap-sensors.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/zmk%2Ckeymap-sensors.yaml)

| Property                | Type | Description                                                     | Default |
| ----------------------- | ---- | --------------------------------------------------------------- | ------- |
| `triggers-per-rotation` | int  | Number of times to trigger the bound behavior per full rotation |         |

#### EC11 nodes

Applies to: `compatible = "alps,ec11"`

Definition file: [zmk/app/module/dts/bindings/sensor/alps,ec11.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/sensor/alps%2Cec11.yaml)

| Property  | Type       | Description                                    | Default |
| --------- | ---------- | ---------------------------------------------- | ------- |
| `a-gpios` | GPIO array | GPIO connected to the encoder's A pin          |         |
| `b-gpios` | GPIO array | GPIO connected to the encoder's B pin          |         |
| `steps`   | int        | Number of encoder pulses per complete rotation |         |

---

### Docs Config Keymap (`docs/docs/config/keymap.md`)

---
title: Keymap Configuration
sidebar_label: Keymap
---

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Keymap

### Devicetree

Applies to: `compatible = "zmk,keymap"`

Definition file: [zmk/app/dts/bindings/zmk,keymap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckeymap.yaml)

The `zmk,keymap` node itself has no properties. It should have one child node per layer of the keymap, starting with the default layer (layer 0).

Each child node can have the following properties:

| Property          | Type          | Description                                                          |
| ----------------- | ------------- | -------------------------------------------------------------------- |
| `display-name`    | string        | Name for the layer in ZMK Studio and on displays                     |
| `bindings`        | phandle-array | List of [key behaviors](../keymaps/index.mdx#behaviors), one per key |
| `sensor-bindings` | phandle-array | List of sensor behaviors, one per sensor                             |

Items for `bindings` must be listed in the order the keys are defined in the [keyboard scan configuration](kscan.md).

Items for `sensor-bindings` must be listed in the order the [sensors](#keymap-sensors) are defined.

## Keymap Sensors

### Devicetree

Applies to: `compatible = "zmk,keymap-sensors"`

| Property  | Type     | Description          |
| --------- | -------- | -------------------- |
| `sensors` | phandles | List of sensor nodes |

The following types of nodes can be used as a sensor:

- [`alps,ec11`](encoders.md#ec11-encoders)

---

### Docs Config Kscan (`docs/docs/config/kscan.md`)

---
title: Keyboard Scan Configuration
sidebar_label: Keyboard Scan
---

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Common

### Kconfig

Definition files:

- [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)
- [zmk/app/module/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/kscan/Kconfig)

| Config                                 | Type | Description                                          | Default |
| -------------------------------------- | ---- | ---------------------------------------------------- | ------- |
| `CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE`    | int  | Size of the event queue for kscan events             | 4       |
| `CONFIG_ZMK_KSCAN_INIT_PRIORITY`       | int  | Keyboard scan device driver initialization priority  | 40      |
| `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS`   | int  | Global debounce time for key press in milliseconds   | -1      |
| `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS` | int  | Global debounce time for key release in milliseconds | -1      |

If the debounce press/release values are set to any value other than `-1`, they override the `debounce-press-ms` and `debounce-release-ms` devicetree properties for all keyboard scan drivers which support them. See the [debouncing documentation](../features/debouncing.md) for more details.

### Devicetree

Applies to: [`/chosen` node](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes)

| Property               | Type | Description                                                            |
| ---------------------- | ---- | ---------------------------------------------------------------------- |
| `zmk,kscan`            | path | The node for the keyboard scan driver to use                           |
| `zmk,matrix-transform` | path | The node for the [matrix transform](layout.md#matrix-transform) to use |

## Demux Driver

Keyboard scan driver which works like a regular matrix but uses a demultiplexer to drive the rows or columns. This allows N GPIOs to drive N<sup>2</sup> rows or columns instead of just N like with a regular matrix.

:::note
Currently this driver does not honor the `CONFIG_ZMK_KSCAN_DEBOUNCE_*` settings.
:::

### Devicetree

Applies to: `compatible = "zmk,kscan-gpio-demux"`

Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-demux.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/kscan/zmk%2Ckscan-gpio-demux.yaml)

| Property                | Type       | Description                      | Default |
| ----------------------- | ---------- | -------------------------------- | ------- |
| `input-gpios`           | GPIO array | Input GPIOs                      |         |
| `output-gpios`          | GPIO array | Demultiplexer address GPIOs      |         |
| `debounce-period`       | int        | Debounce period in milliseconds  | 5       |
| `polling-interval-msec` | int        | Polling interval in milliseconds | 25      |

## Direct GPIO Driver

Keyboard scan driver where each key has a dedicated GPIO.

### Kconfig

Definition file: [zmk/app/module/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/kscan/Kconfig)

| Config                            | Type | Description                                      | Default |
| --------------------------------- | ---- | ------------------------------------------------ | ------- |
| `CONFIG_ZMK_KSCAN_DIRECT_POLLING` | bool | Poll for key presses instead of using interrupts | n       |

### Devicetree

Applies to: `compatible = "zmk,kscan-gpio-direct"`

Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/kscan/zmk%2Ckscan-gpio-direct.yaml)

| Property                  | Type       | Description                                                                                                | Default |
| ------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | ------- |
| `input-gpios`             | GPIO array | Input GPIOs (one per key). Can be either direct GPIO pin or `gpio-key` references                          |         |
| `debounce-press-ms`       | int        | Debounce time for key press in milliseconds. Use 0 for eager debouncing                                    | 5       |
| `debounce-release-ms`     | int        | Debounce time for key release in milliseconds                                                              | 5       |
| `debounce-scan-period-ms` | int        | Time between reads in milliseconds when any key is pressed                                                 | 1       |
| `poll-period-ms`          | int        | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_DIRECT_POLLING` is enabled | 10      |
| `toggle-mode`             | bool       | Use toggle switch mode                                                                                     | n       |
| `wakeup-source`           | bool       | Mark this kscan instance as able to wake the keyboard                                                      | n       |

Assuming the switches connect each GPIO pin to the ground, the [GPIO flags](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/gpio.html#api-reference) for the elements in `input-gpios` should be `(GPIO_ACTIVE_LOW | GPIO_PULL_UP)`:

```dts
    kscan0: kscan {
        compatible = "zmk,kscan-gpio-direct";
        wakeup-source;
        input-gpios
            = <&pro_micro 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
            , <&pro_micro 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
            ;
    };
```

A direct pin defined in the `input-gpios` property is considered a column when used in a [matrix transform](layout.md#matrix-transform); e.g. the 5th pin on the list can be referred to using `RC(0,4)`.

By default, a switch will drain current through the internal pull up/down resistor whenever it is pressed. This is not ideal for a toggle switch, where the switch may be left in the "pressed" state for a long time. Enabling `toggle-mode` will make the driver enable and disable the internal pull up/down resistor as needed when the switch is toggled to minimise power draw. For `toggle-mode` to work correctly each pole of the switch needs a dedicated GPIO pin.

`toggle-mode` applies to all switches handled by the instance of the driver. To use a toggle switch with other, non-toggle, direct GPIO switches, create two instances of the direct GPIO driver, one with `toggle-mode` and the other without. Then, use a [composite driver](#composite-driver) to combine them. The state of the switch is read on power on, so if the switch is moved whilst the board is off this will get correctly interpreted by the driver.

When using `toggle-mode` the pull resistors get automatically set by the driver and should not be set in the devicetree via GPIO flags. Assuming the common pole of the switch is connected to ground with an SP3T switch:

```dts
    kscan_sp3t_toggle: kscan_sp3t_toggle {
        compatible = "zmk,kscan-gpio-direct";
        toggle-mode;

        input-gpios
        = <&pro_micro 4 GPIO_ACTIVE_LOW>
        , <&pro_micro 3 GPIO_ACTIVE_LOW>
        , <&pro_micro 2 GPIO_ACTIVE_LOW>
        ;
    };
```

## Matrix Driver

Keyboard scan driver where keys are arranged on a matrix with one GPIO per row and column.

Definition file: [zmk/app/module/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/kscan/Kconfig)

| Config                                         | Type        | Description                                                               | Default |
| ---------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_KSCAN_MATRIX_POLLING`              | bool        | Poll for key presses instead of using interrupts                          | n       |
| `CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS`   | int (ticks) | How long to wait before reading input pins after setting output active    | 0       |
| `CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0       |

### Devicetree

Applies to: `compatible = "zmk,kscan-gpio-matrix"`

Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/kscan/zmk%2Ckscan-gpio-matrix.yaml)

| Property                  | Type       | Description                                                                                                | Default     |
| ------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | ----------- |
| `row-gpios`               | GPIO array | Matrix row GPIOs in order, starting from the top row                                                       |             |
| `col-gpios`               | GPIO array | Matrix column GPIOs in order, starting from the leftmost row                                               |             |
| `debounce-press-ms`       | int        | Debounce time for key press in milliseconds. Use 0 for eager debouncing                                    | 5           |
| `debounce-release-ms`     | int        | Debounce time for key release in milliseconds                                                              | 5           |
| `debounce-scan-period-ms` | int        | Time between reads in milliseconds when any key is pressed                                                 | 1           |
| `diode-direction`         | string     | The direction of the matrix diodes                                                                         | `"row2col"` |
| `poll-period-ms`          | int        | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_MATRIX_POLLING` is enabled | 10          |
| `wakeup-source`           | bool       | Mark this kscan instance as able to wake the keyboard                                                      | n           |

The `diode-direction` property must be one of:

| Value       | Description                                                           |
| ----------- | --------------------------------------------------------------------- |
| `"row2col"` | Diodes point from rows to columns (cathodes are connected to columns) |
| `"col2row"` | Diodes point from columns to rows (cathodes are connected to rows)    |

Given the `diode-direction`, the [GPIO flags](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/gpio.html#api-reference) for the elements in `row-` and `col-gpios` should be set appropriately.
The output pins (e.g. columns for `col2row`) should have the flag `GPIO_ACTIVE_HIGH`, and input pins (e.g. rows for `col2row`) should have the flags `(GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)`:

```dts
    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
        wakeup-source;
        diode-direction = "col2row";
        col-gpios
            = <&pro_micro 4 GPIO_ACTIVE_HIGH>
            , <&pro_micro 5 GPIO_ACTIVE_HIGH>
            ;
        row-gpios
            = <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            ;
    };
```

## Charlieplex Driver

Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output.

- With `interrupt-gpios` unset, this allows n pins to drive n\*(n-1) keys.
- With `interrupt-gpios` set, n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling.

Definition file: [zmk/app/module/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/kscan/Kconfig)

| Config                                              | Type        | Description                                                               | Default |
| --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS`   | int (ticks) | How long to wait before reading input pins after setting output active    | 0       |
| `CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0       |

### Devicetree

Applies to: `compatible = "zmk,kscan-gpio-charlieplex"`

Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/kscan/zmk%2Ckscan-gpio-charlieplex.yaml)

| Property                  | Type       | Description                                                                                 | Default |
| ------------------------- | ---------- | ------------------------------------------------------------------------------------------- | ------- |
| `gpios`                   | GPIO array | GPIOs used, listed in order.                                                                |         |
| `interrupt-gpios`         | GPIO array | A single GPIO to use for interrupt. Leaving this empty will enable continuous polling.      |         |
| `debounce-press-ms`       | int        | Debounce time for key press in milliseconds. Use 0 for eager debouncing.                    | 5       |
| `debounce-release-ms`     | int        | Debounce time for key release in milliseconds.                                              | 5       |
| `debounce-scan-period-ms` | int        | Time between reads in milliseconds when any key is pressed.                                 | 1       |
| `poll-period-ms`          | int        | Time between reads in milliseconds when no key is pressed and `interrupt-gpois` is not set. | 10      |
| `wakeup-source`           | bool       | Mark this kscan instance as able to wake the keyboard                                       | n       |

Define the transform with a [matrix transform](layout.md#matrix-transform). The row is always the driven pin, and the column always the receiving pin (input to the controller).
For example, in `RC(5,0)` power flows from the 6th pin in `gpios` to the 1st pin in `gpios`.
Exclude all positions where the row and column are the same as these pairs will never be triggered, since no pin can be both input and output at the same time.

The [GPIO flags](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/gpio.html#api-reference) for the elements in `gpios` should be `GPIO_ACTIVE_HIGH`, and interrupt pins set in `interrupt-gpios` should have the flags `(GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)`.

## Composite Driver

Keyboard scan driver which combines multiple other keyboard scan drivers.

### Devicetree

Applies to : `compatible = "zmk,kscan-composite"`

Definition file: [zmk/app/dts/bindings/zmk,kscan-composite.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk,kscan-composite.yaml)

| Property        | Type | Description                                           | Default |
| --------------- | ---- | ----------------------------------------------------- | ------- |
| `rows`          | int  | The number of rows in the composite matrix            |         |
| `columns`       | int  | The number of columns in the composite matrix         |         |
| `wakeup-source` | bool | Mark this kscan instance as able to wake the keyboard | n       |

The `zmk,kscan-composite` node should have one child node per keyboard scan driver that should be composited. Each child node can have the following properties:

| Property     | Type    | Description                                                                    | Default |
| ------------ | ------- | ------------------------------------------------------------------------------ | ------- |
| `kscan`      | phandle | Label of the kscan driver to include                                           |         |
| `row-offset` | int     | Shifts row 0 of the included driver to a new row in the composite matrix       | 0       |
| `col-offset` | int     | Shifts column 0 of the included driver to a new column in the composite matrix | 0       |

If you want one of the composited kscans to be able to wake up the keyboard, make sure to set the `wakeup-source` property in its own definition, in addition to setting it for the composite kscan node itself as listed above.

### Example Configuration

For example, consider a macropad with a 3x3 matrix and two direct GPIO keys:

<div style={{columns: 2, marginBottom: '1em'}}>
<div style={{breakInside: 'avoid'}}>
Matrix:

<table>
    <thead>
        <tr><th></th><th>Col 0</th><th>Col 1</th><th>Col 2</th></tr>
    </thead>
    <tbody>
        <tr><th>Row 0</th><td>A0</td><td>A1</td><td>A2</td></tr>
        <tr><th>Row 1</th><td>A3</td><td>A4</td><td>A5</td></tr>
        <tr><th>Row 2</th><td>A6</td><td>A7</td><td>A8</td></tr>
    </tbody>
</table>
</div>

<div style={{breakInside: 'avoid'}}>
Direct GPIO:

<table>
    <thead>
        <tr><th></th><th>Col 0</th><th>Col 1</th></tr>
    </thead>
    <tbody>
        <tr><th>Row 0</th><td>B0</td><td>B1</td></tr>
    </tbody>
</table>
</div>
</div>

To combine them, we need to create a composite matrix with enough rows and columns to fit both sets of keys without overlapping, then set row and/or columns offsets to shift them so they do not overlap.

One possible way to do this is a 3x4 matrix where the direct GPIO keys are shifted to below the matrix keys...

<table>
    <thead>
        <tr><th></th><th>Col 0</th><th>Col 1</th><th>Col 2</th></tr>
    </thead>
    <tbody>
        <tr><th>Row 0</th><td>A0</td><td>A1</td><td>A2</td></tr>
        <tr><th>Row 1</th><td>A3</td><td>A4</td><td>A5</td></tr>
        <tr><th>Row 2</th><td>A6</td><td>A7</td><td>A8</td></tr>
        <tr><th>Row 3</th><td>B0</td><td>B1</td><td>(none)</td></tr>
    </tbody>
</table>

...which can be configured with the following Devicetree code:

```dts
/ {
    chosen {
        zmk,kscan = &kscan0;
    };

    kscan0: kscan_composite {
        compatible = "zmk,kscan-composite";
        rows = <4>;
        columns = <3>;

        // Include the matrix driver
        matrix {
            kscan = <&kscan1>;
        };

        // Include the direct GPIO driver...
        direct {
            kscan = <&kscan2>;
            row-offset = <3>; // ...and shift it to not overlap
        };
    };

    kscan1: kscan_matrix {
        compatible = "zmk,kscan-gpio-matrix";
        // define 3x3 matrix here...
    };

    kscan2: kscan_direct {
        compatible = "zmk,kscan-gpio-direct";
        // define 2 direct GPIOs here...
    };
};
```

## Mock Driver

Mock keyboard scan driver that simulates key events.

### Devicetree

Applies to: `compatible = "zmk,kscan-mock"`

Definition file: [zmk/app/dts/bindings/zmk,kscan-mock.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckscan-mock.yaml)

| Property       | Type  | Description                                   | Default |
| -------------- | ----- | --------------------------------------------- | ------- |
| `event-period` | int   | Milliseconds between each generated event     |         |
| `events`       | array | List of key events to simulate                |         |
| `rows`         | int   | The number of rows in the composite matrix    |         |
| `columns`      | int   | The number of columns in the composite matrix |         |
| `exit-after`   | bool  | Exit the program after running all events     | false   |

The `events` array should be defined using the macros from [app/module/include/dt-bindings/zmk/kscan_mock.h](https://github.com/zmkfirmware/zmk/blob/main/app/module/include/dt-bindings/zmk/kscan_mock.h).

## Kscan Sideband Behavior Driver

The Kscan sideband behaviors node can be used to assign behaviors to keys in a manner distinctly separate from the keymap. These assignments and definitions will not be affected by nor have any effect on the keymap.

### Devicetree

Applies to: `compatible = "zmk,kscan-sideband-behaviors"`

Definition file: [zmk/app/dts/bindings/kscan/zmk,matrix-transform.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/kscan/zmk%2Ckscan-sideband-behaviors.yaml)

| Property        | Type    | Description                                               |
| --------------- | ------- | --------------------------------------------------------- |
| `kscan`         | phandle | Phandle to a kscan containing keys to assign behaviors to |
| `auto-enable`   | bool    | Enables the sideband instance on startup unconditionally  |
| `wakeup-source` | bool    | Mark this kscan instance as able to wake the keyboard     |

If `auto-enable` is not set, then the sideband behavior will wait for an external activation source before being enabled, e.g. being assigned as the chosen `zmk,kscan`. The `kscan` can contain additional keys, which will be used by the keymap if this node is set as the chosen `zmk,kscan` and has an appropriate matrix transformation defined.

Each child node should have the following properties:

| Property   | Type          | Description                                                                           | Default |
| ---------- | ------------- | ------------------------------------------------------------------------------------- | ------- |
| `row`      | int           | The row index of the key in the `kscan` to intercept and trigger a behavior for       | 0       |
| `column`   | int           | The column index of the key in the `kscan` to intercept and trigger a behavior for    |         |
| `bindings` | phandle-array | The behavior that should be triggered when the matching row and column event triggers |         |

---

### Docs Config Layout (`docs/docs/config/layout.md`)

---
title: Layout Configuration
sidebar_label: Layout
---

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Matrix Transform

Defines a mapping from keymap logical positions to physical [kscan](./kscan.md) positions.

You can define multiple matrix transform nodes, one for each layout, and users can select which one they want from the `/chosen` node in their keymaps.

See the [new shield guide](../hardware-integration/new-shield.mdx#matrix-transform) for more documentation on how to define a matrix transform.

### Devicetree

Applies to: `compatible = "zmk,matrix-transform"`

Definition file: [zmk/app/dts/bindings/zmk,matrix-transform.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cmatrix-transform.yaml)

| Property     | Type  | Description                                                           | Default |
| ------------ | ----- | --------------------------------------------------------------------- | ------- |
| `rows`       | int   | Number of rows in the transformed matrix                              |         |
| `columns`    | int   | Number of columns in the transformed matrix                           |         |
| `row-offset` | int   | Adds an offset to all rows before looking them up in the transform    | 0       |
| `col-offset` | int   | Adds an offset to all columns before looking them up in the transform | 0       |
| `map`        | array | A list of position transforms                                         |         |

The `map` array should be defined using the `RC()` macro from [dt-bindings/zmk/matrix_transform.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/matrix_transform.h). It should have one item per logical position in the keymap. Each item should list the physical row and column that should trigger the key in that position.

### Example: Skipping Unused Positions

Any keyboard which is not a grid of 1 unit keys will likely have some unused positions in the matrix. A matrix transform can be used to skip the unused positions so users don't have to set them to `&none` in keymaps.

```dts
// numpad.overlay
/ {
    chosen {
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &default_transform;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
        // define row-gpios with 5 elements and col-gpios with 4...
    };

    default_transform: matrix_transform {
        compatible = "zmk,matrix-transform";
        rows = <5>;
        columns = <4>;
        // ┌───┬───┬───┬───┐
        // │NUM│ / │ * │ - │
        // ├───┼───┼───┼───┤
        // │ 7 │ 8 │ 9 │ + │
        // ├───┼───┼───┤   │
        // │ 4 │ 5 │ 6 │   │
        // ├───┼───┼───┼───┤
        // │ 1 │ 2 │ 3 │RET│
        // ├───┴───┼───┤   │
        // │ 0     │ . │   │
        // └───────┴───┴───┘
        map = <
            RC(0,0) RC(0,1) RC(0,2) RC(0,3)
            RC(1,0) RC(1,1) RC(1,2) RC(1,3)
            RC(2,0) RC(2,1) RC(2,2)
            RC(3,0) RC(3,1) RC(3,2) RC(3,3)
            RC(4,0)         RC(4,1)
        >;
    };
};
```

```dts
// numpad.keymap
/ {
    keymap {
        compatible = "zmk,keymap";
        default {
            bindings = <
                &kp KP_NUM &kp KP_DIV &kp KP_MULT &kp KP_MINUS
                &kp KP_N7  &kp KP_N8  &kp KP_N9   &kp KP_PLUS
                &kp KP_N4  &kp KP_N5  &kp KP_N6
                &kp KP_N1  &kp KP_N2  &kp KP_N3   &kp KP_ENTER
                &kp KP_N0             &kp KP_DOT
            >;
        };
    }
};
```

### Example: Non-standard Matrix

Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-design/page/matrices-and-duplex-matrix), where the matrix has twice as many rows and half as many columns as the keyboard has keys. A matrix transform can be used to correct for this so that keymaps can match the layout of the keys, not the layout of the matrix.

```dts
/ {
    chosen {
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &default_transform;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
        // define row-gpios with 12 elements and col-gpios with 8...
    };

    default_transform: matrix_transform {
        compatible = "zmk,matrix-transform";
        rows = <6>;
        columns = <16>;
        // ESC F1 F2 F3   ...
        // `   1  2  3    ...
        // Tab  Q  W  E   ...
        // Caps  A  S  D  ...
        // Shift  Z  X  C ...
        // Ctrl Alt       ...
        map = <
            RC(0,0) RC(1,0) RC(0,1) RC(1,1)       // ...
            RC(2,0) RC(3,0) RC(2,1) RC(3,1)       // ...
            RC(4,0)   RC(5,0) RC(4,1) RC(5,1)     // ...
            RC(6,0)     RC(7,0) RC(6,1) RC(7,1)   // ...
            RC(8,0)       RC(9,0) RC(8,1) RC(9,1) // ...
            RC(10,0) RC(11,0)                     // ...
        >;
    };
};
```

### Example: Charlieplex

Since a charlieplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys.
Note that the entire addressable space does not need to be mapped.

```devicetree
/ {
    chosen {
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &default_transform;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-charlieplex";
        wakeup-source;

        interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) >;
        gpios
          = <&pro_micro 16 GPIO_ACTIVE_HIGH>
          , <&pro_micro 17 GPIO_ACTIVE_HIGH>
          , <&pro_micro 18 GPIO_ACTIVE_HIGH>
          , <&pro_micro 19 GPIO_ACTIVE_HIGH>
          , <&pro_micro 20 GPIO_ACTIVE_HIGH>
          ; // addressable space is 5x5, (minus paired values)
    };

    default_transform: matrix_transform {
        compatible = "zmk,matrix-transform";
        rows = <3>;
        columns = <5>;
        //  Q  W  E  R
        //   A  S  D  F
        //    Z  X  C  V
        map = <
            RC(0,1) RC(0,2) RC(0,3) RC(0,4)
              RC(1,0) RC(1,2) RC(1,3) RC(1,4)
                RC(2,0) RC(2,1) RC(2,3) RC(2,4)
        >;
    };
};
```

## Physical Layout

Defines a keyboard layout by joining together a [matrix transform](#matrix-transform), a [keyboard scan](./kscan.md), and a list of physical key properties.
Multiple physical layouts can be defined for keyboards with multiple physical key layouts.
Read through the [page on physical layouts](../hardware-integration/physical-layouts.md) for more information.

### Devicetree

Applies to: `compatible = zmk,physical-layout`

Definition file: [zmk/app/dts/bindings/zmk,physical-layout.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cphysical-layout.yaml)

| Property       | Type          | Description                                                                                                            | Default |
| -------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- |
| `display-name` | string        | The name of this layout, for display purposes                                                                          |         |
| `transform`    | phandle       | The matrix transform to use along with this layout                                                                     |         |
| `kscan`        | phandle       | The kscan to use along with this layout. The `zmk,kscan` chosen will be used as a fallback if this property is omitted |         |
| `keys`         | phandle-array | Array of key physical attributes.                                                                                      |         |

Each element of the `keys` array has the shape `<&key_physical_attrs w h x y r rx ry>`, with the following properties:

| Property   | Type     | Description                          | Unit                                                    |
| ---------- | -------- | ------------------------------------ | ------------------------------------------------------- |
| Width      | int (>0) | Key(cap) width                       | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Height     | int (>0) | Key(cap) height                      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| X          | uint     | Key X position (top-left point)      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Y          | uint     | Key Y position (top-left point)      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Rotation   | int      | Key rotation (positive => clockwise) | [centi-](https://en.wikipedia.org/wiki/Centi-)degree    |
| Rotation X | int      | Rotation origin X position           | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Rotation Y | int      | Rotation origin Y position           | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |

The `key_physical_attrs` node is defined in [`dts/physical_layouts.dtsi`](https://github.com/zmkfirmware/zmk/blob/main/app/dts/physical_layouts.dtsi) and is mandatory.

## Kconfig

| Config                                    | Type | Description                                                   | Default |
| ----------------------------------------- | ---- | ------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_PHYSICAL_LAYOUT_KEY_ROTATION` | bool | Whether to store/support key rotation information internally. | y       |

## Physical Layout Position Map

Defines a mapping between [physical layouts](#physical-layout), allowing key mappings to be preserved in the same locations as previously when using [ZMK Studio](../features/studio.md). Read through the [page on physical layouts](../hardware-integration/physical-layouts.md) for more information.

### Devicetree

Applies to: `compatible = zmk,physical-layout-position-map`

Definition file: [zmk/app/dts/bindings/zmk,physical-layout-position-map.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cphysical-layout-position-map.yaml)

| Property   | Type    | Description                                                                                      | Default |
| ---------- | ------- | ------------------------------------------------------------------------------------------------ | ------- |
| `complete` | boolean | If the mapping complete describes the key mapping, and no position based mapping should be used. |         |

The `zmk,physical-layout-position-map` node should have one child node per physical layout. Each child node should have the following properties:

| Property          | Type    | Description                                                                       | Default |
| ----------------- | ------- | --------------------------------------------------------------------------------- | ------- |
| `physical-layout` | phandle | The physical layout that corresponds to this mapping entry                        |         |
| `positions`       | array   | Array of key positions that match the same array entry in the other sibling nodes |         |

---

### Docs Config Led Indicators (`docs/docs/config/led-indicators.md`)

---
title: LED Indicators Configuration
sidebar_label: LED Indicators
---

See the [LED indicators feature page](../features/led-indicators.md) for more details.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition files:

- [zmk/app/src/indicators/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/indicators/Kconfig)

| Config                                    | Type | Description                                         | Default |
| ----------------------------------------- | ---- | --------------------------------------------------- | ------- |
| `CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY` | int  | Indicator LED device driver initialization priority | 91      |

`CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY` must be set to a larger value than `CONFIG_LED_INIT_PRIORITY`.

## Indicator LED Driver

This driver maps HID indicator states to [LED API](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/led.html) devices.

### Devicetree

Applies to: `compatible = "zmk,indicator-leds"`

Definition file: [zmk/app/dts/bindings/indicators/zmk,indicator-leds.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/indicators/zmk%2Cindicator-leds.yaml)

| Property                  | Type     | Description                                                           | Default |
| ------------------------- | -------- | --------------------------------------------------------------------- | ------- |
| `indicator`               | int      | The `HID_INDICATOR_*` value to indicate                               |         |
| `leds`                    | phandles | One or more LED devices to control                                    |         |
| `active-brightness`       | int      | LED brightness in percent when the indicator is active                | 100     |
| `inactive-brightness`     | int      | LED brightness in percent when the indicator is not active            | 0       |
| `disconnected-brightness` | int      | LED brightness in percent when the keyboard is not connected          | 0       |
| `on-while-idle`           | bool     | Keep LEDs enabled even when the keyboard is idle and on battery power | false   |

The `indicator` property must be one of the `HID_INDICATOR_*` values defined in [zmk/app/include/dt-bindings/zmk/hid_indicators.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/hid_indicators.h). You may also combine values with `|` to make the LED be lit when any of the indicator states are active.

---

### Docs Config Lighting (`docs/docs/config/lighting.md`)

---
title: Lighting Configuration
sidebar_label: Lighting
---

See the [Lighting feature page](../features/lighting.md) for an overview of the available lighting systems in ZMK.

## RGB Underglow

See the [RGB underglow section](../features/lighting.md#rgb-underglow) in the Lighting feature page for more details, and [hardware integration page](../hardware-integration/lighting/underglow.md) for adding underglow support to a board.

See [Configuration Overview](index.md) for instructions on how to change these settings.

### Kconfig

RGB underglow depends on [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/main/drivers/led_strip), which provides additional Kconfig options.

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                                   | Type | Description                                               | Default |
| ---------------------------------------- | ---- | --------------------------------------------------------- | ------- |
| `CONFIG_ZMK_RGB_UNDERGLOW`               | bool | Enable RGB underglow                                      | n       |
| `CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER`     | bool | Underglow toggling also controls external power           | y       |
| `CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE` | bool | Turn off RGB underglow when keyboard goes into idle state | n       |
| `CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB`  | bool | Turn off RGB underglow when USB is disconnected           | n       |
| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP`      | int  | Hue step in degrees (0-359) used by RGB actions           | 10      |
| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP`      | int  | Saturation step in percent used by RGB actions            | 10      |
| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP`      | int  | Brightness step in percent used by RGB actions            | 10      |
| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_START`     | int  | Default hue in degrees (0-359)                            | 0       |
| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_START`     | int  | Default saturation percent (0-100)                        | 100     |
| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_START`     | int  | Default brightness in percent (0-100)                     | 100     |
| `CONFIG_ZMK_RGB_UNDERGLOW_SPD_START`     | int  | Default effect speed (1-5)                                | 3       |
| `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`     | int  | Default effect index from the effect list (see below)     | 0       |
| `CONFIG_ZMK_RGB_UNDERGLOW_ON_START`      | bool | Default on state                                          | y       |
| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN`       | int  | Minimum brightness in percent (0-100)                     | 0       |
| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX`       | int  | Maximum brightness in percent (0-100)                     | 100     |

Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`:

| Value | Effect      |
| ----- | ----------- |
| 0     | Solid color |
| 1     | Breathe     |
| 2     | Spectrum    |
| 3     | Swirl       |

:::note
The `*_START` settings only determine the initial underglow state. Any changes you make with the [underglow behavior](../keymaps/behaviors/underglow.md) are saved to flash after a one minute delay and will be used after that.
:::

### Devicetree

ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip drivers](https://github.com/zephyrproject-rtos/zephyr/tree/main/dts/bindings/led_strip).

See the [RGB underglow hardware integration page](../hardware-integration/lighting/underglow.md) for examples of the properties that must be set to enable underglow.

## Backlight

See the [backlight section](../features/lighting.md#backlight) in Lighting feature page for more details, and [hardware integration page](../hardware-integration/lighting/backlight.mdx) for adding backlight support to a board.

See [Configuration Overview](index.md) for instructions on how to change these settings.

### Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Option                               | Type | Description                                           | Default |
| ------------------------------------ | ---- | ----------------------------------------------------- | ------- |
| `CONFIG_ZMK_BACKLIGHT`               | bool | Enables LED backlight                                 | n       |
| `CONFIG_ZMK_BACKLIGHT_BRT_STEP`      | int  | Brightness step in percent                            | 20      |
| `CONFIG_ZMK_BACKLIGHT_BRT_START`     | int  | Default brightness in percent                         | 40      |
| `CONFIG_ZMK_BACKLIGHT_ON_START`      | bool | Default backlight state                               | y       |
| `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE` | bool | Turn off backlight when keyboard goes into idle state | n       |
| `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB`  | bool | Turn off backlight when USB is disconnected           | n       |

:::note
The `*_START` settings only determine the initial backlight state. Any changes you make with the [backlight behavior](../keymaps/behaviors/backlight.md) are saved to flash after a one minute delay and will be used after that.
:::

### Devicetree

Applies to: [`/chosen` node](https://docs.zephyrproject.org/4.1.0/build/dts/intro-syntax-structure.html#aliases-and-chosen-nodes)

| Property        | Type | Description                                  |
| --------------- | ---- | -------------------------------------------- |
| `zmk,backlight` | path | The node for the backlight LED driver to use |

See the Zephyr devicetree bindings for LED drivers:

- [gpio-leds](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/led/gpio-leds.html)
- [pwm-leds](https://docs.zephyrproject.org/4.1.0/build/dts/api/bindings/led/pwm-leds.html)

See the [backlight hardware integration page](../hardware-integration/lighting/backlight.mdx) for examples of the properties that must be set to enable backlighting.

---

### Docs Config Pointing (`docs/docs/config/pointing.md`)

---
title: Pointing Device Configuration
sidebar_label: Pointing
---

These are settings related to the pointing device/mouse support in ZMK.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition file: [zmk/app/pointing/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/pointing/Kconfig)

### General

| Config                                 | Type | Description                                                                | Default |
| -------------------------------------- | ---- | -------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_POINTING`                  | bool | Enable the general pointing/mouse functionality                            | n       |
| `CONFIG_ZMK_POINTING_SMOOTH_SCROLLING` | bool | Enable smooth scrolling HID functionality (via HID Resolution Multipliers) | n       |

### Advanced Settings

The following settings are from Zephyr and should be defaulted to sane values, but can be adjusted if you encounter problems.

| Config                           | Type | Description                                                | Default                         |
| -------------------------------- | ---- | ---------------------------------------------------------- | ------------------------------- |
| `CONFIG_INPUT_THREAD_STACK_SIZE` | int  | Stack size for the dedicated input event processing thread | 512 (1024 on split peripherals) |

## Input Listener

The following documents settings related to [input listeners](../features/pointing.md#input-listeners).

### Devicetree

Applies to: `compatible = "zmk,input-listener"`

Definition file: [zmk/app/dts/bindings/zmk,input-listener.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cinput-listener.yaml)

| Property           | Type          | Description                                                         |
| ------------------ | ------------- | ------------------------------------------------------------------- |
| `device`           | phandle       | Input device handle                                                 |
| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events |

#### Child Properties

Additional properties can be set on child nodes, which allows changing the settings when certain layers are enabled:

| Property           | Type          | Description                                                                                |
| ------------------ | ------------- | ------------------------------------------------------------------------------------------ |
| `layers`           | array         | List of layer indexes. This config will apply if any layer in the list is active.          |
| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events                        |
| `process-next`     | bool          | Whether to continue applying other input processors after this override if it takes effect |

## Input Split

Input splits are used for [pointing devices on split peripherals](../hardware-integration/pointing.mdx#listener-and-input-split-device).

### Devicetree

Applies to: `compatible = "zmk,input-split"`

Definition file: [zmk/app/dts/bindings/zmk,input-split.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cinput-split.yaml)

| Property           | Type          | Description                                                         |
| ------------------ | ------------- | ------------------------------------------------------------------- |
| `device`           | handle        | Input device handle                                                 |
| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events |

---

### Docs Config Power (`docs/docs/config/power.md`)

---
title: Power Management Configuration
sidebar_label: Power Management
---

See [Configuration Overview](index.md) for instructions on how to
change these settings.

## Low Power States

Configuration for entering [low power states](../features/low-power-states.md) when the keyboard is idle.

### Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                          | Type | Description                                                         | Default |
| ------------------------------- | ---- | ------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_IDLE_TIMEOUT`       | int  | Milliseconds of inactivity before entering idle state               | 30000   |
| `CONFIG_ZMK_SLEEP`              | bool | Enable deep sleep support                                           | n       |
| `CONFIG_ZMK_IDLE_SLEEP_TIMEOUT` | int  | Milliseconds of inactivity before entering deep sleep               | 900000  |
| `CONFIG_ZMK_PM_SOFT_OFF`        | bool | Enable soft off functionality from the keymap or dedicated hardware | n       |

## External Power Control

Driver for enabling or disabling power to peripherals such as displays and lighting. This driver must be configured to use [power management behaviors](../keymaps/behaviors/power.md).

### Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                 | Type | Description                                     | Default |
| ---------------------- | ---- | ----------------------------------------------- | ------- |
| `CONFIG_ZMK_EXT_POWER` | bool | Enable support to control external power output | y       |

### Devicetree

Applies to: `compatible = "zmk,ext-power-generic"`

| Property        | Type       | Description                                                   |
| --------------- | ---------- | ------------------------------------------------------------- |
| `control-gpios` | GPIO array | List of GPIOs which should be active to enable external power |
| `init-delay-ms` | int        | number of milliseconds to delay after initializing the driver |

## GPIO Key Wakeup Trigger

A device similar to a [kscan](./kscan.md) which will be enabled only when the keyboard is entering [soft off](../features/low-power-states.md#soft-off) state. This is used to configure a GPIO key to wake the keyboard from [soft off](../features/low-power-states.md#soft-off) once it is pressed.

### Devicetree

Applies to: `compatible = "zmk,gpio-key-wakeup-trigger"`

Definition file: [zmk/app/dts/bindings/zmk,gpio-key-wakeup-trigger.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cgpio-key-wakeup-trigger.yaml)

| Property        | Type       | Description                                                                                   |
| --------------- | ---------- | --------------------------------------------------------------------------------------------- |
| `trigger`       | phandle    | Phandle to a GPIO key to be used to wake from soft off                                        |
| `wakeup-source` | bool       | Mark this device as able to wake the keyboard                                                 |
| `extra-gpios`   | GPIO array | list of GPIO pins (including the appropriate flags) to set active before going into power off |

The `wakeup-source` property should always be present for this node to be useful. The `extra-gpios` property should be used to ensure the GPIO pin will trigger properly to wake the keyboard. For example, for a `col2row` matrix kscan, these are the column pins relevant for soft off.

## Soft Off Wakeup Sources

Selects a list of devices to enable during [soft off](../features/low-power-states.md#soft-off), allowing those with `wakeup-source` as a property to wake the keyboard.

### Devicetree

Applies to: `compatible = "zmk,soft-off-wakeup-sources"`

| Property         | Type          | Description                                                                                       |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------- |
| `wakeup-sources` | phandle array | List of devices to enable during the shutdown process to be sure they can later wake the keyboard |

---

### Docs Config Settings (`docs/docs/config/settings.md`)

---
title: Persistent Settings
sidebar_label: Settings
---

ZMK uses [Zephyr's settings subsystem](https://docs.zephyrproject.org/4.1.0/services/settings/index.html) to store certain runtime settings in the "storage" partition of the controller's flash memory.
These settings will be saved after certain events and loaded on boot.
For instance, bond information for [paired Bluetooth hosts](../features/bluetooth.md) are stored in this partition so that users do not need to pair to each device again after the controller loses power.

Persisted settings are **not** cleared by flashing regular ZMK firmware: this is by design, since modifications like keymap changes should not cause users to lose their Bluetooth pairings.
They can only be cleared by setting a special Kconfig symbol or flashing a special firmware build as documented below.

Below is a non-comprehensive list of ZMK features that utilize persisted settings.

- [Bluetooth](../features/bluetooth.md): Stores pairing keys and MAC addresses associated with hosts, BT profile selected through the [keymap behavior](../keymaps/behaviors/bluetooth.md)[^1]
- [Split keyboards](../features/split-keyboards.md): Stores pairing keys and MAC addresses for wireless connection between parts
- [Output selection](../keymaps/behaviors/outputs.md): Stores last selected preferred endpoint changed through the keymap behavior[^1]
- [ZMK Studio](../features/studio.md): Stores any runtime keymap modifications and selected physical layouts after they are saved to the keyboard
- [Lighting](../features/lighting.md): Stores current brightness/color/effects for [underglow](../keymaps/behaviors/underglow.md) and [backlight](../keymaps/behaviors/backlight.md) features after being changed through their keymap behaviors[^1]
- [Power management](../keymaps/behaviors/power.md): Stores the state of the external power toggle as changed through the keymap behavior[^1]

[^1]: These are not saved immediately, but after `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` milliseconds in order to reduce potential wear on the flash memory.

## Kconfig

See [Configuration Overview](index.md) for instructions on how to change these settings.

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

| Config                               | Type | Description                                                                   | Default |
| ------------------------------------ | ---- | ----------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_SETTINGS_RESET_ON_START` | bool | Clears all persistent settings from the keyboard at startup                   | n       |
| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE`  | int  | Milliseconds to wait after a setting change before writing it to flash memory | 60000   |

## Clearing Persisted Settings

While regular ZMK builds will not cause any settings to be cleared upon flashing, flashing a build with `CONFIG_ZMK_SETTINGS_RESET_ON_START` enabled as documented above will cause the firmware to run a special procedure when the controller starts that clears the settings partition.

For end users, it is recommended to use a special [shield](../hardware-integration/index.mdx#boards--shields) named `settings_reset` to build a new firmware file, then flash that firmware.
See example for building firmware using this shield in the [troubleshooting docs](../troubleshooting/connection-issues.mdx#building-a-reset-firmware).

In both cases, regular, non-reset firmware will need to be flashed afterwards for normal operation.

:::warning

Since pairing information between split keyboards are also cleared with this process, you will need to clear settings on all parts of a split keyboard.
Please follow the full procedure described in [troubleshooting](../troubleshooting/connection-issues.mdx#split-keyboard-parts-unable-to-pair) so that the parts can pair correctly after clearing.

:::

:::tip

[ZMK Studio](../features/studio.md)-specific settings can be easily cleared using the "Restore Stock Settings" button in the header of the Studio client.

:::

---

### Docs Config Split (`docs/docs/config/split.md`)

---
title: Split Configuration
sidebar_label: Split
---

These are settings that control how split keyboards behave.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Following [split keyboard](../features/split-keyboards.md) settings are defined in [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/Kconfig).

| Config                                       | Type | Description                                                              | Default |
| -------------------------------------------- | ---- | ------------------------------------------------------------------------ | ------- |
| `CONFIG_ZMK_SPLIT`                           | bool | Enable split keyboard support                                            | n       |
| `CONFIG_ZMK_SPLIT_ROLE_CENTRAL`              | bool | `y` for central device, `n` for peripheral                               | n       |
| `CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS` | bool | Enable split keyboard support for passing indicator state to peripherals | n       |

### Bluetooth Splits

Following bluetooth [split keyboard](../features/split-keyboards.md) settings are defined in [zmk/app/src/split/bluetooth/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/bluetooth/Kconfig).

| Config                                                  | Type | Description                                                                | Default                                    |
| ------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | ------------------------------------------ |
| `CONFIG_ZMK_SPLIT_BLE`                                  | bool | Use BLE to communicate between split keyboard halves                       | y                                          |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS`              | int  | Number of peripherals that will connect to the central                     | 1                                          |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING`   | bool | Enable fetching split peripheral battery levels to the central side        | n                                          |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY`      | bool | Enable central reporting of split battery levels to hosts                  | n                                          |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE` | int  | Max number of battery level events to queue when received from peripherals | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS` |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE`      | int  | Max number of key state events to queue when received from peripherals     | 5                                          |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_STACK_SIZE`     | int  | Stack size of the BLE split central write thread                           | 512                                        |
| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE`     | int  | Max number of behavior run events to queue to send to the peripheral(s)    | 5                                          |
| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE`            | int  | Stack size of the BLE split peripheral notify thread                       | 756                                        |
| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY`              | int  | Priority of the BLE split peripheral notify thread                         | 5                                          |
| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE`   | int  | Max number of key state events to queue to send to the central             | 10                                         |

#### Input Split

The following setting only applies when using BLE split together with [input split devices](pointing.md#input-split), such as for pointing devices attached to split peripherals:

| Config                                  | Type | Description                                                     | Default |
| --------------------------------------- | ---- | --------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_INPUT_SPLIT_MSG_QUEUE_SIZE` | int  | Max number of input split events to queue for BLE notifications | 32      |

### Wired Splits

Hardware UARTs have a few different modes/approaches to sending and receiving data, with different levels of complexity and performance. Not all hardware nor drivers support all modes, so ZMK has code to support different interaction modes with the UART as needed. The default mode should be properly selected based on the platform's report support, but you can choose to override the mode if needed.

- Polling Mode - The least efficient mode, this requires the MCU to constantly poll the UART to see if more data has been received, taking time away from other processing. This is basic mode supported by all UART drivers.
- Interrupt Mode - This mode allows the MCU to do other processing until the UART raises an interrupt to signal new data has been received. On platforms where this is combined with a FIFO, there is even less superfluous processing, and high speeds can be achieved while allowing other processing to continue. Examples:
  - RP2040
  - nRF52
- Async (DMA) Mode - Similar to interrupt mode, data reception can occur without involving the MCU. Additionally, larger volumes can be copied directly into accessible memory without the use of the MCU, allowing even further efficiency/rates without tying up the MCU. Only some drivers support this mode (and the current Zephyr 3.5 version of the nRF52 UART has some bugs that prevent its use). Examples:
  - SAM0 (e.g. SAMD21)
  - STM32 (e.g. stm32f072)

Following wired [split keyboard](../features/split-keyboards.md) settings are defined in [zmk/app/src/split/wired/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/wired/Kconfig).

| Config                                       | Type | Description                                                       | Default                                                       |
| -------------------------------------------- | ---- | ----------------------------------------------------------------- | ------------------------------------------------------------- |
| `CONFIG_ZMK_SPLIT_WIRED`                     | bool | Use wired connection to communicate between split keyboard halves | y (if devicetree is set appropriately)                        |
| `CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC`     | bool | Async (DMA) mode                                                  | y if the driver supports it (excluding nRF52 with known bugs) |
| `CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT` | bool | Interrupt mode                                                    | y if the hardware supports it                                 |
| `CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING`   | bool | Polling mode                                                      | y if neither other mode is supported                          |

#### Async (DMA) Mode

The following settings only apply when using wired split in async (DMA) mode:

| Config                                    | Type | Description                                                 | Default |
| ----------------------------------------- | ---- | ----------------------------------------------------------- | ------- |
| `CONFIG_ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT` | int  | RX Timeout (in microseconds) before reporting received data | 20      |

#### Polling Mode

The following settings only apply when using wired split in polling mode:

| Config                                     | Type | Description                                          | Default |
| ------------------------------------------ | ---- | ---------------------------------------------------- | ------- |
| `CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD` | int  | Number of ticks between calls to poll for split data | 10      |

## Devicetree

### Wired Split

Wired splits require a properly configured UART to function. If writing a shield, you may be able to use the standard UART already provided by the board, e.g. `&pro_micro_serial`. See [predefined nodes](../hardware-integration/pinctrl.mdx#predefined-nodes) for details on the UART node labels provided by various interconnects. If you are creating your own board, or using custom pins for the UART, see the documentation on [pin control](../hardware-integration/pinctrl.mdx#additional-examples) to configure the pins for your UART.

Once you have a properly configured UART device, it needs to be assigned in a new node with a compatible value of `"zmk,wired-split"`. For example:

```dts
/ {
    wired_split {
        compatible = "zmk,wired-split";
        device = <&pro_micro_serial>;
    };
};
```

---

### Docs Config Studio (`docs/docs/config/studio.md`)

---
title: ZMK Studio Configuration
sidebar_label: ZMK Studio
---

The following settings affect the ZMK Studio portions of ZMK. See the [ZMK Studio feature](../features/studio.md) for more information on enabling and building with ZMK Studio enabled.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition file: [zmk/app/src/studio/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/studio/Kconfig)

### Keymaps

| Config                                 | Type | Description                             | Default |
| -------------------------------------- | ---- | --------------------------------------- | ------- |
| `CONFIG_ZMK_KEYMAP_LAYER_NAME_MAX_LEN` | int  | Max allowable keymap layer display name | 20      |

### Locking

| Config                                    | Type | Description                                                                         | Default |
| ----------------------------------------- | ---- | ----------------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_STUDIO_LOCKING`               | bool | Enable/disable locking for ZMK Studio                                               | y       |
| `CONFIG_ZMK_STUDIO_LOCK_IDLE_TIMEOUT_SEC` | int  | Seconds of inactivity in ZMK Studio before automatically locking                    | 500     |
| `CONFIG_ZMK_STUDIO_LOCK_ON_DISCONNECT`    | bool | Whether to automatically lock again whenever ZMK Studio disconnects from the device | y       |

### Transport/Protocol Details

| Config                                         | Type | Description                                                                   | Default |
| ---------------------------------------------- | ---- | ----------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY` | int  | Lower latency to request while ZMK Studio is active to improve responsiveness | 10      |
| `CONFIG_ZMK_STUDIO_RPC_THREAD_STACK_SIZE`      | int  | Stack size for the dedicated RPC thread                                       | 1800    |
| `CONFIG_ZMK_STUDIO_RPC_RX_BUF_SIZE`            | int  | Number of bytes available for buffering incoming messages                     | 30      |
| `CONFIG_ZMK_STUDIO_RPC_TX_BUF_SIZE`            | int  | Number of bytes available for buffering outgoing messages                     | 64      |

---

### Docs Config System (`docs/docs/config/system.md`)

---
title: System Configuration
sidebar_label: System
---

These are general settings that control how the keyboard behaves and which features it supports. Several of these settings come from Zephyr and are not specific to ZMK, but they are listed here because they are relevant to how a keyboard functions.

See [Configuration Overview](index.md) for instructions on how to change these settings.

## Kconfig

Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)

### General

| Config                      | Type   | Description                                                                                                                                    | Default |
| --------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_BOARD_COMPAT`   | bool   | A special config for boards to enable. This helps check if users have accidentally used an upstream Zephyr board without ZMK additions applied | n       |
| `CONFIG_ZMK_KEYBOARD_NAME`  | string | The name of the keyboard (max 16 characters)                                                                                                   |         |
| `CONFIG_ZMK_WPM`            | bool   | Enable calculating words per minute                                                                                                            | n       |
| `CONFIG_HEAP_MEM_POOL_SIZE` | int    | Size of the heap memory pool                                                                                                                   | 8192    |

:::info

Because ZMK enables [the Zephyr setting](https://docs.zephyrproject.org/4.1.0/kconfig.html#CONFIG_BT_DEVICE_NAME_DYNAMIC) that allows for runtime modification of the device BT name,
changing `CONFIG_ZMK_KEYBOARD_NAME` requires [clearing the stored settings](./settings.md#clearing-persisted-settings) on the controller in order to take effect.

:::

### HID

:::warning[Refreshing the HID descriptor]

Making changes to any of the settings in this section modifies the HID report descriptor and requires it to be [refreshed](../features/bluetooth.md#refreshing-the-hid-descriptor).

:::

| Config                                       | Type | Description                                                      | Default |
| -------------------------------------------- | ---- | ---------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_HID_INDICATORS`                  | bool | Enable receipt of HID/LED indicator state from connected hosts   | n       |
| `CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE`        | int  | Number of consumer keys simultaneously reportable                | 6       |
| `CONFIG_ZMK_HID_SEPARATE_MOD_RELEASE_REPORT` | bool | Send modifier release event **after** non-modifier release event | n       |

Exactly zero or one of the following options may be set to `y`. The first is used if none are set.

| Config                            | Description                                                                                           |
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `CONFIG_ZMK_HID_REPORT_TYPE_HKRO` | Enable `CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE` key roll over.                                           |
| `CONFIG_ZMK_HID_REPORT_TYPE_NKRO` | Enable full N-key roll over. This may prevent the keyboard from working with some BIOS/UEFI versions. |

:::note[NKRO usages]

By default the NKRO max usage is set so as to maximize compatibility, however certain less frequently used keys (F13-F24 and INTL1-8) will not work with it. One solution is to set `CONFIG_ZMK_HID_KEYBOARD_NKRO_EXTENDED_REPORT=y`, however this is known to break compatibility with Android and thus not enabled by default.

:::

If `CONFIG_ZMK_HID_REPORT_TYPE_HKRO` is enabled, it may be configured with the following options:

| Config                                | Type | Description                                       | Default |
| ------------------------------------- | ---- | ------------------------------------------------- | ------- |
| `CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE` | int  | Number of keyboard keys simultaneously reportable | 6       |

If `CONFIG_ZMK_HID_REPORT_TYPE_NKRO` is enabled, it may be configured with the following options:

| Config                                         | Type | Description                                                          | Default |
| ---------------------------------------------- | ---- | -------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_HID_KEYBOARD_NKRO_EXTENDED_REPORT` | bool | Enable less frequently used key usages, at the cost of compatibility | n       |

Exactly zero or one of the following options may be set to `y`. The first is used if none are set.

| Config                                        | Description                                                                          |
| --------------------------------------------- | ------------------------------------------------------------------------------------ |
| `CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_FULL`  | Enable all consumer key codes, but may have compatibility issues with some host OSes |
| `CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC` | Prevents using some consumer key codes, but allows compatibility with more host OSes |

### USB

| Config                            | Type   | Description                             | Default         |
| --------------------------------- | ------ | --------------------------------------- | --------------- |
| `CONFIG_USB`                      | bool   | Enable USB drivers                      |                 |
| `CONFIG_USB_DEVICE_VID`           | int    | The vendor ID advertised to USB         | `0x1D50`        |
| `CONFIG_USB_DEVICE_PID`           | int    | The product ID advertised to USB        | `0x615E`        |
| `CONFIG_USB_DEVICE_MANUFACTURER`  | string | The manufacturer name advertised to USB | `"ZMK Project"` |
| `CONFIG_USB_HID_POLL_INTERVAL_MS` | int    | USB polling interval in milliseconds    | 1               |
| `CONFIG_ZMK_USB`                  | bool   | Enable ZMK as a USB keyboard            |                 |
| `CONFIG_ZMK_USB_BOOT`             | bool   | Enable USB Boot protocol support        | n               |
| `CONFIG_ZMK_USB_INIT_PRIORITY`    | int    | USB init priority                       | 50              |

:::note[USB Boot protocol support]

By default USB Boot protocol support is disabled, however certain situations such as the input of Bitlocker pins or FileVault passwords may require it to be enabled.

:::

### Bluetooth

See [Zephyr's Bluetooth stack architecture documentation](https://docs.zephyrproject.org/4.1.0/connectivity/bluetooth/bluetooth-arch.html)
for more information on configuring Bluetooth.

| Config                                      | Type | Description                                                           | Default |
| ------------------------------------------- | ---- | --------------------------------------------------------------------- | ------- |
| `CONFIG_BT`                                 | bool | Enable Bluetooth support                                              |         |
| `CONFIG_BT_BAS`                             | bool | Enable the Bluetooth BAS (battery reporting service)                  | y       |
| `CONFIG_BT_MAX_CONN`                        | int  | Maximum number of simultaneous Bluetooth connections                  | 5       |
| `CONFIG_BT_MAX_PAIRED`                      | int  | Maximum number of paired Bluetooth devices                            | 5       |
| `CONFIG_ZMK_BLE`                            | bool | Enable ZMK as a Bluetooth keyboard                                    |         |
| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START`       | bool | Clears all bond information from the keyboard on startup              | n       |
| `CONFIG_ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE` | int  | Max number of consumer HID reports to queue for sending over BLE      | 5       |
| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int  | Max number of keyboard HID reports to queue for sending over BLE      | 20      |
| `CONFIG_ZMK_BLE_INIT_PRIORITY`              | int  | BLE init priority                                                     | 50      |
| `CONFIG_ZMK_BLE_THREAD_PRIORITY`            | int  | Priority of the BLE notify thread                                     | 5       |
| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE`          | int  | Stack size of the BLE notify thread                                   | 768     |
| `CONFIG_ZMK_BLE_PASSKEY_ENTRY`              | bool | Experimental: require typing passkey from host to pair BLE connection | n       |

Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the same value. On a split keyboard they should only be set for the central and must be set to one greater than the desired number of bluetooth profiles.

### Logging

| Config                   | Type | Description                              | Default |
| ------------------------ | ---- | ---------------------------------------- | ------- |
| `CONFIG_ZMK_USB_LOGGING` | bool | Enable USB CDC ACM logging for debugging | n       |
| `CONFIG_ZMK_LOG_LEVEL`   | int  | Log level for ZMK debug messages         | 4       |

## Snippets

:::danger
Using these snippets can erase the SoftDevice on your board.
Erasing the SoftDevice will prevent the board from using firmware built without these snippets.

Flashing such firmware **will** totally brick the board, disabling the USB flashing functionality.
The only way to restore functionality after that is to re-flash the bootloader.

Re-flashing a bootloader built without the SoftDevice will require firmware built with these snippets.
:::

[Snippets](https://docs.zephyrproject.org/4.1.0/build/snippets/index.html) are a way to save common configuration separately when it applies to multiple different applications.

Enable snippets by adding `snippet: <snippet>` to your `build.yaml` for the appropriate board:

```yaml
- board: nrfmicro@1.3.0/nrf52833/zmk
  snippet: nrf52833-nosd
  shield: corne_left
```

For local builds, add `-S <snippet>` to your build command. For example:

```sh
west build -b nrfmicro@1.3.0/nrf52833/zmk -S nrf52833-nosd -- -DSHIELD=corne_left
```

ZMK implements the following system configuration snippets:

### nrf52833-nosd

Definition: [zmk/app/snippets/nrf52833-nosd](https://github.com/zmkfirmware/zmk/blob/main/app/snippets/nrf52833-nosd)

On memory-constrained nRF52833 boards this snippet will extend the code partition to overwrite the Nordic SoftDevice.
This gives 428KB for the code partition as opposed to 280KB with the Nordic SoftDevice.

The added memory allows the nRF52833 to fit displays and other memory-intensive features.

### nrf52840-nosd

Definition: [zmk/app/snippets/nrf52840-nosd](https://github.com/zmkfirmware/zmk/blob/main/app/snippets/nrf52840-nosd)

On nRF52840 boards this snippet will overwrite the Nordic SoftDevice, extending both the code and storage partitions.
This gives 844KB/128KB for the code/storage partitions as opposed to 792KB/32KB with the Nordic SoftDevice.

Firmware built with this snippet can work on boards after accidentally erasing the SoftDevice.
It can also be useful for especially memory-intensive applications.

---

### Docs Development Devicetree (`docs/docs/development/devicetree.md`)

---
title: Devicetree Overview
sidebar_label: Devicetree Overview
---

ZMK makes heavy usage of a type of [tree data structure](<https://en.wikipedia.org/wiki/Tree_(abstract_data_type)>) 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](https://github.com/devicetree-org/devicetree-specification/releases) and [Zephyr's documentation](https://docs.zephyrproject.org/latest/build/dts/index.html#devicetree-guide).

## Running Example

The following segment taken from a keymap will be used as a running example:

```dts
#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)

```dts
[label:] name {
    [properties]
    [child nodes]
};
```

The root node of the devicetree always has the name `/`, i.e. is written as

```dts
 / {
    [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](https://github.com/devicetree-org/devicetree-specification/releases).

#### Property types

These are some of the property types you will see most often when working with ZMK. [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/4.1.0/build/dts/bindings.html) 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:

```dts
/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](#labels-and-phandles).

Example: `property = <&label>`

##### phandles

A list of node references surrounded by angle brackets. Phandles will be explained in more detail in a [later section](#labels-and-phandles).

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](#labels-and-phandles).

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](https://docs.zephyrproject.org/4.1.0/build/dts/bindings.html)
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](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/gpio.html) for a full list of flags. Phandles and labels will be explained in more detail in a [later section](#labels-and-phandles).

Example:

```dts
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](#labels-and-phandles).

Examples:

```dts
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](https://github.com/zmkfirmware/zmk/blob/main/app/src/behaviors/behavior_mod_morph.c#L7) acts on all nodes with `compatible` set to this value. The [ZMK keymap code](https://github.com/zmkfirmware/zmk/blob/main/app/src/keymap.c#L29) 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](https://github.com/zmkfirmware/zmk/tree/main/app/dts/bindings).

The bindings file for `compatible = "zmk,behavior-mod-morph";` is [`app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml`](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml).

```dts title="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](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zero_param.yaml). Bindings files are **the authority** on node properties, with our [documentation of said properties](https://zmk.dev/docs/config/behaviors#devicetree-7) 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](https://docs.zephyrproject.org/4.1.0/build/dts/bindings-syntax.html).

Note that binding files can also specify properties for children, like the [`zmk,keymap.yaml` bindings file](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckeymap.yaml) 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 a `status = "disabled";`.

How this property is used in practice will become more clear after the [devicetree preprocessing](#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 nodes[^1]. 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](https://github.com/zmkfirmware/zmk/blob/main/app/dts/behaviors/key_press.dtsi):

```dts
/ {
    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 so[^2]. 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.

[^1]: A phandle array by definition also includes metadata, i.e. parameters. Strictly speaking, a list of phandles without metadata has type `phandles` rather than `phandle-array`. A property with a single phandle has type `phandle`.

[^2]: The number of parameters passed to the behavior code (and skipped over to find the next behavior phandle) is determined by the `#binding-cells` property mentioned above.

## Devicetree Preprocessing

Much of the complexity in `dts` files comes from preprocessing. The resulting devicetree after all preprocessing has finished [can be inspected](../troubleshooting/building-issues.md#devicetree-related-issues) 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:

1. The [C preprocessor](https://gcc.gnu.org/onlinedocs/cpp/) can be used within Devicetree Source (`dts`) files.
2. 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

```dts
#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](../keymaps/behaviors/macros.md#convenience-c-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:

- `.dtsi` files, which exist exclusively to be included via the C preprocessor (their contents get "pasted" at the location of the `#include` command) and are not used by the build sytem otherwise.
- A `.dts` file, which forms the "base" of the devicetree. A single one of these is always present when a devicetree is constructed. For ZMK, the `.dts` file contains the sections of the devicetree describing the [_board_](hardware-integration/index.mdx#what-is-a-board). This includes importing a number of `.dtsi` files describing the specific SoC that the board uses.
- Any number of `.overlay` files. These files can come from various sources, such as [shields](hardware-integration/index.mdx#what-is-a-shield) or [snippets](https://docs.zephyrproject.org/4.1.0/build/snippets/index.html). An overlay is applied to a `.dts` file by appending its contents to the end of the `.dts` file, 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_view` in your `build.yaml`, then the overlays are applied left to right.
- A single `.keymap` file. This file being included is ZMK-specific, and is treated as the "final" `.overlay` file, 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:

```dts
/ {
    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

```dts
/ {
    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:

```dts
&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;`

---

### Docs Development Events (`docs/docs/development/events.md`)

---
title: ZMK Events
sidebar_label: ZMK Events
---

ZMK makes use of events to decouple individual components such as behaviors and peripherals from the core functionality. For this purpose, ZMK has implemented its own event manager. This page is a (brief) overview of the functionality and methods exposed by the event manager, documenting its API. Its purpose is to aid module developers and contributors, such as for the development of [new behaviors](./new-behavior.mdx) or [new features](./module-creation.md). There is no value in reading this page as an end-user.

To see what events exist and what data they contain, it is best to view the corresponding [event header files](https://github.com/zmkfirmware/zmk/tree/main/app/include/zmk/events) directly. Including the event_manager header via `#include <zmk/event_manager.h>` is required for any interaction with the event system.

## Generic Events

The generic event type is `struct zmk_event_t`. This struct looks like this:

```c
typedef struct {
    const struct zmk_event_type *event;
    uint8_t last_listener_index;
} zmk_event_t;
```

In memory, the struct for a specific raised event `struct zmk_specific_thing_happened_event` **always** consists of a `zmk_event_t` struct **followed immediately afterwards** by the struct containing the data for the actual event.

```c
struct zmk_specific_thing_happened_event {
    zmk_event_t header;
    struct zmk_specific_thing_happened data;
};
```

The contents of `header.event` allows us to identify which type a particular event actually is, so that we may safely access its data in `data`. This is handled by the following function, which allows us to obtain the underlying data from a generic event:

```c
struct zmk_specific_thing_happened *as_specific_thing_happened(const zmk_event_t *eh);
```

This method takes in a pointer to a `zmk_event_t` (which is actually a pointer to a specific event, such as `zmk_specific_thing_happened_event`), and will return the underlying `zmk_specific_thing_happened` data struct if the `zmk_event_t` header indicates that the generic event pointer is indeed a pointer to a `zmk_specific_thing_happened_event`. If the type of the event does not match the function, then the function will return `NULL`. By convention, `zmk_event_t` pointer arguments are named `eh`, short for "event header".

This method will exist for every type of event, so for `zmk_layer_state_changed` we have `as_zmk_layer_state_changed`, etc. It is generated by a macro as part of the event declaration.

## Subscribing To Events

### Subscription and Listener

To subscribe to any events, you will first need to inform the event manager that you wish to add a new listener.
This is done by calling the `ZMK_LISTENER` macro:

```c
ZMK_LISTENER(combo, behavior_combo_listener);
```

This macro takes two parameters:

1.  (`combo` in the example) This gives a name to the listener, for the event manager to refer back to it.
2.  (`behavior_combo_listener` in the example) This is a [callback](<https://en.wikipedia.org/wiki/Callback_(computer_programming)>) that will be called whenever **any** event that the listener subscribes to occurs (if it is not handled by another listener with a higher priority). By convention, the callback should have the suffix `_listener`.

Once you have a listener set up, you can subscribe to individual events by calling the `ZMK_SUBSCRIPTION` macro:

```c
ZMK_SUBSCRIPTION(combo, zmk_keycode_state_changed);
```

The first parameter is the name of the listener created with `ZMK_LISTENER`, while the second is the name of the _struct_ that defines the event's data, which was declared in the corresponding header file. By convention the header file for an event will be named `specific_thing_happened`, with the struct named `zmk_specific_thing_happened`.

Of course, you will also need to import the corresponding event header at the top of your file.

### Listener Callback

The listener will be passed a raised `zmk_event_t` pointer (as described previously) as an argument, and should have `int` as its return type.

The listener should return one of three values (which are of type `int`) back to the event manager:

- `ZMK_EV_EVENT_BUBBLE`: Keep propagating the event `struct` to the next listener.
- `ZMK_EV_EVENT_HANDLED`: Stop propagating the event `struct` to the next listener. The event manager still owns the `struct`'s memory, so it will be `free`d automatically. Do **not** free the memory in this function.
- `ZMK_EV_EVENT_CAPTURED`: Stop propagating the event `struct` to the next listener. The event `struct`'s memory is now owned by your code, so the event manager will not free the event `struct` memory. Make sure your code will release or free the event at some point in the future. (Use the `ZMK_EVENT_*` macros described [below](#raising-events).)

If an error occurs during the listener call, it should return a negative value indicating the appropriate error code.

As mentioned previously, the same callback will be called when any event that is subscribed to occurs. To obtain the underlying event from the generic event passed to the listener, the previously described `as_zmk_specific_thing_happened` function should be used:

```c
int behavior_hold_tap_listener(const zmk_event_t *eh) {
    if (as_zmk_position_state_changed(eh) != NULL) {
        // it is a position_state_changed event, handle it with my_position_state_handler
        return my_position_state_handler(eh);
    } else if (as_zmk_keycode_state_changed(eh) != NULL) {
        // it is a keycode_state_changed event, handle it with my_keycode_state_handler
        return my_keycode_state_handler(eh);
    }
    return ZMK_EV_EVENT_BUBBLE;
}
```

The priority of the listeners is determined by the order in which the linker links the files. Within ZMK, this is the order of the corresponding files in `CMakeLists.txt`. External modules targeting `app` are linked prior to any files within ZMK itself, making them the highest priority. It is thus the module maintainer's responsibility to both ensure that their module does not cause issues by being first in the listener queue. For example, [hold-tap](../keymaps/behaviors/hold-tap.mdx) is the first listener to `position_state_changed`, and may behave inconsistently if a behavior defined in a module listens to `position_state_changed` and invokes a `hold-tap` (e.g. by calling `zmk_behavior_invoke_event` with a `hold-tap` as the binding).

In addition, because modules listen to the events first, they should _never_ capture/handle an event defined in ZMK without releasing it later. Unless it is unavoidable, it is recommended to bubble events whenever possible.

When considering multiple modules, priority is determined by the order in which the modules are present in the user's `west.yml`. Hence there should be no order dependencies between modules, only within a module.

## Raising Events

There are several different ways to raise events, with slight differences between them.

- `int raise_zmk_specific_thing_happened(struct zmk_specific_thing_happened event)`: This function will take an event data structure, add a header to it, and then start handling the event with the first registered event listener.

The following macros can also be used for advanced use cases. These will each take in an event `ev` which already consists of the header & data combination, i.e. `ev` has the type `struct zmk_specific_thing_happened_event`.

- `ZMK_EVENT_RAISE(ev)`: Start handling this event (`ev`) with the first registered event listener.
- `ZMK_EVENT_RAISE_AFTER(ev, mod)`: Start handling this event (`ev`) after the event is captured by the named [event listener](#subscription-and-listener) (`mod`). The named event listener will be skipped as well.
- `ZMK_EVENT_RAISE_AT(ev, mod)`: Start handling this event (`ev`) at the named [event listener](#subscription-and-listener) (`mod`). The named event listener is the first handler to be invoked.
- `ZMK_EVENT_RELEASE(ev)`: Continue handling this event (`ev`) at the next registered event listener.
- `ZMK_EVENT_FREE(ev)`: Free the memory associated with the event (`ev`).

Optionally, some events may also declare an extra function similar to `raise_zmk_specific_thing_happened` named `raise_specific_thing_happened`. This function will take in some or all of the components of the `zmk_specific_thing_happened` struct, and then create the struct (perhaps with some additional data obtained from elsewhere) before calling `raise_zmk_specific_thing_happened`. For example:

```c
static inline int raise_layer_state_changed(uint8_t layer, bool state) {
    return raise_zmk_layer_state_changed(
        (struct zmk_layer_state_changed){
            .layer = layer,
            .state = state,
            .timestamp = k_uptime_get()
        }
    );
}
```

## Creating New Events

### Header File

Your event's header file should have four things:

- A copyright comment
- Any required header includes (along with `#pragma once`)
- The event's data struct
- The macro `ZMK_EVENT_DECLARE`, called with the name of your event's data struct.

For example:

```c
/*
- Copyright (c) 2021 The ZMK Contributors
-
- SPDX-License-Identifier: MIT
*/

#pragma once

#include <zephyr/kernel.h>

#include <zmk/endpoints_types.h>
#include <zmk/event_manager.h>

struct zmk_endpoint_changed {
    struct zmk_endpoint_instance endpoint;
};

ZMK_EVENT_DECLARE(zmk_endpoint_changed);
```

### Code File

Your event's code file merely needs three things:

- A copyright comment
- Any required header files (including that of your event)
- The macro `ZMK_EVENT_IMPL`, called with the name of your event's data struct.

```c
/*
 * Copyright (c) 2021 The ZMK Contributors
 *
 * SPDX-License-Identifier: MIT
 */

#include <zephyr/kernel.h>
#include <zmk/events/endpoint_changed.h>

ZMK_EVENT_IMPL(zmk_endpoint_changed);
```

---

### Docs Development Module Creation (`docs/docs/development/module-creation.md`)

---
title: Module Creation
sidebar_label: ZMK Module Creation
---

[ZMK modules](../features/modules.mdx) are the recommended method of adding content to ZMK, whenever it is possible. This page will guide you through creating a module for ZMK. Distinction is made between modules used for different purposes:

- Modules containing one or more keyboard-related definitions (boards, shields, interconnects, etc.)
- Modules containing behaviors & features
- Modules containing drivers
- Modules containing other features, such as visual effects

See also Zephyr's [page on modules](https://docs.zephyrproject.org/4.1.0/develop/modules.html).

For open source hardware designs, it's recommended to **not** include the hardware design files in the ZMK module itself, since the module will be fetched by users during build and having design files in the module would likely make the module unnecessarily large and slow to fetch.

## Module Setup

ZMK has a template to make creating a module easier. Navigate to [the ZMK module template repository](https://github.com/zmkfirmware/zmk-module-template) and select "Use this template" followed by "Create a new repository" in the top right.

The rest of this page will go through the contents of the template step by step, guiding you through the configuration of the module.

:::tip
The [Unified ZMK Config Template](https://github.com/zmkfirmware/unified-zmk-config-template) is also a module, albeit one focused on keyboards. If you are making a module for a keyboard, you may find it useful to base the module off of the template and use GHA to help troubleshoot any errors. Later, you can extract the keyboard to a dedicated module or remove superfluous parts from your module with the help of the information on this page.
:::

### Zephyr Module File

This is the file that defines your module. Only when this file is configured correctly will your module function as expected.

#### Name

First, you will need to name your module. ZMK has a naming convention that you should follow:

```
zmk-<type>-<description>
```

Valid options for type are:

| Type        | Description                                                                                                           |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `keyboard`  | The module contains definitions for one or multiple keyboards, be they boards or shields.                             |
| `component` | The module contains definitions for one or multiple components, such as boards for shields to use.                    |
| `behavior`  | The module contains a single behavior.                                                                                |
| `driver`    | The module contains a single driver.                                                                                  |
| `feature`   | The module contains a single feature which doesn't fall under the other categories.                                   |
| `vfx`       | The module contains definitions for one or multiple visual effects, such as display customisations or RGB animations. |

For each of these, you can use the singular or plural, i.e. `keyboard` and `keyboards` are both acceptable. Use your judgement to decide between the two.

`<description>` is your choice of name to describe the module.

Note that your _module name_ does not have to be the same as your _repository name_. For example, if you have named your module `zmk-keyboards-corne`, you may for your own organisational purposes want to name the repository e.g. `zmk-corne-module`.

Your module name goes in the `zephyr/module.yml` file:

```yaml title="zephyr/module.yaml"
name: <your module name>
```

#### Build options

Next, you need to define the options to build your module. These also go into `zephyr/module.yml`, as children of the `build` property. Commonly used options are:

- The `depends` child property is used to specify a list of modules which the module depends on. The dependencies are referred to by their [module name](#name).
- The `cmake` child property is used to identify a folder containing a `CMakeLists.txt` file that lists the CMake commands used to build source files.
- The `kconfig` child property is used to identify the `Kconfig` file that defines configuration settings used by the module.
- `settings` is a child property containing additional child properties, two of which are particularly relevant:
  - `board_root` points to the parent directory of a `boards` directory, which contains additional board/shield definitions.
  - `dts_root` points to the parent directory of a `dts` directory, which contains additional devicetree bindings.
  - `snippet_root` points to the parent directory of a `snippets` directory, which contains [snippets](https://docs.zephyrproject.org/4.1.0/build/snippets/index.html).

See the `zephyr/module.yml` found in the template for a usage example.

### Dependencies

If `zephyr/module.yml` has anything listed under `depends`, then you should also define a [west manifest](https://docs.zephyrproject.org/4.1.0/develop/west/manifest.html) file. While the `zephyr/module.yml` file defines _which_ modules your module depends on, the west manifest file defines _where_ said modules are found. This then allows automatic tooling to fetch the modules when building firmware. If `depends` is not present in `zephyr/module.yml`, then this file (named `west.yml` in the template) should be removed.

It is recommended that you place the manifest file at the root of your module, though you can place it elsewhere. Be sure to note in your `README.md` that your module uses dependencies, so that users import these correctly.
Below is an example `west.yml` file for a user that would be using your module, with the necessary `import` field if the module has dependencies:

```yaml title="west.yml"
manifest:
  remotes:
    - name: remote-name
      url-base: https://github.com/remote-name
  projects:
    # Together with the above defined remote, this refers to a module located at
    # https://github.com/remote-name/repository-name
    - name: repository-name
      remote: remote-name
      # This points to a west manifest file in the remote module for further imports
      import: west.yml
```

See the [modules feature page](../features/modules.mdx) for additional information on using west manifest files.

### Informational Files

Make sure to include a `README.md` file and a `LICENSE` file in your repository.

### Predefined Files and Folders

The repository comes with a number of files and folders already positioned for you to edit. The below table describes which files are most likely kept and which are most likely deleted, based on your module's type. Note that these aren't hard rules, merely the most common use case.

|                  | `keyboard` | `component` | `behavior` | `driver` | `feature` | `vfx` |
| ---------------- | ---------- | ----------- | ---------- | -------- | --------- | ----- |
| `boards/`        | ✅         | ✅          | ❌         | ❌       | ❌        | ✅    |
| `dts/`           | ❌         | ❌          | ✅         | ✅       | ✅        | ❌    |
| `CMakeLists.txt` | ❌         | ❌          | ✅         | ✅       | ✅        | ✅    |
| `Kconfig`        | ❌         | ❌          | ✅         | ✅       | ✅        | ✅    |
| `include/`       | ❌         | ❌          | ✅         | ✅       | ✅        | ❌    |
| `src/`           | ❌         | ❌          | ✅         | ✅       | ✅        | ❌    |
| `snippets/`      | ❌         | ❌          | ❌         | ❌       | ✅        | ❌    |

The below table reminds of the purpose of each of these files and folders, if you are not already familiar with them:

| File or Folder   | Description                                                                           |
| ---------------- | ------------------------------------------------------------------------------------- |
| `boards/`        | Folder containing definitions for boards, shields and interconnects                   |
| `dts/`           | Folder containing devicetree bindings and includes with devicetree nodes (.dtsi)      |
| `CMakeLists.txt` | CMake configuration to specify source files to build                                  |
| `Kconfig`        | Kconfig file for the module                                                           |
| `include/`       | Folder for C header files                                                             |
| `src/`           | Folder for C source files                                                             |
| `snippets/`      | Folder for [snippets](https://docs.zephyrproject.org/4.1.0/build/snippets/index.html) |

Note that the `include` and `src` folders are not mandated by the module system, and all of these can be positioned anywhere in your module's filetree if you adjust the `zephyr/module.yml` accordingly. The `west.yml` file is not commonly present in any of the types.

Modules should expose all provided header files with an include path name beginning with the module-name, for example at `include/zmk_<type>_<description>/<header>.h`.

:::info
If your module requires adding drivers to existing subsystems in modules, you will need to use the `zephyr_library_amend()` CMake command, which requires you to have a specific directory structure. See [`zephyr/cmake/modules/extensions.cmake`](https://github.com/zephyrproject-rtos/zephyr/blob/main@%7B2025-Feb-15%7D/cmake/modules/extensions.cmake#L492) for the definition and some documentation in the comments, with an example in [`petejohanson/ec-support-zmk-module`](https://github.com/petejohanson/ec-support-zmk-module/tree/main/drivers/kscan).
:::

## Examples

Below are some examples of modules for different types. Unless under the `zmkfirmware` project, these are not endorsed officially and may not follow our conventions perfectly. For such reason, the modules chosen to be presented here may change with time.

- Keyboard: <https://github.com/petejohanson/zmk-keyboards-katori>
- Behavior: <https://github.com/urob/zmk-leader-key>
- Driver: <https://github.com/petejohanson/cirque-input-module>
- Feature: <https://github.com/joelspadin/zmk-locales>
- VFX: <https://github.com/caksoylar/zmk-rgbled-widget>

---

### Docs Development New Behavior (`docs/docs/development/new-behavior.mdx`)

---
title: New Behavior
sidebar_label: New Behavior Guide
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

:::danger
Before reading this section, it is **vital** that you read through our [clean room policy](./contributing/clean-room.md).
:::

## Overview

[Behaviors](../keymaps/behaviors/index.mdx) refer to the actions that are invoked when a key is pressed or released.
This guide outlines how to create a [ZMK module](../development/module-creation.md) that contains a new [behavior](../keymaps/behaviors/index.mdx).

:::info
If an out-of-tree behavior's use-case is deemed widespread enough to be merged into upstream ZMK, a new pull request may be issued instead.
While this page describes practices that maximize compatibility between module-focused development and pull-request-based workflows, we also note specific modifications required for the latter to work properly.
:::

In the context of the Zephyr RTOS, behaviors are implemented as "devices", which consist of:

- A devicetree binding file, which declares the behavior's properties
- A device driver written in C code
- Optionally, devicetree-source-include files (`.dtsi`), which contain predefined instances of the behavior that may be included directly in keymaps

The general process for developing behaviors is:

1. [Create a new behavior repository](#creating-a-new-behavior-repository)
1. [Develop the behavior functionality](#developing-the-behavior-functionality)
1. [Test changes locally](#testing-changes-locally)
1. [Document behavior functionality](#documenting-behavior-functionality)
1. [Provide licensing information](#licensing-information)

:::info
Before developing new behaviors, developers should have a working knowledge of the Embedded Linux Devicetree.
The following resources are provided for those seeking further understanding:

- [Embedded Linux Wiki - Device Tree Usage](https://elinux.org/Device_Tree_Usage)
- [Zephyr Devicetree API](https://docs.zephyrproject.org/4.1.0/build/dts/api/api.html)
- [Zephyr Device Driver Model](https://docs.zephyrproject.org/4.1.0/kernel/drivers/index.html)

:::

## Creating a new Behavior Repository

### Initializing a new Behavior Module

1. Navigate to [the ZMK module template repository](https://github.com/zmkfirmware/zmk-module-template)
1. Select **"Use this template"** in the upper right corner, followed by **"Create a new repository"**
1. Choose an appropriate name for your new repository.
   ZMK behavior modules should follow the naming convention, **`zmk-behavior-<name-of-behavior>`**, using all lowercase letters and dashes to separate words
1. Complete the module's creation by selecting the repository's visibility, before clicking **"Create repository"**
1. Clone a copy of your module to your development environment.
   The cloned repository should be easily accessible when [building and testing firmware using a local toolchain](./local-toolchain/build-flash.mdx#building-with-external-modules).

Files unrelated to behavior development should be removed from your copy of the ZMK module template.
The minimum viable filesystem for a behavior follows the following structure:

```
zmk-behavior-<name-of-behavior>/
├── CMakeLists.txt
├── Kconfig
├── LICENSE
├── README.md
├── dts
│   ├── behaviors
// highlight-next-line
│   │   └── <name_of_behavior>.dtsi (optional)
│   └── bindings
│       └── behaviors
// highlight-next-line
│           └── zmk,behavior-<name-of-behavior>.yaml
├── include
│   └── dt-bindings
│       └── zmk
// highlight-next-line
│           └── <name_of_behavior>.h (optional)
├── src
│   └── behaviors
// highlight-next-line
│       └── behavior_<name_of_behavior>.c
// highlight-start
├── tests
│   └── <name_of_behavior>
│       ├── behavior_keymap.dtsi
│       └── normal
│           ├── events.patterns
│           ├── keycode_events.snapshot
│           ├── native_posix.keymap
│           └── native_posix_64.keymap
// highlight-end
// highlight-next-line
├── west.yml (optional)
└── zephyr
    └── module.yml
```

For more information on module preparation, such as details on the contents of `west.yml` and `zephyr/module.yml`, refer to the page on [module creation](./module-creation.md).

Once the module's tree has been organized properly, the relevant files are now ready to be populated.
We will explain the purpose of the files listed in the tree above in order of increasing complexity.

### Devicetree Bindings (`.yaml`)

Devicetree bindings use `.yaml` files to declare their properties.

They are stored in `dts/bindings/behaviors/` and follow the same naming convention as the repository itself.

The [mod-morph](../keymaps/behaviors/mod-morph.md)'s devicetree binding is presented below as a simple example.

```yaml title="zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml"
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT

// highlight-next-line
description: Mod Morph Behavior

// highlight-next-line
compatible: "zmk,behavior-mod-morph"

// highlight-next-line
include: zero_param.yaml # Additional parameters

// highlight-next-line
properties:
  bindings:
    type: phandle-array
    required: true
  mods:
    type: int
    required: true
  keep-mods:
    type: int
    required: false

```

It can be seen that the `.yaml` files used for new behaviors' devicetree bindings consist of the following fields:

#### `description`

A brief statement of what the behavior is.
The `description` should be kept less than a sentence long because it is not a property seen by end-users.
Instead, detailed explanations of how the behavior works should be shared in the behavior's documentation.

#### `compatible`

Allows Zephyr to assign the correct devicetree node to the behavior extracted from the keymap or `.dtsi`, which is then connected to the proper driver.
The value of the `compatible` property is same as the name of the [devicetree binding file](#devicetree-bindings-yaml).

In the example above, `zmk,behavior-mod-morph.yaml` lists `compatible: "zmk,behavior-mod-morph"`.

#### `include` additional parameters

Choose between `zero_param.yaml`, `one_param.yaml`, or `two_param.yaml` depending on how many additional parameters are required to complete the behavior's binding in a keymap.

| `include`         | Example keymap binding |
| ----------------- | ---------------------- |
| `zero_param.yaml` | `&sys_reset`           |
| `one_param.yaml`  | `&kp A`                |
| `two_param.yaml`  | `&mt LSHFT Z`          |

:::info
Some behaviors, like the [Bluetooth behavior](../keymaps/behaviors/bluetooth.md), use `two_param.yaml` despite their keymap usage _appearing_ to only use one extra parameter, e.g., `&bt BT_NXT` or `&bt BT_PRV`.
Expanding their C preprocessor definitions reveals the following definitions: `#define BT_NXT BT_NXT_CMD 0` and `#define BT_PRV BT_PRV_CMD 0`, respecting the use of `two_param.yaml`.
This is useful for creating behaviors that may have a primary "command", followed by a secondary parameter.

See [Behavior Metadata](#behavior-metadata) for more information.
:::

#### `properties` (Optional)

These are additional variables required to configure a particular instance of a behavior.
More information can be found in [ZMK's Devicetree primer](./devicetree.md) or [Zephyr's own documentation on Devicetree bindings](https://docs.zephyrproject.org/4.1.0/build/dts/bindings-syntax.html#properties).

### Behavior Source Files (`.c`)

Behavior source files are stored in in `src/behaviors/`.
They are labelled in lowercase, beginning with the prefix `behavior_`, and ending with the behavior's name, using underscores to separate multiple words.

The developer may decide that there is a single global instance of a behavior, or multiple instances that act independently of one another.
Some examples of the former are layer behaviors, backlight control, or endpoint selection.
The latter includes keypresses, hold-taps, or tap-dances.

The code templates below show the differences between these categories, along with the essential components of a behavior source file.

<Tabs
defaultValue="single"
values={[
{label: 'Behavior template with single instantiation', value: "single"},
{label: 'Behavior template with multiple instantiation', value: "multiple"},
]}>
<TabItem value="single">

```
/* Detailed source-code truncated for AI context efficiency. */
```

</TabItem>

<TabItem value="multiple">

```
/* Detailed source-code truncated for AI context efficiency. */
```

</TabItem>
</Tabs>

A more thorough explanation of the contents of a behavior source file can be [found below](#developing-the-behavior-functionality).

Other source files may be created as well, as is often the case when creating new features or [events](events.md) from scratch.
For parity with upstream ZMK, these files will generally be placed in the root `src/` directory, or `src/events/`.

### Updating `Kconfig`

Kconfig files are used to configure the system firmware at compile time.
Behaviors specifically will generally use the `DT_HAS_ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>_ENABLED` macro, which checks if the behavior is defined in the devicetree.
This ensures that behavior-specific properties may be accessed without explicitly enabling the behavior in a keyboard's `.conf` or `defconfig` files.

```Kconfig title="Kconfig"
config ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>
    bool
    default y
    depends on DT_HAS_ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>_ENABLED

if ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>

config ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>_PROPERTY1
    <bool | string | hex | int>
    help <Explanation of property 1>

config ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>_PROPERTY2
    <bool | string | hex | int>
    help <Explanation of property 2>

config ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>_PROPERTY3
    <bool | string | hex | int>
    help <Explanation of property 3>

endif #ZMK_BEHAVIOR_<NAME_OF_BEHAVIOR>
```

:::info
For an overview on Kconfig files, see [Configuration](../config/index.md#kconfig-files).

For more examples of behavior-specific Kconfig settings, see [Behavior Configuration](../config/behaviors.md).
:::

### Updating `CMakeLists.txt`

`CMakeLists.txt` files are used in Zephyr's [configuration stage](https://docs.zephyrproject.org/3.5.0/build/cmake/index.html) when building firmware.
These specify which source files are included in the build, and may depend on the Kconfig settings shown previously.

At this point the developer should consider the behavior's [locality](../features/split-keyboards.md#behaviors-with-locality).

Most behaviors are processed on a unibody keyboard, or the central half of a split board.
An example is shown below.

```txt title="CMakeLists.txt"
# Copyright (c) XXXX The ZMK Contributors
# SPDX-License-Identifier: MIT

target_include_directories(app PRIVATE include)

if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
    target_sources(app PRIVATE src/behaviors/behavior_<name_of_behavior>.c)
endif() # ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
```

Other common ways of enabling/blocking the inclusion of sources via `CMakeLists.txt` include:

| Condition                                                       | CMakeLists.txt entry                                                                                                                              |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Locality is unibody, or the central part of a split keyboard    | `if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)`                                                                                    |
| Locality is **only** on the central part of a split keyboard    | `if (CONFIG_ZMK_SPLIT AND CONFIG_ZMK_SPLIT_ROLE_CENTRAL)`                                                                                         |
| Locality is **only** on the peripheral part of a split keyboard | `if (CONFIG_ZMK_SPLIT AND (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL))`                                                                                   |
| Kconfig Requirement must be met                                 | Use `target_sources_ifdef(CONFIG_<Configuration Requirement> app PRIVATE <name_of_behavior>.c)` instead of `target_sources(<name_of_behavior>.c)` |

:::info
If submitting a pull request to upstream ZMK, the `target_sources` invocation would go inside `zmk/app/CMakeLists.txt` instead.
:::

### Optional: Defining Common Use-Cases for the Behavior (`.dtsi`)

`.dtsi` files, stored in the directory `dts/behaviors/`, are encouraged for behaviors with more common use-cases.
One such example is the mod-tap (`&mt`), which is a predefined type of hold-tap that takes a modifier key as the hold parameter and another key as the tap parameter.

For the purpose of this section, we will discuss the structure of `zmk/app/dts/behaviors/gresc.dtsi` below.

```dts title="zmk/app/dts/behaviors/gresc.dtsi"
/*
 * Copyright (c) 2020 The ZMK Contributors
 *
 * SPDX-License-Identifier: MIT
 */


#include <dt-bindings/zmk/behaviors.h>
#include <dt-bindings/zmk/keys.h>

/ {
    behaviors {
// highlight-start
#if ZMK_BEHAVIOR_OMIT(GRESC)
        /omit-if-no-ref/
#endif
// highlight-end
        gresc: grave_escape {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp ESC>, <&kp GRAVE>;
            mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
            display-name = "Grave/Escape";
        };
    };
};

```

The format of a behavior's `.dtsi` file is identical to declaring an instance of the behavior in a user's keymap.
However, a major difference is that the value `/omit-if-no-ref/` should be placed adjacent to the label and name of the behavior, as highlighted in the example.
This enables the behavior to only be compiled if it is used in the keymap.

:::warning

If your behavior has its [`locality`](#zmk-api-struct) property set to anything other than `BEHAVIOR_LOCALITY_CENTRAL`, then the name of the node must be at most 8 characters long.
Otherwise, it will fail to be invoked on the peripheral half of a split keyboard.

In the above example, `grave_escape` is too long, so it would need to be shortened, e.g.

```dts
// Behavior can be invoked on peripherals, so name must be <= 8 characters.
/omit-if-no-ref/ gresc: gresc { ... };
```

:::

After creating the `.dtsi` from above, you may `#include <behaviors/new_behavior_instance.dtsi>` at the top of your keymap to access the new behavior definition.

:::info
If submitting a pull request to upstream ZMK, this `#include` statement would go inside `zmk/app/dts/behaviors.dtsi`, instead of the keymap.
:::

## Developing the Behavior Functionality

### Overview

This section elaborates on the contents of behavior sources that interact with ZMK directly.
We will review the components from the [behavior source templates](#behavior-source-files-c) in the order they appear and introduce new concepts that are commonly used in the development process.

:::info
Developing drivers for behaviors in ZMK makes extensive use of the Zephyr Devicetree API and Device Driver Model.
Links to the Zephyr Project Documentation for both of these concepts can be found below:

- [Zephyr Devicetree API](https://docs.zephyrproject.org/4.1.0/build/dts/api/api.html)
- [Zephyr Device Driver Model](https://docs.zephyrproject.org/4.1.0/kernel/drivers/index.html)

:::

:::warning
If submitting a pull request, any `.c` files should be formatted according to `clang-format` to ensure that automated checks run smoothly.
:::

### Compatible: `#define DT_DRV_COMPAT`

This field should match the [`compatible` field of your devicetree binding](#compatible), using all lowercase letters and underscores to separate words, instead of hyphens and commas.

For example, the Caps Word behavior's devicetree binding lists `compatible: "zmk,behavior-caps-word"`, so the `DT_DRV_COMPAT` is `zmk_behavior_caps_word`.

### Dependencies

The dependencies required for any ZMK behavior are:

- `zephyr/device.h`: [Zephyr Device APIs](https://docs.zephyrproject.org/apidoc/3.5.0/group__device__model.html)
- `drivers/behavior.h`: ZMK Behavior Functions (e.g. [locality](#zmk-api-struct), `behavior_keymap_binding_pressed`, `behavior_keymap_binding_released`, `behavior_sensor_keymap_binding_triggered`)
- `zephyr/logging/log.h`: [Zephyr Logging APIs](https://docs.zephyrproject.org/3.5.0/services/logging/index.html) (for more information on USB Logging in ZMK, see [USB Logging](usb-logging.mdx)).
- `zmk/behavior.h`: ZMK Behavior Information (e.g. parameters, position and timestamp of events)
  - `return` values:
    - `ZMK_BEHAVIOR_OPAQUE`: Used to terminate `on_<name_of_behavior>_binding_pressed` and `on_<name_of_behavior>_binding_released` functions that accept `(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event)` as parameters
    - `ZMK_BEHAVIOR_TRANSPARENT`: Used in the `binding_pressed` and `binding_released` functions for the transparent (`&trans`) behavior
  - `struct`s:
    - `zmk_behavior_binding`: Stores the name of the behavior device (`char *behavior_dev`) as a `string` and up to two additional parameters (`uint32_t param1`, `uint32_t param2`)
    - `zmk_behavior_binding_event`: Contains layer, position, and timestamp data for an active `zmk_behavior_binding`

Other common dependencies include `zmk/keymap.h`, which allows behaviors to access layer information and extract behavior bindings from keymaps, and `zmk/event_manager.h` which is detailed below.

### Behavior metadata

Behavior metadata documents the possible combinations of parameters that can be used with the behavior when added to your keymap.
The metadata structure allows flexibility to specify different kinds of well known parameter types, such as a HID usage, different second parameters passed on the selected first parameter, etc.

You can see a few examples of how the metadata is implemented in practice for the following behaviors:

- [Key press](https://github.com/zmkfirmware/zmk/blob/main/app/src/behaviors/behavior_key_press.c#L21)
- [RGB underglow](https://github.com/zmkfirmware/zmk/blob/main/app/src/behaviors/behavior_rgb_underglow.c#L23)
- [Hold-tap](https://github.com/zmkfirmware/zmk/blob/main/app/src/behaviors/behavior_hold_tap.c#L680), which is dynamic based on what behaviors are set up in the hold-tap bindings

Behavior metadata consists of one or more metadata sets, where each metadata set has a set of values for the parameter(s) used with the behavior.

For example, a common approach for behaviors is to have a set of possible first parameters that identify the "command" to invoke for the behavior, and the second parameter is a detail/sub-parameter to the action.
You can see an example of this with the `&bt` behavior.
In that scenario, all `&bt` "commands" that take a BT profile as a second parameter are grouped into one set, and all commands that take no arguments are grouped into another.

This allows the ZMK Studio UI to properly show a input for a profile only when the appropriate first "command" selection is made in the UI.
Here is a snippet of that setup from the [behavior_bt.c](https://github.com/zmkfirmware/zmk/blob/main/app/src/behaviors/behavior_bt.c#L25) code:

```
/* Detailed source-code truncated for AI context efficiency. */
```

### ZMK API struct

Comes in the form `static const struct behavior_driver_api <name_of_behavior>_driver_api`.
The most relevant fields of `struct behavior_driver_api`, defined in `drivers/behavior.h`, are shown below.

| Field             | Description                                                                                                                                    |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| .binding_pressed  | The function called when the key is pressed. Typically reserved for a function named [`on_<name_of_behavior>_binding_pressed`](#dependencies)  |
| .binding_released | The function called when the key is pressed. Typically reserved for a function named [`on_<name_of_behavior>_binding_released`](#dependencies) |
| .locality         | Describes how the behavior affects parts of a _split_ keyboard.                                                                                |

Locality has been discussed previously at compile-time.
Locality in the context of the API struct refers to runtime locality.
The `.locality` field may take the following values.

- `BEHAVIOR_LOCALITY_CENTRAL`: Behavior only affects the central half, which is the case for most keymap-related behavior.
- `BEHAVIOR_LOCALITY_EVENT_SOURCE`: Behavior affects only the central _or_ peripheral half depending on which side invoked the behavior binding, such as [reset behaviors](../keymaps/behaviors/reset.md).
- `BEHAVIOR_LOCALITY_GLOBAL`: Behavior affects the entire keyboard, such as [external power](../keymaps/behaviors/power.md) and lighting-related behaviors that need to be synchronized across halves.

:::note
For unibody keyboards, all locality values perform the same as `BEHAVIOR_LOCALITY_GLOBAL`.
:::

The API struct's metadata-specific fields are shown below.

| Field                   | Description                                                                                                                                                                                   |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| .get_parameter_metadata | Callback function that can dynamically provide/populate the metadata describing the parameters to use with the behavior so the behavior may be used with [ZMK Studio](../features/studio.md). |
| .parameter_metadata     | Pointer to metadata describing the parameters to use with the behavior so the behavior may be used with [ZMK Studio](../features/studio.md).                                                  |

### Invoking `BEHAVIOR_DT_INST_DEFINE`

`BEHAVIOR_DT_INST_DEFINE` is a special ZMK macro which uses Zephyr's `DEVICE_DT_INST_DEFINE` macro to define the driver instance, before adding it to a list of ZMK behaviors so that can be found by the function `zmk_behavior_get_binding()`.

:::info
For more information on this function, refer to [Zephyr's documentation on the Device Driver Model](https://docs.zephyrproject.org/4.1.0/kernel/drivers/index.html#c.DEVICE_DT_INST_DEFINE).
:::

The example `BEHAVIOR_DT_INST_DEFINE` call can be left as is with the first parameter, the instance number, equal to `0` for behaviors that only require a single instance (e.g. external power, backlighting, accessing layers).
For behaviors that can have multiple instances (e.g. hold-taps, tap-dances, sticky-keys), `BEHAVIOR_DT_INST_DEFINE` can be placed inside a `#define` statement, usually formatted as `#define <ABBREVIATED BEHAVIOR NAME>_INST(n)`, that sets up any [data pointers](#optional-data-pointers) and/or [configuration pointers](#optional-configuration-pointers) that are unique to each instance.

An example of this can be seen below, taking the `#define KP_INST(n)` from the hold-tap driver.

```c
#define KP_INST(n)                                                                                 \
    static const struct behavior_hold_tap_config behavior_hold_tap_config_##n = {                  \
        .tapping_term_ms = DT_INST_PROP(n, tapping_term_ms),                                       \
        .hold_behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 0), label),               \
        .tap_behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 1), label),                \
        .quick_tap_ms = DT_INST_PROP(n, quick_tap_ms),                                             \
        .flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor),                                             \
        .retro_tap = DT_INST_PROP(n, retro_tap),                                                   \
        .hold_trigger_key_positions = DT_INST_PROP(n, hold_trigger_key_positions),                 \
        .hold_trigger_key_positions_len = DT_INST_PROP_LEN(n, hold_trigger_key_positions),         \
    };                                                                                             \
    BEHAVIOR_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, NULL, &behavior_hold_tap_config_##n,    \
                            APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,                        \
                            &behavior_hold_tap_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
```

Note that in the hold-tap example, the instance number, `0`, has been replaced by `n`, signifying the unique `node_id` of each instance of a behavior.
Furthermore, the DT_INST_FOREACH_STATUS_OKAY(KP_INST) macro iterates through each compatible, non-disabled devicetree node, creating and applying the proper values to any instance-specific configurations or data by invoking the KP_INST macro for each instance of the new behavior.

Behaviors also require the following parameters of `BEHAVIOR_DT_INST_DEFINE` to be changed:

#### Optional: Initialization function

Comes in the form `static int <name_of_behavior>_init(const struct device *dev)`.
Initialization functions preconfigure any data, like resetting timers and position for hold-taps and tap-dances.
All initialization functions `return 0;` once complete.

The **second** argument of `BEHAVIOR_DT_INST_DEFINE` can be set to `NULL` instead if an initialization function is not required.

#### Optional: Data pointers

The data `struct` stores additional data required for **each new instance** of the behavior.
Regardless of the instance number, `n`, `behavior_<name_of_behavior>_data_##n` is typically initialized as an empty `struct`.
The data respective to each instance of the behavior can be accessed in functions like [`on_<name_of_behavior>_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event)`](#dependencies) by extracting the behavior device from the keybind like so:

```c
const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
struct behavior_<name_of_behavior>_data *data = dev->data;
```

The variables stored inside the data `struct`, `data`, can be then modified as necessary.

The **fourth** argument of `BEHAVIOR_DT_INST_DEFINE` can be set to `NULL` instead if instance-specific data is not required.

#### Optional: Configuration pointers

The configuration `struct` stores the properties declared from the behavior's `.yaml` for **each new instance** of the behavior.
As seen in the `#define KP_INST(n)` of the hold-tap example, the configuration `struct`, `behavior_<name_of_behavior>_config_##n`, for each instance number, `n`, can be initialized using the [Zephyr Devicetree Instance-based APIs](https://docs.zephyrproject.org/4.1.0/build/dts/api/api.html#instance-based-apis),
which extract the values from the `properties` of each instance of the [devicetree binding](#devicetree-bindings-yaml) from a user's keymap or [predefined use-case `.dtsi` files](#optional-defining-common-use-cases-for-the-behavior-dtsi) stored in `app/dts/behaviors/`.
We illustrate this further by comparing the [`#define KP_INST(n)` from the hold-tap driver](#invoking-behavior_dt_inst_define) and the [`properties` of the hold-tap devicetree binding](#devicetree-bindings-yaml).
The config structure instances should always be declared `const`
so they are placed into flash, not RAM, by the linker.

The **fifth** argument of `BEHAVIOR_DT_INST_DEFINE` can be set to `NULL` instead if instance-specific configurations are not required.

### Keycodes

Let us examine one of the simplest behavior actions: sending and releasing keycodes.

The core of the [key press behavior](../keymaps/behaviors/key-press.md) is `raise_zmk_keycode_state_changed_from_encoded()`, found in `keycode_state_changed.h`.
This function takes in three arguments: an HID usage, a boolean value to determine if the keycode is pressed or released, and a timestamp.
We present a snippet from the key press behavior source, where it is seen that the HID usage of each keycode is extracted from the keymap, before it is determined to be pressed or released.

```c
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
                                     struct zmk_behavior_binding_event event) {
    LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
    return raise_zmk_keycode_state_changed_from_encoded(binding->param1, true, event.timestamp);
}

static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
                                      struct zmk_behavior_binding_event event) {
    LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
    return raise_zmk_keycode_state_changed_from_encoded(binding->param1, false, event.timestamp);
}

static const struct behavior_driver_api behavior_key_press_driver_api = {
    .binding_pressed = on_keymap_binding_pressed,
    .binding_released = on_keymap_binding_released,
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
    .parameter_metadata = &metadata,
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};
```

### Layers

All functions that interact with layers can be found in `keymap.h`.

Layers can be identified in two ways: an "order-independent" `zmk_keymap_layer_id_t`, and an "order-dependent" `zmk_keymap_layer_index_t`.

| Function                                                             | Description                                                                                                                          |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `zmk_keymap_layer_default(void)`                                     | Returns a `zmk_keymap_layer_id_t` of the default layer.                                                                              |
| `zmk_keymap_layer_state(void)`                                       | Returns the current keyboard's layer state: a bitmask where each bit represents the state of the corresponding order-independent ID. |
| `zmk_keymap_layer_active(zmk_keymap_layer_id_t layer)`               | Returns a `bool` representing if the layer with the chosen `zmk_keymap_layer_id_t` is active.                                        |
| `zmk_keymap_highest_layer_active(void)`                              | Returns a `zmk_keymap_layer_index_t` the ordered layer index of the highest active layer.                                            |
| `zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer)`             | Activates the chosen layer. Returns 0 if successful. Returns values < 0 if an error occurs.                                          |
| `zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer)`           | Deactivates the chosen layer. Returns 0 if successful. Returns values < 0 if an error occurs.                                        |
| `zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer)`               | Toggles the chosen layer. Returns 0 if successful. Returns values < 0 if an error occurs.                                            |
| `zmk_keymap_layer_to(zmk_keymap_layer_id_t layer)`                   | Deactivates every layer, before activating the chosen layer. Returns 0 if successful. Returns values < 0 if an error occurs.         |
| `zmk_keymap_layer_name(zmk_keymap_layer_id_t layer)`                 | Returns a C-string containing the layer's name.                                                                                      |
| `zmk_keymap_layer_index_to_id(zmk_keymap_layer_index_t layer_index)` | Returns the order-independent ID for a given order-dependent layer index.                                                            |

### ZMK Events

The event manager is a queue-based system that can be leveraged by behaviors to check for significant changes in the system's state.
Some common examples of this are determining if one or more key positions have been pressed or released, to check if a specific keycode has been sent, or registering changes between keymap layers.
All events can be found in their headers, stored in `zmk/app/include/zmk/events/`.

To use the event manager, we `#include <zmk/event_manager.h>` at the top of our behavior source file.

Some examples of events that are the most relevant to behavior development can be seen below.

| Event                      | Description                                                                                       |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| `hid_indicators_changed.h` | The current HID indicators (Num Lock, Caps Lock, Scroll Lock, Compose, Kana) as a bitmask         |
| `keycode_state_changed.h`  | [Keycode events' state (on/off)](#keycodes), usage page, keycode value, modifiers, and timestamps |
| `layer_state_changed.h`    | [Layer events' state (bitmask)](#layers), layer index, and timestamps                             |
| `position_state_changed.h` | Position events' state (on/off), source, position, and timestamps                                 |

:::info
See the [`events` directory](https://github.com/zmkfirmware/zmk/tree/main/app/include/zmk/events) for other examples of events.
For more information on how to interact with events and the event manager, see [Events](./events.md).
:::

### Interacting with other behaviors and the ZMK Behavior Queue

This section will refer to features found in `behavior.h` and `behavior_queue.h`.

#### `#include <zmk/behavior.h>`

These functions work with behaviors at a **device** level.
They are used to retrieve the device associated with a keymap binding, or invoke other behaviors, such as ones provided as a parameter to the current behavior.

| Function                                                                                                                             | Description                                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `struct zmk_behavior_binding`                                                                                                        | A `struct` containing the behavior binding's name stored as a C-string, and its parameters.                                                                                                                     |
| `struct zmk_behavior_binding_event`                                                                                                  | A `struct` describing where and when a behavior binding is invoked based on its the layer, key position, and timestamp. For split keyboards, this also includes which part of the keyboard invoked the binding. |
| `zmk_behavior_get_binding(const char *name)`                                                                                         | Get a `const struct device*` for a behavior from its name field.                                                                                                                                                |
| `zmk_behavior_invoke_binding(const struct zmk_behavior_binding *src_binding, struct zmk_behavior_binding_event event, bool pressed)` | Invoke a behavior given its binding and invoking event details.                                                                                                                                                 |

#### `#include <zmk/behavior_queue.h>`

The behavior queue is leveraged by [macros](../keymaps/behaviors/macros.md) and [sensor rotation](../keymaps/behaviors/sensor-rotate.md) behaviors.
This queue ensures that behaviors may be invoked sequentially using specific time-based triggers without blocking the rest of the keyboard functionality.

| Function                 | Description                              |
| ------------------------ | ---------------------------------------- |
| `zmk_behavior_queue_add` | Adds the behavior to the behavior queue. |

## Testing Changes Locally

Create a new folder in `tests/` (or `app/tests/` if submitting a pull request) to develop virtual test sets for all common use cases of the behavior.
For pull requests, behaviors should be tested thoroughly on both virtual testing environments using `west test` and real hardware.

:::note
Zephyr currently does not support logging over Bluetooth, so any use of the serial monitor for hardware testing must be done over hardware UART or USB virtual UART.
:::

:::info

- See [Tests](local-toolchain/tests.md) for more information on how to create virtual test sets.
- For hardware-based testing, see [USB Logging](usb-logging.mdx).

:::

## Documenting Behavior Functionality

Consider the following prompts when writing documentation for new behaviors:

- What does it do? Describe some general use-cases for the behavior.
- Which properties included in the [devicetree binding](#devicetree-bindings-yaml) should be configured manually by the user? What do they do, and if applicable, what are their default values?
- What does an example implementation in a keymap look like? Include a code-snippet of the example implementation in the keymap file's `behaviors` node.
- How does the behavior perform in edge cases? For example, tap-dances invoke the last binding in its list of `bindings` once the maximum number of keypresses has been reached.

Including visual aids alongside written documentation for additional clarity may be helpful.

:::info
If submitting a pull request, see [Documentation](contributing/documentation.md) for more information on writing, testing, and formatting ZMK documentation.
:::

## Licensing Information

:::danger
The ZMK Project and its contributors do not claim to be legal representatives, and any material below should not considered official legal advice.
When distributing your work, please review the terms and conditions associated with any relevant licenses thoroughly.
:::

Developers may wish to share their work with the public, which is often done by sharing a link to a GitHub repository.
However, making a repository public does **not** automatically qualify the repository as open source, or permit others to use the works as they see fit.
To qualify a codebase as open source, authors must provide a license that in addition to their source code, satisfy criteria that includes but is not limited to:

- The source code and license must be freely accessible and redistributable
- The source code may be freely modified, which may result in the creation of derivative works under the conditions of the included license
- The license must not discriminate against any person, group of persons, or specific fields of endeavor

:::info
For more information, consider looking at the following resources:

- [GitHub Open Source Guides](https://opensource.guide/legal/)
- [The System Package Data Exchange (SPDX)](https://spdx.dev/learn/handling-license-info/)
- [List of licenses approved by the Open Source Initiative (OSI)](https://opensource.org/licenses)
- [The OSI Open Source Definition](https://opensource.org/osd)

:::

### Contributing to ZMK (MIT License)

The [MIT License](https://github.com/zmkfirmware/zmk/blob/main/LICENSE) is used for developers submitting a pull request or those wish to make their work usable, modifiable, and distributable in its entirety to the ZMK community.
If the author's intent is to contribute their work to ZMK in these manners, especially when submitting pull requests, they should be aware of the constraints specified in our [clean room policy](./contributing/clean-room.md).

[SPDX copyright headers](https://spdx.dev/learn/handling-license-info/) for each of the files outlined in this document can be copied and pasted from the tabs below.

<Tabs
defaultValue="yaml"
values={[
{label: 'Devicetree Bindings (.yaml) and CMakeLists.txt', value: 'yaml'},
{label: 'Drivers (.c) and predefined use-cases (.dtsi)', value: 'c'},
]}>
<TabItem value="yaml">

```yaml
// highlight-next-line
# Copyright (c) XXXX The ZMK Contributors
# SPDX-License-Identifier: MIT
```

</TabItem>
<TabItem value="c">

```c
/*
// highlight-next-line
 * Copyright (c) XXXX The ZMK Contributors
 *
 * SPDX-License-Identifier: MIT
 */
```

</TabItem>
</Tabs>

:::warning
Remember to change the copyright year (`XXXX`) to the current year when adding the copyright headers to your newly created files.
This also applies to the **`LICENSE`** file at the repository's root.
:::

### Other licenses

Developers may also use other licenses with their work.
Some common example are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) or [GNU Public Licenses (GPL)](https://www.gnu.org/licenses/licenses.html).
However, software licenses are generally treated as "one-way" compatible.
This means that code registered under a more permissive license may be used in a project with a more restrictive license, but not the other way around.

For example, as noted in ZMK's [clean room policy](./contributing/clean-room.md), projects like QMK and TMK use GPL licenses, which are more restrictive than ZMK's MIT license.
Code from ZMK may be used as a reference when developing work for QMK/TMK, but code from QMK/TMK may **not** be used as source material when working on ZMK.

---

--- METRICS ---
- Files Extracted: 91
- Estimated Token Budget: ~130175 tokens
- Canonical Reference: https://codewiki.google/github.com/zmkfirmware/zmk
