0,0 → 1,311 |
#include "SPI.h" |
#include "LPD8806_kopterlight.h" |
|
// Arduino library to control LPD8806-based RGB LED Strips |
// (c) Adafruit industries |
// MIT license |
|
/*****************************************************************************/ |
// This library has been modified to fix the red and green issue with the stripes |
// used by "MikroKopter-Forum" Users, because the design of these stripes is |
// different and red and green are inverted !! |
// Additionally this library has been modified and extended to provide light |
// sequence requirements on multikopter. |
// Magomora |
/*****************************************************************************/ |
|
// Constructor for use with hardware SPI (specific clock/data pins): |
LPD8806::LPD8806(uint16_t n, uint8_t rig) { |
pixels = NULL; |
begun = false; |
updateLength(n); |
updatePins(); |
rigger = rig; |
} |
|
// Constructor for use with arbitrary clock/data pins: |
LPD8806::LPD8806(uint16_t n, uint8_t dpin, uint8_t cpin, uint8_t rig) { |
pixels = NULL; |
begun = false; |
updateLength(n); |
updatePins(dpin, cpin); |
rigger = rig; |
} |
|
// via Michael Vogt/neophob: empty constructor is used when strip length |
// isn't known at compile-time; situations where program config might be |
// read from internal flash memory or an SD card, or arrive via serial |
// command. If using this constructor, MUST follow up with updateLength() |
// and updatePins() to establish the strip length and output pins! |
LPD8806::LPD8806(void) { |
numLEDs = 0; |
pixels = NULL; |
begun = false; |
updatePins(); // Must assume hardware SPI until pins are set |
} |
|
// Activate hard/soft SPI as appropriate: |
void LPD8806::begin(void) { |
if(hardwareSPI == true) startSPI(); |
else startBitbang(); |
begun = true; |
} |
|
// Change pin assignments post-constructor, switching to hardware SPI: |
void LPD8806::updatePins(void) { |
hardwareSPI = true; |
datapin = clkpin = 0; |
// If begin() was previously invoked, init the SPI hardware now: |
if(begun == true) startSPI(); |
// Otherwise, SPI is NOT initted until begin() is explicitly called. |
|
// Note: any prior clock/data pin directions are left as-is and are |
// NOT restored as inputs! |
} |
|
// Change pin assignments post-constructor, using arbitrary pins: |
void LPD8806::updatePins(uint8_t dpin, uint8_t cpin) { |
|
datapin = dpin; |
clkpin = cpin; |
clkport = portOutputRegister(digitalPinToPort(cpin)); |
clkpinmask = digitalPinToBitMask(cpin); |
dataport = portOutputRegister(digitalPinToPort(dpin)); |
datapinmask = digitalPinToBitMask(dpin); |
|
if(begun == true) { // If begin() was previously invoked... |
// If previously using hardware SPI, turn that off: |
if(hardwareSPI == true) SPI.end(); |
startBitbang(); // Regardless, now enable 'soft' SPI outputs |
} // Otherwise, pins are not set to outputs until begin() is called. |
|
// Note: any prior clock/data pin directions are left as-is and are |
// NOT restored as inputs! |
|
hardwareSPI = false; |
} |
|
// Enable SPI hardware and set up protocol details: |
void LPD8806::startSPI(void) { |
SPI.begin(); |
SPI.setBitOrder(MSBFIRST); |
SPI.setDataMode(SPI_MODE0); |
// SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz |
SPI.setClockDivider(SPI_CLOCK_DIV2); // 8 MHz |
// SPI bus is run at 2MHz. Although the LPD8806 should, in theory, |
// work up to 20MHz, the unshielded wiring from the Arduino is more |
// susceptible to interference. Experiment and see what you get. |
|
SPDR = 0; // 'Prime' the SPI bus with initial latch (no wait) |
} |
|
// Enable software SPI pins and issue initial latch: |
void LPD8806::startBitbang() { |
pinMode(datapin, OUTPUT); |
pinMode(clkpin , OUTPUT); |
*dataport &= ~datapinmask; // Data is held low throughout (latch = 0) |
for(uint8_t i = 8; i>0; i--) { |
*clkport |= clkpinmask; |
*clkport &= ~clkpinmask; |
} |
} |
|
// Change strip length (see notes with empty constructor, above): |
void LPD8806::updateLength(uint16_t n) { |
if(pixels != NULL) free(pixels); // Free existing data (if any) |
numLEDs = n; |
n *= 3; // 3 bytes per pixel |
if(NULL != (pixels = (uint8_t *)malloc(n + 1))) { // Alloc new data |
memset(pixels, 0x80, n); // Init to RGB 'off' state |
pixels[n] = 0; // Last byte is always zero for latch |
} else numLEDs = 0; // else malloc failed |
// 'begun' state does not change -- pins retain prior modes |
} |
|
uint16_t LPD8806::numPixels(void) { |
return numLEDs; |
} |
|
// This is how data is pushed to the strip. Unfortunately, the company |
// that makes the chip didnt release the protocol document or you need |
// to sign an NDA or something stupid like that, but we reverse engineered |
// this from a strip controller and it seems to work very nicely! |
void LPD8806::show(void) { |
uint16_t i, n3 = numLEDs * 3 + 1; // 3 bytes per LED + 2 for latch |
|
// write 24 bits per pixel |
if (hardwareSPI) { |
for (i=0; i<n3; i++ ) { |
while(!(SPSR & (1<<SPIF))); // Wait for prior byte out |
SPDR = pixels[i]; // Issue new byte |
} |
while(!(SPSR & (1<<SPIF))); // Wait for prior byte out |
SPDR = 0; // Issue new byte |
} else { |
for (i=0; i<n3; i++ ) { |
for (uint8_t bit=0x80; bit; bit >>= 1) { |
if(pixels[i] & bit) *dataport |= datapinmask; |
else *dataport &= ~datapinmask; |
*clkport |= clkpinmask; |
*clkport &= ~clkpinmask; |
} |
} |
for (uint8_t bit=0x80; bit; bit >>= 1) { |
if(pixels[i] & bit) *dataport |= datapinmask; |
else *dataport &= ~datapinmask; |
*clkport |= clkpinmask; |
*clkport &= ~clkpinmask; |
} |
|
} |
} |
|
// Convert separate R,G,B into combined 32-bit GRB color: |
uint32_t LPD8806::Color(byte g, byte r, byte b) { |
return 0x808080 | ((uint32_t)g << 16) | ((uint32_t)r << 8) | (uint32_t)b; |
} |
|
// Set pixel color from separate 7-bit R, G, B components: |
// All rigger in parallel |
void LPD8806::setPixelColor(uint16_t n, uint8_t g, uint8_t r, uint8_t b) { |
uint8_t i; |
for (i=1; i<rigger; i++){ |
if(n < numLEDs) { // Arrays are 0-indexed, thus NOT '<=' |
uint8_t *p = &pixels[n * 3]; |
*p++ = g | 0x80; // LPD8806 color order is GRB, |
*p++ = r | 0x80; // not the more common RGB, |
*p++ = b | 0x80; // so the order here is intentional; don't "fix" |
} |
n = n+(numLEDs/rigger); |
} |
} |
|
// Set pixel color from separate 7-bit R, G, B components: |
// Only on selected rigger |
void LPD8806::setPixelColor(uint16_t n, uint8_t g, uint8_t r, uint8_t b, uint8_t r1, uint8_t r2, uint8_t r3, uint8_t r4, uint8_t r5, uint8_t r6, uint8_t r7, uint8_t r8) { |
int i; |
int pix; |
uint16_t z; |
|
// Select rigger to set LEDs |
for (i=1; i<=rigger; i++){ |
if (i == 1 && r1 == 1){ |
z = ((numLEDs/rigger)*1)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 2 && r2 == 1){ |
z = ((numLEDs/rigger)*2)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 3 && r3 == 1){ |
z = ((numLEDs/rigger)*3)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 4 && r4 == 1){ |
z = ((numLEDs/rigger)*4)+(n-(numLEDs/rigger)); |
pix = 1; |
} |
if (i == 5 && r5 == 1){ |
z = ((numLEDs/rigger)*5)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 6 && r6 == 1){ |
z = ((numLEDs/rigger)*6)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 7 && r7 == 1){ |
z = ((numLEDs/rigger)*7)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 8 && r8 == 1){ |
z = ((numLEDs/rigger)*8)+n-(numLEDs/rigger); |
pix = 1; |
} |
if((pix == 1) && (z < numLEDs)) { // Arrays are 0-indexed, thus NOT '<=' |
uint8_t *p = &pixels[z * 3]; |
*p++ = g | 0x80; // LPD8806 color order is GRB, |
*p++ = r | 0x80; // not the more common RGB, |
*p++ = b | 0x80; // so the order here is intentional; don't "fix" |
} |
pix = 0; |
} |
} |
|
// Set pixel color from 'packed' 32-bit RGB value: |
// All rigger in parallel |
void LPD8806::setPixelColor(uint16_t n, uint32_t c) { |
uint8_t i; |
for (i=0; i<rigger; i++){ |
if(n < numLEDs) { // Arrays are 0-indexed, thus NOT '<=' |
uint8_t *p = &pixels[n * 3]; |
*p++ = (c >> 16) | 0x80; |
*p++ = (c >> 8) | 0x80; |
*p++ = c | 0x80; |
} |
n = n+(numLEDs/rigger); |
} |
} |
|
// Set pixel color from 'packed' 32-bit RGB value: |
// Only on selected rigger's |
void LPD8806::setPixelColor(uint16_t n, uint32_t c, uint8_t r1, uint8_t r2, uint8_t r3, uint8_t r4, uint8_t r5, uint8_t r6, uint8_t r7, uint8_t r8) { |
int i; |
int pix; |
uint16_t z; |
|
// Select rigger to set LEDs |
for (i=1; i<=rigger; i++){ |
if (i == 1 && r1 == 1){ |
z = ((numLEDs/rigger)*1)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 2 && r2 == 1){ |
z = ((numLEDs/rigger)*2)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 3 && r3 == 1){ |
z = ((numLEDs/rigger)*3)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 4 && r4 == 1){ |
z = ((numLEDs/rigger)*4)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 5 && r5 == 1){ |
z = ((numLEDs/rigger)*5)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 6 && r6 == 1){ |
z = ((numLEDs/rigger)*6)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 7 && r7 == 1){ |
z = ((numLEDs/rigger)*7)+n-(numLEDs/rigger); |
pix = 1; |
} |
if (i == 8 && r8 == 1){ |
z = ((numLEDs/rigger)*8)+n-(numLEDs/rigger); |
pix = 1; |
} |
if((pix == 1) && (z < numLEDs)) { // Arrays are 0-indexed, thus NOT '<=' |
uint8_t *p = &pixels[z * 3]; |
*p++ = (c >> 16) | 0x80; |
*p++ = (c >> 8) | 0x80; |
*p++ = c | 0x80; |
} |
pix = 0; |
} |
|
} |
|
// Query color from previously-set pixel (returns packed 32-bit GRB value) |
uint32_t LPD8806::getPixelColor(uint16_t n) { |
if(n < numLEDs) { |
uint16_t ofs = n * 3; |
return ((uint32_t)((uint32_t)pixels[ofs ] << 16) | |
(uint32_t)((uint32_t)pixels[ofs + 1] << 8) | |
(uint32_t)pixels[ofs + 2]) & 0x7f7f7f; |
} |
|
return 0; // Pixel # is out of bounds |
} |