Multicolor LED strips generally come in two flavors – solid color and addressable. Solid color strips, as the name implies, have all of their LEDs display the same color. Addressable strips on the other hand allow you to control the color value of every individual LED in the strip. The most popular individually-addressable LEDs on the market at the moment are Adafruit Industries’ NeoPixels, based on the WS2182 chipset. They come in a variety of physical packages including strips, rings, and matrices.
Each “NeoPixel” is a self-contained 5050 LED with red, green, blue, and optionally white LEDs with an embedded controller. To set a pixel’s color you need to send a control signal containing color information. The pixel’s controller interprets the digital signal to determine its own color and then passes the data stream along to the next pixel in line. This allows you to string many pixels together and control all of them with a single microcontroller pin.
To start displaying colors on our LEDs we first need to convert our properly formatted color information into a precise datastream for embedded LED controllers. Fortunately others have already done most of the heavy lifting for us. Instead of writing the rather difficult assembly code from scratch we can use an Arduino library, a set of prewritten routines for a given project or device – in this case addressable LEDs.
As of this writing there are two main libraries for addressable LEDs: FastLED and Adafruit_NeoPixel. In almost every way FastLED is the superior choice. It deals with the stored color data more logically, has better scaling capabilities, and has many more functions for manipulating color information (including color temperature).
But unfortunately FastLED does not support RGBW strips!
The creator of FastLED has said that he’s working on RGBW implementation, but that he’ll basically need to rewrite a large portion of the library from scratch. There is no ETA at the moment, and I don’t want to wait to get this project going. The Adafruit_NeoPixel library has almost no color manipulation functions, but it does work perfectly for sending formatted color information to RGBW strips.
I spent a couple of days brainstorming how to mix the color functions of FastLED with the bit bang function of the NeoPixel library (i.e. its ability to output to RGBW strips) without modifying either, and the best implementation I came up with was 3x slower than using either library alone. The key sticking point is because the color information is formatted differently between the two libraries, you have to either store all color information twice (a CRGB array and a *pixels GRBW array), or convert the GRBW information to a temporary CRGB object if you need to call a FastLED function. I’d imagine that the best option would be to rewrite how the NeoPixel library stores its color information to include a CRGB object, but for the time being I’m going to use the Adafruit_NeoPixel library vanilla.
If you have a good solution for mixing the two libraries together, please let me know!
14 Comments
Matthew Ebel · December 12, 2016 at 5:57 pm
Dang, those RGBWs would be perfect in my project, but only if I can use all my FastLED algorithms to generate the RGB patterns. Glad I did a quick search before clicking “add to cart”. Thanks for saving me some time and money!
Jim Bumgardner · June 21, 2017 at 5:47 pm
For a recent project, I came up with a solution that doesn’t slow things down or require double writes. Essentially, I’m using type casting so I can treat a CRGBW array as a CRGB array for the native FastLED calls that require CRGB.
In my header, I define a CRGBW struture and some support functions:
struct CRGBW {
union {
struct {
union {
uint8_t g;
uint8_t green;
};
union {
uint8_t r;
uint8_t red;
};
union {
uint8_t b;
uint8_t blue;
};
union {
uint8_t w;
uint8_t white;
};
};
uint8_t raw[4];
};
inline CRGBW& setHSVW (uint8_t hue, uint8_t sat, uint8_t val, uint8_t white) __attribute__((always_inline))
{
hsv2rgb_rainbow( CHSV(hue, sat, val), (CRGB &) *this);
this->white = white;
return *this;
}
inline CRGBW& setRGBW (uint8_t red, uint8_t grn, uint8_t blu, uint8_t white) __attribute__((always_inline))
{
this->r = red;
this->g = grn;
this->b = blu;
this->white = white;
return *this;
}
};
Then, in my code, I allocate my LED Strip as CRGBW:
CRGBW allLights[NUM_LEDS_TOTAL];
I then use typecasting to create a CRGB pointer to the same data:
CRGB *allLightsRGB = (CRGB *) &allLights[0];
I use this pointer only in the addLEDs call that needs a CRGB structure.
When setting up the LEDs, I use an adjusted LED account, that takes into account the increased size caused by the white components.
FastLED.addLeds(allLightsRGB, NUM_LEDS_TOTAL_adj);
NUM_LEDS_TOTAL_adj is essentially a rounded up version of NUM_LEDS_TOTAL*4/3. I typically hard code it. e.g.
int NUM_LEDS_TOTAL = 30;
int NUM_LEDS_TOTAL_adj = 40;
When modifying my colors, I modify my allLights values directly, like so:
allLights[idx].setRGBW(r,g,b,w);
(or setHSVW(h,s,v,w) )
and call FastLED.show() as usual.
Dave · June 23, 2017 at 12:52 am
Wow! Nice work, I’ll have to give that a try later. What strips are you using? Are you using a stock FastLED controller, or did you add your own?
Jim Bumgardner · June 23, 2017 at 2:42 am
A friend provided the strips, so not sure which ones, but they were fairly tightly spaced (we used them to make a Unicorn horn that pulsated to music). The controller was a Teensy.
Dave · June 23, 2017 at 2:52 am
What I mean is, FastLED has a number of controller types to manage different LED strips and protocols. Do you remember which LED type you were using? Or did you use the NeoPixel library to talk to the LEDs?
Jim Bumgardner · June 23, 2017 at 3:28 am
Oh whoops, WS2812B. Was actually using four strips, so the code looked like this:
FastLED.addLeds(strip1, NUM_LEDS_1_adj);
FastLED.addLeds(strip2, NUM_LEDS_2_adj);
FastLED.addLeds(strip3, NUM_LEDS_3_adj);
FastLED.addLeds(strip4, NUM_LEDS_4_adj);
Jim Bumgardner · June 23, 2017 at 3:30 am
But these were all typecast to point to the same long array, so I could encode color values in the same loop…
CRGBW allLights[NUM_LEDS_TOTAL];
CRGB *strip1 = (CRGB *) &allLights[0];
CRGB *strip2 = (CRGB *) &allLights[26];
CRGB *strip3 = (CRGB *) &allLights[41];
CRGB *strip4 = (CRGB *) &allLights[65];
Dave · June 28, 2017 at 11:36 am
Thanks again for sharing your method Jim, this is great work! I’ve modified your code slightly and created a new post explaining how it works, which you can find here. Let me know if you’d like me to tweak your acknowledgements in any way!
Jim Bumgardner · June 28, 2017 at 2:20 pm
Cool beans! Thanks for the post! I would suggest linking the first mention of my name to my website (https://jbum.com/) so people know which Jim Bumgardner you’re talking about (not the lawyer).
Regards
Jim Bumgardner · June 28, 2017 at 2:21 pm
Doh, just saw the link at the end. That’s fine – thanks!
Dave · June 28, 2017 at 2:42 pm
Fantastic. Cheers!
Nathan · December 25, 2022 at 3:46 pm
Hey Dave and Jim, thanks for the excellent work! I’m using FastLED instead of the NeoPixel library because I need the dithering support to achieve very low dimming (less than 1/256 brightness).
For reasons that yet escape me, your FastLED_RGBW.h works great for 230 LEDs, but at 240 LEDs the dithering stops working (the LEDs suddenly jump from 1/256 to off). Any idea why that might be? (I’m hoping to control ~800 LEDs total).
Here’s a minimal code example.
https://gist.github.com/nathanmelenbrink/b50f6d85eab65c0cf80cd5216d417f9c
I’d be very grateful for any suggestions. Thanks!
Dave · December 26, 2022 at 10:01 am
Hi Nathan. I saw your post on the Reddit group as well. My initial reaction was that it might be a memory issue since the ESP does some more complicated things with data storage and blocks, but on second thought I think it’s because the higher number of LEDs pushes the framerate below the dithering framerate limit.
If you move everything out of `loop()` into `setup()` so it’s just `FastLED.show()` on repeat, do you have the same results?
Nathan · December 26, 2022 at 1:17 pm
Sorry for cross-posting! I wasn’t sure if you might be away for the holidays. Thanks very much for the reply; indeed it is the framerate limit that I’m hitting.
Doing what you suggest allows me to dither 240, but not 250 LEDs. Good to know; but I think I’ll also wire in parallel as suggested on Reddit. Thanks again!