Skip to content

Issues with RGBW LEDs (SK6812) on FAST EXP RGB headers (WS2812 native output) #1939

@bosh

Description

@bosh

There's nothing we can do about this except implement the FAST ER command for native RGBW support. That will then require native RGBW users on FAST to upgrade their EXP firmware to 0.48.

I've spent far, far too long on this problem and had such little success finding relevant information online that I think it's worth spending a little more time documenting for posterity, if someone ever is foolish enough to try what happened here and wants to understand what's happening. Good luck to you! I can describe the problem, the hack solution that we use today (and hopefully not for long into 2026), the underlying reason this all happens, and even the pattern and how to derive it. But it still hurts my head to think about, and the numbers get kind of swimmy if you look too hard at them.

Let's begin.

So you have a FAST EXP board and some RGB LEDs, maybe standalone, maybe GI inserts, maybe some strings of several lights. Awesome! You wire them up in series, configure them in MPF using number or previous, write some shows, and it just works. Great!

Then you pick up some sweet RGBW lights, maybe some Cobrapin rings, or maybe a full-on string. You wire them up to an open EXP header, or maybe at the end of an existing RGB chain, add them to the config (using the RGB type!?)

(Note, light show not shown, but it's a simple R, G, B, W, off test loop for lights by catchall "*".

Common setup for all examples:

hardware:
  platform: fast

fast:
  exp:
    boards:
      exp:
        model: FP-EXP-0081

Your first RGBW config!

lights:
  one: {number: exp-1-1}
  two: {number: exp-1-2}
  three: {number: exp-1-3}

(vid 1)

Well that was weird - the first light was mostly correct, but the second and third lights were completely wrong, and sometimes off when they should have been lit!

So you switch to rgbw and you get a crash:

lights:
  one: {type: rgbw, number: exp-1-1}
mpf.exceptions.config_file_error.ConfigFileError: Config File Error in light.e111: Type rgbw does not match channels [{'number': 'exp-b0-1-1-0', 'subtype': None, 'platform': 'fast', 'platform_settings': {}}, {'number': 'exp-b0-1-1-1', 'subtype': None, 'platform': 'fast', 'platform_settings': {}}, {'number': 'exp-b0-1-1-2', 'subtype': None, 'platform': 'fast', 'platform_settings': {}}] for light one Error Code: CFE-light-12 (https://missionpinball.org/logs)

So you play around and find start_channel:

lights:
  one: {type: rgbw, start_channel: exp-1-1-0}
  two: {type: rgbw, start_channel: exp-1-2-0}
  three: {type: rgbw, start_channel: exp-1-3-0}

Crashes...

mpf.exceptions.config_file_error.ConfigFileError: Config File Error in light: Duplicate number <class 'mpf.platforms.fast.fast_led.FASTLEDChannel'> B4001-0 for light <light.two> Context: ('fast', 'B4001-0', <class 'mpf.platforms.fast.fast_led.FASTLEDChannel'>) Error Code: CFE-light-10 (https://missionpinball.org/logs)

Right, RGBW take four channels per light unit, but FAST addressing is three-channel. So you need to give yourself a channel offset.

lights:
  one: {type: rgbw, start_channel: exp-1-1-0}
  two: {type: rgbw, start_channel: exp-1-2-1}
  three: {type: rgbw, start_channel: exp-1-3-2}

But that just changes the pattern of incorrectness, and somehow even still has moments where the a light is fully off when it should not be...

(vid 2)

It turns out RGBW LED strings are commonly chains of SK6812 units, which can vary, but are most commonly GRBW ordered. So let's also change the type name, maybe that'll fix it! (no)

lights:
  #etc
  three: {type: grbw, start_channel: exp-1-3-2}

(vid3)

Different again, but still not even close to right! Maybe each unit just needs a different custom channel order in its type definition... Spoilers - nope, the correct channel order is impossible to achieve with just simple reordering.

The pattern you actually need of channel numbers is not just R +1 => G + 1 => B + 1 => W, nor is it the corrected G => R => B => W. Thus leading to the pattern R1 G1 B1 R2 G2 B2 R3 G3 B3.
It is in fact, R1 G1 B1 G2 W1 R2 W2 B2 G3 B3 R3 W3... wat
Stated another way, the channel jumps are not always, +1 (or 0 for the first address), they are:
+1, +1, +1, +2, +1, -2, +4, -1, +4, +2, -1, +2, +1 (and then repeat sets of 12)

Or reformatted:

R   G   B   W
+1, +1, +1, +2 # Light 1
+1, -2, +4, -1 # Light 2
+4, -2, +1, +2 # Light 3

Put another way, the lights are offset from what you'd expect their ordering to be (a matrix of +1s) by:

R   G   B   W
=0, =0, =0, +1 # Light 1
+1, -2, +1, -1 # Light 2
+2, -1, -1, =0 # Light 3

or 0,0,0,1,1,-2,1,-1,+2,-1,-1,0

So you give up and hand-define the channels for every component of your RGBWs, like:

lights:
  one:
    channels:
      red:   { number: exp-1-1-0 } #+1
      green: { number: exp-1-1-1 } #+1
      blue:  { number: exp-1-1-2 } #+1
      white: { number: exp-1-2-1 } #+2

  two:
    channels:
      red:   { number: exp-1-2-2 } #+1
      green: { number: exp-1-2-0 } #-2
      blue:  { number: exp-1-3-1 } #+4
      white: { number: exp-1-3-0 } #-1

  three:
    channels:
      red:   { number: exp-1-4-1 } #+4
      green: { number: exp-1-3-2 } #-2
      blue:  { number: exp-1-4-0 } #+1
      white: { number: exp-1-4-2 } #+2  

What a pain. At least you don't need to include type: anymore :)

Anyway why does this happen?

Well it turns out the WS2812 and SK6812 protocols are not generally RGB and RGBW-ordered in how data is transmitted on the LED data line. They're actually GRB and GRBW.

But the FAST serial command is RGB! So under the hood, EXP remaps your RGB values to GRB values, swapping the first and second position of every triplet sent in a RD command. The corrected data stream is sent to the LED chain on the header, which each take off the next four encoded light levels and attempt to use them for their G R B and W components.

So that's it -- the SK6812 protocol needs to evaluate the data input as sets of four values, with the first and second swapping position relative to RGB(W), and the third and fourth position staying in place. But the WS2812 protocol consumes data in triplets, swapping the first and second position relative to RGB, and leaving the third position intact.

If RGB data comes in as A B C D E F G H i J K L (A is red 1, B is green 1, C is blue 1, D is red 4 etc)
then GRB data needs to be read out as B A C, E D F, H G i, K J L
but RGBW data being sent as RGB data will also appear as A B C... J K L (a-d are rgbw1, e is red2, etc)
but needs to be read out as B A C D, F E G H, J i K L,

so compare:

B A C E D F H G i K J L # RGB x4
B A C D F E G H J i K L # RGBW x3

Going from letter B through L left, let's look at how far offset each letter is when an SK6812 RGBW wants to use it, vs where the RGB WS2812 data stream has the correct letter:

B is first in both, so =0
A is second, =0
C is third = 0
D is offset by one, so +1 lookahead
F is offset one, +1
E is offset back, -2
G is +1
H is -1
J is +2
i is -1
K is -1
and L is =0

0,0,0,1,1,-2,1,-1,+2,-1,-1,0
The same pattern as before!

But this means that there is an inherent issue with trying to mix SK6812s with WS2812s when the issuing data is coming in as RGB needing conversion -- the three position swap and the four position swap will always combine to upset the channel ordering in this way.

So in my long-winded opinion, I think this is unfixable as long as we're using commands that take RG-ordering and do conversion behind the scenes instead of letting the caller directly affect real channel values. Our best bet is to implement the ER command features and strongly recommend not mixing LED types.

Can close this issue :P

Thanks for reading! I hope this helps someone at some point!

(videos later, it's late)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions