Skip to content

Commit 6699d4d

Browse files
soburicarlescufi
authored andcommitted
drivers: led_strip: add rpi_pico's PIO based ws2812 driver
Add driver that based on RPI-PICO's PIO feature for ws2812. This driver can handle WS2812 or compatible LED strips. The single PIO node can handle up to 4 strips. Any pins that can be configured for PIO can be used for strips. I verified the samples/driver/led_ws2812 sample working with WS2812(144 pcs) led strip using following patches. - samples/drivers/led_ws2812/boards/rpi_pico.overlay ``` / { aliases { led-strip = &ws2812; }; }; &pinctrl { ws2812_pio0_default: ws2812_pio0_default { ws2812 { pinmux = <PIO0_P21>; }; }; }; &pio0 { status = "okay"; pio-ws2812 { compatible = "worldsemi,ws2812-rpi_pico-pio"; status = "okay"; pinctrl-0 = <&ws2812_pio0_default>; pinctrl-names = "default"; bit-waveform = <3>, <3>, <4>; ws2812: ws2812 { status = "okay"; output-pin = <21>; chain-length = <144>; color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>; reset-delay = <280>; frequency = <800000>; }; }; }; ``` - samples/drivers/led_ws2812/boards/rpi_pico.conf ``` CONFIG_WS2812_STRIP_RPI_PICO_PIO=y ``` Signed-off-by: TOKITA Hiroshi <[email protected]>
1 parent b0dd9a2 commit 6699d4d

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed

drivers/led_strip/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ zephyr_library_sources_ifdef(CONFIG_LPD880X_STRIP lpd880x.c)
77
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_GPIO ws2812_gpio.c)
88
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_SPI ws2812_spi.c)
99
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_I2S ws2812_i2s.c)
10+
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_RPI_PICO_PIO ws2812_rpi_pico_pio.c)
1011
zephyr_library_sources_ifdef(CONFIG_TLC5971_STRIP tlc5971.c)

drivers/led_strip/Kconfig.ws2812

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,11 @@ config WS2812_STRIP_GPIO
4646
Note that this driver is not compatible with the Everlight B1414
4747
controller.
4848

49+
config WS2812_STRIP_RPI_PICO_PIO
50+
bool "Raspberry Pi Pico PIO"
51+
depends on DT_HAS_WORLDSEMI_WS2812_RPI_PICO_PIO_ENABLED
52+
select PICOSDK_USE_PIO
53+
help
54+
Use the PIO feature available on RaspberryPi Pico devices.
55+
4956
endchoice
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* Copyright (c) 2023 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/drivers/pinctrl.h>
8+
#include <zephyr/drivers/led_strip.h>
9+
#include <zephyr/drivers/misc/pio_rpi_pico/pio_rpi_pico.h>
10+
#include <zephyr/dt-bindings/led/led.h>
11+
#include <zephyr/kernel.h>
12+
13+
#include <zephyr/logging/log.h>
14+
LOG_MODULE_REGISTER(ws2812_rpi_pico_pio, CONFIG_LED_STRIP_LOG_LEVEL);
15+
16+
#define DT_DRV_COMPAT worldsemi_ws2812_rpi_pico_pio
17+
18+
struct ws2812_led_strip_data {
19+
uint32_t sm;
20+
};
21+
22+
struct ws2812_led_strip_config {
23+
const struct device *piodev;
24+
uint32_t output_pin;
25+
uint8_t num_colors;
26+
uint32_t frequency;
27+
const uint8_t *const color_mapping;
28+
uint16_t reset_delay;
29+
uint32_t cycles_per_bit;
30+
};
31+
32+
struct ws2812_rpi_pico_pio_config {
33+
const struct device *piodev;
34+
const struct pinctrl_dev_config *const pcfg;
35+
struct pio_program program;
36+
};
37+
38+
static int ws2812_led_strip_sm_init(const struct device *dev)
39+
{
40+
const struct ws2812_led_strip_config *config = dev->config;
41+
const float clkdiv =
42+
sys_clock_hw_cycles_per_sec() / (config->cycles_per_bit * config->frequency);
43+
pio_sm_config sm_config = pio_get_default_sm_config();
44+
PIO pio;
45+
int sm;
46+
47+
pio = pio_rpi_pico_get_pio(config->piodev);
48+
49+
sm = pio_claim_unused_sm(pio, false);
50+
if (sm < 0) {
51+
return -EINVAL;
52+
}
53+
54+
sm_config_set_sideset(&sm_config, 1, false, false);
55+
sm_config_set_sideset_pins(&sm_config, config->output_pin);
56+
sm_config_set_out_shift(&sm_config, false, true, (config->num_colors == 4 ? 32 : 24));
57+
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
58+
sm_config_set_clkdiv(&sm_config, clkdiv);
59+
pio_sm_set_consecutive_pindirs(pio, sm, config->output_pin, 1, true);
60+
pio_sm_init(pio, sm, -1, &sm_config);
61+
pio_sm_set_enabled(pio, sm, true);
62+
63+
return sm;
64+
}
65+
66+
/*
67+
* Latch current color values on strip and reset its state machines.
68+
*/
69+
static inline void ws2812_led_strip_reset_delay(uint16_t delay)
70+
{
71+
k_usleep(delay);
72+
}
73+
74+
static int ws2812_led_strip_update_rgb(const struct device *dev, struct led_rgb *pixels,
75+
size_t num_pixels)
76+
{
77+
const struct ws2812_led_strip_config *config = dev->config;
78+
struct ws2812_led_strip_data *data = dev->data;
79+
PIO pio = pio_rpi_pico_get_pio(config->piodev);
80+
81+
for (size_t i = 0; i < num_pixels; i++) {
82+
uint32_t color = 0;
83+
84+
for (size_t j = 0; j < config->num_colors; j++) {
85+
switch (config->color_mapping[j]) {
86+
/* White channel is not supported by LED strip API. */
87+
case LED_COLOR_ID_WHITE:
88+
color |= 0;
89+
break;
90+
case LED_COLOR_ID_RED:
91+
color |= pixels[i].r << (8 * (2 - j));
92+
break;
93+
case LED_COLOR_ID_GREEN:
94+
color |= pixels[i].g << (8 * (2 - j));
95+
break;
96+
case LED_COLOR_ID_BLUE:
97+
color |= pixels[i].b << (8 * (2 - j));
98+
break;
99+
}
100+
}
101+
102+
pio_sm_put_blocking(pio, data->sm, color << (config->num_colors == 4 ? 0 : 8));
103+
}
104+
105+
ws2812_led_strip_reset_delay(config->reset_delay);
106+
107+
return 0;
108+
}
109+
110+
static int ws2812_led_strip_update_channels(const struct device *dev, uint8_t *channels,
111+
size_t num_channels)
112+
{
113+
LOG_DBG("update_channels not implemented");
114+
return -ENOTSUP;
115+
}
116+
117+
static const struct led_strip_driver_api ws2812_led_strip_api = {
118+
.update_rgb = ws2812_led_strip_update_rgb,
119+
.update_channels = ws2812_led_strip_update_channels,
120+
};
121+
122+
/*
123+
* Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
124+
* "color-mapping" DT property.
125+
*/
126+
static int ws2812_led_strip_init(const struct device *dev)
127+
{
128+
const struct ws2812_led_strip_config *config = dev->config;
129+
struct ws2812_led_strip_data *data = dev->data;
130+
int sm;
131+
132+
if (!device_is_ready(config->piodev)) {
133+
LOG_ERR("%s: PIO device not ready", dev->name);
134+
return -ENODEV;
135+
}
136+
137+
for (uint32_t i = 0; i < config->num_colors; i++) {
138+
switch (config->color_mapping[i]) {
139+
case LED_COLOR_ID_WHITE:
140+
case LED_COLOR_ID_RED:
141+
case LED_COLOR_ID_GREEN:
142+
case LED_COLOR_ID_BLUE:
143+
break;
144+
default:
145+
LOG_ERR("%s: invalid channel to color mapping."
146+
" Check the color-mapping DT property",
147+
dev->name);
148+
return -EINVAL;
149+
}
150+
}
151+
152+
sm = ws2812_led_strip_sm_init(dev);
153+
if (sm < 0) {
154+
return sm;
155+
}
156+
157+
data->sm = sm;
158+
159+
return 0;
160+
}
161+
162+
static int ws2812_rpi_pico_pio_init(const struct device *dev)
163+
{
164+
const struct ws2812_rpi_pico_pio_config *config = dev->config;
165+
PIO pio;
166+
167+
if (!device_is_ready(config->piodev)) {
168+
LOG_ERR("%s: PIO device not ready", dev->name);
169+
return -ENODEV;
170+
}
171+
172+
pio = pio_rpi_pico_get_pio(config->piodev);
173+
174+
pio_add_program(pio, &config->program);
175+
176+
return pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
177+
}
178+
179+
#define CYCLES_PER_BIT(node) \
180+
(DT_PROP_BY_IDX(node, bit_waveform, 0) + DT_PROP_BY_IDX(node, bit_waveform, 1) + \
181+
DT_PROP_BY_IDX(node, bit_waveform, 2))
182+
183+
#define WS2812_CHILD_INIT(node) \
184+
static const uint8_t ws2812_led_strip_##node##_color_mapping[] = \
185+
DT_PROP(node, color_mapping); \
186+
struct ws2812_led_strip_data ws2812_led_strip_##node##_data; \
187+
\
188+
static const struct ws2812_led_strip_config ws2812_led_strip_##node##_config = { \
189+
.piodev = DEVICE_DT_GET(DT_PARENT(DT_PARENT(node))), \
190+
.output_pin = DT_PROP(node, output_pin), \
191+
.num_colors = DT_PROP_LEN(node, color_mapping), \
192+
.color_mapping = ws2812_led_strip_##node##_color_mapping, \
193+
.reset_delay = DT_PROP(node, reset_delay), \
194+
.frequency = DT_PROP(node, frequency), \
195+
.cycles_per_bit = CYCLES_PER_BIT(DT_PARENT(node)), \
196+
}; \
197+
\
198+
DEVICE_DT_DEFINE(node, &ws2812_led_strip_init, NULL, &ws2812_led_strip_##node##_data, \
199+
&ws2812_led_strip_##node##_config, POST_KERNEL, \
200+
CONFIG_LED_STRIP_INIT_PRIORITY, &ws2812_led_strip_api);
201+
202+
#define SET_DELAY(op, inst, i) \
203+
(op | (((DT_INST_PROP_BY_IDX(inst, bit_waveform, i) - 1) & 0xF) << 8))
204+
205+
/*
206+
* This pio program runs [T0+T1+T2] cycles per 1 loop.
207+
* The first `out` instruction outputs 0 by [T2] times to the sideset pin.
208+
* These zeros are padding. Here is the start of actual data transmission.
209+
* The second `jmp` instruction output 1 by [T0] times to the sideset pin.
210+
* This `jmp` instruction jumps to line 3 if the value of register x is true.
211+
* Otherwise, jump to line 4.
212+
* The third `jmp` instruction outputs 1 by [T1] times to the sideset pin.
213+
* After output, return to the first line.
214+
* The fourth `jmp` instruction outputs 0 by [T1] times.
215+
* After output, return to the first line and output 0 by [T2] times.
216+
*
217+
* In the case of configuration, T0=3, T1=3, T2 =4,
218+
* the final output is 1110000000 in case register x is false.
219+
* It represents code 0, defined in the datasheet.
220+
* And outputs 1111110000 in case of x is true. It represents code 1.
221+
*/
222+
#define WS2812_RPI_PICO_PIO_INIT(inst) \
223+
PINCTRL_DT_INST_DEFINE(inst); \
224+
\
225+
DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, WS2812_CHILD_INIT); \
226+
\
227+
static const uint16_t rpi_pico_pio_ws2812_instructions_##inst[] = { \
228+
SET_DELAY(0x6021, inst, 2), /* 0: out x, 1 side 0 [T2 - 1] */ \
229+
SET_DELAY(0x1023, inst, 0), /* 1: jmp !x, 3 side 1 [T0 - 1] */ \
230+
SET_DELAY(0x1000, inst, 1), /* 2: jmp 0 side 1 [T1 - 1] */ \
231+
SET_DELAY(0x0000, inst, 1), /* 3: jmp 0 side 0 [T1 - 1] */ \
232+
}; \
233+
\
234+
static const struct ws2812_rpi_pico_pio_config rpi_pico_pio_ws2812_##inst##_config = { \
235+
.piodev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
236+
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
237+
.program = \
238+
{ \
239+
.instructions = rpi_pico_pio_ws2812_instructions_##inst, \
240+
.length = ARRAY_SIZE(rpi_pico_pio_ws2812_instructions_##inst), \
241+
.origin = -1, \
242+
}, \
243+
}; \
244+
\
245+
DEVICE_DT_INST_DEFINE(inst, &ws2812_rpi_pico_pio_init, NULL, NULL, \
246+
&rpi_pico_pio_ws2812_##inst##_config, POST_KERNEL, \
247+
CONFIG_LED_STRIP_INIT_PRIORITY, NULL);
248+
249+
DT_INST_FOREACH_STATUS_OKAY(WS2812_RPI_PICO_PIO_INIT)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright (c) 2023, TOKITA Hiroshi
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
The pio node configured for ws2812.
6+
7+
compatible: "worldsemi,ws2812-rpi_pico-pio"
8+
9+
include: pinctrl-device.yaml
10+
11+
properties:
12+
bit-waveform:
13+
type: array
14+
description: |
15+
This property defines the waveform for sending 1-bit data.
16+
The program uses the first three elements of the array.
17+
The T0 is equal to T0H in the datasheet.
18+
The T2 is equal to T1L in the datasheet.
19+
The T1 is equal to (T1H-T0H) or (T0L-T1L) in the datasheet.
20+
21+
Code-0
22+
+------+ +---
23+
| | |
24+
| T0 | T1+T2 |
25+
| | |
26+
---+ +-----------------+
27+
28+
Code-1
29+
+---------------+ +---
30+
| | |
31+
| T0+T1 | T2 |
32+
| | |
33+
---+ +--------+
34+
35+
36+
The frequency determines the wave period.
37+
The T0~T2 means ratio in one period.
38+
39+
For example, T0=3, T1=3, T2=4 and the frequency is 800kHz case,
40+
T0H is
41+
(1 / 800kHz) * (3/10) = 375ns
42+
T0L is
43+
(1 / 800kHz) * ((4+3)/10) = 875ns
44+
45+
child-binding:
46+
description: |
47+
Worldsemi WS2812 or compatible LED strip driver based on RaspberryPi Pico's PIO
48+
The LED strip node can put up to 4 instances under a single PIO node.
49+
50+
include: ws2812.yaml
51+
52+
properties:
53+
output-pin:
54+
type: int
55+
required: true
56+
description: |
57+
Select the output pin.
58+
59+
Note: This driver does not configure the output pin.
60+
You need to configure the pin with pinctrl that is in the parent node configuration
61+
for use by PIO.
62+
63+
frequency:
64+
type: int
65+
description: |
66+
Specify the number of times a waveform representing 1 bit is
67+
transmitted per second. It is same meaning as bit-per-seconds.
68+
WS2812 works with 800000. Set the value 400000 if use with WS2811.

0 commit comments

Comments
 (0)