pi2ws2812

USING THE PWM PERIPHERAL FROM USER-SPACE TO DRIVE WS2812 ADDRESSABLE LEDS

WS2812 LEDs require precise timing to drive, requiring some kind of hardware serializer solution. (See Tims Blog, and the excellent work he did characterizing WS2812 LEDs.) Fortunately, the Pi has several such peripherals: the PWM, PCM and SPI controllers. And luckily all three can be serviced by DMA, allowing us to operate them from userspace.

For this project we'll use the PWM controller since it allows us to connect short LED strips to the Pi using a seven pin adapter board. We have to give up something for this compactness though: sound output from the headphone jack!

HARDWARE

You could use jumper wires to connect the LEDs to the Pi, but I've opted instead to use a small circuit board so the display can be plugged directly into the Pi.

Order the circuit board from OSH Park here. You can also use your vendor of choice using the source files in this project's files section.

Order the LED strip from AliExpress or your favorite supplier. It should be under a dollar. The search term to use is "WS2812 RGB 8". Make sure the pin-out is like the photo below:

Order a 7 pin single row 2.54mm female header connector. AliExpress has them for around a dollar for ten pieces, or you can get it in single piece quantities from DigiKey for a bit more.

Orient the LED strip with the LEDs facing down, and lay the board on top of the display, with the "pi2led" lettering down. Line it up so the pads on the LED strip are half covered. Make sure you're soldering to the end with a DI labeled pad - not the one labeled DO. You'll need to figure out some way to hold it together while you're soldering: hot glue will work, just keep it away from the connections. Solder the board to the strip. If you have liquid flux use it! Make sure everything is aligned carefully so that you won't get any shorts. A good soldering job will look like this:

Clean up your hot glue if you used it: alcohol will release the bond and makes this easy. Insert the connector into the board and solder it in. The connector should be facing down when the LEDs are facing up. It should look like this from the side:

SOFTWARE

Since we're using the PWM peripheral we need to disable the headphone jack sound output. To do this put the following line into /boot/config.txt:

        dtparam=audio=off

Next, download the pi2ws2812pwm.c, mailbox.h and mailbox.c files. Then compile using the following command:

    gcc -L/opt/vc/lib -I/opt/vc/include -o pi2ws2812 pi2ws2812.c mailbox.c -lbcm_host

Power off and connect the LED assembly to the Pi. It goes on pins 2,4,6,8,10,12 and 14 like so:

Power on and run the compiled pi2ws2812pwm program with a parameter specifying the number of LEDs, in this case 8, and put it into the background: 

    sudo ./pi2ws2812pwm 8 &

Then send color information to the /dev/pi2ws2812pwm device. Data values are text, 0-255, in green-red-blue order, space delimited. First LED first, second LED second, etc, followed by a carriage return. If you don't include all of the data the rest of the string will be turned off. Here is a sample command line for first LED full brightness green, and second LED half brightness red, the rest off:

    echo "255 0 0 0 128 0"

Since you communicate with the LEDs via a device file, it should be usable from any program. I've used it successfully from a shell script and from C, but Python shouldn't be any problem.

Many thanks to Tims Blog, and the excellent work he did characterizing WS2812 LEDs. I especially appreciate the algorithm he came up with to write to the LEDs: I don't know if I could've come up with something so elegant. It's a great example that a good algorithm can really simplify a problem.

Also, much thanks to servoblaster. I started this project by studying their code until I understood it, then combined it with the code from my kernel module (The kernel module code is in this project's files section, but it probably won't compile in newer versions of Raspbian.) This user-space version has the advantage that it doesn't have to be compiled for each kernel. And it uses DMA (like servoblaster) so task-switching doesn't present a problem.