diff --git a/drivers/include/ws281x.h b/drivers/include/ws281x.h index fe448ee19804..6e82c1e77357 100644 --- a/drivers/include/ws281x.h +++ b/drivers/include/ws281x.h @@ -42,6 +42,15 @@ * The ESP32 implementation is frequency independent, as frequencies above 80MHz * are high enough to support bit banging without assembly. * + * ## STM32 + * + * The STM32 implementation is frequency dependent and currently only supported + * for 84, 100 and 180 MHz MCUs of the STM32f4 family. Support for the STM32f7 + * family is coming soon... The number of NOPs required was determined using a + * saleae logic analyzer. + * + * @warning Since this is bit banged an interrupt can destroy the timing. + * * ## Native/VT100 * * The native (VT100) implementation writes the LED state to the console. @@ -70,6 +79,11 @@ * USEMODULE += ws281x_esp32 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * + * * the STM32 backend: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Makefile + * USEMODULE += ws281x_stm32 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * * * the native/VT100 backend: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Makefile * USEMODULE += ws281x_vt100 diff --git a/drivers/ws281x/Makefile.dep b/drivers/ws281x/Makefile.dep index f539c4de83d7..e76827c45b35 100644 --- a/drivers/ws281x/Makefile.dep +++ b/drivers/ws281x/Makefile.dep @@ -1,5 +1,5 @@ # Actually |(periph_timer_poll and periph_gpio_ll), but that's too complex for FEATURES_REQUIRED_ANY to express -FEATURES_REQUIRED_ANY += cpu_core_atmega|arch_esp32|arch_native|periph_timer_poll +FEATURES_REQUIRED_ANY += cpu_core_atmega|arch_esp32|arch_native|periph_timer_poll|cpu_stm32f4 ifeq (,$(filter ws281x_%,$(USEMODULE))) ifneq (,$(filter cpu_core_atmega,$(FEATURES_USED))) @@ -11,6 +11,9 @@ ifeq (,$(filter ws281x_%,$(USEMODULE))) ifneq (,$(filter arch_esp32,$(FEATURES_USED))) USEMODULE += ws281x_esp32 endif + ifneq (,$(filter cpu_stm32f4,$(FEATURES_USED))) + USEMODULE += ws281x_stm32 + endif # Not only looking for the used feature but also for the absence of any more specific driver ifeq (-periph_timer_poll,$(filter ws281x_%,$(USEMODULE))-$(filter periph_timer_poll,$(FEATURES_USED))) USEMODULE += ws281x_timer_gpio_ll diff --git a/drivers/ws281x/include/ws281x_backend.h b/drivers/ws281x/include/ws281x_backend.h index 01f74fb87789..981475bfbb54 100644 --- a/drivers/ws281x/include/ws281x_backend.h +++ b/drivers/ws281x/include/ws281x_backend.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2019 Marian Buschsieweke + * 2024 Lennart Lutz * * 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 @@ -14,6 +15,7 @@ * @brief Backend configuration for WS2812/SK6812 RGB LEDs * * @author Marian Buschsieweke + * @author Lennart Lutz */ #ifndef WS281X_BACKEND_H @@ -41,6 +43,15 @@ extern "C" { #endif /** @} */ +/** + * @name Properties of the STM32 backend. + * @{ + */ +#ifdef MODULE_WS281X_STM32 +#define WS281X_HAVE_INIT (1) +#endif +/** @} */ + /** * @name Properties of the VT100 terminal backend. * @{ diff --git a/drivers/ws281x/stm32.c b/drivers/ws281x/stm32.c new file mode 100644 index 000000000000..d321e2bd8ca9 --- /dev/null +++ b/drivers/ws281x/stm32.c @@ -0,0 +1,1021 @@ +/* + * Copyright 2024 Lennart Lutz + * + * 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 STM32 MCUs + * + * @note Currently only implemented for 84, 100 and 180 MHz MCUs of the + * STM32F4 family. + * + * @author Lennart Lutz + * + * @} + */ + +#include +#include +#include +#include + +#include "ws281x.h" +#include "ws281x_params.h" +#include "ws281x_constants.h" +#include "periph_cpu.h" +#include "periph/gpio.h" + +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; + + /** + * Get the address of the GPIO pin. + */ + uint32_t gpio_adr = (dev->params.pin & 0xFFFFFF00) + 0x14; + + /** + * The pin number is made up of the first eight LSB bits of the pin address. + */ + uint8_t pin_num = (uint8_t) dev->params.pin & 0xFF; + +#if defined(CPU_LINE_STM32F401xC) || defined(CPU_LINE_STM32F401xE) + + while (pos < end) + { + uint8_t cnt = 8; + uint8_t data = *pos; + while (cnt > 0) { + if (data & 0b10000000) + { + // One + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + else + { + // Zero + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + cnt--; + data = data << 1; + } + pos++; + } + +#elif defined(CPU_LINE_STM32F410Cx) || defined(CPU_LINE_STM32F410Rx) || \ + defined(CPU_LINE_STM32F410Tx) || defined(CPU_LINE_STM32F411xE) || \ + defined(CPU_LINE_STM32F412Cx) || defined(CPU_LINE_STM32F412Rx) || \ + defined(CPU_LINE_STM32F412Vx) || defined(CPU_LINE_STM32F412Zx) || \ + defined(CPU_LINE_STM32F413xx) || defined(CPU_LINE_STM32F423xx) + + while (pos < end) + { + uint8_t cnt = 8; + uint8_t data = *pos; + while (cnt > 0) { + if (data & 0b10000000) + { + // One + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + else + { + // Zero + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + cnt--; + data = data << 1; + } + pos++; + } + +#elif defined(CPU_LINE_STM32F427xx) || defined(CPU_LINE_STM32F437xx) || \ + defined(CPU_LINE_STM32F429xx) || defined(CPU_LINE_STM32F439xx) || \ + defined(CPU_LINE_STM32F446xx) || defined(CPU_LINE_STM32F469xx) || \ + defined(CPU_LINE_STM32F479xx) + + while (pos < end) + { + uint8_t cnt = 8; + uint8_t data = *pos; + while (cnt > 0) { + if (data & 0b10000000) + { + // One + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + else + { + // Zero + __asm__ volatile ( + "LDR R1, [%[gpio_adr]] \n" // Load the current value + "EOR R1, R1, %[pin_mask] \n" // Set the specified pin + "STR R1, [%[gpio_adr]] \n" // Store the updated value + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "EOR R1, R1, %[pin_mask] \n" + "STR R1, [%[gpio_adr]] \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + "NOP \n" + : + : [gpio_adr] "r" (gpio_adr), [pin_mask] "r" (1U << pin_num) + : "r1" + ); + } + cnt--; + data = data << 1; + } + pos++; + } + +#else + + (void) pos; + (void) end; + (void) gpio_adr; + (void) pin_num; + +#error "ws281x: No implementation for current cpu model." + +#endif + +} + +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; +}