zmk/docs/blog/2025-xx-xx-zephyr-4-1.md

20 KiB

title authors tags
Zephyr 4.1 Update nmunnich
firmware
zephyr
core

We're happy to announce that after a long wait, ZMK's main branch is now running Zephyr 4.1!

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.
  • 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.

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. 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

Since the changes are merged to ZMK main, you'll need to be using that version of ZMK to use/tests these changes.

Local Development

To get the update, first pull the latest ZMK main:

git checkout main && git pull main

Once you have the updated ZMK code, update the referenced modules:

west update

To be sure you've got all the updated Python packages needed to build ZMK, use west again:

west packages pip --install

GitHub Actions Builds

To test these changes, you need to verify that the west.yml for your build is using a revision: main property. Once this is verified, simply run a new build and the updated Zephyr version will be used.

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 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 (short: nice_nano@1)
    • nice_nano_v2 -> nice_nano@2.0.0 (short: nice_nano)
  • nRFMicro (nrfmicro/nrf52840)
    • nrfmicro_11 -> nrfmicro@1.1.0/nrf52840 (short: nrfmicro@1.1/nrf52840)
    • nrfmicro_11_flipped -> nrfmicro@1.1.0/nrf52840/flipped (short: nrfmicro@1.1/nrf52840/flipped)
    • nrfmicro_13 -> nrfmicro@1.3.0/nrf52840 (short: nrfmicro/nrf52840)
    • nrfmicro_13_52833 -> nrfmicro@1.3.0/nrf52833 (short: nrfmicro/nrf52833)
  • Mikoto (mikoto)
    • mikoto -> mikoto@5.20.0 (short: mikoto)
    • mikoto@6.1 -> mikoto@6.1.0 (short: mikoto@6)
    • mikoto@7.2 -> mikoto@7.2.0 (short: mikoto@7)
  • XIAO RP2040 (xiao_rp2040)
    • seeeduino_xiao_rp2040 -> xiao_rp2040
  • XIAO nRF52840/BLE (xiao_ble)
    • seeeduino_xiao_ble -> xiao_ble
  • BT60 (bt60)
    • bt60_v1 -> bt60@1.0.0
    • bt60_v2 -> bt60@2.0.0
    • bt60_hs -> bt60_hs
  • Planck (planck)
    • planck_rev6 -> planck
  • BDN9 (bdn9)
    • bdn9_rev2 -> bdn9
  • Ferris Rev2 (ferris)
    • ferris_rev02 -> ferris@2.0.0 (short: ferris)

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.

Getting The Changes

User Config Repositories Using GitHub Actions

Existing user repositories which are currently running ZMK's main branch 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 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 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!

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 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:

$ 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:

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 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:

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 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:

config BOARD_FERRIS
    bool "Ferris rev 0.2"
    depends on SOC_STM32F072XB

Remove the bool and change the depends on to a select:

config BOARD_FERRIS
    select SOC_STM32F072XB

If you had multiple boards specified for different SoCs, you should consolidate them to one:

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.

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:

&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:

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:

&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:

&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:

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:

#include <rpi_pico/rp2040.dtsi>

with

#include <raspberrypi/rpi_pico/rp2040.dtsi>

Next, any fixed clock node needs to be removed:

    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:

&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:

/ {
    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, 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. 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 not supported, and should be removed.
  • 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 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:

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.