Merge pull request #541 from keyboardio/driver/led/ws2812

A reasonably generic WS2812 driver
pull/553/head
Jesse Vincent 6 years ago committed by GitHub
commit 1cf25da48c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 <kaleidoscope/driver/led/WS2812.h>
```
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<PIN_E2, Color, 6> 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.

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
};
}
}
}
}

@ -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 <uint8_t pin, class Color, int8_t ledCount>
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;
}
};
}
}
}

@ -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
Loading…
Cancel
Save