Permalink
Browse files

Latch issues resolved, protocol details expanded

  • Loading branch information...
1 parent 2e5886a commit 9fa17f674885e3d179a0ca2b5eef1f8345891dc8 @PaintYourDragon PaintYourDragon committed Dec 4, 2012
Showing with 113 additions and 35 deletions.
  1. +106 −29 LPD8806.cpp
  2. +6 −5 LPD8806.h
  3. +1 −1 examples/strandtest/strandtest.pde
View
@@ -1,10 +1,73 @@
+/*
+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"
-// Arduino library to control LPD8806-based RGB LED Strips
-// (c) Adafruit industries
-// MIT license
-
/*****************************************************************************/
// Constructor for use with hardware SPI (specific clock/data pins):
@@ -29,7 +92,7 @@ LPD8806::LPD8806(uint16_t n, uint8_t dpin, uint8_t cpin) {
// 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;
+ numLEDs = numBytes = 0;
pixels = NULL;
begun = false;
updatePins(); // Must assume hardware SPI until pins are set
@@ -86,29 +149,36 @@ void LPD8806::startSPI(void) {
// 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)
+ // 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
+ }
}
// 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--) {
+ for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) {
*clkport |= clkpinmask;
*clkport &= ~clkpinmask;
}
}
// 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
- 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
+ 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
}
@@ -121,19 +191,24 @@ uint16_t LPD8806::numPixels(void) {
// 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 + 1 for latch
-
- // write 24 bits per pixel
- if (hardwareSPI) {
- for (i=0; i<n3; i++ ) {
+ 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--) {
while(!(SPSR & (1<<SPIF))); // Wait for prior byte out
- SPDR = pixels[i]; // Issue new byte
+ SPDR = *ptr++; // 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;
+ uint8_t p, bit;
+ while(i--) {
+ p = *ptr++;
+ for(bit=0x80; bit; bit >>= 1) {
+ if(p & bit) *dataport |= datapinmask;
+ else *dataport &= ~datapinmask;
*clkport |= clkpinmask;
*clkport &= ~clkpinmask;
}
@@ -143,20 +218,22 @@ void LPD8806::show(void) {
// Convert separate R,G,B into combined 32-bit GRB color:
uint32_t LPD8806::Color(byte r, byte g, byte b) {
- return 0x808080 | ((uint32_t)g << 16) | ((uint32_t)r << 8) | (uint32_t)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; // LPD8806 color order is GRB,
+ *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 RGB value:
+// 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];
@@ -170,9 +247,9 @@ void LPD8806::setPixelColor(uint16_t n, uint32_t c) {
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 ((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
View
@@ -11,15 +11,15 @@ class LPD8806 {
LPD8806(uint16_t n, uint8_t dpin, uint8_t cpin); // Configurable pins
LPD8806(uint16_t n); // Use SPI hardware; specific pins only
- LPD8806(void); // Empty constructor; init pins/strip length later
+ LPD8806(void); // Empty constructor; init pins & strip length later
void
begin(void),
show(void),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b),
setPixelColor(uint16_t n, uint32_t c),
updatePins(uint8_t dpin, uint8_t cpin), // Change pins, configurable
- updatePins(void), // Change pins, hardware SPI
- updateLength(uint16_t n); // Change strip length
+ updatePins(void), // Change pins, hardware SPI
+ updateLength(uint16_t n); // Change strip length
uint16_t
numPixels(void);
uint32_t
@@ -29,9 +29,10 @@ class LPD8806 {
private:
uint16_t
- numLEDs; // Number of RGB LEDs in strip
+ numLEDs, // Number of RGB LEDs in strip
+ numBytes; // Size of 'pixels' buffer below
uint8_t
- *pixels, // Holds LED color values (3 bytes each)
+ *pixels, // Holds LED color values (3 bytes each) + latch
clkpin , datapin, // Clock & data pin numbers
clkpinmask, datapinmask; // Clock & data PORT bitmasks
volatile uint8_t
@@ -15,7 +15,7 @@ int clockPin = 3;
// First parameter is the number of LEDs in the strand. The LED strips
// are 32 LEDs per meter but you can extend or cut the strip. Next two
// parameters are SPI data and clock pins:
-LPD8806 strip = LPD8806(32, dataPin, clockPin);
+LPD8806 strip = LPD8806(nLEDs, dataPin, clockPin);
// You can optionally use hardware SPI for faster writes, just leave out
// the data and clock pin parameters. But this does limit use to very

0 comments on commit 9fa17f6

Please sign in to comment.