Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(feat): further generalize RGB information #2485

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 89 additions & 72 deletions docs/docs/features/underglow.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ sidebar_label: RGB Underglow
RGB underglow is a feature used to control "strips" of RGB LEDs. Most of the time this is called underglow and creates a glow underneath the board using a ring of LEDs around the edge, hence the name. However, this can be extended to be used to control anything from a single LED to a long string of LEDs anywhere on the keyboard.

:::info
RGB underglow can also be used for under-key lighting. If you have RGB LEDs on your keyboard, this is what you want. For PWM/single color LEDs, see [Backlight](backlight.mdx).
RGB underglow can also be used for per-key lighting. If you have RGB LEDs on your keyboard, this is what you want. For PWM/single color LEDs, see [Backlight](backlight.mdx).
:::

ZMK supports all the RGB LEDs supported by Zephyr. Here's the current list supported:
ZMK relies on Zephyr's `led-strip` drivers for this feature. The following LEDs/LED families have been implemented:

- WS2812-ish (WS2812B, WS2813, SK6812, or compatible)
- WS2812 (includes WS2812B, WS2813, SK6812, and others)
- APA102
- LPD880x (LPD8803, LPD8806, or compatible)
- LPD880x (includes LPD8803, LPD8806, and others)

Of the compatible types, the WS2812 LED family is by far the most popular type. Currently each of these types of LEDs are expected to be run using SPI with a couple of exceptions.
The WS2812 LED family is by far the most popular of these types, so this page will primarily focus on working with it.

Here you can see the RGB underglow feature in action using WS2812 LEDs.

Expand All @@ -34,41 +34,47 @@ CONFIG_ZMK_RGB_UNDERGLOW=y
CONFIG_WS2812_STRIP=y
```

See [Configuration Overview](/docs/config) for more instructions on how to
use Kconfig.
See [Configuration Overview](/docs/config) for more instructions on how to use Kconfig.

If your board or shield does not have RGB underglow configured, refer to [Adding RGB Underglow to a Board](#adding-rgb-underglow-to-a-board).

### Modifying the Number of LEDs

A common issue when enabling underglow is that some of the installed LEDs do not illuminate. This can happen when a board's default underglow configuration accounts only for either the downward facing LEDs or the upward facing LEDs under each key. On a split keyboard, a good sign that this may be the problem is that the unilluminated LEDs on each half are symmetrical.
The number of LEDs specified in the default configuration for your board or shield may not match the number you have installed. For example, the `corne` shield specifies only 10 LEDs per side while supporting up to 27. On a split keyboard, a good sign of this mismatch is if the lit LEDs on each half are symmetrical.

The number of underglow LEDs is controlled by the `chain-length` property in the `led_strip` node. You can [change the value of this property](../config/index.md#changing-devicetree-properties) in the `<keyboard>.keymap` file by adding a stanza like this one outside of any other node (i.e. above or below the `/` node):
The `chain-length` property of the `led_strip` node controls the number of underglow LEDs. If it is incorrect for your build, [you can change this property](../config/index.md#changing-devicetree-properties) in your `<keyboard>.keymap` file by adding a stanza like this one outside of any other node (i.e. above or below the `/` node):

```dts
&led_strip {
chain-length = <21>;
};
```

where the value is the total count of LEDs (per half, for split keyboards).
For split keyboards, set `chain-length` to the number of LEDs installed on each half.

## Configuring RGB Underglow

See [RGB underglow configuration](/docs/config/underglow).

## Adding RGB Underglow to a Board

RGB underglow is always added to a board, not a shield. This is a consequence of needing to configure SPI to control the LEDs.
If you have a shield with RGB underglow, you must add a `boards/` directory within your shield folder to define the RGB underglow individually for each board that supports the shield.
Inside the `boards/` folder, you define a `<board>.overlay` for each different board.
For example, the Kyria shield has a `boards/nice_nano.overlay` file that defines the RGB underglow for the `nice_nano` board specifically.
Support for RGB underglow is always added to a board, not a shield. This is because the LED strip drivers rely on hardware-specific interfaces (e.g. SPI, I2S) and configurations, which shields do not control.

Shields written for boards which support RGB underglow should add a `boards/` folder underneath the shield folder. Inside this `boards/` folder, create a `<board>.overlay` for any of the boards the shield can be used with. Place all hardware-specific configurations in these `.overlay` files.

For example: the `kyria` shield has a [`boards/nice_nano_v2.overlay`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/boards/nice_nano_v2.overlay) and a [`boards/nrfmicro_13.overlay`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/boards/nrfmicro_13.overlay), which configure a WS2812 LED strip for the `nice_nano_v2` and `nrfmicro_13` boards respectively.

### nRF52-Based Boards

With nRF52 boards, you can just use `&spi3` and define the pins you want to use.
Using an SPI-based LED strip driver on the `&spi3` interface is the simplest option for nRF52-based boards. If possible, avoid using pins which are limited to low-frequency I/O for this purpose. The resulting interference may result in poor wireless performance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd move the warning back into the info box and turn the info box into a warning box, personally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like...

-Using an SPI-based LED strip driver on the `&spi3` interface is the simplest option for nRF52-based boards. If possible, avoid using pins which are limited to low-frequency I/O for this purpose. The resulting interference may result in poor wireless performance.
+Using an SPI-based LED strip driver on the `&spi3` interface is the simplest option for nRF52-based boards.
 
-:::info
+:::warning
 
-The list of low frequency I/O pins for the nRF52840 can be found [here](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/pin.html).
+Avoid using pins which are limited to low-frequency I/O for this purpose, as the resulting interference may result in poor wireless performance. [See Nordic's documentation](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/pin.html) for pin assignments.

...isn't immediately terrible, but having more text in the callout box than outside of it strikes me as the docs equivalent of a code smell.

Some other thoughts:

  • Is there a quantifiable measurement of how badly performance is degraded which could support a :::warning?
    • This may be because a.) the most popular designs use high-frequency pins for WS2812 DI, and b.) interference problems are notoriously difficult to root cause—but anecdotally, we've all encountered far more complaints about RGB drastically curtailing battery life than RGB interfering with BLE. And while in either case the problem can be mitigated with specific hardware design decisions, only the former is explicitly called attention to here.
  • Regardless of impact, only a hardware designer with non-finalized work can take meaningful action on this information. The average reader just gets to deal with it. This situation is what it is...but a non-actionable :::warning isn't great.
  • I believe it would be worth noting RGB as a potential cause of connection issues on the related troubleshooting page.

Copy link
Contributor

@Nick-Munnich Nick-Munnich Oct 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the entire warning and much of the section could probably be dropped if we can get #2508 merged, linking to that instead.

I've seen a clip of the SPI nodes on the n!n causing noticeable input lag when used with a cirque, which I am of the (unconfirmed) belief is due to the low-frequency pins. I've not seen an example of RGB, but I think that's because pretty much all shields will just use the pro micro layout's RX or TX pins for that, and designers who stray are usually smart enough to check.

In theory, a user could bodge a connection and adjust the definition. Probably not worth considering, though.

I semi-agree: I think we could note down low-frequency pins being used for high-frequency purposes, and I think it would be better positioned in #2360. EDIT: I have now added such a note to #2360.


:::info

The list of low frequency I/O pins for the nRF52840 can be found [here](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/pin.html).

:::

Here's an example on a definition that uses P0.06:
The following example uses `P0.06` as the "Data In" pin of a WS2812-compatible LED strip:

```dts
#include <dt-bindings/led/led.h>
Expand All @@ -89,38 +95,31 @@ Here's an example on a definition that uses P0.06:
};

&spi3 {
compatible = "nordic,nrf-spim";
status = "okay";

pinctrl-0 = <&spi3_default>;
pinctrl-1 = <&spi3_sleep>;
pinctrl-names = "default", "sleep";

led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";

/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <4000000>;

/* WS2812 */
chain-length = <10>; /* number of LEDs */
spi-one-frame = <0x70>;
spi-zero-frame = <0x40>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
compatible = "nordic,nrf-spim";
status = "okay";

pinctrl-0 = <&spi3_default>;
pinctrl-1 = <&spi3_sleep>;
pinctrl-names = "default", "sleep";

led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";

/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <4000000>;

/* WS2812 */
chain-length = <10>; /* number of LEDs */
spi-one-frame = <0x70>;
spi-zero-frame = <0x40>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};
```

:::info

If you are configuring SPI for an nRF52 based board, double check that you are using pins that aren't restricted to low frequency I/O.
Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins for the nRF52840 [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0).

:::

:::note

Standard WS2812 LEDs use a wire protocol where the bits for the colors green, red, and blue values are sent in that order.
Expand All @@ -130,34 +129,60 @@ If your board/shield uses LEDs that require the data sent in a different order,

### Other Boards

For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip.
Be sure to check the Zephyr documentation for the LED strip and necessary hardware bindings. Not every board has an `spi3` node, or configures `pinctrl` the same way. Reconcile this with any hardware restrictions found in the manufacturer's datasheet. Additional hardware interfaces may need to be enabled via Kconfig.

Here's another example for a non-nRF52 board on `spi3`:
For example: the `sparkfun_pro_micro_rp2040` board can utilize SPI via PIO to run a WS2812 strip on `GP0`:

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

&spi3 {
&pinctrl {
pio0_spi0_default: pio0_spi0_default {
group1 {
pinmux = <PIO0_P0>;
};
};
};

led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";

/* SPI */
reg = <0>;
spi-max-frequency = <5250000>;

/* WS2812 */
chain-length = <10>; /* number of LEDs */
spi-one-frame = <0x70>; /* make sure to configure this properly for your SOC */
spi-zero-frame = <0x40>; /* make sure to configure this properly for your SOC */
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
&pio0 {
status = "okay";

pio0_spi0: pio0_spi0 {
pinctrl-0 = <&pio0_spi0_default>;
pinctrl-names = "default";

compatible = "raspberrypi,pico-spi-pio";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&system_clk>;
clock-frequency = <4000000>;

clk-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>; /* Must be defined. Select a pin that is not used elsewhere. */
mosi-gpios = <&pro_micro 1 GPIO_ACTIVE_HIGH>; /* Data In pin. */
miso-gpios = <&pro_micro 1 GPIO_ACTIVE_HIGH>; /* Must be defined. Re-using the DI pin is OK for WS2812. */

led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";

/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <4000000>;

/* WS2812 */
chain-length = <10>; /* number of LEDs */
spi-one-frame = <0x70>;
spi-zero-frame = <0x40>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};
};
```

Once you have your `led_strip` properly defined you need to add it to the root devicetree node `chosen` element:
### Final Steps

Once the `led_strip` is properly defined, add it to the `chosen` node under the root devicetree node:

```dts
/ {
Expand All @@ -166,11 +191,3 @@ Once you have your `led_strip` properly defined you need to add it to the root d
};
};
```

Finally you need to enable the `CONFIG_ZMK_RGB_UNDERGLOW` and `CONFIG_*_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`):

```ini
CONFIG_ZMK_RGB_UNDERGLOW=y
# Use the STRIP config specific to the LEDs you're using
CONFIG_WS2812_STRIP=y
```