Customized color selection for RGB strip w/fade up/down

Hey folks,

So, after my initial project which involved a single MOSFET powering an RGB strip (all three colors tied together), I started into the next step of wanting to be able to select the particular color instead. I'm thinking of 6 to 8 default colors that can be chosen (Red, green, blue, Yellow, orange, purple, cyan, etc.)

I'm looking for some suggestions. I took my existing code and circuit, added two more MOSFETs to break out each color. This is now my new baseline.

So, thought one was to use a switch/case solution, where depending on the current button count, that will refer to the color I wish to affect in the code (currently fades up, waits 10 min, fades down unless switch is closed sooner than that).

I started to look into functions, but I am thinking the way the code is currently written, I would require a major re-write.

Then I wondered if I would need to instead do an array to carry forward the values of said color.

When all said and done, it seemed to me that I would need to manually write out the code to do what I need for every color instance. I have not figured out the best way to go about this.

So, just asking for suggestions on how to attack this.

Thanks...

FWIW, here is the existing code I am starting from.

#define FADERBLU 3          // MOSFET GATE pin Blue
#define FADERGRN 6          // MOSFET GATE pin Green
#define FADERRED 5          // MOSFET GATE pin Red
#define SWITCH 7            // The contact switch
#define FULLBRIGHTNESSLED 8 // Indictor LED when full brightness reached- Optional
#define LEDONESEC 9         // LED for timer cycle, one 1 sec, off 1 sec durring timer
#define FADESPEED1 20       // Initial Fade up first half. Larger numbers, slower fade up time
#define FADESPEED2 10       // Second half of Fad up. Larger numbers, slower fade up time
int buttonState = 0;        // Used to check switch status if door has been closed and reopened.
int lastButtonState = 0;    // Used to check switch status if door has been closed and reopened.
int currentState = 0;       // Used so that on powerup  the dimming feature is not activated once.

void setup()
{
  pinMode(FADERBLU, OUTPUT);
  pinMode(FADERGRN, OUTPUT);
  pinMode(FADERRED, OUTPUT);
  pinMode(FULLBRIGHTNESSLED, OUTPUT);
  pinMode(LEDONESEC, OUTPUT);
  pinMode(SWITCH, INPUT_PULLUP);
  digitalWrite(FADERBLU, LOW);
  digitalWrite(FADERGRN, LOW);
  digitalWrite(FADERRED, LOW);
 }

void loop()
{
  // Since the switch is set to be tied to ground, LOW state would equal ON when switch is pressed
  // In the mean time, the switch is set with the internal pullup resistor, which could be external
  buttonState = digitalRead(SWITCH); // Capturing SWITCH state LOW = ON (button pressed)

  if (buttonState != lastButtonState && buttonState == LOW) {
    // Fade up to 70 (about 27%) at a faster speed
    int i;
    int switchstatus;
    for (i = 0; i < 70; i++) {
      analogWrite(FADERBLU, i);
      analogWrite(FADERGRN, i);
      analogWrite(FADERRED, i);
      delay(FADESPEED1);
    }
    // Fade up to 255 (100%) at a slower speed
    for (i = 70; i < 256; i++) {
      analogWrite(FADERBLU, i);
      analogWrite(FADERGRN, i);
      analogWrite(FADERRED, i);
      delay(FADESPEED2);
    }
    digitalWrite(FADERBLU, HIGH);
    digitalWrite(FADERGRN, HIGH);
    digitalWrite(FADERRED, HIGH);
    digitalWrite(FULLBRIGHTNESSLED, HIGH); // Used for testing - Optional

    // The Timer function in a FOR loop.
    for (int t = 0; t < 300; t++) {
      // A separate LED is blinked for 1 second on, 1 second off. This status tells us that the
      // loop is running. In this case, the lights will remain on for 10 min. 300 x 2 sec each
      // pass = 600 seconds, or 10 minutes.
      // During each pass, the switchstatus is checked. If the switch went HIGH (button released
      // or door closed in this case), then we break out of loop.
      delay(1000);
      digitalWrite(LEDONESEC, HIGH);
      delay(1000);
      digitalWrite(LEDONESEC, LOW);
      switchstatus = digitalRead(SWITCH);
      if (switchstatus == HIGH) {
        // "currentstate" is needed only when first powering up, as the sketch see's the switch
        // HIGH (not pressed) and wants to run the dim-down part below.
        currentState = 1;
        break;
      }
    }
  }
  // This is the Fade to-off function. It also checks if this is the first time (thus the IF statement
  // , and if so then skip it (only matches this condion on powerup)
  if (currentState == 1 && buttonState == HIGH) {
    int i;
    for (i = 255; i > 0; i--) {
      analogWrite(FADERBLU, i);
      analogWrite(FADERGRN, i);
      analogWrite(FADERRED, i);
      delay(FADESPEED2);
      currentState = 0;
    }
  }
   analogWrite(FADERBLU, 0);
   analogWrite(FADERGRN, 0);
   analogWrite(FADERRED, 0);
  lastButtonState = buttonState;
  digitalWrite(FULLBRIGHTNESSLED, LOW);
}

it seemed to me that I would need to manually write out the code to do what I need for every color instance.

No. What you need to do is to put the final colour into three variables, say redF, greenF and blueF.

then you take the biggest one and calculate how many steps you need to get there, so say the largest is greenF at a value of 120, you are going to need 120 steps so the increment value for green is 1.

Next work out an increment value for the other two by dividing the final colour by the number of steps. Note here these two values will be less than 1 and so you must use floating point variables to hold these values.

Next you need to set up an accumulator variable to hold the current state of each three colours which will start initially with a value of zero. These must be floating point variables as well.

Having got an increment value for each colour you enter a for loop that goes from zero to the maximum value. Then in the loop you add the increment value to the accumulator variable for each colour and set the analogue output accordingly. Note here you must convert the floating point accumulator value to be a byte by casting it, like this:-

analogWrite(pinRed, (byte)accumulatorRed);

So you can write a function to fade up to any given colour passed to it.

Note fading down is just starting off the accumulator variables at the final value and subtracting the increment. Or in fact just just negating the increment value and adding it like before.

Thanks Mike.

So playing with it last night (before seeing your answer today), I basically ended up duplicating each section of the code for the color I wanted.

First I added a push-button switch (capturing it on input 2).

#define BUTTONCOLORPIN 2    // Switch to select default color
int selectedColor = 0;      // Current selected color
int buttonColorState = 0;
int lastColorState = 0;

Then added the code to collect the button value as it was pushed, incremented each time (rolls back to 0 when it hits 8).

 buttonColorState = digitalRead(BUTTONCOLORPIN);
  
  if (buttonColorState == lastColorState) {
    if (buttonColorState == LOW) {
      selectedColor++;
      delay(500);
     Serial.println(selectedColor);
    }
    if (selectedColor == 8) {
      selectedColor = 0;
    }
   }

Then used that number to run through my IF ELSE statements (0 through 7).

Most of the lower colors were easy, as I just took them to 255.
The only slightly more complicated piece is when I wanted a color that was not evenly mixed (IE: ORANGE which was RED=255, GREEN=50ish). I did it this way:

 // ORANGE Color selected
  else if (buttonState != lastButtonState && buttonState == LOW && selectedColor == 7) {
    int i;
    int switchstatus;
    for (i = 0; i < 256; i++) {
      analogWrite(FADERRED, i);
      analogWrite(FADERGRN, i / 5);
      delay(FADESPEED1);
    }

I basically divided the "i" integer by 5. While may not have been the cleanest, it seemed to work.

Finally, to bring the color back to zero, reversed the fade:

  // ORANGE Color selected
  else if (buttonState != lastButtonState && buttonState == LOW && selectedColor == 7) {
    int i;
    int switchstatus;
    for (i = 0; i < 256; i++) {
      analogWrite(FADERRED, i);
      analogWrite(FADERGRN, i / 5);
      delay(FADESPEED1);
    }

If my colors got more "mixed" than that, I would probably have to go the route you mentioned.

Thanks...

I do have one other question though. If I want to implement a seven segment display into this project, I only have 6 available pins, and would really need 7. How is the best way to handle that?

How is the best way to handle that?

Well first off remember you can use the analogue pins as digital outputs or inputs as well as analogue inputs, so you might have some spare pins for that.

Other solutions involve using a shift register, or using a seven segment decoder chip like the CD4511 or the SN7447 or the SN74LS247.

James-WBF,

As you are obviously having fun with this and making good progress AND responding intelligently to the suggestions GM has made, rather than expecting us to do everything for you as many on this forum do (Karma for that), I thought I'd throw something in that you've not asked about but which I think you might need and might like to play with. You will notice that when selecting colours in most computer software there is some kind of colour wheel arrangement that allows you to select any colour, shade and intensity. One way of doing this is HSL, hue, saturation and luminosity, values which have to be converted to RGB to see the resulting colour. I found a function to convert HSL to RGB and I thought you might like to try using it. HSL to RGB

Thanks guys...

Perry,
So interestingly you brought that up, as I had come across a similar coding article about that over on Elemet14's website RGB Strip controlled by Arduino. He talks specifically going the HSL route.

Once I complete this existing project (yea, only have one Arduino at this time) & prototype it (purchased the individual components a while back), I am wanting to dive in a bit further on that. I question if it will play much of a role or not when it comes to RGB strips in general, which the resolution would seem limited. ws281x's are on my list as well.

Mike,
Thanks for the interesting tidbit about able to use the analog pins, I was not aware of that. Just read another post you had about using those as analog input (but not output), and/or digital input/output. Over and above the pin 0 / 1 tx/rx usage.

I had also seen mention about shift registers, but now I can add the 7 segment decoder to dive into at some point.

I question if it will play much of a role or not when it comes to RGB strips in general, which the resolution would seem limited.

Well the resolution of WS2812b chips is fixed at 256 shades for each colour, given the eye can't distinguish more than about 64 shades you have plenty of overhead to play with. Remember if you use HSL or HSV you still have to convert these values into RGB values to output them to the hardware.

Where it gives a big difference using different colour spaces, as it is known, is where you want to fade from one colour to another. You get a different set of intermediate colours depending on what colour space you use. There are lots of different colour spaces and they get used for many things. List of color spaces and their uses - Wikipedia

For example suppose you had a colour picture and you wanted to print it out on a printer. Printers use a subtractive colour model as opposed to monitors which use an additive colour model, you would convert each pixel into CYMK colour space.

There is also the issue of equal steps of brightness. The eye does not respond in a linear way, so we see the difference in brightness between the levels of 10 and 20 as much more than the difference between levels 200 and 220. To get equal levels of pecieved brightness you need to apply a Gamma Correction to the numbers you use.