---
title: Backlight
sidebar_label: Backlight
description: Lighting system that controls an array of single-color LEDs.
---

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

Please see [lighting feature page](../../features/lighting.md#backlight) for an introduction on the feature.

<Tabs
  defaultValue="shieldpin"
  queryString="part-type"
  values={[
  {label: 'Adding to a board', value: 'boardpin'}, {label: 'Adding to a shield', value: 'shieldpin'},
]}>

<TabItem value="boardpin">

First, you must enable PWM by adding the following lines to your `Kconfig.defconfig` file:

```kconfig
if ZMK_BACKLIGHT

config PWM
    default y

config LED_PWM
    default y

endif # ZMK_BACKLIGHT
```

Create a `<board>-pinctrl.dtsi` file if it does not already exist, and include it at the beginning of the `<board>.dts` file. `CONFIG_PINCTRL=y` must be added to `<board>_defconfig` if it isn't already enabled.
See the documentation page on [pin control](../pinctrl.mdx) for detailed information on setting up pins for hardware protocols such as PWM that is used for controlling backlight LEDs.

The pinctrl file has a `&pinctrl` node that encompasses all pinctrl settings, including I2C or SPI peripherals (e.g. WS2812 LEDs, battery fuel gauges):

```dts
&pinctrl {
    // Other pinctrl definitions for other hardware
    pwm0_default: pwm0_default {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 1, 13)>;
        };
    };
    pwm0_sleep: pwm0_sleep {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 1, 13)>;
            low-power-enable;
        };
    };
};
```

Pin numbers are handled differently depending on the MCU. On nRF MCUs pins are configured using `(PWM_OUTX, Y, Z)`, where `X` is the PWM channel used (usually 0), `Y` is the first part of the hardware port (_PY.01_) and `Z` is the second part of the hardware port (_P1.Z_).

For example, _P1.13_ would give you `(PWM_OUT0, 1, 13)` and _P0.15_ would give you `(PWM_OUT0, 0, 15)`.

Add the PWM device to the `board.dts` file and assign the pinctrl definitions to it:

```dts
&pwm0 {
    status = "okay";
    pinctrl-0 = <&pwm0_default>;
    pinctrl-1 = <&pwm0_sleep>;
    pinctrl-names = "default", "sleep";
};
```

Then add the following lines to the same `<board>.dts` file, but inside the root devicetree node:

```dts
/ {
    backlight: pwmleds {
        compatible = "pwm-leds";
        pwm_led_0 {
            pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
        };
    };
};
```

The value inside `pwm_led_0` after `&pwm0` must be the channel number. Since `PWM_OUT0` is defined in the pinctrl node, the channel in this example is 0.

In this example, `PWM_MSEC(10)` is the period of the PWM waveform. This period can be altered if your drive circuitry requires different values or the frequency is audible.

If your board uses a P-channel MOSFET to control backlight instead of a N-channel MOSFET, you may want to change `PWM_POLARITY_NORMAL` for `PWM_POLARITY_INVERTED`.

Finally you need to add backlight to the `chosen` element of the root devicetree node:

```dts
/ {
    chosen {
        zmk,backlight = &backlight;
    };
};
```

</TabItem>
<TabItem value="shieldpin">

You must first add a `boards/` directory within your shield folder. For each board that supports the shield you must create a `<board>.defconfig` file and a `<board>.overlay` file inside the `boards/` folder.

Inside your `<board>.defconfig` file, add the following lines:

```kconfig
if ZMK_BACKLIGHT

config PWM
    default y

config LED_PWM
    default y

endif # ZMK_BACKLIGHT
```

Then add the following lines to your `.overlay` file:

```dts
&pinctrl {
    // Other pinctrl definitions for other hardware
    pwm0_default: pwm0_default {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 1, 13)>;
        };
    };
    pwm0_sleep: pwm0_sleep {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 1, 13)>;
            low-power-enable;
        };
    };
};
```

See the documentation page on [pin control](../pinctrl.mdx) for detailed information on setting up pins for hardware protocols such as PWM that is used for controlling backlight LEDs.

Pin numbers are handled differently depending on the MCU. On nRF MCUs pins are configured using `(PWM_OUTX, Y, Z)`, where `X` is the PWM channel used (usually 0), `Y` is the first part of the hardware port (_PY.01_) and `Z` is the second part of the hardware port (_P1.Z_).

For example, _P1.13_ would give you `(PWM_OUT0, 1, 13)` and _P0.15_ would give you `(PWM_OUT0, 0, 15)`.

Add the PWM device to the `<board>.overlay` file and assign the pinctrl definitions to it:

```dts
&pwm0 {
    status = "okay";
    pinctrl-0 = <&pwm0_default>;
    pinctrl-1 = <&pwm0_sleep>;
    pinctrl-names = "default", "sleep";
};
```

Then add the following lines to the same `<board>.overlay` file, but inside the root devicetree node:

```
/ {
    backlight: pwmleds {
        compatible = "pwm-leds";
        pwm_led_0 {
            pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
        };
    };
};
```

In this example, `PWM_MSEC(10)` is the period of the PWM waveform. This period can be altered if your drive circuitry requires different values or the frequency is audible.

If your board uses a P-channel MOSFET to control backlight instead of a N-channel MOSFET, you may want to change `PWM_POLARITY_NORMAL` for `PWM_POLARITY_INVERTED`.

The value inside `pwm_led_0` after `&pwm0` must be the channel number. Since `PWM_OUT0` is defined in the pinctrl node, the channel in this example is 0.

Finally you need to add backlight to the `chosen` element of the root devicetree node:

```dts
/ {
    chosen {
        zmk,backlight = &backlight;
    };
};
```

</TabItem>
</Tabs>

### Multiple Backlight LEDs

It is possible to control multiple backlight LEDs at the same time. This is useful if, for example, you have a Caps Lock LED connected to a different pin and you want it to be part of the backlight.

In order to do that, first configure PWM for each pin in the pinctrl node:

```dts
&pinctrl {
    // Other Pinctrl definitions go here
    pwm0_default: pwm0_default {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 20)>, // LED 0
                    <NRF_PSEL(PWM_OUT1, 0, 22)>, // LED 1
                    <NRF_PSEL(PWM_OUT2, 0, 24)>; // LED 2
        };
    };
    pwm0_sleep: pwm0_sleep {
        group1 {
            psels = <NRF_PSEL(PWM_OUT0, 0, 20)>, // LED 0
                    <NRF_PSEL(PWM_OUT1, 0, 22)>, // LED 1
                    <NRF_PSEL(PWM_OUT2, 0, 24)>; // LED 2
            low-power-enable;
        };
    };
};
```

This part will vary based on your MCU as different MCUs have a different number of modules, channels and configuration options.

Add each of your LEDs to the backlight node in the same manner as for one LED, using the channel number definitions in the pinctrl node:

```dts
backlight: pwmleds {
    compatible = "pwm-leds";
    pwm_led_0: pwm_led_0 {
        pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
    };
    pwm_led_1: pwm_led_1 {
        pwms = <&pwm0 1 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
    };
    pwm_led_2: pwm_led_2 {
        pwms = <&pwm0 2 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
    };
};
```
