Tag Archives: Potentiometer

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?