Controlling a string of NeoPixels can be a little tricky due to their constrained timing requirements. Fortunately, a UDB-based driver is available for use with Cypress Semiconductor's PSoC devices.
A few weeks ago, I pinged EETimes editor Max Maxfield with a video of a simple audio spectrum analyzer with blinkies, and Max was kind enough to post a blog about it. (See PSoC 4 Emulation of a MSGEQ7 Audio Spectrum Analyzer.)
A little while later, I came across the rather attractive NeoPixel Shield for Arduino from Adafruit. This is a brilliant-looking tri-color LED shield with 40 LEDs requiring only a single pin to drive it. I couldn't help myself. I instantly bought it in a fit of blinky madness.
The Adafruit shield arrived a couple of days later, and I quickly soldered up the headers and fired up my trusty CY8CKIT-042 PSoC 4 Pioneer kit. As you may know, this development board boasts a set of Arduino-compatible headers, thereby allowing Arduino shields to be controlled using the PSoC 4.
It's well known that the timing of the signals used to control a string of NeoPixels can be a little tricky. Fortunately, my colleague Mark Hastings here at Cypress had already created a Universal Digital Block (UDB)-based driver. This component was available to be dragged and dropped into my design. If you are not familiar with the PSoC Creator development environment, "component" refers to something like a library file for Arduino, but in this case it contains a mix of Verilog HDL and firmware-based code.
As you can see in this video, Mark had previously used his UDB to run a massive 960 tri-color LED billboard with a single -042 kit.
With a bit of twiddling, I managed to adapt Mark's component to work with the code from my original MSGEQ7 emulator. The filters are unchanged except for the fact that I added another frequency band at around 4 kHz, so that the whole shield is used. Each LED column represents a different frequency, and the power of blinky exceeded all of my expectations, as you can see in this video.
You may be wondering about the translucent sheet I placed in front of the shield. This is just there because the NeoPixels are so bright that it's hard to take a video of them working if they are fully exposed. As you can see, I added a brightness pulse each time the bass (63Hz) filter crossed a threshold to get a more fun, thumpy feel.
Some nitty-gritty details
The NeoPixel shield is based on WS2812B LEDs, and the datasheet for these devices specifies a very interesting protocol. Each pixel (LED) requires 24 bits of serial data (eight bits each for the red, green, and blue subpixels). Once a pixel has received the data it needs, it simply passes any subsequent 24-bit data values on to the following pixel in the chain. There is a nice timing diagram that shows this in the datasheet.
All in all, this is a pretty neat and lightweight protocol. The data high and low values are specified by a pulse width with some pretty tight timing constraints, as the whole thing is running at either 400 or 800 kHz, depending on the speed you set.
The original Arduino-based code library on Adafruit's NeoPixel Uberguide achieves this by bit-banging and optimizing the code. Quite understandably, Adafruit puts up a disclaimer that this doesn't work on Arduino-compatible boards, since it used low-level, processor-specific assembly instructions.
Using UDBs on a PSoC 4, this driver can be built almost independent of the core processor by utilizing available datapath and macrocells. Essentially, the component splits the protocol into two parts: a master PWM (which runs at 400/800 kHz) and a 24-bit shift register. At the end of each PWM cycle, it simply shifts one data bit and -- depending on whether we wish to transmit a zero or a one -- it loads a pre-specified duty cycle. Thanks to the datapath, this load is independent of the CPU. I'm simplifying the process a bit to exclude things like the end-of-data (also Verilog-based), but the essential idea remains.
All we need to do is load up a 24-bit value and forget about it until the next LED data needs to be written. The component automatically generates a master array depending on the number of LEDs, and the UDB triggers an interrupt until you run out of data in the array. Furthermore, if you were to use a PSoC 5LP, you could employ its direct memory access (DMA) capability at this point to shift out an array of data automatically and just keep modifying the master array to make it completely independent of the core processor.
Generating the desired color map was a little tricky. The component, by default, already has a lookup array for colors, but for my project, I wanted a brightness variation for each pixel, depending on the magnitude of the associated frequency component. Using just five LEDs for each band is a little choppy on the eyes, so giving a little bit of granularity by varying the brightness of the individual pixels made things look much better. Since each color is just a variation of how much each LED is turned on, I simply created a simple lookup table that maps the colors green, light green, yellow, light red, and red to a 256-value array map with variations in brightness for each color.
This ZIP file contains all of the source code available, so please feel free to reuse it and experiment. As always, I look forward to your comments and questions. In the meantime, happy blinking.