WS2812 RGB LED
The EFZ has an RGB LED on it, a WS2812B. The board file leaves it out, so in this lesson you add it with an overlay and drive it with Zephyr's own LED strip sample.
By the end of this lesson you can
- Read an overlay that adds a WS2812 LED, and say what each line of it does.
- Turn a driver on in
prj.conf, and read a sample's own Kconfig options. - Find a device in C by its devicetree alias, and drive it with the LED strip API.
The sample
Code: zephyr/samples/efz_samples/02_basic/led_strip, a copy of Zephyr's samples/drivers/led/led_strip.
Four of its files matter here:
led_strip/
├── boards/
│ ├── efz_esp32s3_procpu.overlay ← adds the LED, for the EFZ only
│ └── esp32s3_devkitc_procpu.overlay ← the same for the DevKitC
├── CMakeLists.txt
├── Kconfig ← the sample's own settings
├── prj.conf ← turns the driver on
└── src/
└── main.c
Step 1 — The overlay
An overlay in a sample's boards/ folder is only used when you build for that board. This one adds the LED.
- EFZ-ESP32S3
- ESP32-S3-DevKitC
#include <zephyr/dt-bindings/led/led.h>
/ {
aliases {
led-strip = &led_strip;
};
};
&i2s0_default {
group1 {
pinmux = <I2S0_O_SD_GPIO21>;
};
};
i2s_led: &i2s0 {
status = "okay";
dmas = <&dma 3>;
dma-names = "tx";
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-i2s";
reg = <0>;
chain-length = <1>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
reset-delay = <500>;
};
};
&dma {
status = "okay";
};
The DevKitC's RGB LED is on GPIO48. The rest of its overlay is the same as the EFZ's.
&i2s0_default {
group1 {
pinmux = <I2S0_O_SD_GPIO48>;
};
};
Read it from the top:
aliasesgives the LED a name the code can look up:led-strip.pinmuxputs the data line on GPIO21. Zephyr drives the LED with I2S, an audio bus, because I2S can send the tight pulse timing a WS2812 expects.ws2812@0is the LED itself.compatiblechooses the driver.chain-lengthis the number of LEDs in a row, one on the EFZ.color-mappingis green, red, blue, because that's the order the WS2812 reads its data in.
Writing Overlays in the Intermediate section explains overlays in depth.
Step 2 — prj.conf
CONFIG_LOG=y
CONFIG_LED_STRIP=y
CONFIG_LED_STRIP_LOG_LEVEL_DBG=y
CONFIG_LED_STRIP=y builds the driver. The logger lets the sample print what it found, and the third line turns on the driver's debug messages.
Step 3 — The sample's own Kconfig
config SAMPLE_LED_UPDATE_DELAY
int "Delay between LED updates in ms"
default 50
config SAMPLE_LED_BRIGHTNESS
int "LED brightness"
default 16
range 1 255
source "Kconfig.zephyr"
Two settings of its own: a 50 ms delay, and a brightness of 16 out of 255. In C you read them as CONFIG_SAMPLE_LED_UPDATE_DELAY and CONFIG_SAMPLE_LED_BRIGHTNESS. The last line pulls in the rest of Zephyr's options.
Step 4 — main.c
#define STRIP_NODE DT_ALIAS(led_strip)
#if DT_NODE_HAS_PROP(DT_ALIAS(led_strip), chain_length)
#define STRIP_NUM_PIXELS DT_PROP(DT_ALIAS(led_strip), chain_length)
#else
#error Unable to determine length of LED strip
#endif
#define DELAY_TIME K_MSEC(CONFIG_SAMPLE_LED_UPDATE_DELAY)
#define RGB(_r, _g, _b) { .r = (_r), .g = (_g), .b = (_b) }
static const struct led_rgb colors[] = {
RGB(CONFIG_SAMPLE_LED_BRIGHTNESS, 0x00, 0x00), /* red */
RGB(0x00, CONFIG_SAMPLE_LED_BRIGHTNESS, 0x00), /* green */
RGB(0x00, 0x00, CONFIG_SAMPLE_LED_BRIGHTNESS), /* blue */
};
static struct led_rgb pixels[STRIP_NUM_PIXELS];
static const struct device *const strip = DEVICE_DT_GET(STRIP_NODE);
The LED is found by its alias. The chain length comes from the devicetree, and DEVICE_DT_GET returns the driver. The pin number only lives in the overlay.
The colors table is built with the RGB macro: each colour gets the Kconfig brightness on its own channel and zero on the other two. pixels has one slot per LED in the chain, so here it has one.
if (device_is_ready(strip)) {
LOG_INF("Found LED strip device %s", strip->name);
} else {
LOG_ERR("LED strip device %s is not ready", strip->name);
return 0;
}
LOG_INF("Displaying pattern on strip");
while (1) {
for (size_t cursor = 0; cursor < ARRAY_SIZE(pixels); cursor++) {
memset(&pixels, 0x00, sizeof(pixels));
memcpy(&pixels[cursor], &colors[color], sizeof(struct led_rgb));
rc = led_strip_update_rgb(strip, pixels, STRIP_NUM_PIXELS);
if (rc) {
LOG_ERR("couldn't update strip: %d", rc);
}
k_sleep(DELAY_TIME);
}
color = (color + 1) % ARRAY_SIZE(colors);
}
Before it uses the LED, main() checks that the device is ready. If the driver didn't start, it logs an error and returns.
In the loop, memset clears the pixels, then memcpy writes the current colour at the cursor. led_strip_update_rgb() pushes that buffer out to the LED, and the loop sleeps for the Kconfig delay. Then it moves on to the next colour, and wraps back to red after blue.
Step 5 — Build, flash, and watch the LED
- EFZ-ESP32S3
- ESP32-S3-DevKitC
west build -b efz_esp32s3/esp32s3/procpu zephyr/samples/efz_samples/02_basic/led_strip -p
west flash
west build -b esp32s3_devkitc/esp32s3/procpu zephyr/samples/efz_samples/02_basic/led_strip -p
west flash
[00:00:00.000,000] <dbg> ws2812_i2s.ws2812_i2s_init: Word clock: freq 100000 Hz period 10 us
*** Booting Zephyr OS build ... ***
[00:00:00.000,000] <inf> main: Found LED strip device ws2812@0
[00:00:00.000,000] <inf> main: Displaying pattern on strip
The first line is the driver's debug message, printed while it starts. Then the sample reports the device, ws2812@0, and starts the pattern. On the board, the LED goes red, green, blue, over and over.
Check your work
-
You should see: the two
<inf> mainlines, and the RGB LED changing colour. -
Change it: add these two lines to
prj.conf, then build and flash again:CONFIG_SAMPLE_LED_UPDATE_DELAY=500CONFIG_SAMPLE_LED_BRIGHTNESS=64 -
It works when: each colour stays on for half a second, and the LED is brighter.
Stuck?
#error Unable to determine length of LED strip→ the build found noled-stripalias with achain-length→ check the overlay is in the sample'sboards/folder and named for your board.- The colours come out in the wrong order →
color-mappingdoesn't match the LED → a WS2812 reads green, red, blue.
What's next
Threads runs three loops at once: two LEDs blinking at their own speed, and a third thread that prints what they report.