Hi All,
I have been working on getting the ws2812-gpio working on the nRF9160 as I want to use the periphs for real stuff, rather than an entire SPI used for an LED.
It's almost working, but it only works with following line in prj.conf
/* 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), \ [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), \ [p] "l" (pin)); } while (false)
Here's what I did to get it going - hopefully someone else can clean it up and get it into Zephyr!
The White LED, is still a bit green so the timing might be slightly off, but I haven't got a scope at home to fine tune it.
Kconfig.ws2812
config WS2812_STRIP_GPIO bool "GPIO driver" # Only an Cortex-M0 inline assembly implementation for the nRF51 # is supported currently. #MKB Added || SOC_SERIES_NRF91X as this part also has a 16MHz periph clock so it might work #lets find out! depends on SOC_SERIES_NRF51X || SOC_SERIES_NRF91X help 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. endchoice
overlay
#include <zephyr/dt-bindings/gpio/gpio.h> #include <zephyr/dt-bindings/led/led.h> / { led_strip: ws2812 { compatible = "worldsemi,ws2812-gpio"; chain-length = <1>; /* arbitrary; change at will */ color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>; /* P0: */ in-gpios = <&gpio0 3 0>; }; aliases { led-strip = &led_strip; }; };
ws2810_gpio.c
/* * Copyright (c) 2018 Intel Corporation * Copyright (c) 2019 Nordic Semiconductor ASA * Copyright (c) 2021 Seagate Technology LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT worldsemi_ws2812_gpio #include <zephyr/drivers/led_strip.h> #include <string.h> #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL #include <zephyr/logging/log.h> LOG_MODULE_REGISTER(ws2812_gpio); #include <zephyr/zephyr.h> #include <soc.h> #include <zephyr/drivers/gpio.h> #include <zephyr/device.h> #include <zephyr/drivers/clock_control.h> #include <zephyr/drivers/clock_control/nrf_clock_control.h> #include <zephyr/dt-bindings/led/led.h> struct ws2812_gpio_cfg { struct gpio_dt_spec in_gpio; uint8_t num_colors; const uint8_t *color_mapping; }; /* * 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 MHZ_64 #ifdef CONFIG_SOC_SERIES_NRF91X #ifdef MHZ_64 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_T0H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #endif #ifdef MHZ_32 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_T0H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #endif #ifdef MHZ_28 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_T0H "nop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #endif #ifdef MHZ_24 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_T0H "nop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n" #endif #ifdef MHZ_16 #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" #endif #ifdef MHZ_8 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\n" #define DELAY_T0H "nop\nnop\n" #define DELAY_TxL "nop\nnop\nnop\nnop\n" #endif #ifdef MHZ_4 #define DELAY_T1H "nop\nnop\nnop\n" #define DELAY_T0H "nop\n" #define DELAY_TxL "nop\nnop\n" #endif #else #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" #endif /* * GPIO set/clear (these make assumptions about assembly details * below). * * This uses OUTCLR == OUTSET+4. * * We should be able to make this portable using the results of * https://github.com/zephyrproject-rtos/zephyr/issues/11917. * * We already have the GPIO device stashed in ws2812_gpio_config, so * this driver can be used as a test case for the optimized API. * * Per Arm docs, both Rd and Rn must be r0-r7, so we use the "l" * constraint in the below assembly. */ #define SET_HIGH "str %[p], [%[r], #0]\n" /* OUTSET = BIT(LED_PIN) */ #define SET_LOW "str %[p], [%[r], #4]\n" /* OUTCLR = BIT(LED_PIN) */ /* 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), \ [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), \ [p] "l" (pin)); } while (false) #ifdef CONFIG_SOC_SERIES_NRF91X static int send_buf(const struct device *dev, uint8_t *buf, size_t len) { const struct ws2812_gpio_cfg *config = dev->config; volatile uint32_t *base = (uint32_t *)&NRF_P0->OUTSET; const uint32_t val = BIT(config->in_gpio.pin); //do we really need to turn on / have access to the perhip clock when the nops are running at sysclk anyway //and we never use the clock for anything, //#define CLOCK_REQUIRED unsigned int key; #ifdef CLOCK_REQUIRED struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF); struct onoff_client cli; int rc; sys_notify_init_spinwait(&cli.notify); rc = onoff_request(mgr, &cli); if (rc < 0) { return rc; } while (sys_notify_fetch_result(&cli.notify, &rc)) { /* pend until clock is up and running */ } #endif key = irq_lock(); while (len--) { uint32_t b = *buf++; int32_t i; /* * Generate signal out of the bits, MSbit first. * * Accumulator maintenance and branching mean the * inter-bit time will be longer than TxL, but the * wp.josh.com blog post says we have at least 5 usec * of slack time between bits before we risk the * signal getting latched, so this will be fine as * long as the compiler does something minimally * reasonable. */ for (i = 7; i >= 0; i--) { if (b & BIT(i)) { ONE_BIT(base, val); } else { ZERO_BIT(base, val); } } } irq_unlock(key); #ifdef CLOCK_REQUIRED rc = onoff_release(mgr); /* Returns non-negative value on success. Cap to 0 as API states. */ rc = MIN(rc, 0); return rc; #else return 0; #endif } #else static int send_buf(const struct device *dev, uint8_t *buf, size_t len) { const struct ws2812_gpio_cfg *config = dev->config; //MKB replaced the commented line below with the #ifdef statement //to support the NRF9160 as well as the default. //volatile uint32_t *base = (uint32_t *)&NRF_GPIO->OUTSET; #ifdef CONFIG_SOC_SERIES_NRF91X volatile uint32_t *base = (uint32_t *)&NRF_P0->OUTSET; #else //NRF_GPIO has actually been renamed to NRF_P0 in any case volatile uint32_t *base = (uint32_t *)&NRF_GPIO->OUTSET; #endif const uint32_t val = BIT(config->in_gpio.pin); struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF); struct onoff_client cli; unsigned int key; int rc; sys_notify_init_spinwait(&cli.notify); rc = onoff_request(mgr, &cli); if (rc < 0) { return rc; } while (sys_notify_fetch_result(&cli.notify, &rc)) { /* pend until clock is up and running */ } key = irq_lock(); while (len--) { uint32_t b = *buf++; int32_t i; /* * Generate signal out of the bits, MSbit first. * * Accumulator maintenance and branching mean the * inter-bit time will be longer than TxL, but the * wp.josh.com blog post says we have at least 5 usec * of slack time between bits before we risk the * signal getting latched, so this will be fine as * long as the compiler does something minimally * reasonable. */ for (i = 7; i >= 0; i--) { if (b & BIT(i)) { ONE_BIT(base, val); } else { ZERO_BIT(base, val); } } } irq_unlock(key); rc = onoff_release(mgr); /* Returns non-negative value on success. Cap to 0 as API states. */ rc = MIN(rc, 0); return rc; } #endif static int ws2812_gpio_update_rgb(const struct device *dev, struct led_rgb *pixels, size_t num_pixels) { const struct ws2812_gpio_cfg *config = dev->config; uint8_t *ptr = (uint8_t *)pixels; size_t i; /* Convert from RGB to on-wire format (e.g. GRB, GRBW, RGB, etc) */ for (i = 0; i < num_pixels; i++) { uint8_t j; for (j = 0; j < config->num_colors; j++) { switch (config->color_mapping[j]) { /* White channel is not supported by LED strip API. */ case LED_COLOR_ID_WHITE: *ptr++ = 0; break; case LED_COLOR_ID_RED: *ptr++ = pixels[i].r; break; case LED_COLOR_ID_GREEN: *ptr++ = pixels[i].g; break; case LED_COLOR_ID_BLUE: *ptr++ = pixels[i].b; break; default: return -EINVAL; } } } return send_buf(dev, (uint8_t *)pixels, num_pixels * config->num_colors); } static int ws2812_gpio_update_channels(const struct device *dev, uint8_t *channels, size_t num_channels) { LOG_ERR("update_channels not implemented"); return -ENOTSUP; } static const struct led_strip_driver_api ws2812_gpio_api = { .update_rgb = ws2812_gpio_update_rgb, .update_channels = ws2812_gpio_update_channels, }; /* * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the * "color-mapping" DT property. */ #define WS2812_COLOR_MAPPING(idx) \ static const uint8_t ws2812_gpio_##idx##_color_mapping[] = \ DT_INST_PROP(idx, color_mapping) #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping)) /* * The inline assembly above is designed to work on nRF51 devices with * the 16 MHz clock enabled. * * TODO: try to make this portable, or at least port to more devices. */ #define WS2812_GPIO_DEVICE(idx) \ \ static int ws2812_gpio_##idx##_init(const struct device *dev) \ { \ const struct ws2812_gpio_cfg *cfg = dev->config; \ uint8_t i; \ \ if (!device_is_ready(cfg->in_gpio.port)) { \ LOG_ERR("GPIO device not ready"); \ return -ENODEV; \ } \ \ for (i = 0; i < cfg->num_colors; i++) { \ switch (cfg->color_mapping[i]) { \ case LED_COLOR_ID_WHITE: \ case LED_COLOR_ID_RED: \ case LED_COLOR_ID_GREEN: \ case LED_COLOR_ID_BLUE: \ break; \ default: \ LOG_ERR("%s: invalid channel to color mapping." \ " Check the color-mapping DT property", \ dev->name); \ return -EINVAL; \ } \ } \ \ return gpio_pin_configure_dt(&cfg->in_gpio, GPIO_OUTPUT); \ } \ \ WS2812_COLOR_MAPPING(idx); \ \ static const struct ws2812_gpio_cfg ws2812_gpio_##idx##_cfg = { \ .in_gpio = GPIO_DT_SPEC_INST_GET(idx, in_gpios), \ .num_colors = WS2812_NUM_COLORS(idx), \ .color_mapping = ws2812_gpio_##idx##_color_mapping, \ }; \ \ DEVICE_DT_INST_DEFINE(idx, \ ws2812_gpio_##idx##_init, \ NULL, \ NULL, \ &ws2812_gpio_##idx##_cfg, POST_KERNEL, \ CONFIG_LED_STRIP_INIT_PRIORITY, \ &ws2812_gpio_api); DT_INST_FOREACH_STATUS_OKAY(WS2812_GPIO_DEVICE)
main.c
/* * Copyright (c) 2017 Linaro Limited * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include <errno.h> #include <string.h> #define LOG_LEVEL 4 #include <zephyr/logging/log.h> LOG_MODULE_REGISTER(main); #include <zephyr/zephyr.h> #include <zephyr/drivers/led_strip.h> #include <zephyr/device.h> #include <zephyr/drivers/spi.h> #include <zephyr/sys/util.h> #define STRIP_NODE DT_ALIAS(led_strip) #define STRIP_NUM_PIXELS DT_PROP(DT_ALIAS(led_strip), chain_length) #define DELAY_TIME K_MSEC(1000) #define RGB(_r, _g, _b) { .r = (_r), .g = (_g), .b = (_b) } static const struct led_rgb colors[] = { RGB(0xff, 0x00, 0x00), /* red */ RGB(0x00, 0xff, 0x00), /* blue */ RGB(0x00, 0x00, 0xff), /* green */ RGB(0xff, 0xff, 0xff), /* white */ RGB(0x00, 0x00, 0x00), /* off */ }; struct led_rgb pixels[STRIP_NUM_PIXELS]; static const struct device *strip = DEVICE_DT_GET(STRIP_NODE); void main(void) { size_t cursor = 0, color = 0; int rc; 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; } LOG_INF("Displaying pattern on strip"); while (1) { 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); } cursor++; if (cursor >= STRIP_NUM_PIXELS) { cursor = 0; LOG_INF("colour = %d", color); color++; if (color == ARRAY_SIZE(colors)) { color = 0; } } k_sleep(DELAY_TIME); } }