SOLVED: Adding a fade effect to LED color changes

Hi All,

With the awesome assistance of the forum have written the following code for lighting effects on a puppet.

I’m using 3 single addressable LEDS to achieve this and am trying to simulate a lava / fire effect.

The only thing I would like to add now is more of a fade between the random colors instead of simply

switching from one to the next.

Any ideas how I could achieve this ? (I’ll obviously need to work on the timing once I get the fades

working?

Thank you.

#include "FastLED.h"

//#define COLOR_ORDER GRB
#define NUM_LEDS 3
#define LED_PIN 6
#define BRIGHTNESS 10

// Define the array of leds
CRGB leds[NUM_LEDS];
CRGBPalette16 gPal;

            CRGB Colours[] =
                   {
                    CRGB(255,0,0), //RED
                    CRGB(255,80,0), // ORANGE
                    CRGB(255,100,0), // LIGHT ORANGE
                    CRGB(255,140,0), // YELLOW
                    CRGB(255,255,255), //WHITE             
                  };   

           

void setup() { 
  
             Serial.begin(115200);
             FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);  
             FastLED.setBrightness( BRIGHTNESS );
}  
void loop() 
{
  int i = random(sizeof(Colours)/sizeof(Colours[0]));
  //middle horn
  leds[1] = Colours[i];
  FastLED.show();
  // below for edge horns to be same
  i = random(sizeof(Colours)/sizeof(Colours[0]));
  leds[0] = Colours[i];
  leds[2] = Colours[i];
  FastLED.show();


// Sets the White to be much quicker.
if  (i == 4) {

delay(1);
               }
if (i != 4)
              {
 
 delay(50);

              }
 
}

The only thing I would like to add now is more of a fade between the random colors instead of simply

that’s actually not as easy as it sounds. Color perception is not a continuum, there are many models representing Color space and there is not just one path that will bring you from one color to the other one.
color.png
So you need to decide on which path you want to take.

An easy one (not the best looking one) if you can decompose the RGB components of your initial color (R0, G0, B0) , and you want to go to a target RGB color (R1, G1, B1) in N steps is to just iterate N times through RGB colors calculated as

fadeRi = R0 + i * (R1 - R0) / N
fadeGi = G0 + i * (G1 - G0) / N
fadeBi = B0 + i * (B1 - B0) / N

with i varying from 1 to N inclusive

if you have HSB / HSV, then just playing the Hue makes it easier and more natural.

You could look at how the FastLed library deals with colors for ideas and gaining knowledge (read this for example)

When fading between colors it is often better to use a color model where Hue (the color) is separated from brightness and saturation. For full saturation and brightness this sketch takes a Hue value from 0 (Red) to 1023 (Violet) and converts it to R, G, and B components:

const byte  potPin = A2;
const byte RedLedPin = 9;
const byte GreenLedPin = 10;
const byte BlueLedPin = 11;


void setup ()
{
  pinMode(RedLedPin, OUTPUT);
  pinMode(GreenLedPin, OUTPUT);
  pinMode(BlueLedPin, OUTPUT);
}
void loop()
{
  int hue = analogRead(potPin);  // A hue value from 0 to 1023
  
  int hextant = (hue * 6) / 1024;  // Divide the range int 6 parts; (0-5)
  
  int fade = (6 * hue) % 1024; // How far into the part we are (0-1023)
  fade /= 4;  // 0-255


  // Convert Hue to RGB
  int Red, Green, Blue;
  switch (hextant)
  {
    case 0:  Red = 255;
      Green = fade;  // Fade from Red to Yellow
      Blue = 0;
      break;


    case 1:  Red = 255 - fade; // Fade from Yellow to Green
      Green = 255;
      Blue = 0;
      break;


    case 2:  Red = 0;
      Green = 255;
      Blue = fade;  // Fade from Green to Cyan
      break;


    case 3:  Red = 0;
      Green = 255 - fade; // Fade from Cyan to Blue
      Blue = 255;
      break;


    case 4:  Red = fade;  // Fade from Blue to Magenta
      Green = 0;
      Blue = 255;
      break;


    case 5:  Red = 255;  // Fade from Magenta to Red
      Green = 0;
      Blue = 255 - fade;
      break;
  }
  analogWrite(RedLedPin, Red);
  analogWrite(GreenLedPin, Green);
  analogWrite(BlueLedPin, Blue);
}

No need to complicate things with all sorts of color models and what not. This will create random colors with 5 second transitions:

#define TRANSITION_MILLIS 5000

byte colSrc[3], colDst[3], colCur[3]; //R=0, G=1, B=2
byte idx; //used as global indexer
unsigned long transitionStart, transitionPos;

void randomColor(byte *col)
{
  for (idx = 0; idx < 3; i++) col[idx] = random(256);
}

void setup()
{
  randomSeed(analogRead(A0));
  randomColor(colSrc);
  randomColor(colDst);
  transitionStart = millis();
}

void loop()
{
  unsigned long loopMs = millis();
  transitionPos = loopMs - transitionStart;
  if (transitionPos >= TRANSITION_MILLIS)
  {
    //done
    memcpy(colSrc, colDst, sizeof(colDst)); //copy dst to src
    memcpy(colCur, colDst, sizeof(colDst)); //set current color
    randomColor(colDst); //new dst
    transitionStart = loopMs;
  }
  else
  {
    //in progress
    for (idx = 0; idx < 3; idx++) colCur[idx] = map(transitionPos, 0, TRANSITION_LENGTH-1, colSrc[idx], colDst[idx]);
  }

  //Here "colCur" is the current RGB value of the transition

}

The code is untested but it should work with minor modifications.

@danois

You can sure do stuff that looks ugly (the RGB maping) - it’s a free world :slight_smile:

(Note that map is very similar to the formula I presented )

Thank you all :slight_smile:

Sounds a bit too complicated for me at this stage :slight_smile:

Venomouse:
Thank you all :slight_smile:
Sounds a bit too complicated for me at this stage :slight_smile:

it’s not… your colors are RGB as you create them with

CRGB Colours[] =
                   {
                    CRGB(255,0,0), //RED
                    CRGB(255,80,0), // ORANGE
                    CRGB(255,100,0), // LIGHT ORANGE
                    CRGB(255,140,0), // YELLOW
                    CRGB(255,255,255), //WHITE             
                  }

imagine you want to move from the second one, orange CRGB([color=red]255[/color], [color=green]80[/color], [color=blue]0[/color]) to the last one white CRGB(255, 255, 255)

An “easy” (but as I said not the best looking path) way to perform the transition is to create a number of steps that will transform each individual components of your RGB initial value orange CRGB([color=red]255[/color], [color=green]80[/color], [color=blue]0[/color]) into the one for the white target CRGB(255, 255, 255)

so you basically need a math formula that takes your parameters of CRGB()
from 255 to 255
from 80 to 255
from 0 to 255

the easy way to do this in N steps is to look at how much you need to change the value at each steps. So you start from the initial value and add (or subtract) a bit at each stage. that’s what the formula above was doing

for example for the red channel:

fadeRi = R0 + i * (R1 - R0) / N

at i = 0, the second part of the equation disappear (multiplied by 0) and you can see that
fadeR0 = R0 + 0 * (something) = R0

and at i = N you can see that
fadeRN = R0 + N * (R1 - R0) / N = R0 + (R1 - R0) = R1

so we start at the initial color, ends at the target one and in between you move between the 2

does it make it easier to understand what we mean ?


that being said, just noticed you are actually using the FastLED library… So instead of declaring your first array with RBG colors, use HSV colors with the function CHSV( Hue, Saturation, Value aka brighness);

if you always want your led at constant brightness and saturation, then you don’t even need to have those values in the array and you can store in your array only the hue of the LED you want (the “hue” is kinda the color).

so when you want to move from one color to the other one, say from Hue = 10 to Hue = 67 then you just need to do the same simple math formula I had above for that hue element

It is not complicated, some ppl are over-complicating it in order to show off! :wink: My example with arrays of 3 bytes (red, green and blue components) are more or less interchangeable with the CRGB structure:

#define TRANSITION_MILLIS 5000

const byte MINIMUM_BRIGHTNESS[] = {
  50, //Red
  50, //Green
  50 //Blue
};

CRGB colSrc, colDst, colCur;
byte idx; //used as global indexer
unsigned long transitionStart, transitionPos;

void randomColor(CRGB &col)
{
  for (idx = 0; idx < 3; i++)
    col.raw[idx] = MINIMUM_BRIGHTNESS[idx] + random(256 - MINIMUM_BRIGHTNESS[idx]);
}

void setup()
{
  randomSeed(analogRead(A0));
  randomColor(colSrc);
  randomColor(colDst);
  transitionStart = millis();
}

void loop()
{
  unsigned long loopMs = millis();
  transitionPos = loopMs - transitionStart;
  if (transitionPos >= TRANSITION_MILLIS)
  {
    //done
    memcpy(colSrc, colDst, sizeof(colDst)); //copy dst to src
    memcpy(colCur, colDst, sizeof(colDst)); //set current color
    randomColor(colDst); //new dst
    transitionStart = loopMs;
  }
  else
  {
    //in progress
    for (idx = 0; idx < 3; idx++)
      colCur.raw[idx] = map(transitionPos, 0, TRANSITION_LENGTH-1, colSrc.raw[idx], colDst.raw[idx]);
  }

  //Here "colCur" is the current CRGB value of the transition

}

I have added “MINIMUM_BRIGHTNESS” to prevent the produced colors from being too dark. The example works for one CRGB value. It should be fairly easy to convert it to an array of CRGB’s! :slight_smile:

Danois90:
It is not complicated, some ppl are over-complicating it in order to show off! :wink:

I take this is for me :slight_smile:

You are proposing code, but no explanation. The aim of this forum is educational.

There is no intent to show off; There was an educational intent there to help OP understand that what appears as a simple question "moving from one color to another color" has actually more than meet the eyes and you can take an infinite number of path.

--> In my example above, do you take the path going towards more green and yellow before reaching the target color, or do you take the path towards the blue before reaching the target color.

Can you describe which path the code you are offering is taking and why it's good ?

--> Secondly you are not even proposing a different solution than the one I presented, you step from each initial R, G and B component to the target ones using the map function, which is implementing the formula I presented.

So basically YOU are showing off by trying to dump code on OP who probably does not understand what it does and how to use it and you provide no explanation that helps members coming here to learn something...

Secondly, the whole industry (which johnwasser commented as well upon) thinks that because of the inadequacy of the RGB model to handle playing with transition or comparing colors, going to the Hue model makes more sense for what the OP wanted to do... again something you failed to mention...

So that's why I find your post poor...

Now, it's a free world, so I respect the fact that you think I'm (or johnwasser is) showing off and respectfully disagree :smiling_imp:

I am assuming that code can be self-explanatory :slight_smile: Often a minimal code sample is more explanatory than 3 pages of theory and by trying the code, experimenting with different variables, one may learn more than by reading the 3-page theory behind it. The code I have posted is supposed to "fade between the random colors" as OP asked for, if OP does not understand the code, he may ask for further dissection. We do not disagree that my sample will not create any "lava effect", but it will fade between colors, so how (and if) OP implements it is OP's choice.

In your signature you have "teach what's right!"... dumping code is not teaching in my view.

but it's OK if we agree to disagree :slight_smile:

Awesome explanation thank you.

J-M-L:
it’s not… your colors are RGB as you create them with

CRGB Colours[] =

{
                    CRGB(255,0,0), //RED
                    CRGB(255,80,0), // ORANGE
                    CRGB(255,100,0), // LIGHT ORANGE
                    CRGB(255,140,0), // YELLOW
                    CRGB(255,255,255), //WHITE           
                  }




imagine you want to move from the second one, orange `CRGB([color=red]255[/color], [color=green]80[/color], [color=blue]0[/color])` to the last one white CRGB(255, 255, 255)

An "easy" (but as I said not the best looking path) way to perform the transition is to create a number of steps that will transform each individual components of your RGB initial value orange `CRGB([color=red]255[/color], [color=green]80[/color], [color=blue]0[/color])` into the one for the white target CRGB(255, 255, 255)

so you basically need a math formula that takes your parameters of ` CRGB()`
from 255 to 255
from 80 to 255
from 0 to 255

the easy way to do this in N steps is to look at how much you need to change the value at each steps. So you start from the initial value and add (or subtract) a bit at each stage. that's what the formula above was doing

for example for the red channel:

fadeR<sub>i</sub> = R<sub>0</sub> + i * (R<sub>1</sub> - R<sub>0</sub>) / N 

at i = 0, the second part of the equation disappear (multiplied by 0) and you can see that 
fadeR<sub>0</sub> = R<sub>0</sub> + 0 * (something) = R<sub>0</sub>

and at i = N you can see that 
fadeR<sub>N</sub> = R<sub>0</sub> + N * (R<sub>1</sub> - R<sub>0</sub>) / N = R<sub>0</sub> + (R<sub>1</sub> - R<sub>0</sub>) = R<sub>1</sub>

so we start at the initial color, ends at the target one and in between you move between the 2

does it make it easier to understand what we mean ?




---



that being said, just noticed you are actually using the FastLED library.... So instead of declaring your first array with RBG colors, use HSV colors with the function `CHSV( Hue, Saturation, Value aka brighness);`

if you always want your led at constant brightness and saturation, then you don't even need to have those values in the array and you can store in your array only the hue of the LED you want (the "hue" is kinda the color).

so when you want to move from one color to the other one, say from Hue = 10 to Hue = 67 then you just need to do the same simple math formula I had above for that hue element

CHSV( Hue, Saturation, Value aka brighness);

this will do the job, another way I can achieve this is to put the same colours in the array with lower brightness.

Seems to work..

Thank you.

Made it simple, got rid of array, basically used random number generator for each value, the CHSV code stuff made more sense in my head, once I programmed it all in I thought…hang on…why not do it this way…

(This is going to be a part of 4 items I’'m trying to keep the code as short and simple (for my brain to comprehend) as possible.

#include "FastLED.h"

//#define COLOR_ORDER GRB
#define NUM_LEDS 3
#define LED_PIN 6

// Define the array of leds
CRGB leds[NUM_LEDS];


void setup() { 
  
             Serial.begin(115200);
             FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);  
  
}  
void loop() 
{
  int H = random(0,50); //HUE
  int S = random(180,255); //SAT
  int V = random(180,255); //BRIGHT
  //middle horn
  leds[1] = CHSV(H,S,V);
    FastLED.show();
  // below for edge horns to be same
  H = random(0,50); //HUE
  S = random(180,255); //SAT
  V = random(180,255); //BRIGHT
  leds[0] = CHSV(H,S,V);
  leds[2] = CHSV(H,S,V);
  FastLED.show();
  


// Sets the White to be much quicker.
if ( H + S + V == 0) 
  {
    delay(1);
  }
  
if ( H + S + V != 0) 
  {
   delay(120);
  }
 
}

Hello J-M-L

J-M-L:
it’s not… your colors are RGB as you create them with

CRGB Colours[] =

{…

Many thanks for your detailled example.

Best Regards,
bidouilleelec[/code]

Venomouse:
Made it simple, got rid of array, basically used random number generator for each value, the CHSV code stuff made more sense in my head, once I programmed it all in I thought...hang on....why not do it this way....

Simplifying is always good :slight_smile:

careful on types

  int H = random(0,50); //HUE
  int S = random(180,255); //SAT
  int V = random(180,255); //BRIGHT

you should used byte or uint8_t as you want to hold a value between 0 and 255

if you wan't different random numbers everytime you reboot, you should use randomSeed() and add into your setup()randomSeed(analogRead(0));

Note that the random() function never returns the upper bound, so you will never get 255 with your code. if you want the max value, use 256 in the function call.

When you do

  // Sets the White to be much quicker.
  if ( H + S + V == 0)
  {
    delay(1);
  }

you can't add H,S and V to see if it's 0 to have white.

Actually when you use the HSV mode, white means "any color that is totally desaturated and still emits some light"

-> so if S is 0 (desaturated) then you see white - unless value is 0 which means means "completely dark: black.

from the documentation

"saturation" is a one-byte value ranging from 0-255, where 255 means "completely saturated, pure color", 128 means "half-saturated, a light, pale color", and 0 means "completely de-saturated: plain white".

"value" is a one-byte value ranging from 0-255 representing brightness, where 255 means "completely bright, fully lit", 128 means "somewhat dimmed, only half-lit", and zero means "completely dark: black."

so if you want to test for white, use

if ((S == 0) && (V != 0)) {
   // white
   ...
} else {
   // not white
   ...
}

Pardon me for hijacking this thread, but your explanation to the OP was very good for any novice indeed.

The aim of this forum is educational.

Maybe you could explain another FastLED issue, one I'm struggling with.

I have a function that fills the leds array with a new colour fetched from a FastLED gradient palette via CRGB colour = ColorFromPalette(activePalette, paletteIndex, maxBrightness, LINEARBLEND) and fill_solid(leds, ledCount, colour) with EVERY_N_SECONDS. A two-hour transition through a palette, so to speak. This works well.

But now I want to darken/brighten randomly selected LEDs in leds around 20%, so the entire strip is shimmering while the palette transition does its thing independently. You wrote that HSV is preferred. That means I somehow need to operate on the V (value) of the randomly selected LEDs - but FastLED has no "leds(i).incrementV" or "leds(i).decrementV" methods I'm aware of.

How would you do that? Many thanks in advance!