Last fall when I was working on the now-defunct ‘Footwell NeoPixels’ project I wrote a short post talking the fact that you cannot use the FastLED library with RGBW leds, and have to deal with the clunkier Adafruit NeoPixels library.

Well last week, a man named Jim Bumgardner commented on that post and shared his method for doing just that: using the FastLED library with RGBW leds.

Preface

Before we get started, you should know one thing: this is a hackIt is not a supported part of the library, it only works with SK6812 leds (that I’m aware of), and it does not work perfectly. But, it does work!

Daniel Garcia and the rest of the FastLED team have said that they are working on an RGBW upgrade behind the scenes, but they have no time table for when it will be completed. In the meantime, this is a quick and dirty fix.

The Hack

This hack works by exploiting the fact that WS2812B LEDs and SK6812 LEDs have similar data timing. It’s not identical, but it’s close enough to make this work. The trick is to tell FastLED that we’re writing to a WS2812B strip, but insert an extra ‘white’ byte between each RGB group.

To do this, we create a special ‘CRGBW’ struct that adds the extra byte, and a few helper functions to integrate with FastLED. I rewrote Jim’s code a bit so the assignment methods more closely match their FastLED counterparts, and I also added an operator overwrite so you can assign CRGB colors directly. Here’s the code, in full:

Download and save this file as “FastLED_RGBW.h”, and place it the same folder as your sketch. Then include it in your sketch by typing:

When defining the LED array, we also need a CRGB pointer to send to the addLEDs function. We do this like so:

Lastly, when we create the FastLED controller we need to inflate the number of LEDs to account for the extra bytes. The header file has a helper function for this, named getRGBWsize . The function takes the number of RGBW LEDs as an argument and returns the number of CRGB elements that cover that many bytes.

Now when we create the FastLED controller in the setup  function, we use both the CRGB pointer and the size function:

Once the strip is set up, you can set LED colors just like you would normally:

Example Sketch

Here is a basic example sketch that works with SK6812 strips. It does a color wipe for each channel (red, green, blue, and white), then does a rainbow pattern for a 5 seconds and repeats.

Limitations

FastLED is a huge library, and unfortunately not all of its helper functions work with this hack. From my testing, while the basic features of the library work the more advanced ones do not. For instance – while non-destructive brightness works perfectly, the color temperature functions do not work at all.

These problems come down to the additional bytes added by the CRGBW struct, in combination with the hard-coded color order. Any function that references the raw array members will be cyclically referencing the wrong color channels.

The other obvious limitation is that this only works with RGBW strips that share protocols already in the FastLED library. SK6812 strips (RGBW NeoPixels) work fine using the WS2812B protocol, but you may need to write your own protocol class if you cannot find one already supported.

Lastly, a not-so-obvious limitation is that for LED byte counts that are not factors of 12, junk data is sent past the defined LED array, causing stray colors to show up on the next LED. This is only an issue if the number of LEDs in the code is less than the number of physical LEDs. You can also get around this by defining the CRGBW array as one element larger than necessary, and leaving those last four bytes at 0.

Conclusion

This is mostly a proof of concept, rather than a turn-key solution. But if you have some color patterns generated using the FastLED library, this will let you use those patterns with RGBW strips until the library properly supports RGBW strips.

A huge thanks to Jim Bumgardner for sharing his method. If you found this post useful, please check out his website at http://krazydad.com/.


25 Comments

Joel · August 14, 2017 at 10:28 pm

Thanks for this. Just setup a whole FastLED project with these not realizing I bought the RGBW. Hope I can get this working!

    Dave · August 15, 2017 at 12:26 am

    Best of luck!

Niklas · October 17, 2017 at 8:39 am

Thanks a lot for your article!
However the script doesn’t compile and gives me that error:

sketch\FastLED_RGBW.h:45:64: error: ISO C++ forbids declaration of ‘operator=’ with no type [-fpermissive]

What could cause the issue?

    Dave · October 17, 2017 at 9:22 am

    What compiler are you using? This compiles fine for me using the Arduino IDE (1.8.2).

    Trying adding the keyword ‘void’ between ‘inline’ and ‘operator’ in the header file. That might be an oversight on my part.

      Niklas · October 17, 2017 at 10:15 am

      I’m using the IDEversion 1.8.5. Declaring it as void helped.

      Now I tried running your demo script, but the LEDs (SK6812 RGBW) do nothing.
      Everything works fine with the Neopixel Library.
      What else could have gone wrong?

      Thanks in advance!

        Dave · October 17, 2017 at 10:32 am

        I’m not sure. I just tried using the code above and it works perfectly for me.

        Make sure you have the latest version of FastLED installed, and then check the hardware. My hardware setup for the RGBW strips is an Arduino Uno connected through a 470 Ω resistor, with everything powered directly by USB (only 8 LEDs on my demo board). Check that the voltages are as expected and if you have a logic analyzer or oscilloscope, check the output of the data pin.

        As I said in the post – this is a hack, and the timings between the two strip types are at their min/max limits. If your hardware is different the timings may be off enough to not work at all.

Manne Danneman · July 12, 2018 at 4:45 pm

Hi.
Impressive work!

I did get the example sketch to run all RGBW. Do you have any idea how to get this to work with Ambibox or Prismatik?

note: I know very little about this stuff. Just trying to learn and to get some ambilight up and running. 🙂

    Dave · July 22, 2018 at 9:55 am

    I wouldn’t use RGBW leds if you’re getting started, because none of the ambilight software I know of uses RGBW either. You’d just be using an RGBW strip as RGB, and going through quite a bit of hassle to get it to work.

    That being said, you could use my ambilight code as a foundation. Though you’d need to add some extra code to keep track of the channel index to insert a ‘0’ byte at each white address.

      Manne Danneman Lundkvist · September 9, 2018 at 12:09 pm

      Hey! Thanks for the answer! Didn’t notice it until now.

      Yes I noticed it was way harder to figure it out with RGBW. So much I actually put it on hold for now. Haha. I just figured that eventually I will find more time to learn more about this or that someone would put up a working template that I could use. I was looking for a challenge but soon realised it was above my head.

      One thought I had was to just buy a ordinary RGB strip and use that. But it feels like such a shame when I have 3 m of this amazing looking (and expensive) strip. 🙂

      Thank you again for the answer!

Manne Danneman Lundkvist · July 18, 2018 at 2:51 pm

Hi.

I did manage to get the example sketch to work with a 144leds/m SK6812. But got no connection with prismatik or ambibox. Any idea why not? 🙂

Pete · December 1, 2018 at 6:10 am

awesome stuff, up and running in no time 🙂

Daniel Stelzer · December 3, 2018 at 5:51 pm

This saved my project! I made a couple enhancements, having it auto-calculate the white value instead of leaving at zero if it’s not specified. Add this constructor in addition to the given one, and overwrite operator= as follows:

CRGBW(uint8_t rd, uint8_t grn, uint8_t blu){ // If we pass in three values, correct for white
w = min(rd, grn);
w = min(w, blu); // Now w is the smallest of the three values

r = rd – w;
g = grn – w;
b = blu – w;
}

inline void operator = (const CRGB c) __attribute__((always_inline)){ // Turn RGB into RGBW with automatic white correction
uint8_t w = min(c.r, c.g);
w = min(w, c.b); // Now w is the smallest of the three values

this->r = c.r – w;
this->g = c.g – w;
this->b = c.b – w;
this->w = w;
}

    Dave · December 3, 2018 at 6:25 pm

    I’m glad you found it useful! Those look like useful changes as well.

    John · March 7, 2019 at 9:49 pm

    Nice, but this transform will lead to unsaturated colors shifting as you bring up the brightness. This leads to colors that appear pastel. Correcting for this requires a little bit of calibration, but the results are worth it.

    What we need to do is to find what values of RGB correspond to the white LED’s output. The good news is SK6812s appear to be using linear PWM. This actually makes it much easier as the apparent lumens will be linear as well. This lets us keep all the calculations in luminosity and avoid that nasty cube-root that lightness calculations would bring in – and a single point of reference will be enough to figure this out.

    The trick is to find the brightest RGB-only value that is the closest match to a White-only value. I am using the warm white version of the LEDs. With the batch I have, RGBW of 255,128,34,0 is a very close match to 0,0,0,100, both in color and in brightness.

    Here the ratio of red to white is 255/100, green to white is 128/100, and blue to white is 34/100. We use these weights to figure out the maximum amount of output we can use from the white LED without altering the color. Just as in your example we can then subtract the white LEDs contribution from the other colors.

    This is completely unoptimized with floats and lots of divisions:

    //Calibrated values
    const float RC = 255;
    const float GC = 128;
    const float BC = 34;
    const float WC = 100;

    CRGBW(uint8_t rd, uint8_t grn, uint8_t blu) {
    //Normalized red, green, and blue
    float rn = rd * WC / RC;
    float gn = grn * WC / GC;
    float bn = blu * WC / BC;
    //Find maximum white value that won’t desaturate
    float wn = min( rn, gn );
    wn = min( wn, bn );
    //Remove red, green, and blue contributions to be supplied by the white LED
    r = rd – wn * RC / WC;
    g = grn – wn * GC / WC;
    b = blu – wn * BC / WC;
    w = wn;
    }

    With a few overflow checks, some values with RGB components greater than 255 are possible:

    CRGBW(uint16_t rd, uint16_t grn, uint16_t blu) {
    //Normalized red, green, and blue
    float rn = rd * WC / RC;
    float gn = grn * WC / GC;
    float bn = blu * WC / BC;
    //Find maximum white value that won’t desaturate
    float wn = min( rn, gn );
    wn = min( wn, bn );
    wn = min( wn, 255 ); //maximum white contribution reached.
    //Remove red, green, and blue contributions supplied by the white LED
    rn = rd – wn * RC / WC;
    gn = grn – wn * GC / WC;
    bn = blu – wn * BC / WC;
    //Cap red, green, and blue values at 255.
    r = max( rn, 255 );
    g = max( gn, 255 );
    b = max( bn, 255 );
    w = wn;
    }

      cyberponk · March 15, 2019 at 1:14 am

      If anyone is trying to modify the code with the enhancements above, and is getting compiling errors, I had do re-type the “-” chars because the copied ones were not read as subtraction by the compiler.

      Daniel Stelzer · March 23, 2019 at 6:01 pm

      Very nice! I’m adding these modifications to my version. But, in the last assignments, don’t you want min instead of max?

John · March 24, 2019 at 1:44 pm

Quite right. I was doing on the logic on the PC side, sending the rgbw values across the serial port. When rewritting it inside the CRGBW ctor, I replaced the ternary operator to attempt make it clear why it was being done… oops. At some point I’ll probably revisit it to make it more 8-bit friendly unless someone else does it first.
For anyone cutting and pasting, this is probably what you want to start with:


//Calibrated values
const float RC = 255;
const float GC = 128;
const float BC = 34;
const float WC = 100;

void CRGBW(uint16_t rd, uint16_t grn, uint16_t blu) {
//Normalized red, green, and blue
float rn = rd * WC / RC;
float gn = grn * WC / GC;
float bn = blu * WC / BC;
//Find maximum white value that won’t desaturate
float wn = min( rn, gn );
wn = min( wn, bn );
wn = min( wn, 255 ); //maximum white contribution reached.
//Remove red, green, and blue contributions supplied by the white LED
rn = rd - wn * RC / WC;
gn = grn - wn * GC / WC;
bn = blu - wn * BC / WC;
//Cap red, green, and blue values at 255.
r = min( rn, 255 );
g = min( gn, 255 );
b = min( bn, 255 );
w = wn;
}

Raineer · June 9, 2019 at 6:48 pm

Does someone perhaps have a similar code also for RGBWW? (Rgb + WarmWhite + Coldwhite).
Got a TM1812 5 in 1 Strip, red and green are working properly, but when I set the pixels to Blue, they turn to WarmWhite…. ColdWhite is working fine with your little hack (leds[0].w = 255 gives me perfect WarmWhite).

    Daniel · June 11, 2019 at 3:59 pm

    I don’t have a TM1812 to test this with, but: try making an array of two CRGBs (just normal RGB, not RGBW), telling FastLED to use WS2812s, then turn on each of the six channels in sequence and see what happens? If each channel corresponds to one of your colors, then it would only take a small modification to make this code work for you.

Jonas Andersson · July 2, 2019 at 2:49 am

Hi thank you for sharing this awesome hack 🙂 I have been struggling with my SK6812 LED strips and getting it to work with a proper driver library. I have been playing around with “marcmerlin”‘s FAST_LED library combined with the NEO_MATRIX librray for easy matrix text and GFX handeling.
Here—-> https://github.com/marcmerlin/FastLED_NeoMatrix

But i am straggling to get it working even tho I follow you setup and include the RGBW header file like you show.

Code:
// Adafruit_NeoMatrix example for single NeoPixel Shield.
// Scrolls ‘Howdy’ across the matrix in a portrait (vertical) orientation.

#include
#include
#include
#include

#define PIN 6

// MATRIX DECLARATION:
// Parameter 1 = width of NeoPixel matrix
// Parameter 2 = height of matrix
// Parameter 3 = pin number (most are valid)
// Parameter 4 = matrix layout flags, add together as needed:
// NEO_MATRIX_TOP, NEO_MATRIX_BOTTOM, NEO_MATRIX_LEFT, NEO_MATRIX_RIGHT:
// Position of the FIRST LED in the matrix; pick two, e.g.
// NEO_MATRIX_TOP + NEO_MATRIX_LEFT for the top-left corner.
// NEO_MATRIX_ROWS, NEO_MATRIX_COLUMNS: LEDs are arranged in horizontal
// rows or in vertical columns, respectively; pick one or the other.
// NEO_MATRIX_PROGRESSIVE, NEO_MATRIX_ZIGZAG: all rows/columns proceed
// in the same order, or alternate lines reverse direction; pick one.
// See example below for these values in action.
// Parameter 5 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic ‘v1’ (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

// Example for NeoPixel Shield. In this application we’d like to use it
// as a 5×8 tall matrix, with the USB port positioned at the top of the
// Arduino. When held that way, the first pixel is at the top right, and
// lines are arranged in columns, progressive order. The shield uses
// 800 KHz (v2) pixels that expect GRB color data.
#define mw 8
#define mh 32
#define NUMMATRIX (mw*mh)

CRGBW leds[NUMMATRIX];
CRGB *ledsRGB = (CRGB *) & leds[0];
//CRGB matrixleds[NUMMATRIX];

FastLED_NeoMatrix *matrix = new FastLED_NeoMatrix(ledsRGB, mw, mh, mw/8, 1,
NEO_MATRIX_TOP + NEO_MATRIX_RIGHT +
NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG );

const uint16_t colors[] = {
matrix->Color(255, 0, 0), matrix->Color(0, 255, 0), matrix->Color(0, 0, 255) };

void setup() {
FastLED.addLeds(ledsRGB, getRGBWsize(NUMMATRIX));
matrix->begin();
matrix->setTextWrap(false);
matrix->setBrightness(40);
matrix->setTextColor(colors[0]);
}

int x = mw;
int pass = 0;

void loop() {

matrix->fillScreen(0);
matrix->setCursor(x, 0);
matrix->print(F(“Howdy”));
if(–x width();
if(++pass >= 3) pass = 0;
matrix->setTextColor(colors[pass]);
}
matrix->show();
delay(100);
}

    Dave · July 2, 2019 at 5:53 pm

    My guess is that the matrix library is incompatible. This is a good example of how adding an additional byte per pixel throws off a lot of code. The matrix library likely sees each LED as a group of 3 bytes and not as a struct, so when you treat it like a matrix everything goes sideways.

    I’d recommend trying to modify the matrix library or write your own implementation that accounts for 4 bytes per pixel.

Manu · September 26, 2019 at 12:08 am

Hi Dave,

i want to try your Code to make the Adalightsketch working for Prismatic… but it doesnt work … only your example is working fine … It is possible that you post the complete working Code that i can load them to my arduino nano and make my sk8612 working with prismatik again ? if i understand all of them here correctly than your code will change rgb into rgbw … in my head it worked … but not on my PC … please help me … THX a lot

    Dave · September 26, 2019 at 2:26 am

    There’s no real benefit to using an RGBW strip with Prismatik because Prismatik only outputs RGB colors. You would also need to modify whatever Arduino code you’re using for Prismatik to take the different color channels into account – most of them (mine included) just increment the pointer address.

Mark Kriegsman · January 20, 2020 at 4:14 pm

Hi, FastLED maintainer Mark Kriegsman here — Nice hack! Nice way to get some data going out pin, using the built-in data transfer functions! We were working on RGBW support when we lost Dan Garcia in a tragic accident last fall, and then I took some time off. But we’re now starting to get back up and running, and full-on support for RGBW is definitely one of our top topics. Thanks for sharing this great hack in the meantime!
-Mark

    Dave · January 20, 2020 at 10:17 pm

    Hi Mark!

    Those are some very kind words, although I can’t take all the credit. It was Jim Bumgardner (https://krazydad.com/) who discovered and shared this method, I was just the one who wrote it down.

    I was so sorry to hear about Dan. I didn’t interact with him much but he always struck me as a helpful and friendly guy. I’m glad you guys are finding a way to keep the library alive in his absence. Good luck with the RGBW conversion – I’m eager to see how it turns out!

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Would you like to know more?