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.


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.


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.


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


  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?

    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.

      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!

        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.

  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.

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

    Manne Danneman Lundkvist
  4. 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;

      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.

  5. 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;

  6. 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).

    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.

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

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

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


    #define PIN 6

    // 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:
    // 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)

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

    FastLED_NeoMatrix *matrix = new FastLED_NeoMatrix(ledsRGB, mw, mh, mw/8, 1,

    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));

    int x = mw;
    int pass = 0;

    void loop() {

    matrix->setCursor(x, 0);
    if(–x width();
    if(++pass >= 3) pass = 0;

    Jonas Andersson
    1. 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.

  8. 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

    1. 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 point address.


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.