Home > Arduino > HSL to RGB tutorial + Updated Rotary Encoder Library

HSL to RGB tutorial + Updated Rotary Encoder Library

April 22nd, 2009

Background:

I am in the design process of a new project that uses a lot of RGB LEDs. In the project I want to allow the user to select various colours and I was looking for a fairly intuitive way to do this.

If one has ever worked with RGB LEDs you know you have to PWM the values of each channel (R, G or B). Therefore for Red, I would PWM (255,0,0), for Green – (0,255,0), etc.

Now since each LED has 3 channels, you have to control three outputs. Therefore if I wanted to allow the user to be able to control the colour of an LED, I would have to have him control three sensors. That is a lot of inputs for one output (3 inputs per output). For an application like this I would normally consider three potentiometer’s to control the LED

But I wanted to be able to control the LED with one sensor. Doing some research I found that the HSL colour scheme was exactly what I need. I stumbled across this Rotary Encoder on Seeedstudio and it fit my price range. The reason I chose an encoder is you can use it to act like a pot but it allows continuous rotation.

My solution was to use an HSL Color Schemer. If I set the luminance and saturation to a suitable value, I can then use the rotary encoder to cycle through the Hue spectrum. Now while this does not give me every single RGB combination, it does give me 360 combinations across all the RGB colour channels.

Integration:

The major stumbling block I had was the Rotary Encoder. The reason it was so cheap was because it was a Mechanical Encoder. Therefore I has some serious debounce factor to consider – either through software or hardware. I found a little Rotary Encoder library written by SunBox on the Arduino Forums. However, the way it worked was you had to set a max and a min and once either extreme was reached, it no longer counted forward or backward.

I added a restart function to the library. Setting restart to a value of 1, tells the library to start counting from the min value.

restart(1);

I’ve attached the library below:
Rotary Encoder Library

Note: the library currently counts up by 20 on every edge. I used this cause I had a 20 detent encoder and that means a full rotation goes just over the whole hue range. I will add a function that allows you to choose how much to count by on each edge

Here is some sample code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "RotaryEncoder.h"
 
RotaryEncoder rotary(3, 2, 4);
int Rpin=10;
int Gpin=9;
int Bpin=11;
float H,S,L,Rval,Gval,Bval;
 
void HSL(float H, float S, float L, float& Rval, float& Gval, float& Bval);
float Hue_2_RGB( float v1, float v2, float vH );
 
void setup()
{
  Serial.begin(9600);
 
  //set up encoder with rotary library
  rotary.minimum(0);
  rotary.maximum(360);
  rotary.position(320);
  rotary.restart(1);
  pinMode(12, OUTPUT); 
}
 
 
void loop()
{
  H=rotary.position();
  S=1;
  L=.5;
  Rval=0;
  Gval=0;
  Bval=0;
  HSL(float(rotary.position())/360,S,L,Rval,Gval,Bval);
 
  //common anode configuration
  //analogWrite(Rpin, 255-Rval);
  //analogWrite(Gpin, 255-Gval);
  //analogWrite(Bpin, 255-Bval);
  //digitalWrite(12,HIGH);
 
  //common cathode configuration
  analogWrite(Rpin, Rval);
  analogWrite(Gpin, Gval);
  analogWrite(Bpin, Bval);
  digitalWrite(12,LOW);
 
  //print statements for debug
  Serial.print("position:");
  Serial.print(H);
  Serial.print(" R:");
  Serial.print(Rval);
  Serial.print(" G:");
  Serial.print(Gval);
  Serial.print(" B:");
  Serial.println(Bval);
  delay(1000);
}
 
void HSL(float H, float S, float L, float& Rval, float& Gval, float& Bval)
{
  float var_1;
  float var_2;
  float Hu=H+.33;
  float Hd=H-.33;
  if ( S == 0 )                       //HSL from 0 to 1
  {
     Rval = L * 255;                      //RGB results from 0 to 255
     Gval = L * 255;
     Bval = L * 255;
  }
  else
  {
     if ( L < 0.5 ) 
       var_2 = L * ( 1 + S );
     else           
       var_2 = ( L + S ) - ( S * L );
 
     var_1 = 2 * L - var_2;
 
     Rval = round(255 * Hue_2_RGB( var_1, var_2, Hu ));
     Serial.print("Rval:");
     Serial.println(Hue_2_RGB( var_1, var_2, Hu ));
     Gval = round(255 * Hue_2_RGB( var_1, var_2, H ));
     Bval = round(255 * Hue_2_RGB( var_1, var_2, Hd ));
  }
 
}
float Hue_2_RGB( float v1, float v2, float vH )             //Function Hue_2_RGB
{
   if ( vH < 0 ) 
     vH += 1;
   if ( vH > 1 ) 
     vH -= 1;
   if ( ( 6 * vH ) < 1 ) 
     return ( v1 + ( v2 - v1 ) * 6 * vH );
   if ( ( 2 * vH ) < 1 ) 
     return ( v2 );
   if ( ( 3 * vH ) < 2 ) 
     return ( v1 + ( v2 - v1 ) * (.66-vH) * 6 );
   return ( v1 );
}

Hope this helps someone, I will be adding pictures soon. If you need any other modifications made to the library don’t hesitate to ask.

Darius Gai Arduino

  1. Barcud
    April 29th, 2009 at 14:25 | #1

    I am using the same rotary encoder and had some problems getting it to work. Then I came across the following link.
    http://www.neufeld.newton.ks.us/electronics/?p=248
    I used that Library with the following change in quadrature.cpp:
    //const int _full [4][4] = {
    // { 0, 0, 0, 0 }, // 00 -> 10 is silent CW
    // { 1, 0, 0, -1 }, // 01 -> 00 is CW
    // { -1, 0, 0, 1 }, // 10 -> 11 is CW
    // { 0, 0, 0, 0 } // 11 -> 01 is silent CW
    //};
    const int _full [4][4] = {
    { 0, 0, 0, 0 },
    { 1, 0, 0, 0 },
    { -1, 0, 0, 0 },
    { 0, 0, 0, 0 }
    };

    And it works perfectly.

    Just if anybody is interested.

    Barcud

  2. Darius Gai
    April 29th, 2009 at 16:18 | #2

    Hmm… Those two libraries seem very similar. It seems SunBox built on keith’s library by adding the pressed feature for encoders that havea push button as well. I then built on top of SunBox’s library by adding the restart function. Interesting…

    I think I’m gonna create headers on both the files and add them into the Revision History

  3. May 7th, 2009 at 16:55 | #3

    @Darius Gai

    I just ran across your post, and it’s nice to see someone using what was originally my rotary encoder library! I hope it’s serving you well.

    However, I also just discovered SunboX’s Arduino forum post plagiarizing my library, and I’m very disappointed by that.

    First, he or she plagiarized my code that I had posted six months earlier, removing the attribution mandated by the Creative Commons attribution / share-alike license and claiming the code as his or her “‘own’ little library,” with no credit given to me.

    Second, he or she stripped the code and removed functionality — support for multiple concurrent encoders, among other things.

    So on a technical note –

    If I understand correctly, your encoder.restart(bool) method is designed to make the encoder wrap around from end to end when you’re using min and max values — rotate higher than max and it wraps around to min, and vice-versa.

    I hadn’t thought to use this functionality, and I like the idea a lot. I’ll add it to the library, and I thank you for thinking of it. Because you already have a working program, I understand that you may not care if I publish a new version; but I wanted you to know that it would be out there and that I’ll credit you for the suggestion.

    And on a copyright note –

    My original code is published under a Creative Commons attribution / share-alike license. Even though it’s not your fault that SunboX violated my copyright by plagiarizing my code, the cc-by-sa license means that because your code is based on mine, you need to attribute me in the way I specify, *and* post your code under the same license, if you want to distribute your code.

    So I need to ask you that if you want to continue making your version of the RotaryEncoder library available for download, you have to add my attribution and license terms back into the comments at the top of each file. The specific method to do that is mentioned at the link Barcud pasted above.

    Alternatively, the wrap functionality that you contributed (*) will soon be added to my library, and you’re very welcome to use my updated version rather than the one based on SunboX’s work, if you prefer.

  4. Darius Gai
    May 19th, 2009 at 16:52 | #4

    @ Keith

    Sounds good to me. I’m going to modify the library to add the header as I said I would in the earlier comment. Once you modify your library, to add the restart feature, I will take off the library from this page and instead link to your blog. Sounds good?

  1. No trackbacks yet.