Tag Archives: Button

Extension: Exploring Color with an Arduino and an RGB LED

By Robert Walsh

In a previous lesson, we built a circuit to control an RGB LED using three buttons and an Arduino. The buttons allowed us to cycle each individual color through five states: OFF 0%), DIM (25%), HALF (50%), SEMI (75%), and FULL (100%). This approach limited the number of distinct colors that could be represented by the RGB LED. With three colors and five possible values for each color, a total of 125 colors could be represented (5x5x5). The RGB LED, however, is capable of using a number between 0 and 255 for each of its colors, and that means that is can display over 16 million colors (256x256x256)!

In this extension, we will replace the three buttons with a single button and a potentiometer. A potentiometer is a type of variable resistor, and it can provide the Arduino with a value between 0 and 1023. We will convert the value from the potentiometer into a number between 0 and 255, and we will use the button to indicate which of the color’s values should be set. While this is not an ideal solution from a user experience stand point, it will allow us to produce a much wider range of colors than we could with the circuit with three buttons. (A better solution would require three potentiometers, one for each of the colors. However, the ELEGOO Mega 2560 Starter Kit includes only two potentiometers, so we are limiting ourselves to the components we have available.)

To complete this activity, you will need:

  • An Arduino Mega 2560 (other models will also work, but the pin numbers will be different)
  • One red, one blue, and one green LED
  • One common cathode RGB LED
  • Six 220 Ohm or 330 Ohm resistors
  • One push buttons
  • One 5K Ohm or 10K Ohm resistors
  • One 10k potentiometer
  • A breadboard
  • Several male-to-male jumper wires
  • A computer and USB cable to program the Arduino

Note: All of the components needed for this lesson (except the computer) are available in the ELEGOO Mega 2560 Starter Kit available from Amazon.

Controlling the RED LED

We will start by building a circuit that uses the potentiometer to set just the red color value. Here is the wiring diagram:

Wiring diagram for a circuit to control the value of the RED LED using a potentiometer

The potentiometer has three legs. The left-most leg connects to the common power rail on the breadboard. The right-most leg connects to the common ground (GND) rail. The middle leg connects to one of the analog pins on the Arduino (I used pin A0). When the dial on the potentiometer is turned all the way to the left, the resistance is almost 0 and the signal pin reads the maximum value (1023). When the dial is turned all the way to the right, the resistance is near 10k Ohms, and the signal pin reads the minimum value (0). The dial varies the resistance and allows more or less current to flow through the signal pin.

Note: The potentiometer is a large component, and it may need to span the center trough on the breadboard. If so, the center pin may be on the opposite side of the trough from the left and right pins. Be sure to connect the jumper wires on the correct sides of the breadboard. For example, using the “front” half of the breadboard for the jumper wire that connects to the analog pin on the Arduino when the center pin is on the “back” half will not work.

For now, we are connecting only the red leg and the common cathode (negative leg) of the RGB LED. The common cathode connects to GND, and the red leg connects to a 220 Ohm or 330 Ohm resistor. The other leg of the resistor connects to a pulse-width modulated (PWM) output pin on the Arduino (I used pin 7). Finally, the cathode of the red LED connects to GND, and the anode (positive leg) connects to a 220 Ohm or 330 Ohm resistor whose other leg connects to a PWM-capable output pin on the Arduino (I used pin 4).

Here is the Arduino sketch for this circuit:

const int POT = A0;
const int RGB_RED = 7;
const int RED = 4;

void setup() 
{
  pinMode(POT, INPUT);
  pinMode(RGB_RED, OUTPUT);
  pinMode(RED, OUTPUT);
}

void loop() 
{
  int potValue = analogRead(POT);
  int redValue = map(potValue, 0, 1023, 0, 255);
  analogWrite(RED, redValue);
  analogWrite(RGB_RED, redValue);
}

In the setup function, we set each of the pins as either inputs or outputs. In the loop function, we first read the value from the potentiometer (which will be a number between 0 and 1023). We then convert it to a number between 0 and 255 using the built-in map function. The map function takes a value as its first parameter, the low and high values for the “from” range, and the low and high values from the “to” range. The map function linearly scales the value from the “from” range into the “to” range. Finally, we use analogWrite to write the resulting value to the red LED and the red leg of the RGB led.

Once the sketch is compiled and uploaded to the Arduino, turning the dial on the potentiometer to the right (increasing the resistance) should make the LEDs dimmer, and turning to the left (reducing the resistance) should make them brighter.

Adding the Button and Green LED

With just one LED, we do not need any way to determine color value is being set. Since there is only one, the we can assume the potentiometer is controlling it. But when we add a second LED, we also need a button to indicate which LED’s value should be set by the potentiometer.

Here is the wiring diagram that adds the button and green LED:

Wiring diagram for a circuit to control the values of the red and green LEDs using a potentiometer and a button

As with the original lesson, we are using a 5k Ohm or 10k Ohm pull down resistor for the button. Otherwise, the changes should be straightforward. The button’s negative leg is connected to both the pull down resistor (which is connected to GND) and to an input pin on the Arduino (I used pin 8). The two new resistors are 220 Ohm or 330 Ohm, and each needs to connect to an output pin on the Arduino (I used pin 6 for the green leg of the RGB LED and pin 3 for the green LED).

Here is the modified Arduino sketch:

const int POT = A0;
const int BTN = 8;
const int RGB_RED = 7;
const int RGB_GREEN = 6;
const int RED = 4;
const int GREEN = 3;

int redValue;
int greenValue;

int lastPotValue;
bool wasPressed;
int selectedColor;  // 0 = unselected, 1 = red, 2 = green

int getLedIntensity(int potValue)
{
  /* 
   * Convert the value read from the potentiometer (between 0 and 1023) 
   * to a value that can be written to the LED (between 0 and 255) 
   */
  return map(potValue, 0, 1023, 0, 255);  
}

void setLed(int which, bool on, int value)
{
  /*
   * If the LED is supposed to be on, write its value
   * otherwise, turn it off
   */
  if (on)
  {
    analogWrite(which, value);  
  }
  else
  {
    digitalWrite(which, LOW);
  }
}

void setLeds(int currentColor, int red, int green)
{
  /*
   * Set the value of the individual LED if it is the current color, 
   * then set the value of each component color of the RGB LED
   */
  setLed(RED, currentColor == 1, red);
  setLed(GREEN, currentColor == 2, green);

  analogWrite(RGB_RED, red);
  analogWrite(RGB_GREEN, green);
}

void setup() 
{
  pinMode(POT, INPUT);
  pinMode(BTN, INPUT);
  pinMode(RGB_RED, OUTPUT);
  pinMode(RGB_GREEN, OUTPUT);
  pinMode(RED, OUTPUT);
  pinMode(GREEN, OUTPUT);

  // set initial values
  wasPressed = false;
  selectedColor = 0;
  lastPotValue = analogRead(POT);
  int ledIntensity = getLedIntensity(lastPotValue);
  redValue = ledIntensity;
  greenValue = ledIntensity;

  setLeds(selectedColor, redValue, greenValue);
}

bool debounce(bool lastState)
{
  /*
   * Determine if the button is pressed (ignoring spurious values during press or release)
   */
  bool isPressed = digitalRead(BTN);
  if (isPressed != lastState)
  {
    delay(5);
    isPressed = digitalRead(BTN);
  }
  return isPressed;  
}

void loop() 
{
  bool isPressed = debounce(wasPressed);
  // if the button is pressed now but was not pressed before
  if (isPressed && !wasPressed)
  {
    // switch to the next color (and "roll over" if at the end of the valid colors)
    selectedColor = (selectedColor + 1) % 3;

    // read the state of the potentiometer
    lastPotValue = analogRead(POT);  
  }
  wasPressed = isPressed;

  // if a color is selected
  if (selectedColor != 0)
  {
    // read the state of the potentiometer again
    int potValue = analogRead(POT);

    // if the potentiometer has changed since this color was selected
    if (potValue != lastPotValue)
    {
      // then set the appropriate color value
      switch (selectedColor)
      {
        case 1: // red
          redValue = getLedIntensity(potValue);
          break;

         case 2: // green
          greenValue = getLedIntensity(potValue);
          break;
      }

      // "remember" the potentiometer's value for the next time through loop
      lastPotValue = potValue;
    }
  }

  // set the LEDs based on the current values
  setLeds(selectedColor, redValue, greenValue);
}

This code is a bit more complicated that the original. Because we now need to keep track of the value of two different colors, we need to use global variables to “remember” them each time the loop function is executed. We have also added some functions to help us organize and separate some of the logic. This makes the main part of the code easier to read, and it helps to eliminate duplication when we need to do essentially the same thing more than once (e.g., for each of the LEDs).

The getLedIntensity function simply maps the potentiometer value between 0 and 1023 to a value between 0 and 255 than can be written to the LED.

The setLed function is used to set the value for the individual LEDs. We only want the individual LED to be lit when it is the selected color, so we pass two parameters in addition to the pin number for the LED: a boolean (true or false) value that indicates whether the LED should be on or off and an integer that contains the value for the LED if it is on. Where the setLed function is called, we have used a curious construct (see lines 46-47): currentColor == 1 for the call that sets the red LED. This is a short-hand way to say “if the currentColor is equal to 1.” In many computer languages, a double equals sign is used to mean “is equal to,” and it evaluates to either true or false. By using currentColor == 1 as a value to be passed as a parameter to the setLed function, we are telling the program to convert the expression into true (meaning currentColor is equal to 1) or false (meaning currentColor is not equal to 1) and pass the result to the function.

The setLeds function sets the values for all of the LEDs. In addition to calling setLed for each of the individual LEDs, it uses analogWrite to write the current value for each color to the appropriate leg of the RGB LED. Again, the individual LEDs will be on only when the corresponding color is the selected color, but the RGB LED will always set the value for each of the component colors. (This is why this circuit does not wire the individual legs of the RGB LED in series with the individual LEDs as we did in the original lesson.)

The debounce function was used in the original lesson. It reads the state of the button (i.e., whether it is pressed or not pressed), and it ignores spurious signals sent while the button is being pressed or released.

The loop function can be divided into three sections. The first (lines 88-99) determines whether the button is currently pressed but was not pressed the last time through the loop. If it is, we want to cycle to the next color. The selectedColor variable starts at 0 (which indicates no color is selected). When the button is pressed, we increment selectedColor so that its value becomes 1 (which indicates red is selected). When it is pressed again, we increment the value again to 2 (which indicates green is selected). When it is pressed a third time, the value becomes 3. However, we do not yet have another color, so we want selectedColor’s value to go back to 0. The % symbol represents the modulus operator, and modulus returns the remainder for integer division. For example, when selectedColor is initially 0, it is incremented to 1. 1 divided by 3 is 0 with a remainder of 1, so 1 % 3 = 1 (one modulus three equals one), so the new value for selectedColor is 1. When selectedColor is incremented to 2, 2 % 3 = 2 (two modulus three equals two), so the new value for selectedColor is 2. However, when selectedColor is incremented a third time, its value becomes 3. 3 % 3 = 0 (three modulus three equals zero because three divided by three equals one with no remainder; the modulus operator tells us the remainder from the division operation). Therefore, the new value for selectedColor is 0. This is a common way to increment a value across a range of sequential values without going beyond the last valid value.

In addition to incrementing the selectedColor when the button is pressed, this first part of the loop function also reads the current value from the potentiometer. This is because, when we move to the next color, we only want to reset its value if the potentiometer is moved after the color is selected. This allows us to bypass a color without changing its value.

The second part of the loop function (lines 102-124) checks to see if a color is currently selected (selectedColor is not 0). If so, we read the potentiometer again. If the value is the same as the last time we checked it, we do not want to make any changes to the color values. That way, just cycling through the colors without turning the potentiometer will not set all colors to the same value. Without this check, the green LED would immediately inherit the same value as the red LED as soon as the button is pressed. Now, though, the potentiometer has to be turned after green is selected before the value of the green LED is changed. However, if there is a new value, we set the value for the currently selected color. The switch statement is like a series of if/else statements. If there is a case block that matches selectedColor, those statements will be executed. The break statement is used to separate one case from another; without break, execution will “fall through” and continue into the next case. The switch statement also accepts a default block (which we did not use) that executes if none of the cases match (or if there are no break statements between the matching case and the default block).

The final part of the loop function (line 128) sets the values of all of the LEDs.

When this sketch is compiled and uploaded to the Arduino, the red and green LEDs should initially be off. However, the RGB LED might be lit with equal amounts of red and green based on the current position of the potentiometer. (If the potentiometer is turned all the way to the right, the RGB LED should be off.) Pressing the button once should select red, and turning the potentiometer should change the value of both the red LED and the RGB LED. Pressing the button again should select green. The red LED should go off, and the green LED should come on (unless the last value for green was 0). However, the amount of green in the RGB LED should not change until the potentiometer is turned. Turning the potentiometer should change both the green LED and the RGB LED. Pressing the button a third time returns the system to the “no color selected” state, and turning the potentiometer should have no effect.

Adding the Blue LED

The final change is to add the blue LED. Here is the new wiring diagram:

Wiring diagram for a circuit to control the red, green, and blue LEDs with a potentiometer and a button

Adding the blue LED is exactly the same is adding the green LED except for which pins o the Arduino are connected (I used pin 5 for the blue leg of the RGB LED and pin 2 for the blue LED).

The changes to the Arduino sketch are also relatively easy:

const int POT = A0;
const int BTN = 8;
const int RGB_RED = 7;
const int RGB_GREEN = 6;
const int RGB_BLUE = 5;
const int RED = 4;
const int GREEN = 3;
const int BLUE = 2;

int redValue;
int greenValue;
int blueValue;

int lastPotValue;
bool wasPressed;
int selectedColor;  // 0 = unselected, 1 = red, 2 = green, 3 = blue

int getLedIntensity(int potValue)
{
  /* 
   * Convert the value read from the potentiometer (between 0 and 1023) 
   * to a value that can be written to the LED (between 0 and 255) 
   */
  return map(potValue, 0, 1023, 0, 255);  
}

void setLed(int which, bool on, int value)
{
  /*
   * If the LED is supposed to be on, write its value
   * otherwise, turn it off
   */
  if (on)
  {
    analogWrite(which, value);  
  }
  else
  {
    digitalWrite(which, LOW);
  }
}

void setLeds(int currentColor, int red, int green, int blue)
{
  /*
   * Set the value of the individual LED if it is the current color, 
   * then set the value of each component color of the RGB LED
   */
  setLed(RED, currentColor == 1, red);
  setLed(GREEN, currentColor == 2, green);
  setLed(BLUE, currentColor == 3, blue);

  analogWrite(RGB_RED, red);
  analogWrite(RGB_GREEN, green);
  analogWrite(RGB_BLUE, blue);
}

void setup() 
{
  pinMode(POT, INPUT);
  pinMode(BTN, INPUT);
  pinMode(RGB_RED, OUTPUT);
  pinMode(RGB_GREEN, OUTPUT);
  pinMode(RGB_BLUE, OUTPUT);
  pinMode(RED, OUTPUT);
  pinMode(GREEN, OUTPUT);
  pinMode(BLUE, OUTPUT);

  // set initial values
  wasPressed = false;
  selectedColor = 0;
  lastPotValue = analogRead(POT);
  int ledIntensity = getLedIntensity(lastPotValue);
  redValue = ledIntensity;
  greenValue = ledIntensity;
  blueValue = ledIntensity;

  setLeds(selectedColor, redValue, greenValue, blueValue);
}

bool debounce(bool lastState)
{
  /*
   * Determine if the button is pressed (ignoring spurious values during press or release)
   */
  bool isPressed = digitalRead(BTN);
  if (isPressed != lastState)
  {
    delay(5);
    isPressed = digitalRead(BTN);
  }
  return isPressed;  
}

void loop() 
{
  bool isPressed = debounce(wasPressed);
  // if the button is pressed now but was not pressed before
  if (isPressed && !wasPressed)
  {
    // switch to the next color (and "roll over" if at the end of the valid colors)
    selectedColor = (selectedColor + 1) % 4;

    // read the state of the potentiometer
    lastPotValue = analogRead(POT);  
  }
  wasPressed = isPressed;

  // if a color is selected
  if (selectedColor != 0)
  {
    // read the state of the potentiometer again
    int potValue = analogRead(POT);

    // if the potentiometer has changed since this color was selected
    if (potValue != lastPotValue)
    {
      // then set the appropriate color value
      switch (selectedColor)
      {
        case 1: // red
          redValue = getLedIntensity(potValue);
          break;

        case 2: // green
          greenValue = getLedIntensity(potValue);
          break;

        case 3: // blue
          blueValue = getLedIntensity(potValue);
          break;
      }

      // "remember" the potentiometer's value for the next time through loop
      lastPotValue = potValue;
    }
  }

  // set the LEDs based on the current values
  setLeds(selectedColor, redValue, greenValue, blueValue);
}

It is important to add a new integer parameter, blueValue to the setLeds function (lines 43, 78, and 140). It is also important to change the calculation that determines the selectedColor (line 102). Since we now have another valid value, we need to take the remainder (modulus) of dividing by 4, not 3.

The behavior after compiling and uploading should be the same as before except that you can now set the value for blue.

Further Improvements

As was stated above, a better interface would involve three potentiometers, one dedicated to each of the colors. That would improve the code significantly since there would be no need to remember each colors value when it was not the selected color. Instead, each time through the loop, we would simply read each potentiometer and update the individual LEDs and the RGB LED. All of the LEDs could be on at the same time (as they were in the original lesson).

Also, using “raw” numeric values to represent states (e.g., 0 means unselected, 1 means red, etc.) makes it difficult for others to understand the code. The specific values are sometimes referred to as “magic numbers” since they are not immediately recognizable as having any particular significance. We did use comments to explain what they mean in the places where they are used, but using constants would have been better (e.g., UNSELECTED, RED_SELECTED, GREEN_SELECTED, etc.). Better still would be to use an enumeration (or enum), something that was suggested as an improvement to the code in the original lesson, too.

What do you think?

While neither the three-button approach used in the original lesson nor the potentiometer-plus-one-button approach used in this extension provides the ideal user experience, which do you think is better and why?

Lesson: Exploring Color with an Arduino and an RGB LED

By Robert Walsh

Engage

Have you ever wondered how we see color? Or how our TVs, phones, tablets, and computers can display such vivid and detailed pictures? In this lesson, we will use an Arduino to control red, green, and blue light emitting diodes (LEDs) to create a whole spectrum of colors, and then we will see how the concepts are applied in LED displays. To complete this activity, you will need:

  • An Arduino Mega 2560 (other models will also work, but the pin numbers will be different)
  • One red, one blue, and one green LED
  • One common cathode RGB LED
  • Three 220 Ohm or 330 Ohm resistors
  • Three push buttons
  • Three 5K Ohm or 10K Ohm resistors
  • A breadboard
  • Several male-to-male jumper wires
  • A computer and USB cable to program the Arduino

Note: All of the components needed for this lesson (except the computer) are available in the ELEGOO Mega 2560 Starter Kit available from Amazon.

Explore

The finished product will have three buttons, each of which will gradually increase the brightness of one of the colored LEDs as well as the corresponding component in the RGB LED. This will let us observe how combining differing amounts of red, green, and blue results in light that our eyes perceive as various shades of color.

We are going to build the circuit and write the sketch for this lesson in several small parts. There is essentially one small circuit that will be repeated for each of the three colors.

Controlling a Red LED

The first circuit will control the red LED. Here is the wiring diagram:

Wiring diagram for the portion of the circuit that will control the red LED

For this circuit, we have connected the common ground rail on the breadboard to a GND (ground) pin on the Arduino, and we have connected the common power rail to a 5V pin. The cathode (negative leg) of the red LED is connected to ground. The anode (positive leg) is connected to a 220 Ohm (or 330 Ohm) resistor. The other end of the resistor is connected to both a signal pin on the Arduino (I used pin 12) and to the red leg of the RGB LED. With the RGB LED held so that the longest leg (typically the common cathode) is second from the left, the red leg should be the first leg from the left. The common cathode of the RGB LED is connected to ground.

The button is inserted into the breadboard across the center channel. One leg is connected to the common power rail, while the other connects both to a signal pin on the Arduino (I used pin 4) and to a 5K Ohm (or 10K Ohm) resistor that is connected to ground. This is called a pull down resistor.

Which signal pin you choose for the LED is not important except that it must be capable of pulse width modulation (PWM) output. This is how we will eventually control the brightness of the LED.

Here is the Arduino sketch for this circuit:

const int RED_LED = 12; /* the signal pin to which the red LED anode is connected */
const int RED_BTN = 4; /* the signal pin to which the button for red is connected */

const int OFF = 0; /* the value for the LED when it is off */
const int ON = 1; /* the value for the LED when it is on */

boolean redPressed = false; /* indicates whether the button for red is currently pressed */
int redState = OFF; /* indicates the current state of the red LED */

void setup()
{
    pinMode(RED_LED, OUTPUT); /* set the red LED pin to output */
    pinMode(RED_BTN, INPUT); /* set the pin for the button to control the red LED to input */

    digitalWrite(RED_LED, OFF); /* ensure the red LED is off */
}

boolean debounce(int whichButton, boolean lastState)
{
    /* 
     * The debounce function ensures the button state has 
     * had time to "settle" before returning whether 
     * it is pressed 
     */

    boolean currentState = digitalRead(whichButton); /* get the state of the button */

    if (currentState != lastState) /* if the new state is different from the last */
    {
        delay(5); /* wait 5 milliseconds and read the state again */
        currentState = digitalRead(whichButton);
    }
    return currentState;
}

int nextState(int currentState)
{
    /*
     * The nextState function determines the new state
     * for the LED based on its current state
     */

    if (currentState == OFF) /* if the LED is currently off */
    {
        return ON; /* its next state should be on */
    }

    return OFF; /* otherwise, it is on and should be off */
}

void loop()
{
    boolean isPressed = debounce(RED_BTN, redPressed); /* check to see if the button for the red LED is pressed */
    if (isPressed && !redPressed) /* if the button is pressed now but was not pressed before */
    {
        redState = nextState(redState); /* get the new state for the red LED */
    }
    redPressed = isPressed; /* save the pressed state of the button for the red LED */

    digitalWrite(RED_LED, redState);
}


Compile the sketch and upload it to the Arduino. The red LED and the RGB LED should be off. Pressing the button should turn on the red LED and cause the RGB LED to light up red. Pressing the button again should turn both LEDs off.

Gradually Increasing the Brightness

So far, we are only turning the LEDs on and off. It would be more interesting if we could gradually increase the brightness so that the lights behave like a 3-way light bulb. To do that, we need to make some changes to the sketch. Specifically, we need to add more states than just off and on, and we will need to use pulse width modulation (PWM) to vary the voltage going to the circuit. Here is the new sketch with the changes highlighted:

const int RED_LED = 12; /* the signal pin to which the red LED anode is connected */
const int RED_BTN = 4; /* the signal pin to which the button for red is connected */

const int OFF = 0; /* the value for the LED when it is off */
const int DIM = 256 / 4; /* the value for the LED when it is 1/4 bright */
const int HALF = 256 / 2; /* the value for the LED when it is 1/2 bright */
const int SEMI = (256 / 4) * 3; /* the value for the LED when it is 3/4 bright */
const int FULL = 255; /* the value for the LED when it is fully lit */

boolean redPressed = false; /* indicates whether the button for red is currently pressed */
int redState = OFF; /* indicates the current state of the red LED */

void setup()
{
    pinMode(RED_LED, OUTPUT); /* set the red LED pin to output */
    pinMode(RED_BTN, INPUT); /* set the pin for the button to control the red LED to input */

    digitalWrite(RED_LED, OFF); /* ensure the red LED is off */
}

boolean debounce(int whichButton, boolean lastState)
{
    /* 
     * The debounce function ensures the button state has 
     * had time to "settle" before returning whether 
     * it is pressed 
     */

    boolean currentState = digitalRead(whichButton); /* get the state of the button */

    if (currentState != lastState) /* if the new state is different from the last */
    {
        delay(5); /* wait 5 milliseconds and read the state again */
        currentState = digitalRead(whichButton);
    }
    return currentState;
}

int nextState(int currentState)
{
    /*
     * The nextState function determines the new state
     * for the LED based on its current state
     */

    if (currentState == OFF) /* if the LED is currently off */
    {
        return DIM; /* its next state should be on */
    }

    if (currentState == DIM) /* if the LED is currently dim */
    {
        return HALF; /* its next state should be half */
    }

    if (currentState == HALF) */ if the LED is currently half */
    {
        return SEMI; /* its next state should be 3/4 */
    }

    if (currentState == SEMI) /* if the LED is currently 3/4 */
    {
        return FULL; /* its next state should be full */
    }

    return OFF; /* otherwise, it is already fully lit and should next be off */
}

void loop()
{
    boolean isPressed = debounce(RED_BTN, redPressed); /* check to see if the button for the red LED is pressed */
    if (isPressed && !redPressed) /* if the button is pressed now but was not pressed before */
    {
        redState = nextState(redState); /* get the new state for the red LED */
    }
    redPressed = isPressed; /* save the pressed state of the button for the red LED */

    analogWrite(RED_LED, redState);
}


Once this sketch is compiled and uploaded, pressing the button should cycle the red LED and the RGB LED through five different states, each with a different brightness.

Controlling a Green LED

Now that we can control the red LED, let’s add a green one. We are going to replicate the existing circuit by adding a green LED and button to control it. Here is the new wiring diagram:

Wiring diagram for the portion of the circuit that will control the red and green LEDs

The cathode of the green LED is connected to ground, and the anode is connected to a 220 Ohm or 330 Ohm resistor. The other end of the resistor connects both to the green leg of the RGB LED and to a signal pin on the Arduino (I used pin 11). Again, which signal pin is not important so long as it is capable of PWM. The green leg of the LED is typically to the right of the common cathode. The button is the same as for the red LED except that it is connected to a different signal pin on the Arduino (I used pin 3).

We also need to make some changes to the sketch:

const int RED_LED = 12; /* the signal pin to which the red LED anode is connected */
const int RED_BTN = 4; /* the signal pin to which the button for red is connected */

const int GREEN_LED = 11; /* the signal pin to which the green LED anode is connected */
const int GREEN_BTN = 3; /* the signal pin to which the button for green is connected */

const int OFF = 0; /* the value for the LED when it is off */
const int DIM = 256 / 4; /* the value for the LED when it is 1/4 bright */
const int HALF = 256 / 2; /* the value for the LED when it is 1/2 bright */
const int SEMI = (256 / 4) * 3; /* the value for the LED when it is 3/4 bright */
const int FULL = 255; /* the value for the LED when it is fully lit */

boolean redPressed = false; /* indicates whether the button for red is currently pressed */
int redState = OFF; /* indicates the current state of the red LED */

boolean greenPressed = false; /* indicates whether the button for green is currently pressed */
int greenState = OFF; /* indicates the current state of the green LED */

void setup()
{
    pinMode(RED_LED, OUTPUT); /* set the red LED pin to output */
    pinMode(RED_BTN, INPUT); /* set the pin for the button to control the red LED to input */

    pinMode(GREEN_LED, OUTPUT); /* set the green LED pin to output */
    pinMode(GREEN_BTN, INPUT); /* set the pin for the button to control the green LED to input */

    digitalWrite(RED_LED, OFF); /* ensure the red LED is off */
    digitalWrite(GREEN_LED, OFF); /* ensure the green LED is off */
}

boolean debounce(int whichButton, boolean lastState)
{
    /* 
     * The debounce function ensures the button state has 
     * had time to "settle" before returning whether 
     * it is pressed 
     */

    boolean currentState = digitalRead(whichButton); /* get the state of the button */

    if (currentState != lastState) /* if the new state is different from the last */
    {
        delay(5); /* wait 5 milliseconds and read the state again */
        currentState = digitalRead(whichButton);
    }
    return currentState;
}

int nextState(int currentState)
{
    /*
     * The nextState function determines the new state
     * for the LED based on its current state
     */

    if (currentState == OFF) /* if the LED is currently off */
    {
        return DIM; /* its next state should be on */
    }

    if (currentState == DIM) /* if the LED is currently dim */
    {
        return HALF; /* its next state should be half */
    }

    if (currentState == HALF) */ if the LED is currently half */
    {
        return SEMI; /* its next state should be 3/4 */
    }

    if (currentState == SEMI) /* if the LED is currently 3/4 */
    {
        return FULL; /* its next state should be full */
    }

    return OFF; /* otherwise, it is already fully lit and should next be off */
}

void loop()
{
    boolean isPressed = debounce(RED_BTN, redPressed); /* check to see if the button for the red LED is pressed */
    if (isPressed && !redPressed) /* if the button is pressed now but was not pressed before */
    {
        redState = nextState(redState); /* get the new state for the red LED */
    }
    redPressed = isPressed; /* save the pressed state of the button for the red LED */

    isPressed = debounce(GREEN_BTN, greenPressed); /* check to see if the button for the green LED is pressed */
    if (isPressed && !greenPressed) /* if the button is pressed now but was not pressed before */
    {
        greenState = nextState(greenState); /* get the new state for the green LED */
    }
    greenPressed = isPressed; /* save the pressed state of the button for the green LED */

    analogWrite(RED_LED, redState);
    analogWrite(GREEN_LED, greenState);
}


We simply added constants and variables to represent the state of the green LED and the button that controls it. Then we replicated the behavior that controls the red LED so that it works for the green one, too. Once the code is compiled and uploaded, pressing the button for red will increase the brightness on the red LED, and pressing the button for green will control the green LED. The interesting part, though, is that when both the red and green LEDs are lit, the RGB LED is no longer either red or green but a combination of both!

Controlling a Blue LED

The final step is to replicate the circuit once more so that we can control a blue LED. Here is the wiring diagram:

Wiring diagram for the completed circuit that will control the all three LEDs

The breadboard may be starting to get a little bit crowded, but the new components should be familiar. The cathode of the blue LED is connected to ground, and the anode is connected to a 220 Ohm or 330 Ohm resistor. The other end of the resistor connects to the blue leg of the RGB LED and to a signal pin (capable of PWM) on the Arduino (I used pin 10). The button is the same as for the buttons for red and green, but it should be connected to a different signal pin (I used pin 2).

The new sketch is here:

const int RED_LED = 12; /* the signal pin to which the red LED anode is connected */
const int RED_BTN = 4; /* the signal pin to which the button for red is connected */

const int GREEN_LED = 11; /* the signal pin to which the green LED anode is connected */
const int GREEN_BTN = 3; /* the signal pin to which the button for green is connected */

const int BLUE_LED = 10; /* the signal pin to which the blue LED anode is connected */
const int BLUE_BTN = 2; /* the signal pin to which the button for blue is connected */

const int OFF = 0; /* the value for the LED when it is off */
const int DIM = 256 / 4; /* the value for the LED when it is 1/4 bright */
const int HALF = 256 / 2; /* the value for the LED when it is 1/2 bright */
const int SEMI = (256 / 4) * 3; /* the value for the LED when it is 3/4 bright */
const int FULL = 255; /* the value for the LED when it is fully lit */

boolean redPressed = false; /* indicates whether the button for red is currently pressed */
int redState = OFF; /* indicates the current state of the red LED */

boolean greenPressed = false; /* indicates whether the button for green is currently pressed */
int greenState = OFF; /* indicates the current state of the green LED */

boolean bluePressed = false; /* indicates whether the button for blue is currently pressed */
int blueState = OFF; /* indicates the current state of the blue LED */

void setup()
{
    pinMode(RED_LED, OUTPUT); /* set the red LED pin to output */
    pinMode(RED_BTN, INPUT); /* set the pin for the button to control the red LED to input */

    pinMode(GREEN_LED, OUTPUT); /* set the green LED pin to output */
    pinMode(GREEN_BTN, INPUT); /* set the pin for the button to control the green LED to input */

    pinMode(BLUE_LED, OUTPUT); /* set the blue LED pin to output */
    pinMode(BLUE_BTN, INPUT); /* set the pin for the button to control the blue LED to input */

    digitalWrite(RED_LED, OFF); /* ensure the red LED is off */
    digitalWrite(GREEN_LED, OFF); /* ensure the green LED is off */
    digitalWrite(BLUE_LED, OFF); /* ensure the blue LED is off */
}

boolean debounce(int whichButton, boolean lastState)
{
    /* 
     * The debounce function ensures the button state has 
     * had time to "settle" before returning whether 
     * it is pressed 
     */

    boolean currentState = digitalRead(whichButton); /* get the state of the button */

    if (currentState != lastState) /* if the new state is different from the last */
    {
        delay(5); /* wait 5 milliseconds and read the state again */
        currentState = digitalRead(whichButton);
    }
    return currentState;
}

int nextState(int currentState)
{
    /*
     * The nextState function determines the new state
     * for the LED based on its current state
     */

    if (currentState == OFF) /* if the LED is currently off */
    {
        return DIM; /* its next state should be on */
    }

    if (currentState == DIM) /* if the LED is currently dim */
    {
        return HALF; /* its next state should be half */
    }

    if (currentState == HALF) */ if the LED is currently half */
    {
        return SEMI; /* its next state should be 3/4 */
    }

    if (currentState == SEMI) /* if the LED is currently 3/4 */
    {
        return FULL; /* its next state should be full */
    }

    return OFF; /* otherwise, it is already fully lit and should next be off */
}

void loop()
{
    boolean isPressed = debounce(RED_BTN, redPressed); /* check to see if the button for the red LED is pressed */
    if (isPressed && !redPressed) /* if the button is pressed now but was not pressed before */
    {
        redState = nextState(redState); /* get the new state for the red LED */
    }
    redPressed = isPressed; /* save the pressed state of the button for the red LED */

    isPressed = debounce(GREEN_BTN, greenPressed); /* check to see if the button for the green LED is pressed */
    if (isPressed && !greenPressed) /* if the button is pressed now but was not pressed before */
    {
        greenState = nextState(greenState); /* get the new state for the green LED */
    }
    greenPressed = isPressed; /* save the pressed state of the button for the green LED */

    isPressed = debounce(BLUE_BTN, bluePressed); /* check to see if the button for the blue LED is pressed */
    if (isPressed && !bluePressed) /* if the button is pressed now but was not pressed before */
    {
        blueState = nextState(blueState); /* get the new state for the blue LED */
    }
    bluePressed = isPressed; /* save the pressed state of the button for the blue LED */

    analogWrite(RED_LED, redState);
    analogWrite(GREEN_LED, greenState);
    analogWrite(BLUE_LED, blueState);
}


Again, we simply replicated the code that handles the red and the green buttons and LEDs for blue. After compiling and uploading, you should be able to control all three LEDs independently, and the color of the RGB LED should depend on the values of each of the individual color LEDs.

Explain

Perceiving Color

What we perceive as color is light at different wavelengths1. Receptors in the eye stimulated by light at varying wavelengths and convert these stimuli into signals that the brain perceives as colors2. Combining different amounts of red, green, and blue light can produce the full spectrum2.

LED computer displays and televisions use this same technique to produce high-resolution images3. These displays are composed of many (over 8 million for a 4K TV) RGB LEDs that can be lit individually. By controlling the red, green, and blue values of these picture elements (called pixels), the display can represent images. The more pixels that are available, typically referred to as the resolution of the display, the better the quality of the resulting image.

Pulse Width Modulation (PWM)

This lesson leverages a concept called pulse width modulation4 or PWM. In order to control the brightness of the LEDs, we need to somehow vary the amount of voltage sent to them. However, the Arduino is capable of setting the value of output pins to either high (5 volts) or low (0 volts). By controller how long the signal is high, though, we can fool the circuit into behaving as if it is getting less voltage. The effect is similar to flicking a light switch on and off very quickly. The light can only be on or off; there are no other possible values. If, though, the switch is flipped fast enough, it will look like it is dim. The ratio of the time spent on and the time spent off dictates how bright the light will appear. Similarly, if the Arduino toggles the state of the pin between high and low very quickly, the LED’s brightness can be controlled. Just as with the light switch, setting the pin high longer makes the LED appear brighter. The time spent on is called the duty cycle. In this lesson, we defined DIM to be 25% of the maximum value. When we use analogWrite to send that value to the signal pin, we are telling the Arduino to use a 25% duty cycle. In other words, the pin will be high 25% of the time and low 75% of the time. Only some of the Arduino’s pins are capable of PWM.

Pull Down Resistors

You probably noticed that we connected a high-value resistor to ground with each of the buttons. This is called a pull down resistor5. The reason for this is to ensure that there is a complete circuit even when the button is open (not pressed). One side of the button is connected to the common power rail on the breadboard. When the button is pressed, the switch is closed, and the signal pin reads high. This is because power is able to flow through the button. Without the resistor, when the button is not pressed, the signal pin would be connected to an open circuit, and the Arduino would not be able to accurately determine its state. By adding the resistor, even when the button is not pressed, there is a closed circuit from the signal bin to ground. Also, since current always follows the path of least resistance, when the button is pressed, there is less resistance through the switch to the power rail than through the resistor to the ground rail. Therefore, the Arduino will detect that the pin is high. In short, when the button is not pressed (and the switch is open), there is only one path: through the resistor to ground. When the button is pressed (and the switch is closed), there are two paths, so current will travel the path with less resistance which goes through the button to the power rail.

Debounce

When a button is initially pressed or released, its state may briefly vary between high and low before settling into the correct final state6. To deal with this, we use a technique called debouncing. That’s a fancy word to mean that we read the button’s state, and if we think it is changing, we wait a very short amount of time and read it again. In our code, the debounce function first reads the state of the button. Then, if the current state is different from the previous state, we wait (delay) for 5 milliseconds and read the state again. This gives us a reliable value for the button’s current state.

Extend

Improving the Code

The final sketch for this project is pretty clean. It uses constants to represent the pins and the various states for the LEDs rather than having “magic numbers” scattered throughout the code. It uses functions for determining whether a button is pressed and for calculating the next state of the LED. The functions are written so that they can be used for any of the buttons or LEDs rather than having to replicate the copy for each button and LED.

There are, though, some improvements that could be made. First, the nextState function could be rewritten to use a switch statement rather than a series of if statements. Switch is better when there are multiple outcomes that are mutually exclusive. Alternatively, an enum could be used to represent that states so that the next state could be determined by simply iterating over the set of possible values.

There is some duplication in the loop function for determining whether a button is pressed and then calculating the next state. This could be extracted into a function that returns a struct containing the next state and whether the button is pressed.

Improving the User Experience

It is great that we can set each of the colored LEDs to one of five possible values. This give us 125 possible colors for the RGB LED (5 red * 5 green * 5 blue). However, analogWrite can accept up to 256 values (where 0 is off and 255 is fully lit)! We could give our user more control over the lights if we added new brightness levels. If the user could set each individual LED to any value between 0 and 255, over 16 million colors would be possible! At some point, though, it would become difficult to get a specific color by having to cycle through more and more values with individual button presses. Changing the circuit to use potentiometers instead of buttons would provide a better mapping between the value of the control mechanism and the shade of the RGB LED. With buttons, we are using digital sensors to create analog values, but with potentiometers, we would be reading analog values directly from the controls. We could even use a single two-axis joystick to control the colors, but we would need to find a way to map two-dimensional coordinate values into a set of three possible color values between 0 and 255. A color wheel might help.

Color Blindness

Color blindness is an inability to see the full spectrum of color. It is caused when some of the color receptors in the eye (called the cones) do not work properly7. About 8% of men and 1% of women are affected by some form of color blindness1. Is there anything that you learned about color in this lesson that might help people who suffer from color blindness?

Evaluate

What we learned in this lesson:

  • How red, green, and blue can be combined to create the full spectrum of color
  • How to use pulse width modulation to vary the brightness of an LED
  • How to use a pull down resistor to ensure that a button’s state can be determined reliable when it is not pressed
  • How to use the debounce technique to eliminate spurious and potentially inaccurate readings of a button’s state while it is being pressed or released

How did you do with this lesson?

  • What parts were easy and what parts were confusing?
  • Were any parts a review of things you already knew?
  • What would you like to know more about?

References

1Science Learning Hub. (n.d.). Colours of light. https://www.sciencelearn.org.nz/resources/47-colours-of-light

2Pantone. (n.d.). How do we see color? An introduction to color an the human eye. https://www.pantone.com/articles/color-fundamentals/how-do-we-see-color

3Eizo. (n.d.). EIZO 4K monitors: High definition and large screen sizes. https://www.eizo.com/library/basics/eizo_4k_monitors/

4Heath, J. (2017, April 4). PWM: Pulse width modulation: What is it and how does it work? Analog IC Tips. https://www.analogictips.com/pulse-width-modulation-pwm/

5EE Power. (n.d.) Pull up resistor / pull down resistor. https://eepower.com/resistor-guide/resistor-applications/pull-up-resistor-pull-down-resistor

6Arduino. (n.d.). Debounce. https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce

7Turbert, D. (2019, September 6). What is color blindness? American Academy of Ophthalmology. https://www.aao.org/eye-health/diseases/what-is-color-blindness