Using WS2812-based NeoPixels in embedded systems - Embedded.com

Using WS2812-based NeoPixels in embedded systems

I don’t know about you, but I personally don’t think you can have too many LEDs (light-emitting diodes) in an electronic system, irrespective of whether you are working on a hobby project or a real-world product.

Given a choice, I prefer to use tri-colored LEDs, because that way I can use the same element to reflect a variety of different conditions. Now, just to make sure we're all tap-dancing to the same drum beat, let's remind ourselves that tri-colored LEDs contain three sub-LEDS — one red (R), one green (G), and one blue (B). These sub-LEDs are mounted in such close proximity that an observer perceives their light as being merged together to form other colors.

The traditional way to present a tri-colored LED is in a package with four pins as illustrated below. Some of these devices have a common anode (the longest lead), which would be connected to the +ve power rail. In this case, connecting the other leads to 0V (ground) — typically by setting MCU (microcontroller) pins connected to these leads LOW — would turn the corresponding sub-LEDs on.

Alternatively, in the case of a common cathode device, the cathode would be connected to 0V, and connecting the other leads to the +ve power source — typically by setting MCU pins connected to these leads HIGH — would turn the corresponding sub-LEDs on. The diagram below reflects a common cathode configuration.

Often, external current-limiting resistors would be required as shown above. The advantage here is that you can vary these resistors to accommodate different MCU source voltages (e.g., 3.3V versus 5V); the disadvantages are that you have more components consuming more real estate on your circuit board, and every solder joint provides a potential point of failure. Alternatively, some tri-colored LEDs come equipped with their current-limiting resistors inside the package. This makes things a lot simpler, but it does mean that you have to ensure your LED is matched to the voltage source you are using.

Now, assuming we are using an MCU to drive these devices (an FPGA is another possibility), there are two main ways in which we can drive these devices. The first is to use standard MCU outputs, which can be in one of two states: LOW (0V) or HIGH (+ve). In this case, and assuming a common cathode device, we can achieve 2^3 = 8 different colors (if we take Black to be a color) as illustrated below:

In this case we can use a single device to indicate a variety of different conditions, such as “Everything is good” (green), “Start worrying” (red), “I'm in a low-power mode” (magenta), and so forth.

Another alternative is to vary the brightness of each of the sub-pixels. Now, LEDs can't be dimmed like traditional incandescent bulbs — they can only be on or off (generally speaking). However, we can turn them on and off very quickly indeed — hundreds of thousands times a second — and by varying the amount of time they are on compared to the amount of time they are off, we can effectively control their brightness.

The typical way we do this is to use MCU outputs that have a PWM (pulse-width modulation) capability. In the case of the Arduino Uno, there are six such pins, each having an 8-bit PWM to which we can assign values ranging between 0 (fully off) to 255 (fully on). (The Arduino Mega has 14 PWM-capable outputs.) In the case of our tri-colored LED, this means that we now have the ability to drive each red, green, and blue channel/sub-pixel with 2^8 = 256 different values, which gives us a total of 256 * 256 * 256 = 16,777,216 different color combination possibilities.

The problem with driving traditional tri-colored LEDs as discussed above is that each one requires three MCU or FPGA pins. If we wish to use the PWM technique for driving them, then we can drive a maximum of two tri-colored LEDs using an Arduino Uno or four such LEDs using an Arduino Mega. There are ways around this using shift registers and/or multiplexing, but things can get very messy very quickly. If only a cunning alternative were available…

To Page 2 >

Introducing WS2812-based NeoPixels
One semiconductor manufacturer that has really changed the LED landscape over the past few years is Worldsemi. First, they have a tiny little surface-mount control integrated circuit called the WS2811. This includes a simple serial communications capability, three 8-bit PWM channels, current limiting circuitry, and so forth.

Next we have the WS2812, which is is a 5mm x 5mm square package containing super-bright red, green, and blue LEDs along with a WS2811 control chip as illustrated below.

The great thing about these devices is that they have only four pins: 0V (ground), 5V (power), Data-In, and Data-Out. This means that they can be daisy-chained together, with the MCU driving the Data-In of the first element, the Data-Out from the first element driving the Data-In of the second, and so forth as illustrated below.

The way to visualize this working is that the MCU transmits a series of 24-bit values, each of which represents three 8-bit RGB values. Every time the MCU transmits a 24-bit value, this value is loaded into the first pixel in the chain. At the same time, the first pixel passes its original 24-bit value to the second pixel, the second pixel passes its original value to the third pixel, and so on down the chain. All of this happens so fast that it appears to be instantaneous to the human eye.

The end result is that we can drive a chain of hundreds of these tri-colored pixels using only a single standard (i.e., non-PWM) digital output pin.

Many companies have used Worldsemi's WS2812 devices as the basis for their own products. The folks at Adafruit, for example, have created a suite products under their “umbrella name” of NeoPixels. These NeoPixels are packaged in a variety of different ways; for example, there are NeoPixel rings as illustrated below.

The NeoPixel ring above contains 16 pixel elements. Other rings are available with 12, 24 and 60 elements. This video shows a 16-element ring in one of my own hobby projects.

Adafruit also offers a variety of NeoPixel strips. The one shown below has 30 pixels per meter, but you can also get them with 60 pixels per meter and 144 pixels per meter.

I've used these little beauties in all sorts of projects, including a rather tasty Infinity Mirror. My biggest project based on NeoPixel strips thus far has been my Bodacious Acoustic Diagnostic Astoundingly Superior Spectromatic (BADASS) Display. This video shows the BADASS Display in action.

This incarnation of the BADASS display uses 16 NeoPixel Strips, each containing 16 elements and each driven by its own MCU pin (I'm using an Arduino Mega for this project). Had I wished, I could have connected these together to form a single strip with 256 elements, but for a number of reasons I decided to use multiple strips.

As an aside, the folks at Adafruit have also started offering NeoPixels in traditional 8mm through-hole packages as illustrated below. In this case, the four leads are 0V (ground), 5V (power), Data-In, and Data-Out, so these devices can be daisy-chained just like all of the other NeoPixel products.

Feel the power!
We tend to think of LEDs as consuming relatively little current, and this is true as far as it goes, but it's amazing how things start to add up when you use more than a handful of LEDs. Each NeoPixel can consume up to 60mA when all three channels are fully on (20mA each for the red, green, and blue LEDs). This means that you are limited as to how many elements you can drive if you are powering your MCU from a USB port or a small “wall wart” power supply. In the case of my BADASS display, for example, its 256 NeoPixels can potentially consume ~15A, so you have to plan accordingly. Personally, I prefer to modify my MCU such that it runs off the same 5V supply as my NeoPixels (see also Modifying an Arduino Mega and chipKIT Max32 for 5V Operation ).

There are two very important points that are well-worth highlighting here. The first is that you should connect a 1000µF electrolytic capacitor between the 0V and 5V terminals of the power supply you are using to drive the NeoPixels. The second is that you should insert a 300 to 500Ω resistor in the signal path between the MCU pin and the Data-In to the first NeoPixel element (I typically use a 390Ω part); furthermore, this resistor should be at the NeoPixel end of the signal. The reason for inserting this resistor is that it prevents the signal overshooting and undershooting, which could potentially damage the first NeoPixel in the chain.

Speaking of damaging things, it's important to be aware that NeoPixels are very susceptible to electrostatic discharge (ESD), so it's very important to follow good ESD practices when working with these little scamps (see also Zapping Things With ESD — Just One More Service I Offer ).

If you are planning on using NeoPixels yourself, Adafruit have created a very useful NeoPixel Uberguide that's well worth reading.

To Page 3 >

Timing is everything
If you wish to use WS2812-based LEDs in your systems, the easiest approach is to base your code on a tried and trusted library of functions that have been created and tested by someone else. Since I use the combination of the Arduino and Adafruit's NeoPixels for a lot of my projects, I tend to use the Adafruit NeoPixel Library.

The important thing to note about this library is that it's been hand-crafted for use with the Arduino Uno and Arduino Mega MCUs, both of which are based on 8-bit, 12MHz Atmel processors. When I say “hand-crafted,” I'm referring to the fact that the library functions use assembly code to get the timing precisely right. The result is a really easy-to-use library, but it's not portable to other MCUs in the Arduino family. Also, these library functions are pretty single-minded. When you call the function to upload new values into a string of NeoPixels, for example, that function first disables all of your interrupts. This may not be an issue for users who aren’t using interrupts, but it can be a pain in the rear end if you are.

Libraries are available for other devices; for example, the folks at Cypress Semiconductor have a WS2812 library for their PSoC (programmable SoC) devices. Also, the Teensy 3.1 from PJRC offers a very interesting solution involving the OctoWS2811 Adaptor and the OctoWS2811 Library. This uses the 8-bit DMA channel on the Teensy's 32-bit ARM processor to drive eight NeoPixel strips simultaneously. Furthermore, since the DMA is handling everything in the background, it leaves the main processor free to perform other tasks, including servicing interrupts.

If you wish to create your own library, then you are a braver man than me there is an extremely useful article by a guy called Josh that is not (but should be) titled WS2812-based NeoPixel timing isn’t as bad as many people think .

Example programs using the Adafruit library
For the remainder of this article, we'll focus on example code based on the Adafruit library running on an Arduino Uno or Arduino Mega. As fate would have it, just the other day I was chatting to my chum Lee Goldberg, who is LEDitor-in-Chief for the EDN LED Design Center. Lee has been following my Vetinari Clock and BADASS Display hobby projects, and he asked: “Would it be possible to share a few of your code segments with my readers?”

Well, I was a bit worried that the code for these projects might not be particularly understandable for beginners, so instead I created a handful of simple examples called Lee_01 , Lee_02 , Lee_03 , and Lee_04 as starting points. We then ran these examples on a 16-element NeoPixel ring and a 16-element NeoPixel strip and captured everything on This Video for your delectation and delight.

Lee_01: Lighting the pixels one after the other
Let's look at the code in a little more detail. Let's start with Lee_01 as shown below (click here to see the full code with comments in the form of an ASCII text file, which you can copy and paste into the Arduino IDE if you so desire).

We start by including the Adafruit NeoPixel Library:

#include

Next, we define the pin we're going to use to drive our NeoPixel ring or string (we called this “pinPix”) and the number of NeoPixel elements in the chain, which happens to be 16 in this example (we called this “numPix”).

#define pinPix 12
#define numPix 16

Our next task is to instantiate the NeoPixel ring or string — we're going to call this one “myNeoPixels” — which we do using the following statement:

Adafruit_NeoPixel myNeoPixels = Adafruit_NeoPixel
                 (numPix, pinPix, NEO_GRB + NEO_KHZ800);

The first parameter (“numPix”) is the pin we're using to drive the ring or string; the second parameter (“pinPix”) is the number of elements in the chain; and we don’t need to worry about the third parameter here.

When we come to the setup() function, which runs only once at the beginning of the program, we first call the begin() function to initialize our string in the Arduino's memory, followed by the show() function to initialize the string in the real world (we'll return to this second function in a moment).

void setup() {
    myNeoPixels.begin();
    myNeoPixels.show();
}

Now, let's pause for a moment to wrap our brains around something. As part of the instantiation and initialization process discussed above, the Adafruit library creates an array in the Arduino's memory. I personally always visualize this array as having the same name we used to declare the ring or string. This array has the same number of elements as our ring or string. This is 16 in our example, and the little scamps are numbered from 0 to 15 as illustrated below:

Each 24-bit element is composed of three 8-bit sub-fields, which are used to represent that element's R, G, and B values, respectively. The Adafruit library includes a function called setPixelColor() , which accepts four parameters. The first parameter is the element in which we're interested (0 to 15 in this example), and the remaining three parameters are the values we wish to assign to that element's R, G, and B sub-elements. The important thing to note here is that the setPixelColor() function only changes the values in the array in the Arduino's memory — it's not until we use the show() function that a copy of all of the values contained in the array is streamed out of the memory into the physical NeoPixel ring or string.

Returning to our simple program above, we next come to the loop() function, which is called over and over again:

void loop() {
    letsDoIt(100, 255,255,255, 0,0,0);
}

The only thing we do in our loop() function is to call another function named letsDoIt() . The first parameter to this function is the pause, or delay we wish to use between actions occurring in the real world. In the case of this example we've set this value to 100 milliseconds (1/10 of a second).

We're trying to make this example as generic as possible, so we're thinking about things in terms of “foreground” and “background” colors (we could also think about this in terms of “on” and “off” colors). Whatever terms we wish to use, the next three parameters (255, 255, 255 in this case) are the R, G, and B values we wish to use for our “foreground” or “on” color (which equates to white), while the final three parameters (0, 0, 0 in this case) are the R, G, and B values we wish to use for our “background” or “off” color (which equates to black).

Finally, we have the letsDoIt() function itself. In the case of our first example, this is as follows:

void letsDoIt (int pause, byte Rf, byte Gf, byte Bf, byte Rb, byte Gb, byte Bb) {

    for (int i=0; i
        myNeoPixels.setPixelColor(i,Rf,Gf,Bf);
        myNeoPixels.show();
        delay(pause);
    }

    for (int i=0; i        myNeoPixels.setPixelColor(i,Rb,Gb,Bb);
        myNeoPixels.show();
        delay(pause);
    }
}

In the case of the first for() loop, we set each of the pixels to the foreground color pixel-by-pixel with a delay between each change. This is why the show() and pause() functions appear inside the for() loop immediately after the setPixelColor() function.

We then use the second for() loop to return the pixels to the background color. Once again we do this pixel-by-pixel with a delay after each change. If you return to the video above, you'll see that this program reflects the first example with the foreground color set to white (255,255,255) and the background color set to black (0,0,0). Then we perform a second run with the background color to red (255,0,0).

Lee_02: Lighting all the pixels simultaneously
The next thing we do in the video is to swap over to program Lee_02 (click here to see the full code with comments presented as an ASCII text file).

Once again, we start with a foreground color of white and a background color of black. In this case, all of the pixels in the ring transition at the same time. In fact, our Lee_01 and Lee_02 programs are almost identical — the only difference in is the letsDoIt() function as shown below:

void letsDoIt (int pause, byte Rf, byte Gf, byte Bf, byte Rb, byte Gb, byte Bb) {

    for (int i=0; i        myNeoPixels.setPixelColor(i,Rf,Gf,Bf);
    }
    myNeoPixels.show();
    delay(pause);

    for (int i=0; i
        myNeoPixels.setPixelColor(i,Rb,Gb,Bb);
    }
    myNeoPixels.show();
    delay(pause);
}

This time, we've moved the show() and pause() functions outside of the for() loops. This means that the setPixelColor() functions in the for() loops change the values of all of the pixels stored in the Arduino's memory before we use the show() function to stream these values to the physical pixels.

If you return to the video, you'll see us playing around with the value of the pause between transitions and with the colors. Also, we swap over from driving a NeoPixel ring to a string. Eventually, we move on to run program Lee_03 , which is where things start to get interesting…

To Page 4 >

Lee_03: Racing pixels & a cunning mask operation
Now, for all you programming experts out there, I know you already know all of this stuff, so please don’t laugh. This portion of our discussions is aimed at the folks who are not familiar with low-level logical operations.

What we want to do in program Lee_03 is to set a pixel to our foreground color while — at the same time — returning the previous pixel to its background color, and then repeating for the next pixel in turn. The end effect in the case of a NeoPixel strip will be to see a single pixel appear to “run” down the strip. In the case of a NeoPixel ring, it will appear as though a single pixel is chasing itself round and round the ring (click here to see the full code with comments presented as an ASCII text file).

In order to determine the address of the current pixel, we can use exactly the same type of for() loop we've seen in our previous programs; i.e., “for (i = 0; i < numPix; i++)..." But how are we going to determine the address of the previous pixel? Let's start by trying to use "i-1" as illustrated in the following table:

As we see, our “i-1” strategy works wonderfully for every value except “i=0”, in which case we end up with a previous pixel address of -1, which doesn’t exist in our array. This is a classic end condition problem. One solution would be to simply test for this special case in our letsDoIt() function and deal with it appropriately. Consider the following implementation, for example:

void letsDoIt (int pause, byte Rf, byte Gf, byte Bf, byte Rb, byte Gb, byte Bb) {

    for (int i=0; i        myNeoPixels.setPixelColor(i,Rf,Gf,Bf);
        if (i == 0)
              myNeoPixels.setPixelColor(15,Rb,Gb,Bb);
        else
              myNeoPixels.setPixelColor(i-1,Rb,Gb,Bb);

        myNeoPixels.show();
        delay(pause);
    }
}

As we see, when it comes to setting the previous pixel to our background color, we test to see if 'i' is zero and — if it is — we explicitly set pixel 15 to the background color; otherwise, we use our “i-1” rule. Now, the above is perfectly acceptable, and we would have to use this — or a similar approach — if the length of our string was not a power of two. In the case of our example, however, we have 16 pixels, which is a power of two (2^4), which therefore lets us employ a solution so cunning we could pin a tail on it and call it a weasel. Assuming that our integers are 16-bits wide, we start by defining a mask as follows:

#define maskPix 0x000F      // Mask = 0000 0000 0000 1111 in binary

Note that we show three leading zeros in our mask here for clarity, but I omitted them in the main program. Using this mask, we can now implement our letsDoIt() function as follows:

void letsDoIt (int pause, byte Rf, byte Gf, byte Bf, byte Rb, byte Gb, byte Bb) {

    for (int i=0; i        myNeoPixels.setPixelColor(i,Rf,Gf,Bf);
        myNeoPixels.setPixelColor(((i-1) & maskPix),Rb,Gb,Bb);

        myNeoPixels.show();
        delay(pause);
    }
}

What we're doing here is using our knowledge of twos complement arithmetic and bitwise operations. In the former, we know that the way a computer represents -1 as a twos complement value is using all ones. Using our bitwise '&' (AND) operation, we can set all of the most-significant bits to zero, and just keep the least-significant four bits as defined by our mask. The end result is illustrated in the following table:

If you return to the video of my BADASS Display, you can see a variation of this effect used to ripple columns of pixels back and forth across the display and rows of pixels up and down the display.

If you want to know more about twos complement numbers and bitwise operations, may I be so bold as to recommend Bebop to the Boolean Boogie (An Unconventional Guide to Electronics and How Computers Do Math , both of which were penned by yours truly. Each word was hand-plucked at the crack of dawn while the morning dew still glistened on the little beauties, but we digress…

Lee_04: Breathing effect
Have you seen a computer or some other piece of electronic equipment where a LED cycles around gradually brightening up and fading down? This is sometimes called a “breathing” effect. In our case — driving a 16-element ring or string — we want to fade all of our pixels up and down at the same time using the program illustrated below:

We'll leave this one as an “exercise for the reader” (click here to see the full code with comments presented as an ASCII text file). Once again, in addition to seeing this effect running on our NeoPixel ring in our original video, if you return to the video of my BADASS Display, you can see a variation of this effect used to control all 256 NeoPixels forming the array.

Phew! This column grew to be a lot bigger than I originally expected, but I'm hoping it will prove useful to a lot of folks who are just starting out with tri-colored LEDs in general and with WS2812-based NeoPixels in particular. If you have any questions you wish to ask or thoughts you wish to share, please post them in the comments below.


Join over 2,000 technical professionals and embedded systems hardware, software, and firmware developers at ESC Boston May 6-7, 2015, and learn about the latest techniques and tips for reducing time, cost, and complexity in the development process.

Passes for the ESC Boston 2015 Technical Conference are available at the conference's official site, with discounted advance pricing until May 1, 2015. Make sure to follow updates about ESC Boston's talks, programs, and announcements via the Destination ESC blog on Embedded.com and social media accounts Twitter, Facebook, LinkedIn, and Google+.

The Embedded Systems Conference, EE Times, and Embedded.com areowned by UBM Canon.

20 thoughts on “Using WS2812-based NeoPixels in embedded systems

  1. “I've only used the Adafruit library with NeoPixels. With that, they are pretty easy to use.nnI made a clock with the Adafruit 60 pixel ring and a custom Arduino-compatible PC board. The 60 pixel ring comes as four 15 pixel segments, which leads to the o

    Log in to Reply
  2. “Actually — using a 12-pixel ring for hours inside a 60-pixel ring for seconds all mounted behind a diffuser would make quite a nice and simple clock project… “

    Log in to Reply
  3. “I tried the 12 pixel ring, but I found that it was difficult to discern exactly which pixel was on at a glance. I think it would work well for a desk clock, but I hang mine on the wall, so I need to be able to see it better from a distance.”

    Log in to Reply
  4. “A really well written and informative article for someone looking to get started with their LED projects. Even I understood it LOL.nnAs you know, I have created a few NeoPixel projects myself, like a 300 LED strip wrapped around my Christmas Tree, a Chr

    Log in to Reply
  5. “Thanks for the kind words Steve — I've seen the videos of the NeoPixels embedded in your hallway skirting boards — very impressive. I might try them on a Christmas tree myself.”

    Log in to Reply
  6. “Thanks David — now I've set them up as 16 separate strips, I probably won't change anything (I like the look of my wiring harness). Elizabeth came up with a way of declaring the strips so I can access them as strip[0], strip[1], etc (rather than strip0,

    Log in to Reply
  7. “Maxnn”Another alternative is to vary the brightness of each of the sub-pixels. Now, LEDs can't be dimmed like traditional incandescent bulbs — they can only be on or off (generally speaking). “nnI am not sure what you are trying to say here- LEDs c

    Log in to Reply
  8. “Well, sort of — you can vary the brightness by a certain amount using the techniques you suggest — but you can't dim them in smooth continuous linear fashion by varying the voltage (or current) from 0V to 5V.”

    Log in to Reply
  9. “MaxnnGood article, I like this. I did LED signage once and we had to do all this in FPGAs (large signs).nnAnyway unless I am reading this datahseet wrong you have 256 PWM levels for each color. That being said, intensity of each is typically not equ

    Log in to Reply
  10. “Max,nnHere's a couple of links that show 3,072 WS2812Bs being driven from a single microcontroller. It shows how to achieve real time animation with them. I hope you find them interesting.nnnhttps://www.youtube.com/watch?v=8dYcjvWnKQo nnhttps:/

    Log in to Reply
  11. “I agree — about the gamma correction table — I just didn't want to get into that level of depth for this column, which had already grown way larger than I was originally planning.nnHaving said that, I'd forgotten that Adafruit had their article on thi

    Log in to Reply
  12. “I have used analog dimming and digital PWM dimming, and here is the scoop. Analog dimming done right, uses a controlled/feeback current source, to remove the non-linear brightness curve. Done right, the brightness can be controlled all the way down to di

    Log in to Reply
  13. “Very nice and complete coverage of this subject Max. And it confirms my suspicion that if you wanted to rearrange your Neopixel strips in your BADASS display from 16 x 16 strips to 1 x 256 strip, the interconnects between each strip would only have to ca

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.