---
title: New Keyboard Shield
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import KeymapExampleFile from "../../keymap-example-file.md";

import InterconnectTabs from "@site/src/components/interconnect-tabs";
import Metadata from "@site/src/data/hardware-metadata.json";

:::danger
Before reading this section, it is **vital** that you read through our [clean room policy](../contributing/clean-room.md).
:::

## Overview

This guide will walk through the steps necessary to add ZMK support for a keyboard that uses an add-on MCU board (e.g. Pro Micro compatible) to provide the microprocessor.

The high level steps are:

- From a template, create a new [Zephyr module](https://docs.zephyrproject.org/3.5.0/develop/modules.html) housed in a git repository containing one or more custom shields.
- Create a new shield directory.
- Add the base Kconfig files.
- Add the shield overlay file to define the KSCAN driver for detecting key press/release.
- Add the matrix transform for mapping KSCAN row/column values to key positions in the keymap.
- Add a physical layout definition to select the matrix transform and KSCAN instance.
- Add a default keymap, which users can override in their own configs as needed.
- Add a `<my_shield>.zmk.yml` metadata file to document the high level details of your shield, and the features it supports.
- Update the `build.yaml` file from the repository template to have some sample builds of the firmware to test.

After adding ZMK support for a basic shield using this guide, check the sidebar for guides on adding any additional features (such as encoders) that your keyboard has. It may be helpful to review the upstream [shields documentation](https://docs.zephyrproject.org/3.5.0/hardware/porting/shields.html#shields) to get a proper understanding of the underlying system before continuing.

:::note
ZMK support for [split keyboards](../../features/split-keyboards.md) requires a few more files than single boards to ensure proper connectivity between the central and peripheral units. Check the following guides thoroughly to ensure that all the files are in place.
:::

## New Zephyr Module Repository

The first step to creating the shield is to create a new Zephyr module repository from a template.

:::note
This guide assumes you already have a configured GitHub account. If you don't yet have one, go ahead and [sign up](https://github.com/join) before continuing.
:::

Follow these steps to create your new repository:

- Visit https://github.com/zmkfirmware/unified-zmk-config-template
- Click the green "Use this template" button
- In the drop down that opens, click "Use this template".
- In the following screen, provide the following information:
  - A repository name, e.g. `my-shield-module`.
  - A brief description, e.g. `ZMK Support For MyShield Keyboard`.
  - Select Public or Private, depending on your preference.
- Click the green "Create repository" button

The repository is a combination of the directories and files required of a ZMK config, and those required of a shield module. To create a shield module, the following components are needed:

- The `boards/shields` directory, where the keyboard's files will go
- The `zephyr/module.yml` file, which identifies and describes the module. See the [Zephyr documentation](https://docs.zephyrproject.org/3.5.0/develop/modules.html#module-yaml-file-description) for details on customising this file. For the purposes of creating a shield module, the default found in the template can be left untouched.

Neither of these should be moved out of their parent directory. The other files and directories such as `config` are not necessary for the purposes of a shield module, but rather intended to be used for user configuration and testing.

## New Shield Directory

Shields in Zephyr module "board root" go into the `boards/shields/` directory; that means the new shield directory in your module repository should be:

```bash
mkdir boards/shields/<keyboard_name>
```

## Base Kconfig Files

:::tip[Example shields]
You can check out the [`shields` folder](https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields) in the ZMK repo that houses [the in-tree supported shields](../../hardware.mdx) in order to copy and modify as a starting point.
:::

There are two required Kconfig files that need to be created for your new keyboard
shield to get it picked up for ZMK, `Kconfig.shield` and `Kconfig.defconfig`.

### Kconfig.shield

The `Kconfig.shield` file defines any additional Kconfig settings that may be relevant when using this keyboard. For most keyboards, there is just one additional configuration value for the shield itself.

```kconfig
config SHIELD_MY_BOARD
    def_bool $(shields_list_contains,my_board)
```

:::warning
Kconfig uses only commas for delimiters, and keeps all whitespaces in the function call. Therefore do not add a whitespace after the comma when configuring your shield as this would be treated as <code>&nbsp;my_board</code> (with a leading whitespace) and will cause issues.
:::

This will make sure that a new configuration value named `SHIELD_MY_BOARD` is set to true whenever `my_board` is used as the shield name, either as the `SHIELD` variable [in a local build](../local-toolchain/build-flash.mdx) or in your `build.yaml` file [when using Github Actions](../../customization.md). Note that this configuration value will be used in `Kconfig.defconfig` to set other properties about your shield, so make sure that they match.

**For split boards**, you will need to add configurations for the left and right sides. For example, if your split halves are named `my_board_left` and `my_board_right`, it would look like this:

```kconfig
config SHIELD_MY_BOARD_LEFT
    def_bool $(shields_list_contains,my_board_left)

config SHIELD_MY_BOARD_RIGHT
    def_bool $(shields_list_contains,my_board_right)
```

### Kconfig.defconfig

The `Kconfig.defconfig` file is where overrides for various configuration settings
that make sense to have different defaults when this shield is used. One main item
that usually has a new default value set here is the `ZMK_KEYBOARD_NAME` value,
which controls the display name of the device over USB and BLE.

The updated new default values should always be wrapped inside a conditional on the shield config name defined in the `Kconfig.shield` file. Here's the simplest example file.

:::danger
The keyboard name must be less than or equal to 16 characters in length, otherwise the bluetooth advertising might fail and you will not be able to find your keyboard from your device.
:::

```kconfig
if SHIELD_MY_BOARD

config ZMK_KEYBOARD_NAME
    default "My Board"

endif
```

For split keyboards, `Kconfig.defconfig` needs to specify a few more options.
Which side is central (usually the left) is determined via the configuration in this file.
For that side, the keyboard name is assigned and the central config is set.
The peripheral side is typically not assigned a name since only the central will be advertising for connections to other devices.
Finally, the split config needs to be set for both sides:

```kconfig
if SHIELD_MY_BOARD_LEFT

config ZMK_KEYBOARD_NAME
    default "My Board"

config ZMK_SPLIT_ROLE_CENTRAL
    default y

endif

if SHIELD_MY_BOARD_LEFT || SHIELD_MY_BOARD_RIGHT

config ZMK_SPLIT
    default y

endif
```

## Shield Overlays

<InterconnectTabs items={Metadata} />

To use GPIO pins that are not part of the interconnects as described above, you can use the GPIO labels that are specific to each controller type.
For instance, pins numbered `PX.Y` in nRF52840-based boards can be referred to via `&gpioX Y` labels.
An example is `&gpio1 7` for the `P1.07` pin that the nice!nano exposes in the middle of the board.

<Tabs
defaultValue="unibody"
values={[
{label: 'Unibody Shields', value: 'unibody'},
{label: 'Split Shields', value: 'split'},
]}>

<TabItem value="unibody">

The `<shield_name>.overlay` is the devicetree description of the keyboard shield that is merged with the primary board devicetree description before the build. For ZMK, this file at a minimum should include the chosen node named `zmk,kscan` that references a KSCAN driver instance. For a simple 3x3 macropad matrix,
this might look something like:

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

    kscan0: kscan_0 {
        compatible = "zmk,kscan-gpio-matrix";
        diode-direction = "col2row";
        wakeup-source;

        col-gpios
            = <&pro_micro 15 GPIO_ACTIVE_HIGH>
            , <&pro_micro 14 GPIO_ACTIVE_HIGH>
            , <&pro_micro 16 GPIO_ACTIVE_HIGH>
            ;

        row-gpios
            = <&pro_micro 19 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            , <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
            ;
    };
};
```

See the [Keyboard Scan configuration documentation](../../config/kscan.md) for details on configuring the KSCAN driver.

</TabItem>

<TabItem value="split">

### .dtsi files and Shield Overlays (Split Shields)

Unlike unibody keyboards, split keyboards have a core .dtsi file with shield overlays for each half of the keyboard.
It is preferred to define only the `col-gpios` or `row-gpios` in the common shield .dtsi, depending on the `diode-direction` value.
For `col2row` directed boards like the iris, the shared .dtsi file may look like this:

```dts
#include <dt-bindings/zmk/matrix_transform.h>

/ {
    chosen {
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &default_transform;
    };

    default_transform: keymap_transform_0 {
        compatible = "zmk,matrix-transform";
        columns = <16>;
        rows = <4>;
// | SW6  | SW5  | SW4  | SW3  | SW2  | SW1  |                 | SW1  | SW2  | SW3  | SW4  | SW5  | SW6  |
// | SW12 | SW11 | SW10 | SW9  | SW8  | SW7  |                 | SW7  | SW8  | SW9  | SW10 | SW11 | SW12 |
// | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 |                 | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 |
// | SW24 | SW23 | SW22 | SW21 | SW20 | SW19 | SW25 |   | SW25 | SW19 | SW20 | SW21 | SW22 | SW23 | SW24 |
//                      | SW29 | SW28 | SW27 | SW26 |   | SW26 | SW27 | SW28 | SW29 |
        map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5)                 RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5)                 RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5)                 RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11)
RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(4,2) RC(4,9) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11)
                                RC(4,3) RC(4,4) RC(4,5) RC(4,6) RC(4,7) RC(4,8)
        >;
    };

    kscan0: kscan {
        compatible = "zmk,kscan-gpio-matrix";
        diode-direction = "col2row";
        wakeup-source;

        row-gpios
            = <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // Row A from the schematic file
            , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // Row B from the schematic file
            , <&pro_micro 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // Row C from the schematic file
            , <&pro_micro 0 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // Row D from the schematic file
            , <&pro_micro 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // Row E from the schematic file
            ;

    };
};
```

:::note
Notice that in addition to the common `row-gpios` that are declared in the kscan, the [matrix transform](#matrix-transform) is defined in the .dtsi.
:::

The missing `col-gpios` would be defined in your `<boardname>_left.overlay` and `<boardname>_right.overlay` files.
Keep in mind that the mirrored position of the GPIOs means that the `col-gpios` will appear reversed when the .overlay files are compared to one another.
Furthermore, the column offset for the [matrix transform](#matrix-transform) should be added to the right half of the keyboard's overlay
because the keyboard's switch matrix is read from left to right, top to bottom.
This is exemplified with the iris .overlay files.

```dts title=iris_left.overlay
#include "iris.dtsi" // Notice that the main dtsi files are included in the overlay.

&kscan0 {
    col-gpios
        = <&pro_micro 19 GPIO_ACTIVE_HIGH> // col1 in the schematic
        , <&pro_micro 18 GPIO_ACTIVE_HIGH> // col2 in the schematic
        , <&pro_micro 15 GPIO_ACTIVE_HIGH> // col3 in the schematic
        , <&pro_micro 14 GPIO_ACTIVE_HIGH> // col4 in the schematic
        , <&pro_micro 16 GPIO_ACTIVE_HIGH> // col5 in the schematic
        , <&pro_micro 10 GPIO_ACTIVE_HIGH> // col6 in the schematic
        ;
};
```

```dts title=iris_right.overlay
#include "iris.dtsi"

&default_transform { // The matrix transform for this board is 6 columns over because the left half is 6 columns wide according to the matrix.
    col-offset = <6>;
};

&kscan0 {
    col-gpios
        = <&pro_micro 10 GPIO_ACTIVE_HIGH> // col6 in the schematic
        , <&pro_micro 16 GPIO_ACTIVE_HIGH> // col5 in the schematic
        , <&pro_micro 14 GPIO_ACTIVE_HIGH> // col4 in the schematic
        , <&pro_micro 15 GPIO_ACTIVE_HIGH> // col3 in the schematic
        , <&pro_micro 18 GPIO_ACTIVE_HIGH>  // col2 in the schematic
        , <&pro_micro 19 GPIO_ACTIVE_HIGH>  // col1 in the schematic
        ;
};

```

See the [Keyboard Scan configuration documentation](../../config/kscan.md) for details on configuring the KSCAN driver.

### .conf files (Split Shields)

While unibody boards only have one .conf file that applies configuration characteristics to the entire keyboard,
split keyboards are unique in that they contain multiple .conf files with different scopes.
For example, a split board called `my_awesome_split_board` would have the following files:

- `my_awesome_split_board.conf` - Configuration elements affect both halves
- `my_awesome_split_board_left.conf` - Configuration elements only affect left half
- `my_awesome_split_board_right.conf` - Configuration elements only affect right half

In most case you'll only need to use the .conf file that affects both halves of a split board. It's used for adding features like deep-sleep or rotary encoders.

```ini title=my_awesome_split_board.conf
CONFIG_ZMK_SLEEP=y
```

:::note
The shared configuration in `my_awesome_split_board.conf` is only applied when you are building with a [`zmk-config` folder](../local-toolchain/build-flash.mdx#building-from-zmk-config-folder) and when it is present at `config/my_awesome_split_board.conf`. If you are not using a `zmk-config` folder, you will need to include the shared configuration in both `my_awesome_split_board_left.conf` and `my_awesome_split_board_right.conf` files.
:::

</TabItem>
</Tabs>

## Matrix Transform

Internally ZMK translates all row/column events into "key position" events to maintain a consistent model that works no matter what any possible GPIO matrix may look like for a certain keyboard. This is particularly helpful when:

1. To reduce the used pins, an "efficient" number of rows/columns for the GPIO matrix is used, that does _not_ match the physical layout of rows/columns of the actual key switches.
1. For non rectangular keyboards with thumb clusters, non `1u` locations, etc.

A "key position" is the numeric index (zero-based) of a given key, which identifies
the logical key location as perceived by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values.

The `<shield_name>.overlay` must include a matrix transform that defines this mapping from row/column values to key positions.

Here is an example for the [nice60](https://github.com/Nicell/nice60), which uses an efficient 8x8 GPIO matrix, and uses a transform:

```dts
#include <dt-bindings/zmk/matrix_transform.h>

/ {
    /* define kscan node with label `kscan0`... */

    default_transform: keymap_transform_0 {
        compatible = "zmk,matrix-transform";
        columns = <8>;
        rows = <8>;
// | MX1  | MX2  | MX3  | MX4  | MX5  | MX6  | MX7  | MX8  | MX9  | MX10 | MX11 | MX12 | MX13 |    MX14     |
// |   MX15   | MX16 | MX17 | MX18 | MX19 | MX20 | MX21 | MX22 | MX23 | MX34 | MX25 | MX26 | MX27 |  MX28   |
// |    MX29    | MX30 | MX31 | MX32 | MX33 | MX34 | MX35 | MX36 | MX37 | MX38 | MX39 | MX40 |     MX41     |
// |     MX42      | MX43 | MX44 | MX45 | MX46 | MX47 | MX48 | MX49 | MX50 | MX51 | MX52 |       MX53       |
// |  MX54  |  MX55  |  MX56  |                  MX57                   |  MX58  |  MX59  |  MX60  |  MX61  |
        map = <
RC(3,0)  RC(2,0) RC(1,0) RC(0,0) RC(1,1) RC(0,1) RC(0,2) RC(1,3) RC(0,3) RC(1,4) RC(0,4) RC(0,5) RC(1,6)     RC(1,7)
RC(4,0)    RC(4,1) RC(3,1) RC(2,1) RC(2,2) RC(1,2) RC(2,3) RC(3,4) RC(2,4) RC(2,5) RC(1,5) RC(2,6) RC(2,7)   RC(3,7)
RC(5,0)     RC(5,1) RC(5,2) RC(4,2) RC(3,2) RC(4,3) RC(3,3) RC(4,4) RC(4,5) RC(3,5) RC(4,6) RC(3,6)          RC(4,7)
RC(6,0)       RC(6,1) RC(6,2) RC(6,3) RC(5,3) RC(6,4) RC(5,4) RC(6,5) RC(5,5) RC(6,6) RC(5,6)                RC(5,7)
RC(7,0)    RC(7,1)   RC(7,2)                     RC(7,3)                    RC(7,5)    RC(7,6)    RC(6,7)    RC(7,7)
        >;
    };

    /* potentially other overlay nodes... */
};
```

Some important things to note:

- The `#include <dt-bindings/zmk/matrix_transform.h>` is critical. The `RC` macro is used to generate the internal storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK.
- `RC(row, column)` is placed sequentially to define what row and column values that position corresponds to.
- If you have a keyboard with options for `2u` keys in certain positions, ANSI vs. ISO layouts, or break away portions, define one matrix transform for each possible arrangement to be used in the physical layouts. This will allow the users to select the right layout in their keymap files.

See the [matrix transform section](../../config/kscan.md#matrix-transform) in the Keyboard Scan configuration documentation for details and more examples of matrix transforms.

## Physical Layout

The physical layout is the top level entity that aggregates all details about a certain possible layout for a keyboard: the matrix transform that defines the set of key positions and what row/column they correspond to, what kscan driver is used for that layout, etc.
For keyboards that support multiple layouts, setting a `chosen` node to a defined physical layout in your keymap will allow selecting the specific layout that you've built.

A physical layout is very basic, e.g.:

```
/ {
    default_layout: default_layout {
        compatible = "zmk,physical-layout";
        display-name = "Default Layout";
        transform = <&default_transform>;
        kscan = <&kscan0>;
    };
};
```

When supporting multiple layouts, define the multiple layout nodes and then set a `chosen` for the default:

```
/ {
    chosen {
        zmk,physical-layout = &default_layout;
        ...
    };

    default_layout: default_layout {
        compatible = "zmk,physical-layout";
        display-name = "Default Layout";
        transform = <&default_transform>;
        kscan = <&kscan0>;
    };

    alt_layout: alt_layout {
        compatible = "zmk,physical-layout";
        display-name = "Alternate Layout";
        transform = <&alt_transform>;
        kscan = <&alt_kscan0>;
    };
};
```

This way, users can select a different layout by overriding the `zmk,physical-layout` chosen node in their keymap file.

:::note
Some keyboards use different GPIO pins for different layouts, and need different kscan nodes created for each layout.
However, if all of your physical layouts use the same `kscan` node under the hood, you can skip setting the `kscan` property on each
layout and instead assign the `zmk,kscan` chosen node to your single kscan instance.
:::

## Default Keymap

Each keyboard should provide a default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `boards/shields/<shield_name>/<shield_name>.keymap` file. The keymap is configured as an additional devicetree overlay that includes the following:

- A node with `compatible = "zmk,keymap"` where each child node is a layer with a `bindings` array that binds each key position to a given behavior (e.g. key press, momentary layer, etc).

Here is an example simple keymap for the Kyria, with only one layer:

<KeymapExampleFile />

:::note
The two `#include` lines at the top of the keymap are required in order to bring in the default set of behaviors to make them available to bind, and to import a set of defines for the key codes, so keymaps can use parameters like `N2` or `K` instead of the raw keycode numeric values.
:::

### Keymap Behaviors

For documentation on the available behaviors for use in keymaps, see the [overview page for behaviors](../../keymaps/behaviors/index.mdx).

## Metadata

ZMK makes use of an additional metadata YAML file for all boards and shields to provide high level information about the hardware to be incorporated into setup scripts/utilities, website hardware list, etc.

The naming convention for metadata files is `{item_id}.zmk.yml`, where the `item_id` is the board/shield identifier, including version information but excluding any optional split `_left`/`_right` suffix, e.g. `corne.zmk.yml` or `nrfmicro_11.zmk.yml`.

Here is a sample `corne.zmk.yml` file from the repository:

```yaml
file_format: "1"
id: corne
name: Corne
type: shield
url: https://github.com/foostan/crkbd/
requires: [pro_micro]
exposes: [i2c_oled]
features:
  - keys
  - display
siblings:
  - corne_left
  - corne_right
```

You should place a properly named `foo.zmk.yml` file in the directory next to your other shield values, and fill it out completely and accurately. See [Hardware Metadata Files](hardware-metadata-files.md) for the full details.

## Build File

To help you test/verify your firmware, update the `build.yaml` to list your particular board/shield combinations you want built whenever changes are published to GitHub. Open `build.yaml` with your editor and add a combination, e.g.:

```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
#
---
include:
  - board: nice_nano_v2
    shield: <my_shield>
```

For split keyboards, you will need to specify the halves/siblings separately, e.g.:

```yaml
include:
  - board: mikoto_520
    shield: <my_shield>_left
  - board: mikoto_520
    shield: <my_shield>_right
```

## Testing

### GitHub Actions

Using GitHub Actions to build your new firmware can save you from doing any local [development setup](../local-toolchain/setup/index.md),
at the expense of a longer feedback loop if there are issues. To push your changes and trigger a build:

- Add all your pending changes with `git add .`
- Commit your changes with `git commit -m "Initial shield"`
- Push the changes to GitHub with `git push`

Once pushed, click on the "Actions" tab of the repo you created in the first step, and you should see a new build running. If the build is successful, there will be a new `firmware.zip` artifact shown on the summary screen you can download that will contain the new `.uf2` files that can be flashed to the device.

### Local Build

:::note
To build locally, be sure you've followed the [development setup](../local-toolchain/setup/index.md) guide first.
:::

Once you've fully created the new keyboard shield definition,
you should be able to test with a build command like:

```sh
west build --pristine -b nice_nano_v2 -- -DSHIELD=<my_shield> -DZMK_EXTRA_MODULES=/full/path/to/your/module
# replace <my_shield> with e.g. <my_shield>_left for split keyboards, then repeat for <my_shield>_right
```

The above build command generates a `build/zephyr/zmk.uf2` file that you can flash using the steps from the following section. See the dedicated [building and flashing page](../local-toolchain/build-flash.mdx) for more details.

### Flashing

If your board
supports USB Flashing Format (UF2), copy that file onto the root of the USB mass
storage device for your board. The controller should flash your built firmware
and automatically restart once flashing is complete. If you need to flash an updated
UF2 file with fixes, you can re-enter the bootloader by double tapping the reset button.

Alternatively, if your board supports flashing and you're not developing from
within a Dockerized environment, enable Device Firmware Upgrade (DFU) mode on
your board and run the following command to test your build:

```sh
west flash
```

Please have a look at documentation specific to
[building and flashing](../local-toolchain/build-flash.mdx) for additional information.

:::note
Further testing your keyboard shield without altering the root keymap file can be done with the use of `-DZMK_CONFIG` in your `west build` command,
shown [here](../local-toolchain/build-flash.mdx#building-from-zmk-config-folder)
:::
