Skip to content

Commit 9639c00

Browse files
Johan Hedbergcarlescufi
authored andcommitted
drivers: led_strip: Add driver for software-based WS2812B operation
This driver uses a bit-banging based technique of generating a signal for the WS2812B LED strip. Since bit-banging is very timing sensitive, where each CPU cycle counts, the driver uses inline assembly to perform the most critical operataions. This initial version of the driver only supports a Cortex-M0 implementation, and can e.g. be used with the ZIP Halo LED strip for the BBC microbit: https://www.kitronik.co.uk/5625-zip-halo-for-the-bbc-microbit.html Signed-off-by: Johan Hedberg <[email protected]>
1 parent 1cf09e8 commit 9639c00

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

drivers/led_strip/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
zephyr_sources_ifdef(CONFIG_LPD880X_STRIP lpd880x.c)
22
zephyr_sources_ifdef(CONFIG_WS2812_STRIP ws2812.c)
3+
zephyr_sources_ifdef(CONFIG_WS2812B_SW ws2812b_sw.c)
34
zephyr_sources_ifdef(CONFIG_APA102_STRIP apa102.c)

drivers/led_strip/Kconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#
22
# Copyright (c) 2017 Linaro Limited
3+
# Copyright (c) 2018 Intel Corporation
34
#
45
# SPDX-License-Identifier: Apache-2.0
56
#
@@ -46,6 +47,8 @@ source "drivers/led_strip/Kconfig.lpd880x"
4647

4748
source "drivers/led_strip/Kconfig.ws2812"
4849

50+
source "drivers/led_strip/Kconfig.ws2812b_sw"
51+
4952
source "drivers/led_strip/Kconfig.apa102"
5053

5154
endif # LED_STRIP
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) 2018 Intel Corporation
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
menuconfig WS2812B_SW
8+
bool "Enable WS2812B software-based LED strip driver"
9+
# Only an Cortex-M0 inline assembly implementation for the nRF51
10+
# is supported currently.
11+
depends on SOC_SERIES_NRF51X
12+
help
13+
A software-based (bit-banging) LED strip driver for daisy
14+
chains of WS2812B devices. This driver implements the signal
15+
sending with software-based bit-banging. If a more efficient
16+
option, such as SPI, is available, another driver is recommended
17+
to be used.
18+
19+
if WS2812B_SW
20+
21+
config WS2812B_SW_NAME
22+
string "Driver name"
23+
default "ws2812b_sw"
24+
help
25+
Device name for WS2812B LED strip.
26+
27+
config WS2812B_SW_GPIO_NAME
28+
string "GPIO port that the LED strip is connected to"
29+
default GPIO_NRF5_P0_DEV_NAME if SOC_FAMILY_NRF5
30+
help
31+
GPIO port name.
32+
33+
config WS2812B_SW_GPIO_PIN
34+
int "GPIO pin that the LED strip is connected to"
35+
default 3 if BOARD_BBC_MICROBIT # P0
36+
help
37+
GPIO pin number that the LED strip is connected to.
38+
39+
endif

drivers/led_strip/ws2812b_sw.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2018 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <led_strip.h>
8+
9+
#include <string.h>
10+
11+
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LED_STRIP_LEVEL
12+
#include <logging/sys_log.h>
13+
14+
#include <zephyr.h>
15+
#include <board.h>
16+
#include <gpio.h>
17+
#include <device.h>
18+
#include <clock_control.h>
19+
20+
#define BLOCKING ((void *)1)
21+
22+
static int send_buf(u8_t *buf, size_t len)
23+
{
24+
/* Address of OUTSET. OUTCLR is OUTSET + 4 */
25+
volatile u32_t *base = (u32_t *)(NRF_GPIO_BASE + 0x508);
26+
u32_t pin = BIT(CONFIG_WS2812B_SW_GPIO_PIN);
27+
struct device *clock;
28+
unsigned int key;
29+
/* Initilization of i is strictly not needed, but it avoids an
30+
* uninitialized warning with the inline assembly.
31+
*/
32+
u32_t i = 0;
33+
34+
clock = device_get_binding(CONFIG_CLOCK_CONTROL_NRF5_M16SRC_DRV_NAME);
35+
if (!clock) {
36+
SYS_LOG_ERR("Unable to get HF clock");
37+
return -EIO;
38+
}
39+
40+
/* The inline assembly further below is designed to work only with
41+
* the 16 MHz clock enabled.
42+
*/
43+
clock_control_on(clock, BLOCKING);
44+
key = irq_lock();
45+
46+
while (len--) {
47+
u32_t b = *buf++;
48+
49+
/* Generate signal out of the bits, MSB. 1-bit should be
50+
* roughly 0.85us high, 0.4us low, whereas a 0-bit should be
51+
* roughly 0.4us high, 0.85us low.
52+
*/
53+
__asm volatile ("movs %[i], #8\n" /* i = 8 */
54+
".start_bit:\n"
55+
56+
/* OUTSET = BIT(LED_PIN) */
57+
"strb %[p], [%[r], #0]\n"
58+
59+
/* if (b & 0x80) goto .long */
60+
"tst %[b], %[m]\n"
61+
"bne .long\n"
62+
63+
/* 0-bit */
64+
"nop\nnop\n"
65+
/* OUTCLR = BIT(LED_PIN) */
66+
"strb %[p], [%[r], #4]\n"
67+
"nop\nnop\nnop\n"
68+
"b .next_bit\n"
69+
70+
/* 1-bit */
71+
".long:\n"
72+
"nop\nnop\nnop\nnop\nnop\nnop\nnop\n"
73+
/* OUTCLR = BIT(LED_PIN) */
74+
"strb %[p], [%[r], #4]\n"
75+
76+
".next_bit:\n"
77+
/* b <<= 1 */
78+
"lsl %[b], #1\n"
79+
/* i-- */
80+
"sub %[i], #1\n"
81+
/* if (i > 0) goto .start_bit */
82+
"bne .start_bit\n"
83+
:
84+
[i] "+r" (i)
85+
:
86+
[b] "l" (b),
87+
[m] "l" (0x80),
88+
[r] "l" (base),
89+
[p] "r" (pin)
90+
:);
91+
}
92+
93+
irq_unlock(key);
94+
clock_control_off(clock, NULL);
95+
96+
return 0;
97+
}
98+
99+
static int ws2812b_sw_update_rgb(struct device *dev, struct led_rgb *pixels,
100+
size_t num_pixels)
101+
{
102+
u8_t *ptr = (u8_t *)pixels;
103+
size_t i;
104+
105+
/* Convert from RGB to GRB format */
106+
for (i = 0; i < num_pixels; i++) {
107+
u8_t r = pixels[i].r;
108+
u8_t b = pixels[i].b;
109+
u8_t g = pixels[i].g;
110+
111+
*ptr++ = g;
112+
*ptr++ = r;
113+
*ptr++ = b;
114+
}
115+
116+
return send_buf((u8_t *)pixels, num_pixels * 3);
117+
}
118+
119+
static int ws2812b_sw_update_channels(struct device *dev, u8_t *channels,
120+
size_t num_channels)
121+
{
122+
SYS_LOG_ERR("update_channels not implemented");
123+
return -ENOSYS;
124+
}
125+
126+
static int ws2812b_sw_init(struct device *dev)
127+
{
128+
struct device *gpio;
129+
130+
gpio = device_get_binding(CONFIG_WS2812B_SW_GPIO_NAME);
131+
if (!gpio) {
132+
SYS_LOG_ERR("Unable to find %s", CONFIG_WS2812B_SW_GPIO_NAME);
133+
return -ENODEV;
134+
}
135+
136+
gpio_pin_configure(gpio, CONFIG_WS2812B_SW_GPIO_PIN, GPIO_DIR_OUT);
137+
138+
return 0;
139+
}
140+
141+
static const struct led_strip_driver_api ws2812b_sw_api = {
142+
.update_rgb = ws2812b_sw_update_rgb,
143+
.update_channels = ws2812b_sw_update_channels,
144+
};
145+
146+
DEVICE_AND_API_INIT(ws2812b_sw, CONFIG_WS2812B_SW_NAME, ws2812b_sw_init, NULL,
147+
NULL, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY,
148+
&ws2812b_sw_api);

0 commit comments

Comments
 (0)