drivers: ws2812_gpio: Make timing configurable and less hardware dependend

The current driver contains assembly code which is specific for the nRF51
SOC which makes it incompatible with other SOC's. This patch adds support
for other nRF SOC's as well. The timing is calucated based on the CPU clock
frequency, but can be configured manually as well if needed.

Changes have been verified on a Adafruit Feather nRF52840 Express board,
which contains a single NeoPixel RGB LED. Timings have been verified using
a scope connected to the WS1812 data line.

Signed-off-by: Chaim Zax <chaim.zax@zaxx.pro>
This commit is contained in:
Chaim Zax 2024-04-28 13:27:44 +02:00 committed by Anas Nashif
commit f54a53b4b3
6 changed files with 169 additions and 41 deletions

View file

@ -453,3 +453,6 @@ device.
* - zephyr,usb-device
- USB device node. If defined and has a ``vbus-gpios`` property, these
will be used by the USB subsystem to enable/disable VBUS
* - zephyr,led-strip
- A LED-strip node which is used to determine the timings of the
WS2812 GPIO driver

View file

@ -170,6 +170,8 @@ Drivers and Sensors
* LED Strip
* Updated ws2812 GPIO driver to support dynamic bus timings
* LoRa
* Mailbox

View file

@ -30,20 +30,68 @@ config WS2812_STRIP_I2S
config WS2812_STRIP_GPIO
bool "WS2812 LED strip GPIO driver"
# Only an Cortex-M0 inline assembly implementation for the nRF51
# is supported currently.
# Only an Cortex-M inline assembly implementation for the nRF91, nRF51,
# nRF52 and nRF53 is supported currently.
default y
depends on DT_HAS_WORLDSEMI_WS2812_GPIO_ENABLED
depends on SOC_SERIES_NRF51X
depends on (SOC_SERIES_NRF91X || SOC_SERIES_NRF51X || SOC_SERIES_NRF52X || SOC_SERIES_NRF53X)
select LED_STRIP_RGB_SCRATCH
help
Enable driver for WS2812 (and compatibles) LED strip directly controlling with GPIO.
The GPIO driver does bit-banging with inline assembly,
and is not available on all SoCs.
Enable driver for WS2812 (and compatibles) LED strip directly
controlling with GPIO. The GPIO driver does bit-banging with inline
assembly, and is not available on all SoCs.
Note that this driver is not compatible with the Everlight B1414
controller.
if WS2812_STRIP_GPIO
DT_CHOSEN_LED_STRIP := zephyr,led-strip
DT_CHOSEN_LED_STRIP_PATH := $(dt_chosen_path,$(DT_CHOSEN_LED_STRIP))
config DELAY_T1H
int "Delay 1 bit high pulse"
default $(dt_node_int_prop_int,$(DT_CHOSEN_LED_STRIP_PATH),delay-t1h) \
if $(dt_node_has_prop,$(DT_CHOSEN_LED_STRIP_PATH),delay-t1h)
default $(div,$(mul,700,$(dt_node_int_prop_int,/cpus/cpu@0,clock-frequency)),1000000000) \
if $(dt_node_has_prop,/cpus/cpu@0,clock-frequency)
default 7
help
Number of NOP assembly operations to create a delay for a 1 bit, high voltage period (default 700 nsec)
config DELAY_T1L
int "Delay 1 bit low pulse"
default $(dt_node_int_prop_int,$(DT_CHOSEN_LED_STRIP_PATH),delay-t1l) \
if $(dt_node_has_prop,$(DT_CHOSEN_LED_STRIP_PATH),delay-t1l)
default $(div,$(mul,600,$(dt_node_int_prop_int,/cpus/cpu@0,clock-frequency)),1000000000) \
if $(dt_node_has_prop,/cpus/cpu@0,clock-frequency)
default 6
help
Number of NOP assembly operations to create a delay for a 1 bit, low voltage period (default 600 nsec)
config DELAY_T0H
int "Delay 0 bit high pulse"
default $(dt_node_int_prop_int,$(DT_CHOSEN_LED_STRIP_PATH),delay-t0h) \
if $(dt_node_has_prop,$(DT_CHOSEN_LED_STRIP_PATH),delay-t0h)
default $(div,$(mul,350,$(dt_node_int_prop_int,/cpus/cpu@0,clock-frequency)),1000000000) \
if $(dt_node_has_prop,/cpus/cpu@0,clock-frequency)
default 3
help
Number of NOP assembly operations to create a delay for a 0 bit, high voltage period (default 350 nsec)
config DELAY_T0L
int "Delay 0 bit low pulse"
default $(dt_node_int_prop_int,$(DT_CHOSEN_LED_STRIP_PATH),delay-t0l) \
if $(dt_node_has_prop,$(DT_CHOSEN_LED_STRIP_PATH),delay-t0l)
default $(div,$(mul,800,$(dt_node_int_prop_int,/cpus/cpu@0,clock-frequency)),1000000000) \
if $(dt_node_has_prop,/cpus/cpu@0,clock-frequency)
default 8
help
Number of NOP assembly operations to create a delay for a 0 bit, low voltage period (default 800 nsec)
endif
config WS2812_STRIP_RPI_PICO_PIO
bool "WS2812 LED strip Raspberry Pi Pico PIO driver"
default y

View file

@ -23,6 +23,7 @@ LOG_MODULE_REGISTER(ws2812_gpio);
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <zephyr/dt-bindings/led/led.h>
#include <zephyr/sys/util_macro.h>
struct ws2812_gpio_cfg {
struct gpio_dt_spec gpio;
@ -31,25 +32,6 @@ struct ws2812_gpio_cfg {
size_t length;
};
/*
* This is hard-coded to nRF51 in two ways:
*
* 1. The assembly delays T1H, T0H, TxL
* 2. GPIO set/clear
*/
/*
* T1H: 1 bit high pulse delay: 12 cycles == .75 usec
* T0H: 0 bit high pulse delay: 4 cycles == .25 usec
* TxL: inter-bit low pulse delay: 8 cycles == .5 usec
*
* We can't use k_busy_wait() here: its argument is in microseconds,
* and we need roughly .05 microsecond resolution.
*/
#define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
#define DELAY_T0H "nop\nnop\nnop\nnop\n"
#define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
/*
* GPIO set/clear (these make assumptions about assembly details
* below).
@ -68,24 +50,27 @@ struct ws2812_gpio_cfg {
#define SET_HIGH "str %[p], [%[r], #0]\n" /* OUTSET = BIT(LED_PIN) */
#define SET_LOW "str %[p], [%[r], #4]\n" /* OUTCLR = BIT(LED_PIN) */
#define NOPS(i, _) "nop\n"
#define NOP_N_TIMES(n) LISTIFY(n, NOPS, ())
/* Send out a 1 bit's pulse */
#define ONE_BIT(base, pin) do { \
__asm volatile (SET_HIGH \
DELAY_T1H \
SET_LOW \
DELAY_TxL \
:: \
[r] "l" (base), \
#define ONE_BIT(base, pin) do { \
__asm volatile (SET_HIGH \
NOP_N_TIMES(CONFIG_DELAY_T1H) \
SET_LOW \
NOP_N_TIMES(CONFIG_DELAY_T1L) \
:: \
[r] "l" (base), \
[p] "l" (pin)); } while (false)
/* Send out a 0 bit's pulse */
#define ZERO_BIT(base, pin) do { \
__asm volatile (SET_HIGH \
DELAY_T0H \
SET_LOW \
DELAY_TxL \
:: \
[r] "l" (base), \
#define ZERO_BIT(base, pin) do { \
__asm volatile (SET_HIGH \
NOP_N_TIMES(CONFIG_DELAY_T0H) \
SET_LOW \
NOP_N_TIMES(CONFIG_DELAY_T0L) \
:: \
[r] "l" (base), \
[p] "l" (pin)); } while (false)
static int send_buf(const struct device *dev, uint8_t *buf, size_t len)

View file

@ -6,9 +6,95 @@ description: |
Driver bindings for bit-banging a WS2812 or compatible LED strip.
The GPIO driver uses inline assembly, and isn't available for all
boards.
The CPU driver uses inline assembly, and isn't available for all
boards. The timing is automatically derived from the CPU clock frequency,
or can be provided by setting the delay-txx properties in the device
tree, or can be manually provided by the Kconfig settings DELAY_Txx.
The four delays provided (calculated based on the clock frequency,
provided by a dts, or Kconfig file) determine the delays as depicted
below. The exact timings of the LED strip data line might vary on the
type of LEDs used, consult the data-sheet for the precise timings.
0 code
+-------+ +---
| | |
| T0H | T0L |
| | |
---+ +-----------------+
1 code
+---------------+ +---
| | |
| T1H | T1L |
| | |
---+ +---------+
Example dts file:
/ {
cpus {
cpu@0 {
clock-frequency = <64000000>;
};
};
rgb_led: ws2812 {
compatible = "worldsemi,ws2812-gpio";
chain-length = <1>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
gpios = <&gpio0 16 0>;
};
};
Example dts file:
/ {
chosen {
zephyr,led-strip = &rgb_led;
};
rgb_led: ws2812 {
compatible = "worldsemi,ws2812-gpio";
chain-length = <1>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
reset-delay = <50>;
gpios = <&gpio0 16 0>;
delay-t1h = <48>;
delay-t1l = <32>;
delay-t0h = <16>;
delay-t0l = <32>;
};
};
compatible: "worldsemi,ws2812-gpio"
include: [base.yaml, ws2812-gpio.yaml]
properties:
delay-t1h:
type: int
description: |
Number of NOP assembly operations to create a delay for a 1 bit, high
voltage period (default 700 nsec)
delay-t1l:
type: int
description: |
Number of NOP assembly operations to create a delay for a 1 bit, low
voltage period (default 600 nsec)
delay-t0h:
type: int
description: |
Number of NOP assembly operations to create a delay for a 0 bit, high
voltage period (default 350 nsec)
delay-t0l:
type: int
description: |
Number of NOP assembly operations to create a delay for a 0 bit, low
voltage period (default 800 nsec)

View file

@ -0,0 +1,4 @@
CONFIG_DELAY_T1H=12
CONFIG_DELAY_T1L=8
CONFIG_DELAY_T0H=4
CONFIG_DELAY_T0L=8