Driving multiple SPI RGB LED strips

Red Green Blue, Light Emitting Diode, per pixel controllable strips.

Adafruit and various other vendors sell RGB LED strips with one LPD8806 chip per 2 RGB LED modules. The chips shift in 1 byte per led color (3 per led module) and use 7 bits of the byte to drive the intensity of the LED via PWM. On the other side the chips shift out the bits 6 bytes delayed. 1 meter of these strips has 32 LED’s and 16 chips, forming a 384 byte long shift register. One (or more) zero bytes latch the pattern in the strip, eliminating the need to constantly refresh the strip. The chip could use a blog post on it’s own.


The single strip examples.

Adafruit have some excellent examples driving one led strip with the arduino SPI. You can scale this solution by daisy chaining strips but there are come complications with this approach. One is that all the power for all strips (1 Ampere per meter or more) will flow through the first strip. The other is that if you want to run strips in parallel you’ll need one Arduino per strand. The last problem is that the Arduino is too small and slow to handle large bitmaps by itself and a bit cumbersome as an IO extender.


The teensy solution.

Paul Stoffregen’s (of Teensy fame) post on the teensy as a USB byte pump ( http://forums.adafruit.com/viewtopic.php?f=47&t=25854&p=143049#p143049 and http://www.adafruit.com/forums/viewtopic.php?f=47&t=27898&start=0#p143649) showed me the right direction.

In this solution a Teensy is used to drive 8 strips simultaneously. One of the 8 bit ports is used to drive the data pin on 8 led strips, one pin on one other port is used to drive all clock pins. This reduces the number of boards per strand.

Paul’s code worked right away (a classic example of loop unrolling, trading code size for execution speed). The only challenge now was to hook it all up and write a example program that creates an image and serializes  it to the teensy.


Teensy and USB

Below is an image of the first USB frame in the led strip image. The time the teensy needs for pushing out one USB frame is 58 us. There are 64 bytes in this frame, driving a little more than 2 columns with 8 leds each.

Notes: 2

The first USB frame of the image, the lower line is the clock line. The time between the markers is 58us.

The byte clock in a frame is about 800 ns. (1.16 MHz), One 64 byte frame takes 56 us. The pause between the frames is 2.8 us. A complete image of 13 (64 byte) usb frames takes about 1 ms.

Notes: 1

Driving 8 LED strips.

You can just see the “H”, “E” and “L” in the pattern driving the LED strips.

One image in the LED strip

Above is one complete LED strip image, the last block (right) only contains o’s and  latches the pattern in the strip. The pauses between the USB frames are caused by the combination of the Teensy USB, the OS USB stack and the Processing application. USB hubs and main board chipsets can  cause additional artifacts.


Pictures and movies

Brightness and dynamic range

The LPD8806 have 7 bits of PWM resolution allowing for 221  (about 2 million) colors. Seems like plenty. The problem is that the LED strips are pretty bright, when the the LED are driven full brightness my eyes and the camera are pretty much blinded. So for indoor use I’m using it at about 10%, effectively killing off 3 of the 7 bits. That explains the posterising you can see in movies below.  The competing chip, the WS2801, has 8 bits PWM resolution and may suffer less from posterising,

Problems, complications and stuff to fix later.

The led strip driver combines 4 parts, each with their own complications and limitations. Theses are :

Java :

Java has different primitive integer types but does ALL operations on those types as if they are ints. As we are working with bytes here (serial stream is a byte stream and the teensy moves bytes from the USB buffer to the port) well need to do a lot of “v & 0xFF” to mask out the unneeded bits and “(byte) x” to cast to a byte.

Also, in Java the byte is signed. For the operations we are doing this doesn’t really matter (with one exception below) but the bytes are presented in the debugger and print as signed integers. 0xFF will appear as -1.
Logical bit shifts are of course also done on 32 bit integers. Because the max shift is 32 bits, Java only looks at the lowest 5 bits of the shift amount by masking out the other bits with 0x1F. This also means shifting with negative amounts is not possible.

Finally, because the shift operation is done on an integer and the the integer is signed, we do not want sign extension on the right shift. Use the true binary “>>>” instead of the numerical “>>”. In other words, “>>>” shifts the real bits, “>>” keeps the sign as it was.


The maximum packet size USB serial device is 64 bytes. The teensy code is built to only handle complete 64 byte packets. Shorter packets will have to be padded to multiples of 64 bytes.

Teensy :

The teensy waits for a full buffer and the dumps the bytes to a port. The frame timing is determined by the teensy clock frequency and by “nop” instuctions. The timing between the frames is complicated by the USB stack on the host and device, the OS and USB hubs.

The LED strips with LPD8806 :

There is no English datasheet for the LPD8806, some of the documentation is from Adafruit and some information can be figured out from the source code. One of the undocumented issues is the latch pattern, this post says something about it : http://www.adafruit.com/forums/viewtopic.php?f=47&t=27898&start=0#p143649

Better Tag Cloud