From c0615ad7262ee8af51a15b2085e69e017be304f3 Mon Sep 17 00:00:00 2001 From: Christian Friedrich Coors Date: Wed, 11 Mar 2020 12:40:12 +0100 Subject: [PATCH 1/2] sys/timex: Add NS_PER_SEC definition --- sys/include/timex.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sys/include/timex.h b/sys/include/timex.h index f022ad362a..2c4506cce3 100644 --- a/sys/include/timex.h +++ b/sys/include/timex.h @@ -63,6 +63,11 @@ extern "C" { */ #define NS_PER_US (1000U) +/** + * @brief The number of nanoseconds per second + */ +#define NS_PER_SEC (1000000000U) + /** * @brief The maximum length of the string representation of a timex timestamp */ From 08703766fc60296ad6fa39683ebb2555bbe4d108 Mon Sep 17 00:00:00 2001 From: Christian Friedrich Coors Date: Wed, 25 Mar 2020 17:25:35 +0100 Subject: [PATCH 2/2] drivers/ws281x: Add ESP32 support --- drivers/Makefile.dep | 3 + drivers/include/ws281x.h | 12 ++- drivers/ws281x/esp32.c | 106 ++++++++++++++++++++++ drivers/ws281x/include/ws281x_backend.h | 9 ++ drivers/ws281x/include/ws281x_constants.h | 17 ++++ tests/driver_ws281x/Makefile | 5 +- 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 drivers/ws281x/esp32.c diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 32adb97497..d5fcbd11e3 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -788,6 +788,9 @@ ifneq (,$(filter ws281x,$(USEMODULE))) ifneq (,$(filter arch_native,$(FEATURES_USED))) USEMODULE += ws281x_vt100 endif + ifneq (,$(filter arch_esp32,$(FEATURES_USED))) + USEMODULE += ws281x_esp32 + endif endif ifneq (,$(filter ws281x_atmega,$(USEMODULE))) FEATURES_REQUIRED += arch_avr8 diff --git a/drivers/include/ws281x.h b/drivers/include/ws281x.h index 608ee82c89..497ef25ce2 100644 --- a/drivers/include/ws281x.h +++ b/drivers/include/ws281x.h @@ -15,7 +15,8 @@ * * The WS2812 or SK6812 RGB LEDs, or more commonly known as NeoPixels, can be * chained so that a single data pin of the MCU can control an arbitrary number - * of RGB LEDs. + * of RGB LEDs. This driver supports both the WS2812/SK6812 and the WS2812b + * LEDs. * * # Support * @@ -36,6 +37,15 @@ * @warning On 8MHz ATmegas, only pins at GPIO ports B, C, and D are supported. * (On 16MHz ATmegas, any pin is fine.) * + * ## ESP32 + * + * The ESP32 implementation is frequency independent, as frequencies above 80MHz + * are high enough to support big banging without assembly. + * + * ## Native/VT100 + * + * The native (VT100) implementation writes the LED state to the console. + * * ### Usage * * Add the following to your `Makefile` to use the ATmega backend: diff --git a/drivers/ws281x/esp32.c b/drivers/ws281x/esp32.c new file mode 100644 index 0000000000..bee43117ad --- /dev/null +++ b/drivers/ws281x/esp32.c @@ -0,0 +1,106 @@ +/* + * Copyright 2020 Christian Friedrich Coors + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_ws281x + * + * @{ + * + * @file + * @brief Implementation of `ws281x_write_buffer()` for the ESP32 CPU + * + * @author Christian Friedrich Coors + * + * @} + */ +#include +#include +#include +#include + +#include "ws281x.h" +#include "ws281x_params.h" +#include "ws281x_constants.h" +#include "periph_cpu.h" +#include "xtimer.h" +#include "xtensa/core-macros.h" +#include "soc/rtc.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + + +static inline __attribute__((always_inline)) uint32_t get_cycle_count(void) { + uint32_t ccount; + __asm__ __volatile__("rsr %0,ccount":"=a" (ccount)); + return ccount; +} + +void ws281x_write_buffer(ws281x_t *dev, const void *buf, size_t size) +{ + assert(dev); + const uint8_t *pos = buf; + const uint8_t *end = pos + size; + + // Cycles + uint32_t total_cycles, one_on, one_off, zero_on, zero_off, on_wait, off_wait; + + // Current frequency + rtc_cpu_freq_t freq = rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()); + + total_cycles = freq / (NS_PER_SEC / WS281X_T_DATA_NS); + one_on = freq / (NS_PER_SEC / WS281X_T_DATA_ONE_NS); + one_off = total_cycles - one_on; + zero_on = freq / (NS_PER_SEC / WS281X_T_DATA_ZERO_NS); + zero_off = total_cycles - zero_on; + + DEBUG("[ws281x] esp32 freq=%d total=%d\n", freq, total_cycles); + DEBUG("[ws281x] esp32 cycles %d/%d/%d/%d\n", one_on, one_off, zero_on, zero_off); + + uint32_t current_wait = 0, start = 0; + + while (pos < end) { + uint8_t data = *pos; + for (uint8_t cnt = 8; cnt > 0; cnt--) { + if (data & 0b10000000) { + on_wait = one_on; + off_wait = one_off; + } + else { + on_wait = zero_on; + off_wait = zero_off; + } + start = get_cycle_count(); + gpio_set(dev->params.pin); + current_wait = start + on_wait; + while (get_cycle_count() < current_wait) { } + gpio_clear(dev->params.pin); + start = get_cycle_count(); + current_wait = start + off_wait; + while (get_cycle_count() < current_wait) { } + data <<= 1; + } + pos++; + } +} + +int ws281x_init(ws281x_t *dev, const ws281x_params_t *params) +{ + if (!dev || !params || !params->buf) { + return -EINVAL; + } + + memset(dev, 0, sizeof(ws281x_t)); + dev->params = *params; + + if (gpio_init(dev->params.pin, GPIO_OUT)) { + return -EIO; + } + + return 0; +} diff --git a/drivers/ws281x/include/ws281x_backend.h b/drivers/ws281x/include/ws281x_backend.h index eb396ca743..888c279a90 100644 --- a/drivers/ws281x/include/ws281x_backend.h +++ b/drivers/ws281x/include/ws281x_backend.h @@ -32,6 +32,15 @@ extern "C" { #endif /** @} */ +/** + * @name Properties of the ESP32 backend. + * @{ + */ +#ifdef MODULE_WS281X_ESP32 +#define WS281X_HAVE_INIT (1) +#endif +/** @} */ + /** * @name Properties of the VT100 terminal backend. * @{ diff --git a/drivers/ws281x/include/ws281x_constants.h b/drivers/ws281x/include/ws281x_constants.h index b80243c268..740d76ca59 100644 --- a/drivers/ws281x/include/ws281x_constants.h +++ b/drivers/ws281x/include/ws281x_constants.h @@ -27,6 +27,23 @@ extern "C" { * @name Timing parameters for WS2812/SK6812 RGB LEDs * @{ */ + +/** + * @brief Data transmission time in nanoseconds + * + * For the SK6812, WS2812 and WS2812b this is 1.25 µs. This is the total time + * required to transmit one bit. + */ +#define WS281X_T_DATA_NS (1250U) + +/** + * @brief The high-times in nanoseconds. + * @{ + */ +#define WS281X_T_DATA_ONE_NS (650U) +#define WS281X_T_DATA_ZERO_NS (325U) +/**@}*/ + /** * @brief Time in microseconds to pull the data line low to signal end of data * diff --git a/tests/driver_ws281x/Makefile b/tests/driver_ws281x/Makefile index 43323538e9..af34a7959a 100644 --- a/tests/driver_ws281x/Makefile +++ b/tests/driver_ws281x/Makefile @@ -7,11 +7,10 @@ N ?= 8 USEMODULE += ws281x -# Currently the ws281x only supports AVR-based platforms and native -# (via VT100 terminals). +# Currently the ws281x only supports AVR-based platforms, the ESP32 +# and native (via VT100 terminals). # See https://doc.riot-os.org/group__drivers__ws281x.html FEATURES_BLACKLIST += arch_arm -FEATURES_BLACKLIST += arch_esp32 FEATURES_BLACKLIST += arch_esp8266 FEATURES_BLACKLIST += arch_mips32r2 FEATURES_BLACKLIST += arch_msp430