From 3f2ac16865b6968728e65b56b44cb5a656702f86 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Thu, 24 Jan 2019 18:09:54 +0100 Subject: [PATCH] A reasonably generic WS2812 driver Based on cpldcpu's Light_WS2812 library, packaged up as a Kaleidoscope library. Signed-off-by: Gergely Nagy --- doc/driver/led/WS2812.md | 69 ++++++++ src/kaleidoscope/driver/led/Color.h | 55 +++++++ src/kaleidoscope/driver/led/WS2812.h | 169 ++++++++++++++++++++ src/kaleidoscope/driver/led/ws2812/config.h | 90 +++++++++++ 4 files changed, 383 insertions(+) create mode 100644 doc/driver/led/WS2812.md create mode 100644 src/kaleidoscope/driver/led/Color.h create mode 100644 src/kaleidoscope/driver/led/WS2812.h create mode 100644 src/kaleidoscope/driver/led/ws2812/config.h diff --git a/doc/driver/led/WS2812.md b/doc/driver/led/WS2812.md new file mode 100644 index 00000000..e4b5f491 --- /dev/null +++ b/doc/driver/led/WS2812.md @@ -0,0 +1,69 @@ +# kaleidoscope::driver::led::WS2812 + +This driver provides a generic base class for driving WS2812-based LED strips. +This is not a plugin, and is not meant to be user-facing. It is meant to be used +by developers, and hardware plugins in particular. See the [KBDFans +KBD4x][kbd4x] plugin for a practical, existing example about how to use the +driver. + + [kbd4x]: ../../../src/kaleidoscope/hardware/kbdfans/KBD4x.h + +## Using the driver + +To use the driver, we need to include the header: + +```c++ +#include +``` + +For performance reasons, the driver is templated, and requires three template +arguments: + +1. `pin`, the PIN the driver will use to communicate with the LED strip. +2. `class Color`, the color class that determines the order of the RGB + components, and should match the component order the LED strip uses. We + provide three orders out of the box, all in the + `kaleidoscope::driver::led::color` namespace: `RGB`, `GRB`, and `BGR`. +3. `ledCount` is the number of LEDs on the strip this instance of the driver + should be able to address. + +Armed with this knowledge, instantiating an object is as easy as: + +```c++ +using Color = kaleidoscope::driver::led::color::GRB; + +kaleidoscope::driver::led::WS2812 LEDs; +``` + +## Driver methods + +The instantiated `WS2812` object will have the following methods: + +### `.led_count()` + +> Returns the number of LEDs, the same value as the `ledCount` template +> argument. + +### `.sync()` + +> Synchronises the internal LED state with the hardware, by sending over all of +> the LED data. +> +> It is recommended to call this at most once per cycle. Calling it less +> frequently isn't wrong either. + +### `.setColorAt(index, color)` +### `.setColorAt(index, r, g, b)` + +> Sets the color at the given `index` to the specified value. He value can +> either be a `Color` object (the same type as the template argument), or a list +> of RGB component values. + +### `.getColorAt(index)` + +> Returns the color at the given `index`, as a `Color` object. + +## Further information + +To have a better idea how to use the driver in practice, looking at the +[KBD4x][kbd4x] hardware library is recommended. diff --git a/src/kaleidoscope/driver/led/Color.h b/src/kaleidoscope/driver/led/Color.h new file mode 100644 index 00000000..62da7728 --- /dev/null +++ b/src/kaleidoscope/driver/led/Color.h @@ -0,0 +1,55 @@ +/* -*- mode: c++ -*- + * kaleidoscope::driver::led::color -- LED Color classes for Kaleidoscope + * Copyright (C) 2019 Keyboard.io, Inc + * + * 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, version 3. + * + * 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 . + */ + +#pragma once + +namespace kaleidoscope { +namespace driver { +namespace led { +namespace color { + +struct RGB { + RGB(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + RGB() {} + + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct GRB { + GRB(uint8_t r_, uint8_t g_, uint8_t b_) : g(g_), r(r_), b(b_) {} + GRB() {} + + uint8_t g; + uint8_t r; + uint8_t b; +}; + +struct BGR { + BGR(uint8_t r_, uint8_t g_, uint8_t b_) : b(b_), g(g_), r(r_) {} + BGR() {} + + uint8_t b; + uint8_t g; + uint8_t r; +}; + +} +} +} +} diff --git a/src/kaleidoscope/driver/led/WS2812.h b/src/kaleidoscope/driver/led/WS2812.h new file mode 100644 index 00000000..a79daa85 --- /dev/null +++ b/src/kaleidoscope/driver/led/WS2812.h @@ -0,0 +1,169 @@ +/* + * Based on Light_WS2812, from: + * https://github.com/cpldcpu/light_ws2812 + * + * Original copyright: + * + * light weight WS2812 lib V2.1 - Arduino support + * + * Controls WS2811/WS2812/WS2812B RGB-LEDs + * Author: Matthias Riegler + * + * Mar 07 2014: Added Arduino and C++ Library + * + * September 6, 2014: Added option to switch between most popular color orders + * (RGB, GRB, and BRG) -- Windell H. Oskay + * + * License: + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "kaleidoscope/hardware/avr/pins_and_ports.h" +#include "kaleidoscope/driver/led/Color.h" +#include "ws2812/config.h" + +namespace kaleidoscope { +namespace driver { +namespace led { + +template +class WS2812 { + public: + WS2812() : pinmask_(_BV(pin & 0xF)) {} + + int8_t led_count() { + return ledCount; + } + + void sync() { + if (!modified_) + return; + + DDR_OUTPUT(pin); + + sendArrayWithMask(pinmask_); + _delay_us(50); + modified_ = false; + } + + void setColorAt(int8_t index, Color color) { + if (index >= ledCount) + return; + modified_ = true; + leds_[index] = color; + } + void setColorAt(int8_t index, uint8_t r, uint8_t g, uint8_t b) { + if (index >= ledCount) + return; + modified_ = true; + leds_[index] = Color(r, g, b); + } + Color getColorAt(int8_t index) { + if (index >= ledCount) + return Color(0, 0, 0); + return leds_[index]; + } + + private: + Color leds_[ledCount]; + uint8_t pinmask_; + bool modified_ = false; + + void sendArrayWithMask(uint8_t maskhi) { + uint8_t *data = (uint8_t *)leds_; + uint16_t datalen = ledCount * sizeof(Color); + uint8_t curbyte, ctr, masklo; + uint8_t sreg_prev; + + masklo = ~ maskhi & PORT_REG_FOR_PIN(pin); + maskhi |= PORT_REG_FOR_PIN(pin); + + sreg_prev = SREG; + cli(); + + while (datalen--) { + 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(PORT_REG_FOR_PIN(pin))), "r"(maskhi), "r"(masklo) + ); + } + + SREG = sreg_prev; + } + +}; +} +} +} diff --git a/src/kaleidoscope/driver/led/ws2812/config.h b/src/kaleidoscope/driver/led/ws2812/config.h new file mode 100644 index 00000000..0dbd8fec --- /dev/null +++ b/src/kaleidoscope/driver/led/ws2812/config.h @@ -0,0 +1,90 @@ +/* + * Based on Light_WS2812, from: + * https://github.com/cpldcpu/light_ws2812 + * + * Original copyright: + * + * light weight WS2812 lib V2.1 - Arduino support + * + * Controls WS2811/WS2812/WS2812B RGB-LEDs + * Author: Matthias Riegler + * + * Mar 07 2014: Added Arduino and C++ Library + * + * September 6, 2014: Added option to switch between most popular color orders + * (RGB, GRB, and BRG) -- Windell H. Oskay + * + * License: + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +// Timing in ns +#define w_zeropulse 350 +#define w_onepulse 900 +#define w_totalperiod 1250 + +// 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)*w_zeropulse )/1000000) +#define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000) +#define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000) + +// w1 - nops between rising edge and falling edge - low +#define w1 (w_zerocycles-w_fixedlow) +// w2 nops between fe low and fe high +#define w2 (w_onecycles-w_fixedhigh-w1) +// w3 nops to complete loop +#define w3 (w_totalcycles-w_fixedtotal-w1-w2) + +#if w1>0 +#define w1_nops w1 +#else +#define w1_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 + +#if w2>0 +#define w2_nops w2 +#else +#define w2_nops 0 +#endif + +#if w3>0 +#define w3_nops w3 +#else +#define w3_nops 0 +#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