Skip to content

Conversation

waihongtam
Copy link
Contributor

This commit introduces a new driver for WS2812 and compatible LED strips that uses a UART peripheral.

The driver generates the precise, high-speed signal required by the WS2812 protocol by encoding each data bit into a multi-bit "symbol" and using a frame-aware packing strategy for transmission:

  • Signal Inversion: The UART's TX line must be inverted (tx-invert) to create the protocol's required idle-low signal. A UART start bit then generates the initial high pulse of a WS2812 bit.
  • Frame-Aware Packing: The driver reuses the UART's hardware- generated start and stop bits as the first and last bits of the on-wire symbol. The inner bits of the symbol are packed into the UART data payload.

This packing scheme imposes a configuration constraint: the total number of bits in a UART frame (1 start + N data + 1 stop) must be an integer multiple of the symbol's length (bits-per-symbol).

@zephyrbot zephyrbot added the area: LED Label to identify LED subsystem label Aug 29, 2025
@waihongtam waihongtam force-pushed the ws2812_uart branch 2 times, most recently from e79c8b8 to b5f5982 Compare August 29, 2025 20:50
@waihongtam waihongtam force-pushed the ws2812_uart branch 2 times, most recently from 00042f7 to 171a528 Compare September 5, 2025 17:54
Comment on lines +179 to +180
/* Lock the driver to ensure thread-safe access to the buffer and UART */
k_mutex_lock(&data->lock, K_FOREVER);
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be a semaphore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good question. While a binary semaphore can be used to implement mutual exclusion, a mutex is more appropriate here. A mutex clearly states that the code is protecting a shared resource (px_buf), whereas a semaphore is typically for signaling (the ISR signaling the thread). Moreover, a mutex provides priority inversion protection that a semaphore does not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Where is priority inversion needed here?

Copy link
Member

Choose a reason for hiding this comment

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

Sempahores are normally preferred for driver code, few reasons for that, Andy goes in more details in https://youtu.be/p8OgqVQxklo?si=iaVjKvUwvntmViGn&t=1304 but yeah if you don't need priority inversion semaphore seems to be the way to go, also since most core code do not use mutexes the code has a chance of being compiled out if we keep not using it in drivers.

Copy link
Contributor Author

@waihongtam waihongtam Sep 8, 2025

Choose a reason for hiding this comment

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

Thank you both for the feedback, and especially for the link to Andy's talk. It provided great context on Zephyr's common practices.

I understand the general preference for semaphores in Zephyr drivers, as they are a smaller and faster implementation when priority inversion protection is not required.

My choice to use a mutex here is a trade-off that prioritizes robustness over performance gains. I particularly aim to support the following scenario:

  • A low-priority thread (e.g., a workqueue thread) handles a continuous LED animation, like a breathing effect, by periodically calling update_rgb().
  • A high-priority thread handles commands from an external host (the application processor) that must immediately overwrite the LED state for factory testing or to indicate a critical status. This thread also calls update_rgb().

In this situation, the high-priority thread could be blocked if the low-priority thread is preempted by a medium-priority task while holding the lock. A mutex with priority inheritance solves this problem transparently, making the driver more robust.

Given that we can't predict how developers will integrate this driver, I feel the safer approach is better suited for a general-purpose driver.


for (int i = 0; i < cfg->num_colors; i++) {
switch (cfg->color_mapping[i]) {
case LED_COLOR_ID_WHITE:
Copy link
Contributor

Choose a reason for hiding this comment

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

if not supported as above, then should return an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current behavior is consistent with other ws2812 drivers.

Due to an API limitation, the update_rgb() API doesn't support the WHITE color, as the argument pixels is an array of RGB colors, not RGBW. For WS2812 devices that support a dedicated WHITE channel, the API will set the WHITE channel to a value of 0. An error is not returned because the update_rgb() API still successfully performs its intended task.

fabiobaltieri
fabiobaltieri previously approved these changes Sep 9, 2025
@simonguinot
Copy link
Contributor

Here is a previous attempt to merge an UART driver for WS2812 controller: #72713

@simonguinot
Copy link
Contributor

Hi @matt-wood-ct. Maybe you would be interested in testing this driver ?

Copy link
Contributor

@simonguinot simonguinot left a comment

Choose a reason for hiding this comment

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

Hi @waihongtam,

Thanks for this very nice driver. I don't have much to say. Please see my comments below.

Additionally, I'll test it on an Everlight B1414 controller. The timings are different from the Worldsemi WS2812 (4 bits are needed to represent a "zero" or "one" bit).

Add a new devicetree binding, worldsemi,ws2812-uart, for controlling
WS2812-compatible LED strips using a UART peripheral.

Signed-off-by: Wai-Hong Tam <waihong@google.com>
This commit introduces a new driver for WS2812 and compatible LED
strips that uses a UART peripheral.

The driver generates the precise, high-speed signal required by the
WS2812 protocol by encoding each data bit into a multi-bit "symbol"
and using a frame-aware packing strategy for transmission:
 - Signal Inversion: The UART's TX line must be inverted (tx-invert)
   to create the protocol's required idle-low signal. A UART start
   bit then generates the initial high pulse of a WS2812 bit.
 - Frame-Aware Packing: The driver reuses the UART's hardware-
   generated start and stop bits as the first and last bits of the
   on-wire symbol. The inner bits of the symbol are packed into the
   UART data payload.

This packing scheme imposes a configuration constraint: the total
number of bits in a UART frame (1 start + N data + 1 stop) must be
an integer multiple of the symbol's length (bits-per-symbol).

Signed-off-by: Wai-Hong Tam <waihong@google.com>
Add the `uart-controller-pin-inversion.yaml` include to the `vnd,serial`
test binding. This enables testing for other drivers that require pin
inversion to function correctly. For example, the
`worldsemi,ws2812-uart` LED strip driver relies on `tx-invert`.

Signed-off-by: Wai-Hong Tam <waihong@google.com>
…driver

This commit adds a build test for the new `worldsemi,ws2812-uart`
driver. A devicetree node for the driver is added to the app.overlay,
instantiated on a `vnd,serial` test UART.

Signed-off-by: Wai-Hong Tam <waihong@google.com>
Copy link

@waihongtam waihongtam requested a review from dsseng September 25, 2025 00:53
@matt-wood-ct
Copy link
Contributor

Hi @matt-wood-ct. Maybe you would be interested in testing this driver ?

Sorry I don't have HW to test this on at the moment, but I had a quick review of the code and it looks good to me

Copy link
Contributor

@simonguinot simonguinot left a comment

Choose a reason for hiding this comment

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

Hi @waihongtam,

Sorry for the delay. It took me a while to test the driver. I had trouble finding a board with a serial controller capable of delivering a 2.5-3 Mbps rate. And I also had trouble configuring it...

So I was able to test the driver with a strip of 18 Everlight B1414 controllers (with slightly different timings than the WS2812) connected to an LPC11U68 LPCXpresso board (with modified serial and clock drivers).

The driver looks very good and I only have one comment / question left.

@simonguinot simonguinot self-requested a review September 30, 2025 19:55
Copy link
Contributor

@simonguinot simonguinot left a comment

Choose a reason for hiding this comment

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

Thanks for this nice driver. Well done !

@kartben kartben merged commit ca68c77 into zephyrproject-rtos:main Sep 30, 2025
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: LED Label to identify LED subsystem
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants