Merge pull request #541 from keyboardio/driver/led/ws2812
A reasonably generic WS2812 driverpull/553/head
commit
1cf25da48c
@ -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…
Reference in new issue