From 647c2835e65995339fb9380830c53725a62f6299 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 30 Mar 2023 12:21:02 +1100 Subject: WS2812 driver improvements (#20262) --- platforms/avr/drivers/ws2812.c | 172 ----------------------------- platforms/avr/drivers/ws2812_bitbang.c | 172 +++++++++++++++++++++++++++++ platforms/avr/drivers/ws2812_i2c.c | 10 +- platforms/chibios/drivers/ws2812.c | 109 ------------------ platforms/chibios/drivers/ws2812_bitbang.c | 109 ++++++++++++++++++ 5 files changed, 286 insertions(+), 286 deletions(-) delete mode 100644 platforms/avr/drivers/ws2812.c create mode 100644 platforms/avr/drivers/ws2812_bitbang.c delete mode 100644 platforms/chibios/drivers/ws2812.c create mode 100644 platforms/chibios/drivers/ws2812_bitbang.c (limited to 'platforms') diff --git a/platforms/avr/drivers/ws2812.c b/platforms/avr/drivers/ws2812.c deleted file mode 100644 index 5c0cb3b718..0000000000 --- a/platforms/avr/drivers/ws2812.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * light weight WS2812 lib V2.0b - * - * Controls WS2811/WS2812/WS2812B RGB-LEDs - * Author: Tim (cpldcpu@gmail.com) - * - * Jan 18th, 2014 v2.0b Initial Version - * Nov 29th, 2015 v2.3 Added SK6812RGBW support - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include -#include -#include "ws2812.h" -#include "pin_defs.h" - -#define pinmask(pin) (_BV((pin)&0xF)) - -/* - * Forward declare internal functions - * - * The functions take a byte-array and send to the data output as WS2812 bitstream. - * The length is the number of bytes to send - three per LED. - */ - -static inline void ws2812_sendarray_mask(uint8_t *data, uint16_t datlen, uint8_t masklo, uint8_t maskhi); - -void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds) { - DDRx_ADDRESS(RGB_DI_PIN) |= pinmask(RGB_DI_PIN); - - uint8_t masklo = ~(pinmask(RGB_DI_PIN)) & PORTx_ADDRESS(RGB_DI_PIN); - uint8_t maskhi = pinmask(RGB_DI_PIN) | PORTx_ADDRESS(RGB_DI_PIN); - - ws2812_sendarray_mask((uint8_t *)ledarray, number_of_leds * sizeof(LED_TYPE), masklo, maskhi); - - _delay_us(WS2812_TRST_US); -} - -/* - This routine writes an array of bytes with RGB values to the Dataout pin - using the fast 800kHz clockless WS2811/2812 protocol. -*/ - -// Fixed cycles used by the inner loop -#define w_fixedlow 2 -#define w_fixedhigh 4 -#define w_fixedtotal 8 - -// Insert NOPs to match the timing, if possible -#define w_zerocycles (((F_CPU / 1000) * WS2812_T0H) / 1000000) -#define w_onecycles (((F_CPU / 1000) * WS2812_T1H + 500000) / 1000000) -#define w_totalcycles (((F_CPU / 1000) * WS2812_TIMING + 500000) / 1000000) - -// w1_nops - nops between rising edge and falling edge - low -#if w_zerocycles >= w_fixedlow -# define w1_nops (w_zerocycles - w_fixedlow) -#else -# define w1_nops 0 -#endif - -// w2_nops - nops between fe low and fe high -#if w_onecycles >= (w_fixedhigh + w1_nops) -# define w2_nops (w_onecycles - w_fixedhigh - w1_nops) -#else -# define w2_nops 0 -#endif - -// w3_nops - nops to complete loop -#if w_totalcycles >= (w_fixedtotal + w1_nops + w2_nops) -# define w3_nops (w_totalcycles - w_fixedtotal - w1_nops - w2_nops) -#else -# define w3_nops 0 -#endif - -// The only critical timing parameter is the minimum pulse length of the "0" -// Warn or throw error if this timing can not be met with current F_CPU settings. -#define w_lowtime ((w1_nops + w_fixedlow) * 1000000) / (F_CPU / 1000) -#if w_lowtime > 550 -# error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" -#elif w_lowtime > 450 -# warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." -# warning "Please consider a higher clockspeed, if possible" -#endif - -#define w_nop1 "nop \n\t" -#define w_nop2 "rjmp .+0 \n\t" -#define w_nop4 w_nop2 w_nop2 -#define w_nop8 w_nop4 w_nop4 -#define w_nop16 w_nop8 w_nop8 - -static inline void ws2812_sendarray_mask(uint8_t *data, uint16_t datlen, uint8_t masklo, uint8_t maskhi) { - uint8_t curbyte, ctr, sreg_prev; - - sreg_prev = SREG; - cli(); - - while (datlen--) { - curbyte = (*data++); - - asm volatile(" ldi %0,8 \n\t" - "loop%=: \n\t" - " out %2,%3 \n\t" // '1' [01] '0' [01] - re -#if (w1_nops & 1) - w_nop1 -#endif -#if (w1_nops & 2) - w_nop2 -#endif -#if (w1_nops & 4) - w_nop4 -#endif -#if (w1_nops & 8) - w_nop8 -#endif -#if (w1_nops & 16) - w_nop16 -#endif - " sbrs %1,7 \n\t" // '1' [03] '0' [02] - " out %2,%4 \n\t" // '1' [--] '0' [03] - fe-low - " lsl %1 \n\t" // '1' [04] '0' [04] -#if (w2_nops & 1) - w_nop1 -#endif -#if (w2_nops & 2) - w_nop2 -#endif -#if (w2_nops & 4) - w_nop4 -#endif -#if (w2_nops & 8) - w_nop8 -#endif -#if (w2_nops & 16) - w_nop16 -#endif - " out %2,%4 \n\t" // '1' [+1] '0' [+1] - fe-high -#if (w3_nops & 1) - w_nop1 -#endif -#if (w3_nops & 2) - w_nop2 -#endif -#if (w3_nops & 4) - w_nop4 -#endif -#if (w3_nops & 8) - w_nop8 -#endif -#if (w3_nops & 16) - w_nop16 -#endif - - " dec %0 \n\t" // '1' [+2] '0' [+2] - " brne loop%=\n\t" // '1' [+3] '0' [+4] - : "=&d"(ctr) - : "r"(curbyte), "I"(_SFR_IO_ADDR(PORTx_ADDRESS(RGB_DI_PIN))), "r"(maskhi), "r"(masklo)); - } - - SREG = sreg_prev; -} diff --git a/platforms/avr/drivers/ws2812_bitbang.c b/platforms/avr/drivers/ws2812_bitbang.c new file mode 100644 index 0000000000..5c0cb3b718 --- /dev/null +++ b/platforms/avr/drivers/ws2812_bitbang.c @@ -0,0 +1,172 @@ +/* + * light weight WS2812 lib V2.0b + * + * Controls WS2811/WS2812/WS2812B RGB-LEDs + * Author: Tim (cpldcpu@gmail.com) + * + * Jan 18th, 2014 v2.0b Initial Version + * Nov 29th, 2015 v2.3 Added SK6812RGBW support + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include "ws2812.h" +#include "pin_defs.h" + +#define pinmask(pin) (_BV((pin)&0xF)) + +/* + * Forward declare internal functions + * + * The functions take a byte-array and send to the data output as WS2812 bitstream. + * The length is the number of bytes to send - three per LED. + */ + +static inline void ws2812_sendarray_mask(uint8_t *data, uint16_t datlen, uint8_t masklo, uint8_t maskhi); + +void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds) { + DDRx_ADDRESS(RGB_DI_PIN) |= pinmask(RGB_DI_PIN); + + uint8_t masklo = ~(pinmask(RGB_DI_PIN)) & PORTx_ADDRESS(RGB_DI_PIN); + uint8_t maskhi = pinmask(RGB_DI_PIN) | PORTx_ADDRESS(RGB_DI_PIN); + + ws2812_sendarray_mask((uint8_t *)ledarray, number_of_leds * sizeof(LED_TYPE), masklo, maskhi); + + _delay_us(WS2812_TRST_US); +} + +/* + This routine writes an array of bytes with RGB values to the Dataout pin + using the fast 800kHz clockless WS2811/2812 protocol. +*/ + +// Fixed cycles used by the inner loop +#define w_fixedlow 2 +#define w_fixedhigh 4 +#define w_fixedtotal 8 + +// Insert NOPs to match the timing, if possible +#define w_zerocycles (((F_CPU / 1000) * WS2812_T0H) / 1000000) +#define w_onecycles (((F_CPU / 1000) * WS2812_T1H + 500000) / 1000000) +#define w_totalcycles (((F_CPU / 1000) * WS2812_TIMING + 500000) / 1000000) + +// w1_nops - nops between rising edge and falling edge - low +#if w_zerocycles >= w_fixedlow +# define w1_nops (w_zerocycles - w_fixedlow) +#else +# define w1_nops 0 +#endif + +// w2_nops - nops between fe low and fe high +#if w_onecycles >= (w_fixedhigh + w1_nops) +# define w2_nops (w_onecycles - w_fixedhigh - w1_nops) +#else +# define w2_nops 0 +#endif + +// w3_nops - nops to complete loop +#if w_totalcycles >= (w_fixedtotal + w1_nops + w2_nops) +# define w3_nops (w_totalcycles - w_fixedtotal - w1_nops - w2_nops) +#else +# define w3_nops 0 +#endif + +// The only critical timing parameter is the minimum pulse length of the "0" +// Warn or throw error if this timing can not be met with current F_CPU settings. +#define w_lowtime ((w1_nops + w_fixedlow) * 1000000) / (F_CPU / 1000) +#if w_lowtime > 550 +# error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" +#elif w_lowtime > 450 +# warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." +# warning "Please consider a higher clockspeed, if possible" +#endif + +#define w_nop1 "nop \n\t" +#define w_nop2 "rjmp .+0 \n\t" +#define w_nop4 w_nop2 w_nop2 +#define w_nop8 w_nop4 w_nop4 +#define w_nop16 w_nop8 w_nop8 + +static inline void ws2812_sendarray_mask(uint8_t *data, uint16_t datlen, uint8_t masklo, uint8_t maskhi) { + uint8_t curbyte, ctr, sreg_prev; + + sreg_prev = SREG; + cli(); + + while (datlen--) { + curbyte = (*data++); + + asm volatile(" ldi %0,8 \n\t" + "loop%=: \n\t" + " out %2,%3 \n\t" // '1' [01] '0' [01] - re +#if (w1_nops & 1) + w_nop1 +#endif +#if (w1_nops & 2) + w_nop2 +#endif +#if (w1_nops & 4) + w_nop4 +#endif +#if (w1_nops & 8) + w_nop8 +#endif +#if (w1_nops & 16) + w_nop16 +#endif + " sbrs %1,7 \n\t" // '1' [03] '0' [02] + " out %2,%4 \n\t" // '1' [--] '0' [03] - fe-low + " lsl %1 \n\t" // '1' [04] '0' [04] +#if (w2_nops & 1) + w_nop1 +#endif +#if (w2_nops & 2) + w_nop2 +#endif +#if (w2_nops & 4) + w_nop4 +#endif +#if (w2_nops & 8) + w_nop8 +#endif +#if (w2_nops & 16) + w_nop16 +#endif + " out %2,%4 \n\t" // '1' [+1] '0' [+1] - fe-high +#if (w3_nops & 1) + w_nop1 +#endif +#if (w3_nops & 2) + w_nop2 +#endif +#if (w3_nops & 4) + w_nop4 +#endif +#if (w3_nops & 8) + w_nop8 +#endif +#if (w3_nops & 16) + w_nop16 +#endif + + " dec %0 \n\t" // '1' [+2] '0' [+2] + " brne loop%=\n\t" // '1' [+3] '0' [+4] + : "=&d"(ctr) + : "r"(curbyte), "I"(_SFR_IO_ADDR(PORTx_ADDRESS(RGB_DI_PIN))), "r"(maskhi), "r"(masklo)); + } + + SREG = sreg_prev; +} diff --git a/platforms/avr/drivers/ws2812_i2c.c b/platforms/avr/drivers/ws2812_i2c.c index 709f382254..f4a2fbe0b3 100644 --- a/platforms/avr/drivers/ws2812_i2c.c +++ b/platforms/avr/drivers/ws2812_i2c.c @@ -5,12 +5,12 @@ # error "RGBW not supported" #endif -#ifndef WS2812_ADDRESS -# define WS2812_ADDRESS 0xb0 +#ifndef WS2812_I2C_ADDRESS +# define WS2812_I2C_ADDRESS 0xB0 #endif -#ifndef WS2812_TIMEOUT -# define WS2812_TIMEOUT 100 +#ifndef WS2812_I2C_TIMEOUT +# define WS2812_I2C_TIMEOUT 100 #endif void ws2812_init(void) { @@ -25,5 +25,5 @@ void ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) { s_init = true; } - i2c_transmit(WS2812_ADDRESS, (uint8_t *)ledarray, sizeof(LED_TYPE) * leds, WS2812_TIMEOUT); + i2c_transmit(WS2812_I2C_ADDRESS, (uint8_t *)ledarray, sizeof(LED_TYPE) * leds, WS2812_I2C_TIMEOUT); } diff --git a/platforms/chibios/drivers/ws2812.c b/platforms/chibios/drivers/ws2812.c deleted file mode 100644 index 55ac333b1e..0000000000 --- a/platforms/chibios/drivers/ws2812.c +++ /dev/null @@ -1,109 +0,0 @@ -#include "quantum.h" -#include "ws2812.h" -#include -#include - -/* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */ - -#ifndef NOP_FUDGE -# if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) -# define NOP_FUDGE 0.4 -# else -# error("NOP_FUDGE configuration required") -# define NOP_FUDGE 1 // this just pleases the compile so the above error is easier to spot -# endif -#endif - -// Push Pull or Open Drain Configuration -// Default Push Pull -#ifndef WS2812_EXTERNAL_PULLUP -# define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_PUSHPULL -#else -# define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_OPENDRAIN -#endif - -// The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased -// to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. -#ifndef WS2812_RES -# define WS2812_RES (1000 * WS2812_TRST_US) // Width of the low gap between bits to cause a frame to latch -#endif - -#define NUMBER_NOPS 6 -#define CYCLES_PER_SEC (CPU_CLOCK / NUMBER_NOPS * NOP_FUDGE) -#define NS_PER_SEC (1000000000L) // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives -#define NS_PER_CYCLE (NS_PER_SEC / CYCLES_PER_SEC) -#define NS_TO_CYCLES(n) ((n) / NS_PER_CYCLE) - -#define wait_ns(x) \ - do { \ - for (int i = 0; i < NS_TO_CYCLES(x); i++) { \ - __asm__ volatile("nop\n\t" \ - "nop\n\t" \ - "nop\n\t" \ - "nop\n\t" \ - "nop\n\t" \ - "nop\n\t"); \ - } \ - } while (0) - -void sendByte(uint8_t byte) { - // WS2812 protocol wants most significant bits first - for (unsigned char bit = 0; bit < 8; bit++) { - bool is_one = byte & (1 << (7 - bit)); - // using something like wait_ns(is_one ? T1L : T0L) here throws off timings - if (is_one) { - // 1 - writePinHigh(RGB_DI_PIN); - wait_ns(WS2812_T1H); - writePinLow(RGB_DI_PIN); - wait_ns(WS2812_T1L); - } else { - // 0 - writePinHigh(RGB_DI_PIN); - wait_ns(WS2812_T0H); - writePinLow(RGB_DI_PIN); - wait_ns(WS2812_T0L); - } - } -} - -void ws2812_init(void) { - palSetLineMode(RGB_DI_PIN, WS2812_OUTPUT_MODE); -} - -// Setleds for standard RGB -void ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) { - static bool s_init = false; - if (!s_init) { - ws2812_init(); - s_init = true; - } - - // this code is very time dependent, so we need to disable interrupts - chSysLock(); - - for (uint8_t i = 0; i < leds; i++) { - // WS2812 protocol dictates grb order -#if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB) - sendByte(ledarray[i].g); - sendByte(ledarray[i].r); - sendByte(ledarray[i].b); -#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB) - sendByte(ledarray[i].r); - sendByte(ledarray[i].g); - sendByte(ledarray[i].b); -#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR) - sendByte(ledarray[i].b); - sendByte(ledarray[i].g); - sendByte(ledarray[i].r); -#endif - -#ifdef RGBW - sendByte(ledarray[i].w); -#endif - } - - wait_ns(WS2812_RES); - - chSysUnlock(); -} diff --git a/platforms/chibios/drivers/ws2812_bitbang.c b/platforms/chibios/drivers/ws2812_bitbang.c new file mode 100644 index 0000000000..55ac333b1e --- /dev/null +++ b/platforms/chibios/drivers/ws2812_bitbang.c @@ -0,0 +1,109 @@ +#include "quantum.h" +#include "ws2812.h" +#include +#include + +/* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */ + +#ifndef NOP_FUDGE +# if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) +# define NOP_FUDGE 0.4 +# else +# error("NOP_FUDGE configuration required") +# define NOP_FUDGE 1 // this just pleases the compile so the above error is easier to spot +# endif +#endif + +// Push Pull or Open Drain Configuration +// Default Push Pull +#ifndef WS2812_EXTERNAL_PULLUP +# define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_PUSHPULL +#else +# define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_OPENDRAIN +#endif + +// The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased +// to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. +#ifndef WS2812_RES +# define WS2812_RES (1000 * WS2812_TRST_US) // Width of the low gap between bits to cause a frame to latch +#endif + +#define NUMBER_NOPS 6 +#define CYCLES_PER_SEC (CPU_CLOCK / NUMBER_NOPS * NOP_FUDGE) +#define NS_PER_SEC (1000000000L) // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives +#define NS_PER_CYCLE (NS_PER_SEC / CYCLES_PER_SEC) +#define NS_TO_CYCLES(n) ((n) / NS_PER_CYCLE) + +#define wait_ns(x) \ + do { \ + for (int i = 0; i < NS_TO_CYCLES(x); i++) { \ + __asm__ volatile("nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t"); \ + } \ + } while (0) + +void sendByte(uint8_t byte) { + // WS2812 protocol wants most significant bits first + for (unsigned char bit = 0; bit < 8; bit++) { + bool is_one = byte & (1 << (7 - bit)); + // using something like wait_ns(is_one ? T1L : T0L) here throws off timings + if (is_one) { + // 1 + writePinHigh(RGB_DI_PIN); + wait_ns(WS2812_T1H); + writePinLow(RGB_DI_PIN); + wait_ns(WS2812_T1L); + } else { + // 0 + writePinHigh(RGB_DI_PIN); + wait_ns(WS2812_T0H); + writePinLow(RGB_DI_PIN); + wait_ns(WS2812_T0L); + } + } +} + +void ws2812_init(void) { + palSetLineMode(RGB_DI_PIN, WS2812_OUTPUT_MODE); +} + +// Setleds for standard RGB +void ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) { + static bool s_init = false; + if (!s_init) { + ws2812_init(); + s_init = true; + } + + // this code is very time dependent, so we need to disable interrupts + chSysLock(); + + for (uint8_t i = 0; i < leds; i++) { + // WS2812 protocol dictates grb order +#if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB) + sendByte(ledarray[i].g); + sendByte(ledarray[i].r); + sendByte(ledarray[i].b); +#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB) + sendByte(ledarray[i].r); + sendByte(ledarray[i].g); + sendByte(ledarray[i].b); +#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR) + sendByte(ledarray[i].b); + sendByte(ledarray[i].g); + sendByte(ledarray[i].r); +#endif + +#ifdef RGBW + sendByte(ledarray[i].w); +#endif + } + + wait_ns(WS2812_RES); + + chSysUnlock(); +} -- cgit v1.2.3