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-desctructive 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/.

19 Comments

  1. 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?

    Niklas
    1. 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.

      Dave
      1. 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!

        Niklas
        1. 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.

          Dave
  2. 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. 🙂

    Manne Danneman
    1. 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.

      Dave
      1. 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
  3. 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;
    }

    Daniel Stelzer
    1. 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;
      }

      John
      1. 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.

        cyberponk
  4. 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;
    }

    John
  5. 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).

    Raineer
    1. 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.

      Daniel

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.