NeoPixel Serial LEDs
LEDs, of course, are of huge interest to a lot of people. One of the coolest LEDs available today is the NeoPixel from Adafruit. One type of NeoPixel is the WS2812 “Intellegent Control LED Integrated Light Source” from Worldsemi. There are sources of information all over the place on this chip. And the chinglish datasheet can be deciphered with some concentrated effort. I want to try to bring together enough detailed information to allow you to work with this chip on any project you may have use for it.
The WS2812 contains a logic IC (WS2811 chip) and 3 LEDs (5050) green, red, and blue in a 6-pin package. Besides 3 power supply pins, the chip has Data-In and Data-Out pins. This serial data communication allows the control of many LEDs without the need for any multiplexing circuitry at all. Of course, cost is higher for each individual LED, but wiring is greatly simplified. The user sends a data stream consisting of 8 bits of “brightness” for each of the 3 LEDs on the Data-In pin. When the data has been transmitted, the user sets the data line low for a specified period and the chip then “loads” the data to the PWM drivers. Further refresh is not needed. The LEDs remain illuminated as instructed until the next 24 bits and “load” signals are received.
How Does This Thing Work?
The communication protocol on this chip is the feature that really sets it apart from other hybrid LEDs. The datasheet identified this protocol as “NZR”. I have no idea what that is. Perhaps they mean to say “NRZ”. That, at least, is a real transmission protocol. However, the method used is actually a PWM signal. A data bit is represented by one high to low cycle. The period of each cycle is roughly constant, and the duration of the “high” period determines if the data bit is a “1” or a “0”. But since many people will want to bit-bang this part, I wanted to find out the best way to drive this thing.
I first thought to drive it with the PWM resource on the 16F886. That seemed natural enough. However, all my efforts were in vain. I would occasionally get a randomly flashing color. But mostly I got a pure white light when I got it to light up at all. I was having a problem doing the housekeeping of counting bits, counting bytes, keeping track of LED boundaries, and changing my memory accesses fast enough to get reliable operation.
But after searching the WEB enough times, I finally ran across a blog that described the use of SPI communications to send the data. Two bits were sent in each 8-bit SPI transmission. The data was chosen to simulate the PWM “High” and “Low” periods by sending “1110” for a “1” and “1100” for a “0” (as best as I can remember). I thought that was a pretty neat trick. But eventually (I’m slow) I realized that the time between SPI transmissions violated the spec. in the datasheet. What’s up with that? Why did SPI work? It finally dawned on me that the timing spec in the datasheet might not be exactly accurate. So I gave up on PWM. But I also did not want to use the SPI bus if I didn’t have to. I had vague ideas of the future use of SPI memory to provide more LED data space than was available on the ‘F886. I didn’t want to waste a comm. port on something where it was not required. I needed another plan.
It seemed to me that if this was going to work at all, it should be possible to “hard code” a data sequence of 48 bits to drive the two LEDs on my breadboard. If that worked, it might be possible to work “backwards” and figure out how to count bits, bytes, and LEDs efficiently. At least, with this code, I had the basis of a test platform. I could put in delays and figure out what the actual timing is for this part. Here is a short section of the code I generated:
This code is the first eight bits for the first of two LEDs. It is the value for “green”. The value represented is 0x4c. First, the output port pin is set to start the bit-time. Then you either wait for one NOP or 2 NOPs before clearing the port pin. Waiting for one NOP will be interpreted as a “0”. Waiting for two NOPs will be interpreted as a “1”. Then either one or two NOPS are added to the “low” time to make each bit-time five cycles long. The entire sequence repeats five more times like this to send LED1’s red then blue data and then LED2’s data. The advantage of having code written like this (you would never do this normally) is to ensure that there are no timing problems or variations between bits, bytes, and LED sections. Code written like this is only for timing tests. Much to my relief, it worked. Now I could start playing with the timing between bits, bytes, LEDs, and the reset period.
The video uses this code to do the timing analysis. A delay was placed after the “red” byte of the first LED’s data. Then, variable delays were generated with the following code (comments may not be up-to-date):
By placing calls to the delay routine in various places, you can test the effect between bit transmissions in the bit stream. Delaying within one color byte, between color bytes, or between LEDs seemed to produce identical results. Testing of the “RESET” pulse period also proved to be interesting. That showed that you have nowhere near the 50uS specified in the datasheet before a reset occurs. Results were as follows:
Maximum delay time between bits: 6.8uS
Minimum RESET pulse: 9.6uS
These numbers are just a guideline. Use them at your own risk. They are mainly useful as a starting point. At 16MHz on a 16F886 you have about 27 instruction cycles available between bits. Between LEDs you have to watch out for RESET becoming active. In that case you have about 38 instruction cycles available. Needless to say, you will have to use assembly language. C just will not cut it.
There are limitations on the accuracy of these results. First, I only had a timing resolution of 250nS at my 16MHz processor speed. I would have gone to a 20MHz clock if I thought that increase would have been significant. What is really required here is an arbitrary function generator. Second, a sample of 3 parts is probably not statistically significant. And third, my questionable soldering skills after a 10 year layoff from electronics is not that great on small parts like this. I may have effected the timing by applying a little too much heat. But the encouraging thing about these tests is that there is definitely more time for processing with a microcontroller than the datasheet suggests.
Short “HIGH” Periods
You will notice in the video that I accidentally generated a too short “HIGH” period on one of the data bits. That resulted in a completely missing pulse out of the LED. I made no comment on it in the video, but I did pop in a note about what was going on. This will probably never be a problem as going too fast is the hard thing to do. But I’m glad I stumbled upon it and was able to show it. This was totally unexpected behavior to me. I would have thought that the bit decoding would have been dependent upon the rising edge. If a pulse was “too short”, it should be interpreted as a “0”. But something else is going on inside this chip. This has led me to thinking about more tests that might be relevant. I may tack on some more testing in the following video on color mixing and intensity.