Friday, January 21, 2011

Arduino notebook: an RGB LED and the color wheel (Part 2)

In the last Arduino Notebook article, I showed how to take one of the standard tutorials and modify the sketch to do something a bit different than given. I've updated that article with breadboard and schematic diagrams.

In this article I'll add a potentiometer that came with my kit to allow additional control over the LED. With this new input, I'll show how to use it to control two different aspects of the LED. First, I will change the sketch so the potentiometer controls the speed the LED cycles through the color wheel. At one end it will be so fast it will look more like a rapid switch between a reddish color and a blueish color. At the other extreme end, it will take about 30 seconds to cycle all the way through.

Second, I will make the position of the potentiometer control the color. So, as you turn the control, it will change to the hue represented by that position of the control. In other words at the starting position it will be red. Turn it one third of the way and it'll be green. Another one third and it will be blue. Turn it all the way to the end and it'll go back to red.

So, let's get started.

First, I place a potentiometer on the breadboard so each of the three pins is in a separate row of the breadboard. Then I connect one side to ground and the other side to 5V. The middle pin I connect to the Arduino's analog 0 pin. That's it for the hardware. It should look something like this.
RGB LED color wheel with pot_bb
Click the image for a larger view or here for a schematic.

Ok, now for the software.

The first set of changes will let the potentiometer change the speed the LED cycles through the color wheel. Starting with the sketch I ended with in the previous article, in the setup function, I add a call to initialize pin 0 for input. Next I change the loop to get the current value of the analog pin. This value will be from 0 to 1024 depending on the position of the potentiometer. Since I want the total time to cycle through the colors to be 30 seconds and there are 360 steps, then each step should take 83 milliseconds. To convert the number read from pin 0 from one range to another the Arduino has a function called map. This is much easier to use than doing the math to handle the conversion. So, I call this function with the value read from the potentiometer and the beginning and ending values of the two ranges. The result is fed into the delay in the loop. Compile and upload to the Arduino and now the potentiometer controls the speed.
int ledPins[] = { 11, 9, 10 };
int inputPin = 0;

void setup()
{
  pinMode(inputPin, INPUT);
  for(int i = 0; i < 3; i++)
    pinMode(ledPins[i], OUTPUT);
}

void loop()
{
  int inputValue = analogRead(inputPin);
  int delayValue = map(inputValue, 0, 1024, 0, 83);

  setHueValue(computeNextValue());
  
  delay(delayValue);
}

int colorValue = 0;

int computeNextValue()
{
  colorValue = (colorValue+1) % 360;
  return colorValue;
}

void setHueValue(int hueValue)
{
  setColor(ledPins, hsvToRgb(hueValue, 1, 1));
}

void setColor(int* led, const byte* color)
{
  for(int i = 0; i < 3; i++)
    analogWrite(led[i], 255-color[i]);
}

byte rgb[3];

byte* hsvToRgb(int h, double s, double v)
{
        // Make sure our arguments stay in-range
        h = max(0, min(360, h));
        s = max(0, min(1.0, s));
        v = max(0, min(1.0, v));
        if(s == 0)
        {
                // Achromatic (grey)
                rgb[0] = rgb[1] = rgb[2] = round(v * 255);
                return rgb;
        }
        double hs = h / 60.0; // sector 0 to 5
        int i = floor(hs);
        double f = hs - i; // factorial part of h
        double p = v * (1 - s);
        double q = v * (1 - s * f);
        double t = v * (1 - s * (1 - f));
        double r, g, b;
        switch(i)
        {
                case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;
                case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;
                case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;
                case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;
                case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;
                default: // case 5:
                        r = v;
                        g = p;
                        b = q;
        }
        rgb[0] = round(r * 255.0);
        rgb[1] = round(g * 255.0);
        rgb[2] = round(b * 255.0);
        return rgb;
}

The second set of changes let the potentiometer set the color. This is a really simple change. Everything is done in the loop function. Remember, setColorWheel takes a value in the range 0 to 360 representing the angle of the hue on the color wheel. All that needs to be done is to change the second range in the map function to end at 360 instead of 83. The result is now fed into the setColorWheel function instead of computeNextValue. And finally, the time delay is removed and the computeNextValue can be deleted.

And that's it. Pretty simple changes and now the potentiometer can be used to set the color.
int ledPins[] = { 11, 9, 10 };
int inputPin = 0;

void setup()
{
  pinMode(inputPin, INPUT);
  for(int i = 0; i < 3; i++)
    pinMode(ledPins[i], OUTPUT);
}

void loop()
{
  int inputValue = analogRead(inputPin);
  int hueValue = map(inputValue, 0, 1024, 0, 360);

  setHueValue(hueValue);
}

void setHueValue(int hueValue)
{
  setColor(ledPins, hsvToRgb(hueValue, 1, 1));
}

void setColor(int* led, const byte* color)
{
  for(int i = 0; i < 3; i++)
    analogWrite(led[i], 255-color[i]);
}

byte rgb[3];

byte* hsvToRgb(int h, double s, double v)
{
        // Make sure our arguments stay in-range
        h = max(0, min(360, h));
        s = max(0, min(1.0, s));
        v = max(0, min(1.0, v));
        if(s == 0)
        {
                // Achromatic (grey)
                rgb[0] = rgb[1] = rgb[2] = round(v * 255);
                return rgb;
        }
        double hs = h / 60.0; // sector 0 to 5
        int i = floor(hs);
        double f = hs - i; // factorial part of h
        double p = v * (1 - s);
        double q = v * (1 - s * f);
        double t = v * (1 - s * (1 - f));
        double r, g, b;
        switch(i)
        {
                case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;
                case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;
                case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;
                case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;
                case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;
                default: // case 5:
                        r = v;
                        g = p;
                        b = q;
        }
        rgb[0] = round(r * 255.0);
        rgb[1] = round(g * 255.0);
        rgb[2] = round(b * 255.0);
        return rgb;
}


All in all, I think this demonstrates pretty well how a small amount of hardware and some trivial software changes can provide very different end user results. The combination of hardware and software that Arduino makes available to the experimenter I find really cool.

Next time, I have some other ideas to add another input method to this circuit and some minor tweaks in software to increase the range of colors that can be generated by the RGB LED.

No comments: