---
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)!

<!-- truncate -->

[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
