zmk/docs/docs/development/hardware-integration/new-shield.mdx

562 lines
22 KiB
Plaintext

---
title: New Keyboard Shield
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import InterconnectTabs from "@site/src/components/interconnect-tabs";
import Metadata from "@site/src/data/hardware-metadata.json";
export const SplitTabs = (props) => (
<Tabs
groupId="keyboard-type"
defaultValue="unibody"
values={[
{ label: "Unibody Keyboard", value: "unibody" },
{ label: "Split Keyboard", value: "split" }
]}
>
{/* eslint-disable-next-line */}
{props.children}
</Tabs>
);
export const SplitInvisTabs = (props) => (
<Tabs
groupId="keyboard-type"
defaultValue="unibody"
className="secrettabs"
values={[
{ label: "Unibody Keyboard", value: "unibody" },
{ label: "Split Keyboard", value: "split" }
]}
>
{/* eslint-disable-next-line */}
{props.children}
</Tabs>
);
:::danger
Before reading this section, it is **vital** that you read through our [clean room policy](../contributing/clean-room.md).
:::
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:
- Create a new [Zephyr module](https://docs.zephyrproject.org/3.5.0/develop/modules.html) to contain your shield.
- Create a new shield directory.
- Add the base Kconfig files.
- Add the shield overlay file defining:
- The keyboard scan driver for detecting key press/release.
- The matrix transform for mapping keyboard scan row/column values to key positions in the keymap.
- The physical layout definition to select the matrix transform and keyboard scan 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.
Many of the above files will differ depending on whether your keyboard is a unibody or is [split into multiple parts](../../features/split-keyboards.md).
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.
## 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](https://docs.zephyrproject.org/3.5.0/build/kconfig/index.html) files that need to be created for your new keyboard shield to get it picked up for ZMK, `Kconfig.shield` and `Kconfig.defconfig`.
<SplitTabs></SplitTabs>
### Kconfig.shield
The `Kconfig.shield` file defines the shield name used to build your keyboard.
<SplitInvisTabs>
<TabItem value="unibody">
```kconfig title="Kconfig.shield"
# No whitespace after the comma or in your keyboard name!
config SHIELD_MY_KEYBOARD
def_bool $(shields_list_contains,my_keyboard)
```
This will set the `SHIELD_MY_KEYBOARD` flag to `y` whenever `my_keyboard` is used as the shield name.
The `SHIELD_MY_KEYBOARD` flag will be used in `Kconfig.defconfig` to set other properties about your shield, so make sure that they match.
</TabItem>
<TabItem value="split">
Split keyboards have multiple shield names defined, one for each part.
For example, if your keyboard consists of two halves named `my_keyboard_left` and `my_keyboard_right`, it would look like this:
```kconfig title="Kconfig.shield"
# No whitespace after the comma or in your part name!
config SHIELD_MY_KEYBOARD_LEFT
def_bool $(shields_list_contains,my_keyboard_left)
# No whitespace after the comma or in your part name!
config SHIELD_MY_KEYBOARD_RIGHT
def_bool $(shields_list_contains,my_keyboard_right)
```
This will set the `SHIELD_MY_KEYBOARD_LEFT` flag to `y` whenever `my_keyboard_left` is used as the shield name.
Likewise, when `my_keyboard_right` is used as the shield name the `SHIELD_MY_KEYBOARD_RIGHT` flag is set to `y`.
The `SHIELD_MY_KEYBOARD_LEFT` and `SHIELD_MY_KEYBOARD_RIGHT` flags will be used in `Kconfig.defconfig` to set other properties about your shields, so make sure that they match.
</TabItem>
</SplitInvisTabs>
### Kconfig.defconfig
The `Kconfig.defconfig` file is used to set new defaults for configuration settings 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.
<SplitInvisTabs>
<TabItem value="unibody">
```kconfig title="Kconfig.defconfig"
if SHIELD_MY_KEYBOARD
# Name must be less than 16 characters long!
config ZMK_KEYBOARD_NAME
default "My Keyboard"
endif
```
</TabItem>
<TabItem value="split">
For split keyboards, a central side (usually the left) is specified via the configuration in this file.
For that side, the keyboard name is assigned and the central config is set.
The peripheral side is not assigned a name.
Finally, the split config needs to be set for both sides:
```kconfig title="Kconfig.defconfig"
if SHIELD_MY_KEYBOARD_LEFT
# Name must be less than 16 characters long!
config ZMK_KEYBOARD_NAME
default "My Keyboard"
config ZMK_SPLIT_ROLE_CENTRAL
default y
endif
if SHIELD_MY_KEYBOARD_LEFT || SHIELD_MY_KEYBOARD_RIGHT
config ZMK_SPLIT
default y
endif
```
</TabItem>
</SplitInvisTabs>
### User Configuration Files
In addition to the `Kconfig.shield` and `Kconfig.defconfig` files, many shields will also define a user configuration file called `my_keyboard.conf`.
This file exists to provide "suggestions" of [configuration settings](../../config/index.md) for a user to select, such as enabling deep sleep.
Note that the name should match the shield/part name defined in the [Kconfig.shield file](#kconfigshield).
:::warning
This file can also be used to set configuration options.
However, if a flag is set in this file, **the user can no longer change it**.
Though sometimes necessary, this method of setting configuration options is discouraged.
The case for which this is necessary is due to be eliminated in the future, making this method redundant.
:::
<SplitInvisTabs>
<TabItem value="unibody"></TabItem>
<TabItem value="split">
Split keyboards can have multiple `.conf` files, one for each part. For example:
- `my_keyboard.conf` - Configuration elements affect both halves
- `my_keyboard_left.conf` - Configuration elements only affect left half
- `my_keyboard_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.
:::note
The shared configuration in `my_keyboard.conf` is only applied when you are building with a [`zmk-config` folder](../local-toolchain/build-flash.mdx#building-from-zmk-config-folder) and it is present at `config/my_keyboard.conf`.
:::
</TabItem>
</SplitInvisTabs>
## Shield Overlays
<SplitTabs></SplitTabs>
Shield overlay files contain a devicetree description that is merged with the primary board devicetree description during the firmware building process.
There are three main things that need to be defined in this file:
- Your keyboard scan (kscan) driver, which determines which GPIO pins to scan for key press events
- Your matrix transform, which acts as a "bridge" between the kscan and the keymap
- Your physical layout, which aggregates the above and (optionally) defines physical key positions so that the keyboard can be used with [ZMK Studio](../../features/studio.md).
<SplitInvisTabs>
<TabItem value="unibody">
A unibody keyboard will have a single overlay file named `my_keyboard.overlay`, where `my_keyboard` is the shield name defined in the [Kconfig.shield file](#kconfigshield).
</TabItem>
<TabItem value="split">
A split keyboard will have an overlay file defined for each split part.
For example, if the keyboard is split into a left and a right half, these can be named:
- `my_keyboard_left.overlay`
- `my_keyboard_right.overlay`
Here `my_keyboard_left` and `my_keyboard_right` are the shield names defined in the [Kconfig.shield file](#kconfigshield).
Split keyboards often share some of their devicetree description.
The standard approach is to have a core `my_keyboard.dtsi` (devicetree include) file, which is included into each of the shield overlays.
</TabItem>
</SplitInvisTabs>
### Kscan
The kscan node defines the controller GPIO pins that are used to scan for key press and release events. The pins are referred to using the GPIO labels noted in the pinouts below:
<InterconnectTabs items={Metadata} gpio={true} />
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.
The [Keyboard Scan configuration documentation](../../config/kscan.md) has the full details on configuring the kscan driver.
<SplitTabs>
<TabItem value="unibody">
For a simple 3x3 macropad matrix, the kscan might look something like:
```dts title="my_keyboard.overlay"
/ {
kscan0: kscan0 {
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)>
;
};
};
```
</TabItem>
<TabItem value="split">
For split keyboards you should define your kscan in `my_keyboard.dtsi`.
If your `row-gpios` or your `col-gpios` (or both) are identical between the parts, then they should also be defined in `my_keyboard.dtsi`.
For example, for a `col2row` 2-part split keyboard (18 keys split into a 3x3 macropad on both halves) where the "row" GPIOs used are the same for both halves:
```dts title="my_keyboard.dtsi"
/ {
kscan0: kscan0 {
compatible = "zmk,kscan-gpio-matrix";
diode-direction = "col2row";
wakeup-source;
row-gpios
= <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
;
};
};
```
The missing `col-gpios` would be defined in your `my_keyboard_left.overlay` and `my_keyboard_right.overlay` files.
```dts title="my_keyboard_left.overlay"
#include "my_keyboard.dtsi" // The shared dtsi file is included in the overlay
// Label of the kscan node in the dtsi
&kscan0 {
col-gpios
= <&pro_micro 19 GPIO_ACTIVE_HIGH>
, <&pro_micro 18 GPIO_ACTIVE_HIGH>
, <&pro_micro 15 GPIO_ACTIVE_HIGH>
;
};
```
```dts title="my_keyboard_right.overlay"
#include "my_keyboard.dtsi" // The shared dtsi file is included in the overlay
// Label of the kscan node in the dtsi
&kscan0 {
col-gpios
= <&pro_micro 10 GPIO_ACTIVE_HIGH>
, <&pro_micro 11 GPIO_ACTIVE_HIGH>
, <&pro_micro 13 GPIO_ACTIVE_HIGH>
;
};
```
</TabItem>
</SplitTabs>
### Matrix Transform
The matrix transform is used to transform row/column events into "key position" events.
When a key is pressed, a kscan event is generated from it with a `row` and a `column` value corresponding to the zero-based indices of the `row-gpios` and `col-gpios` pins that triggered the event, respectively.
Then, the "key position" triggered is the index of the `RC(row, column)` in the matrix transform where `row` and `column` are the indices as mentioned above.
This key position will in turn have a behavior binding associated with it in the keymap.
<SplitTabs>
<TabItem value="unibody">
The `my_keyboard.overlay` must include a matrix transform that defines this mapping from row/column values to key positions.
Add `#include <dt-bindings/zmk/matrix_transform.h>` to the top of the file.
Here is an example of a matrix transform for the previous 3x3 macropad:
```dts title="my_keyboard.overlay"
#include <dt-bindings/zmk/matrix_transform.h> // Put this with the other includes at the top of your overlay
/ {
default_transform: keymap_transform0 {
compatible = "zmk,matrix-transform";
columns = <3>; // Length of the "col-gpios" array
rows = <3>; // Length of the "row-gpios" array
map = <
// Key 1 | Key 2 | Key 3
RC(0,0) RC(0,1) RC(0,2)
// Key 4 | Key 5 | Key 6
RC(1,0) RC(1,1) RC(1,2)
// Key 7 | Key 8 | Key 9
RC(2,0) RC(2,1) RC(2,2)
>;
};
};
```
</TabItem>
<TabItem value="split">
Split keyboards should define their matrix transform in the shared `my_keyboard.dtsi`. Add `#include <dt-bindings/zmk/matrix_transform.h>` to the top of the file.
Here is an example of a matrix transform for the previous example (18-key double macropad):
```dts title="my_keyboard.dtsi"
#include <dt-bindings/zmk/matrix_transform.h> // Put this with the other includes at the top of your dtsi
/ {
default_transform: keymap_transform0 {
compatible = "zmk,matrix-transform";
columns = <6>;
rows = <3>;
map = <
// LKey 1 |LKey 2 |LKey 3 RKey 1 |RKey 2 |RKey 3
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5)
// LKey 4 |LKey 5 |LKey 6 RKey 4 |RKey 5 |RKey 6
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5)
// LKey 7 |LKey 8 |LKey 9 RKey 7 |RKey 8 |RKey 9
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5)
>;
};
};
```
The above transform has 6 columns and three rows, while each half of the keyboard only has three columns and three rows.
To allow the kscan matrices to be joined in the matrix transform, an offset is applied to the matrix transform of peripherals.
```dts title="my_keyboard_right.overlay"
&default_transform { // Offset of 3 because the left side has 3 columns
col-offset = <3>;
};
```
This offset means that when the right half of the keyboard has a key event triggered by the GPIO pins at the indices `0,0` of its `row-gpios` and `col-gpios` arrays respectively, it will interpret it as an `RC(0,3)` event rather than an `RC(0,0)` event.
Additional peripherals would need their columns to be offset by an ever increasing number equal to the sum of the columns in the central + any peripherals that came before it.
You can also apply row offsets with `row-offset`.
</TabItem>
</SplitTabs>
The matrix transform is also used to "correct" pin orderings into something that more closely matches the physical order of keys. Causes of abnormal pin orderings include:
- 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.
- For non-rectangular keyboards with thumb clusters, non `1u` locations, etc.
See the [in-tree keyboards](https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields) that ZMK defines for examples of more complex matrix transformations.
Also see the [matrix transform section](../../config/layout.md#matrix-transform) in the Keyboard Scan configuration documentation for further details and examples of matrix transforms.
### Physical Layout
Your keyboard will need to have a physical layout defined.
Read through our [dedicated page on physical layouts](./physical-layouts.md) for information on how to define a physical layout.
Once you have finished creating your physical layout, you should import the file in which it was created:
```dts
#include "my_keyboard-layouts.dtsi"
```
### Chosen Node
Set the `chosen` node to a defined "default" physical layout. This should also be placed in the same file as the physical layout, i.e. `my_keyboard.overlay` for unibodies and `my_keyboard.dtsi` for split keyboards.
```dts
/ {
chosen {
zmk,physical-layout = &physical_layout0;
// Other chosen items
};
};
```
If you define multiple physical layouts, users can select a different layout by overriding the `zmk,physical-layout` chosen node in their keymap file or by using [ZMK Studio](../../features/studio.md) if your board is compatible with it.
:::note
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:
```dts
/ {
chosen {
zmk,kscan = &kscan0;
zmk,physical-layout = &physical_layout0;
// Other chosen items
};
};
```
:::
## 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/my_keyboard/my_keyboard.keymap` file.
The keymap is configured as an additional devicetree overlay that includes the following:
Here is an example simple keymap for a 3x3 macropad, with only one layer:
```dts title="my_keyboard.keymap"
/ {
keymap {
compatible = "zmk,keymap";
default_layer { // Layer 0
// -------------------------------------
// | Z | M | K |
// | A | B | C |
// | D | E | F |
bindings = <
&kp Z &kp M &kp K
&kp A &kp B &kp C
&kp D &kp E &kp F
>;
};
};
};
```
The keymap should match the order of the keys in the [matrix transform](#matrix-transform) exactly, left to right, top to bottom (they are both 1 dimensional arrays rearranged with newline characters for better legibility).
See [Keymaps](../../keymaps/index.mdx) for information on defining keymaps in ZMK.
If you wish to use [ZMK Studio](../../features/studio.md) with your keyboard, make sure to assign the [ZMK Studio unlocking behavior](../../keymaps/behaviors/studio-unlock.md) to a key in your keymap.
## 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.
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 `my_keyboard.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.
## Testing
Once you've defined everything as described above, you can build your firmware to make sure everything is working.
If you wish to test that your keyboard works with [ZMK Studio](../../features/studio.md), you'll also need to follow the [instructions for enabling Studio](../../features/studio.md#building).
### GitHub Actions
To use GitHub Actions to test, push the files defining the keyboard to GitHub.
Next, [update the `build.yaml`](../../customization.md#building-additional-keyboards) of your `zmk-config` to build your keyboard.
- If your shield is defined in your `zmk-config`, then the shield should start building.
- If the shield is defined in a separate module, you will need to [adjust your `west.yml` to reference the module](https://zmk.dev/docs/features/modules#building-with-modules).
### Local Toolchain
You can also use a local toolchain setup to test your keyboard.
Follow [our guide for getting set up](../local-toolchain/setup/index.md), then follow the [instructions for building and flashing locally](../local-toolchain/build-flash.mdx).
You will need to specify the module of your keyboard when building.