summaryrefslogtreecommitdiff
path: root/lpd8806/src/LPD8806.cpp
diff options
context:
space:
mode:
authorYuval Adam <yuv.adm@gmail.com>2013-05-16 21:02:19 +0300
committerYuval Adam <yuv.adm@gmail.com>2013-05-16 21:02:19 +0300
commit2ff68176a3fa5b4390abcf2ae28276c4510f95f5 (patch)
treeaa778f7f16996fedc9aa73a90a2d8c5ca8e45b69 /lpd8806/src/LPD8806.cpp
parent8111ed7c07a31dd99a323a0a29f9e99dd54c6d80 (diff)
Funky LPD8806 stuff
Diffstat (limited to 'lpd8806/src/LPD8806.cpp')
-rw-r--r--lpd8806/src/LPD8806.cpp296
1 files changed, 296 insertions, 0 deletions
diff --git a/lpd8806/src/LPD8806.cpp b/lpd8806/src/LPD8806.cpp
new file mode 100644
index 0000000..b6632d9
--- /dev/null
+++ b/lpd8806/src/LPD8806.cpp
@@ -0,0 +1,296 @@
+/*
+Arduino library to control LPD8806-based RGB LED Strips
+Copyright (C) Adafruit Industries
+MIT license
+
+Clearing up some misconceptions about how the LPD8806 drivers work:
+
+The LPD8806 is not a FIFO shift register. The first data out controls the
+LED *closest* to the processor (unlike a typical shift register, where the
+first data out winds up at the *furthest* LED). Each LED driver 'fills up'
+with data and then passes through all subsequent bytes until a latch
+condition takes place. This is actually pretty common among LED drivers.
+
+All color data bytes have the high bit (128) set, with the remaining
+seven bits containing a brightness value (0-127). A byte with the high
+bit clear has special meaning (explained later).
+
+The rest gets bizarre...
+
+The LPD8806 does not perform an in-unison latch (which would display the
+newly-transmitted data all at once). Rather, each individual byte (even
+the separate G, R, B components of each LED) is latched AS IT ARRIVES...
+or more accurately, as the first bit of the subsequent byte arrives and
+is passed through. So the strip actually refreshes at the speed the data
+is issued, not instantaneously (this can be observed by greatly reducing
+the data rate). This has implications for POV displays and light painting
+applications. The 'subsequent' rule also means that at least one extra
+byte must follow the last pixel, in order for the final blue LED to latch.
+
+To reset the pass-through behavior and begin sending new data to the start
+of the strip, a number of zero bytes must be issued (remember, all color
+data bytes have the high bit set, thus are in the range 128 to 255, so the
+zero is 'special'). This should be done before each full payload of color
+values to the strip. Curiously, zero bytes can only travel one meter (32
+LEDs) down the line before needing backup; the next meter requires an
+extra zero byte, and so forth. Longer strips will require progressively
+more zeros. *(see note below)
+
+In the interest of efficiency, it's possible to combine the former EOD
+extra latch byte and the latter zero reset...the same data can do double
+duty, latching the last blue LED while also resetting the strip for the
+next payload.
+
+So: reset byte(s) of suitable length are issued once at startup to 'prime'
+the strip to a known ready state. After each subsequent LED color payload,
+these reset byte(s) are then issued at the END of each payload, both to
+latch the last LED and to prep the strip for the start of the next payload
+(even if that data does not arrive immediately). This avoids a tiny bit
+of latency as the new color payload can begin issuing immediately on some
+signal, such as a timer or GPIO trigger.
+
+Technically these zero byte(s) are not a latch, as the color data (save
+for the last byte) is already latched. It's a start-of-data marker, or
+an indicator to clear the thing-that's-not-a-shift-register. But for
+conversational consistency with other LED drivers, we'll refer to it as
+a 'latch' anyway.
+
+* This has been validated independently with multiple customers'
+ hardware. Please do not report as a bug or issue pull requests for
+ this. Fewer zeros sometimes gives the *illusion* of working, the first
+ payload will correctly load and latch, but subsequent frames will drop
+ data at the end. The data shortfall won't always be visually apparent
+ depending on the color data loaded on the prior and subsequent frames.
+ Tested. Confirmed. Fact.
+*/
+
+
+#include "SPI.h"
+#include "LPD8806.h"
+
+/*****************************************************************************/
+
+// Constructor for use with hardware SPI (specific clock/data pins):
+LPD8806::LPD8806(uint16_t n) {
+ pixels = NULL;
+ begun = false;
+ updateLength(n);
+ updatePins();
+}
+
+// Constructor for use with arbitrary clock/data pins:
+LPD8806::LPD8806(uint16_t n, uint8_t dpin, uint8_t cpin) {
+ pixels = NULL;
+ begun = false;
+ updateLength(n);
+ updatePins(dpin, cpin);
+}
+
+// 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 = numBytes = 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 = dataport = 0;
+ clkpinmask = datapinmask = 0;
+
+#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
+ clkport = portOutputRegister(digitalPinToPort(cpin));
+ clkpinmask = digitalPinToBitMask(cpin);
+ dataport = portOutputRegister(digitalPinToPort(dpin));
+ datapinmask = digitalPinToBitMask(dpin);
+#endif
+
+ 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;
+}
+
+#ifndef SPI_CLOCK_DIV8
+ #define SPI_CLOCK_DIV8 4
+#endif
+
+// 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 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.
+
+#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
+
+ // Issue initial latch/reset to strip:
+ SPDR = 0; // Issue initial byte
+ for(uint16_t i=((numLEDs+31)/32)-1; i>0; i--) {
+ while(!(SPSR & (1<<SPIF))); // Wait for prior byte out
+ SPDR = 0; // Issue next byte
+ }
+#else
+ SPI.transfer(0);
+ for(uint16_t i=((numLEDs+31)/32)-1; i>0; i--) {
+ SPI.transfer(0);
+ }
+#endif
+}
+
+// Enable software SPI pins and issue initial latch:
+void LPD8806::startBitbang() {
+ pinMode(datapin, OUTPUT);
+ pinMode(clkpin , OUTPUT);
+ if (dataport != 0) {
+ // use low level bitbanging when we can
+ *dataport &= ~datapinmask; // Data is held low throughout (latch = 0)
+ for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) {
+ *clkport |= clkpinmask;
+ *clkport &= ~clkpinmask;
+ }
+ } else {
+ // can't do low level bitbanging, revert to digitalWrite
+ digitalWrite(datapin, LOW);
+ for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) {
+ digitalWrite(clkpin, HIGH);
+ digitalWrite(clkpin, LOW);
+ }
+ }
+}
+
+// Change strip length (see notes with empty constructor, above):
+void LPD8806::updateLength(uint16_t n) {
+ uint8_t latchBytes = (n + 31) / 32;
+ if(pixels != NULL) free(pixels); // Free existing data (if any)
+ numLEDs = n;
+ n *= 3; // 3 bytes per pixel
+ numBytes = n + latchBytes;
+ if(NULL != (pixels = (uint8_t *)malloc(numBytes))) { // Alloc new data
+ memset( pixels , 0x80, n); // Init to RGB 'off' state
+ memset(&pixels[n], 0 , latchBytes); // Clear latch bytes
+ } else numLEDs = numBytes = 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) {
+ uint8_t *ptr = pixels;
+ uint16_t i = numBytes;
+
+ // This doesn't need to distinguish among individual pixel color
+ // bytes vs. latch data, etc. Everything is laid out in one big
+ // flat buffer and issued the same regardless of purpose.
+ if(hardwareSPI) {
+ while(i--) {
+#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
+ while(!(SPSR & (1<<SPIF))); // Wait for prior byte out
+ SPDR = *ptr++; // Issue new byte
+#else
+ SPI.transfer(*ptr++);
+#endif
+ }
+ } else {
+ uint8_t p, bit;
+
+ while(i--) {
+ p = *ptr++;
+ for(bit=0x80; bit; bit >>= 1) {
+ if (dataport != 0) {
+ if(p & bit) *dataport |= datapinmask;
+ else *dataport &= ~datapinmask;
+ *clkport |= clkpinmask;
+ *clkport &= ~clkpinmask;
+ } else {
+ if (p&bit) digitalWrite(datapin, HIGH);
+ else digitalWrite(datapin, LOW);
+ digitalWrite(clkpin, HIGH);
+ digitalWrite(clkpin, LOW);
+ }
+ }
+ }
+ }
+}
+
+// Convert separate R,G,B into combined 32-bit GRB color:
+uint32_t LPD8806::Color(byte r, byte g, byte b) {
+ return ((uint32_t)(g | 0x80) << 16) |
+ ((uint32_t)(r | 0x80) << 8) |
+ b | 0x80 ;
+}
+
+// Set pixel color from separate 7-bit R, G, B components:
+void LPD8806::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) {
+ if(n < numLEDs) { // Arrays are 0-indexed, thus NOT '<='
+ uint8_t *p = &pixels[n * 3];
+ *p++ = g | 0x80; // Strip color order is GRB,
+ *p++ = r | 0x80; // not the more common RGB,
+ *p++ = b | 0x80; // so the order here is intentional; don't "fix"
+ }
+}
+
+// Set pixel color from 'packed' 32-bit GRB (not RGB) value:
+void LPD8806::setPixelColor(uint16_t n, uint32_t c) {
+ 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;
+ }
+}
+
+// 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)(pixels[ofs ] & 0x7f) << 16) |
+ ((uint32_t)(pixels[ofs + 1] & 0x7f) << 8) |
+ (uint32_t)(pixels[ofs + 2] & 0x7f);
+ }
+
+ return 0; // Pixel # is out of bounds
+}