Arduino RGB LED Controller
When reorganizing our stock I found a whole bunch of diffused RGB LEDs (>100pcs) so I thought I would make something cool with them. Maybe some kind of individual controllable christmas light or some lights you could use for showcases etc.
So I decided once again to make a project using the Arduino Duemilanove, as it is so quick and easy to get a prototype working.
After some brainstorming, thinking about how I should do the PWM multiplexing, I designed a circuit capable of driving 8 individual RGB LEDs using 11 pins. The project would be easily expandable and would only cost 1 extra pin for each extra RGB LED.
On the schematic above you will see how I decided to use an ULN2803 for the multiplex “enabling”, though it can sinc up to 500mA, this circuit only requires a maximum of 75mA pr. LED – and because of the multiplexing this would actually only be a maximum of 75mA in total.
For the driving of each color I am using 3 PNP transistors (BC557), capable of driving 100mA – more than enough for a single color. The limiting resistors are set to about 25mA for each color (1.9V dropout on RED, 0.7V on green and blue)
Here is a short video demonstration of the circuit connected to the Arduino, displaying colors from the HSV Color model
The multiplexing is done using TIMER2 on the Arduino, which unfortunately also is used by the PWM. This took my some time to figure out a smart way (if it is) to deal with this, as the PWM frequency has to be higher than the multiplexing frequency to avoid random flickering.
I achived this by increasing the PWM frequency of the used PWM channels (9, 10, 11), which is located at TIMER2 and TIMER3. Then I set the TIMER2 to interrupt at overflow and made a prescaler in that interrupt, so the multiplex function would only be called once in a while.
The code used in the demonstration can be found beneath.
#define PrescalerOverflowValue 4
ISR(TIMER2_OVF_vect)
{
if (Prescaler < PrescalerOverflowValue)
Prescaler++;
else {
Prescaler = 0;
Multiplex();
}
}
unsigned char CurrentLED = 1;
unsigned char LEDValues[8][3];
bool LEDMoveDir[8][3];
void Multiplex(void)
{
PORTD &= 0b00000011; // Control pin 0-5
PORTB &= 0b11101110; // Control pin 6-7
analogWrite(9, 255-LEDValues[CurrentLED][0]);
analogWrite(10, 255-LEDValues[CurrentLED][1]);
analogWrite(11, 255-LEDValues[CurrentLED][2]);
switch (CurrentLED)
{
case 0:
//digitalWrite(2, 1); // Turn on LED 1
PORTD |= 0b00000100;
break;
case 1:
//digitalWrite(3, 1); // Turn on LED 1
PORTD |= 0b00001000;
break;
case 2:
//digitalWrite(4, 1); // Turn on LED 1
PORTD |= 0b00010000;
break;
case 3:
//digitalWrite(5, 1); // Turn on LED 1
PORTD |= 0b00100000;
break;
case 4:
//digitalWrite(6, 1); // Turn on LED 1
PORTD |= 0b01000000;
break;
case 5:
//digitalWrite(7, 1); // Turn on LED 1
PORTD |= 0b10000000;
break;
case 6:
//digitalWrite(8, 1); // Turn on LED 1
PORTB |= 0b00000001;
break;
case 7:
//digitalWrite(12, 1); // Turn on LED 1
PORTB |= 0b00010000;
break;
}
CurrentLED++;
if (CurrentLED > 7)
CurrentLED = 0;
}
void setPwmFrequency(int pin, int divisor) {
byte mode;
if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
switch(divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 64: mode = 0x03; break;
case 256: mode = 0x04; break;
case 1024: mode = 0x05; break;
default: return;
}
if(pin == 5 || pin == 6) {
TCCR0B = TCCR0B & 0b11111000 | mode;
} else {
TCCR1B = TCCR1B & 0b11111000 | mode;
}
} else if(pin == 3 || pin == 11) {
switch(divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 32: mode = 0x03; break;
case 64: mode = 0x04; break;
case 128: mode = 0x05; break;
case 256: mode = 0x06; break;
case 1024: mode = 0x7; break;
default: return;
}
TCCR2B = TCCR2B & 0b11111000 | mode;
}
}
int Red, Green, Blue;
int Hue1, Hue2, Hue3, Hue4, Hue5, Hue6, Hue7, Hue8, Saturation, Value;
void setup(void) {
//Set the pin we want the ISR to toggle for output.
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(7,OUTPUT);
pinMode(8,OUTPUT);
pinMode(12,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
digitalWrite(2, 0); // Turn off LED 4
digitalWrite(3, 0); // Turn off LED 4
digitalWrite(4, 0); // Turn off LED 4
digitalWrite(5, 0); // Turn off LED 4
digitalWrite(6, 0); // Turn off LED 4
digitalWrite(7, 0); // Turn off LED 4
digitalWrite(8, 0); // Turn off LED 4
digitalWrite(12, 0); // Turn off LED 4
digitalWrite(9, 1);
digitalWrite(10, 1);
digitalWrite(11, 1);
setPwmFrequency(9, 8);
setPwmFrequency(10, 8);
setPwmFrequency(11, 8);
analogWrite(9, 255);
analogWrite(10, 255);
analogWrite(11, 255);
LEDValues[0][0] = 100;
LEDValues[0][1] = 0;
LEDValues[0][2] = 20;
LEDValues[1][0] = 0;
LEDValues[1][1] = 30;
LEDValues[1][2] = 144;
LEDValues[2][0] = 66;
LEDValues[2][1] = 245;
LEDValues[2][2] = 3;
LEDValues[3][0] = 30;
LEDValues[3][1] = 30;
LEDValues[3][2] = 200;
//Start up the serial port
Serial.begin(19200);
//Signal the program start
Serial.println("RGB LED Controller");
// Enable Timer 2 interrupt (also used for PWM though)
// This interrupt is divided by a prescaler, and takes care of the multiplexing
TIMSK2 = 1<<TOIE2;
Hue1 = 0;
Hue2 = 10;
Hue3 = 20;
Hue4 = 30;
Hue5 = 40;
Hue6 = 50;
Hue7 = 60;
Hue8 = 70;
Saturation = 255;
Value = 255;
}
unsigned char incomingByte = 0;
unsigned char getNumPos = 0;
unsigned char tempNum = 0;
unsigned char LEDSetNum = 0;
unsigned char LEDSetColor = 0;
void loop(void)
{
delay(20);
HSVtoRGB(&Red, &Green, &Blue, Hue1, Saturation, Value);
LEDValues[0][0] = Red;
LEDValues[0][1] = Green;
LEDValues[0][2] = Blue;
Hue1++;
if (Hue1 > 359)
Hue1 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue2, Saturation, Value);
LEDValues[1][0] = Red;
LEDValues[1][1] = Green;
LEDValues[1][2] = Blue;
Hue2++;
if (Hue2 > 359)
Hue2 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue3, Saturation, Value);
LEDValues[2][0] = Red;
LEDValues[2][1] = Green;
LEDValues[2][2] = Blue;
Hue3++;
if (Hue3 > 359)
Hue3 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue4, Saturation, Value);
LEDValues[3][0] = Red;
LEDValues[3][1] = Green;
LEDValues[3][2] = Blue;
Hue4++;
if (Hue4 > 359)
Hue4 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue5, Saturation, Value);
LEDValues[4][0] = Red;
LEDValues[4][1] = Green;
LEDValues[4][2] = Blue;
Hue5++;
if (Hue5 > 359)
Hue5 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue6, Saturation, Value);
LEDValues[5][0] = Red;
LEDValues[5][1] = Green;
LEDValues[5][2] = Blue;
Hue6++;
if (Hue6 > 359)
Hue6 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue7, Saturation, Value);
LEDValues[6][0] = Red;
LEDValues[6][1] = Green;
LEDValues[6][2] = Blue;
Hue7++;
if (Hue7 > 359)
Hue7 = 0;
HSVtoRGB(&Red, &Green, &Blue, Hue8, Saturation, Value);
LEDValues[7][0] = Red;
LEDValues[7][1] = Green;
LEDValues[7][2] = Blue;
Hue8++;
if (Hue8 > 359)
Hue8 = 0;
}
void HSVtoRGB( int *r, int *g,int *b, int h, int s, int v )
{
int f;
long p, q, t;
if( s == 0 )
{
*r = *g = *b = v;
return;
}
f = ((h%60)*255)/60;
h /= 60;
p = (v * (256 - s)) / 256;
q = (v * ( 256 - (s * f)/256 ))/256;
t = (v * ( 256 - (s * ( 256 - f ))/256))/256;
switch( h ) {
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:
*r = v;
*g = p;
*b = q;
break;
}
}
After playing around with the low-power diffused RGB LEDs I thought it was time for some more luminance so we ordered a couple of 1W RGB LEDs – 150mA for each color. After some redesign, using a BC327 (1A) instead, I hooked up the LED and was shocked by the brightness. It’s difficult to show on camera how bright they really are, but I shot a couple of short clips with them.



What’s the difference between the Duemilanove and the Uno? Is this circuit capable for another 8 LEDs connected in parallel one by one?
Thanks.
Hi,
The way you control the LEDs is very interesting! Did you use the concept of “Persistent of Vision” to cycle through the LEDs really fast? Meaning, you enabled LED1, sent PWM signals to LED1, disabled LED1, enabled LED2, sent PWM signals to LED2, disabled LED2…..all done really fast?
@John
Hi John.
Yes we are using the persistent of vision technique, which in the electronics world is also known as multiplexing.
The multiplexing is done in an interrupt routine, controlled by a timer. Have a look at the code and the multiplex() routine to see how it works.
Best Regards
Thomas Jespersen
@Jesús
The difference is not much. The board layout and functionality is exactly the same, and they are using the same processor: ATMEGA328.
The only difference is that the UNO is the most recent Arduino board, and they decided to use another chip for the USB to serial conversion (programming etc.). So instead of using an FTDI FT232 chip, they are now using another Atmel microprocessor for the USB to UART conversion (Atmega16U2).
Best Regards
Thomas Jespersen
Hello
Already I want to apologize for my English … I’m French
^ ^
I found your article on Arduino RGB LED Controller
(http://blog.tkjelectronics.dk/2011/08/arduino-rgb-led-
controller/).
I want to make a suit with LEDs inside, I’ve already
made one last year: http://www.youtube.com/watch?
v=meBAXw0mf0Y
But this time I use RGB LED to play with colors, not just
the effect of delayed ignition LED
I buy LED RGB common cathode:
I made a ProtoBoard with 6 LED arduino to test my
program and my mount, so far so good … except for now
I only have six LEDs connected to my card … as I have
to plug in between 200 and 300, I need more ULN2803
of course … but the question is should I also use
another component for the RGB output …
Here is a diagram to explain:
http://www.mimigyaru.com/autres/cosplay/Maiotome/sh
emas_RGB.jpg
… I guess I’m missing a component between the RGB
outputs of the Arduino board and pulp of LED …
if you could advise me! it would be really cool!
thank you in advance!
@Nadia Zeghloul
Hi Nadia.
Actually your circuit seems to be fine, as you are using the ULN2803 devices as you should for the sincing. The Arduino board is able to source up to 40mA per pin, so as long as you only drive a single LED a time, you would definitely be able to use this setup.
But if you switch out the LEDs to some high power ones, as I did, you will have to add some driving/sourcing transistors at the common RED, GREEN and BLUE connection.
But for your project I will say your schematic and setup will work fine.
Best Regards
Thomas Jespersen
@Thomas Jespersen So If I have an LED DC Forward Current: 20mA
I have 270 LED in the costume I calculated it would take me 2 to ULN2803 pin cathode, I will need how many transistor for RGB ports … knowing that the moment I plugged 6LED of the RGB port (and that each LED is then connected to a pin for the A output … and it did not scorched my card …)
@Nadia Zeghloul
Dear Nadia.
Unfortunately I don’t understand how you are trying to connect all those LEDs.
Do you want all to be individual controllable, or should some of them be alight simultaneously?
Best Regards
Thomas Jespersen
ok ^ ^ I start again:
I have my Arduino board, on the pins 11, 10 and 9 are respectively connected the inputs R, G and B of all the LED (as on the diagram above). For all the costume LED should change color at the same time!
However they will not all be lit simultaneously: I simply put 6 different LED, multiply by 45 to get the number of LED 270;
So the LEDs A, B, C, D, E and F are respectively connected to Pin A0, A1, A2, A3, A4 and A5. And since there are many LED between the cathodes of the LEDs and Arduino I put two ULN2803.
Wholesale it’s like on the diagram, except that instead of having 3 LEDs per color that I would have 45!
Knowing that the moment my ProtoBoard is exactly like on the diagram, except that I only have one LED per color! and it works perfectly, I did not even put resistance between my card and my LED! LEDs are a DC Forward Current 20mA Reverse Voltage and 5.0 V:
http://www.ebay.com/itm/370262039446?item=370262039446&pt=Motors_Car_Truck_Parts_Accessories&cmd=ViewItem&hash=item5635565b96&vxp=mtr#ht_3465wt_952
So my question is do I really add the transistor and if so how many, and what are their references?
@Thomas Jespersen
Thanks for you reply,
So, I can use the UNO instead the Duemilanove. Which is better to work with a Mac? And, regarding another question I asked before, is this circuit capable for another 16 LEDs connected in parallel one by one?
Best regards,
Jesús
Thomas,
I’m not an engineer, but I am wondering if there are enough I/O on an arduino to multiplex 2 of ULN controllers to be able to control 16 separate LED.
@Bryan
The Arduino Uno itself only have 13 digital pins that is compatible with the digitalWrite function.
But to be true the 5 analog input pins is actually just simple GPIO pins on the ATMEGA who have an alternate function as AD input. To use these pins you can simply write “digitalWrite(A0, HIGH);” etc.
So if you take these 5 pins into account you would have a total of 18 pins, which will make it possible to multiplex 16 seperate LEDs but only with two colors (as you only have to pins left for the PWM).
Another option would be to use a Shift register like the 74HC595 which gives you 8 outputs per chip and only requires 3 pins. But the most important thing is that you can chain them without any extra pins required.
Is it possible to control the leds individually? so say i want to give each led a different color by retrieving values from a kind of ‘database’
And i read often that the uln2803a is not suited for common cathode leds, do you know more about that? (i have these: https://www.sparkfun.com/products/10390)
Kind regards,
Chris
@Chris
Hi Chris.
I agree that it can be a bit confusing whether or not the ULN2803 can be used to sink or source current.
I have been looking and reading much on this IC recently and I can’t come up with any good explanation that our schematic works based solely on the datasheet.
But you might find this page interesting and descriptive regarding this discussion: http://jaspreetscodezone.blogspot.dk/2008/02/uln-2803-pinout-working.html
But as you can see with this project you are able to sink current and drive common cathode LEDs through the ULN2803. The maximum allowed sinking current is untested though!
Regards Thomas
hi,
nice web site man.
i tried building this circute but found out that my rgb leds are common anode and not common cathode.
how can i change the code to work with the leds i have?
i’m new to electronics so any help is appreciated.
thanks
Mor
@Pargit
Hi Mor.
It is actually very simply to change the project to common anodes.
The ULN chip outputs should still be connected to the common pin, in your case the common anode pin.
For the 3 cathode pins these should then be connected to 3 BC547 NPN transistors -- please notice the difference here as the one in the schematic is PNP transistors. And while the emitter on the PNP transistors were connected to 5V the emitter on the NPN transistor in your case should be connected to GND.
Then your circuit will work.
Next is a slight code change were you find the line in the code saying:
analogWrite(10, 255-LEDValues[CurrentLED][1]);
analogWrite(11, 255-LEDValues[CurrentLED][2]);
And change it to:
analogWrite(10, LEDValues[CurrentLED][1]);
analogWrite(11, LEDValues[CurrentLED][2]);
I hope you can get it working.
Regards Thomas
I thank you for the awesome tutorial, and am pleased to say I have 16 or so LEDs shipping in the mail right now for this project. I’m completely new to arduino programming so it’s a tad hard to follow your code (some java experience), but how difficult would it be to have a button that controls the pattern, with the fading lights as one, and perhaps lights flashing down the line as another? What would have to be changed in the code?
@Spring
Thank you for the kind words.
The only thing for you to change in the code would be all the code inside the loop() function.
This code should be replaced with your own that set the LEDValues arrays with the color information for each attached LED.
The timer controller interrupt loop that I made for the multiplexing will then take care of the rest.
Regards Thomas
After looking through your code and analyzing it a bit, I think I’ve figured out how it works fairly well. With that I think I’ve got a code snippet that will flash opposite lights on and off at hue[k] (unchanging at the moment) depending on the value of onOffCount. In order to change between these, would it be best to put a switch case controlled by a variable that counts external button presses, and would it be more efficient in the code to disregard the forloop and replace it with static declarations for each variable like in your version? I’m thinking of other patterns to try out right now, but suggestions on the current one are appreciated.
{
HSVtoRGB(&Red, &Green, &Blue, Hue[k], Saturation, Value[k]
LEDValues[k][0] = Red;
LEDValues[k][1] = Green;
LEDValues[k][2] = Blue;
//Turns every other LED on or off depending on counter
if (onOffCount < 255)
{
if ((k+1)%2 == 0)
Value[k] = 0;
else
Value[k] = 255;
}
else
{
if ((k+1)%2 == 0)
Value[k] = 255;
else
Value[k] = 0;
}
onOffCount++
if (onOffCount > 511)
onOffCount = 0;
}
Are you sure you pasted the correct code in the box? I copied it line for line and when I ran it, I found that half of the LEDs weren’t lighting. After looking into the setup loop, I found that you forgot to toggle the pinmodes of 6,7,8, and 12 to OUTPUT, as well as zero them out.
I also found that the entire spectrum sweep was ‘lacking’ in colors; the orange part just looks a dull white, which, after examining the LEDs, leads me to believe that the channels aren’t going from 0-255 as designed, and that the HSVtoRGB void is the culprit, because changing saturation to 0 leaves the LEDs at an unchanging color, (probably 128 in each channel?) and changing the Value ends up with the same effect. (All LEDs that same color, unchanging).
The biggest confusion I have is that when both saturation and value are at 255, the color goes back to cycling from 128-255 on each value (I believe) but every LED is the same color -- there is no separation of hues by 10, and changing hue values in the setup loop doesn’t do anything. If you could check to see if this is your latest code, i would be extremely grateful, as i have a Halloween party to go to on Saturday
TL;DR: No hue separation, half of pins not initialized, HSVtoRGB probably not working properly.
@Spring
Dear Spring.
I would say that your code seems fine. I won’t use a switch-case for this.
Though I guess you should move the HSVtoRGB and LEDValues setting just above the onOffCount++ so you use the latest Value[k] when setting the LED colors.
As your code is now the LEDs will be updated, but 1 cycle shifted.
I will get back to you about the code errors, though I don’t remember anything to be wrong.
But as I still have the project on a breadboard I will test it for you and make a reply soon.
Best Regards
Thomas Jespersen
@Spring
Dear Spring.
I have now tested the code, and you were right that I was missing the initialization of the 4 last output pins. I have now updated the code in the blog post.
But even before I didn’t experience the issues you are talking about with the hues -- all of my LEDs are changing colors (hues) as demonstrated in the video.
Regards Thomas
Hi Thomas,
i have managed to build the circute, and tried to change it to work with 74595 chip (instead of using 8 legs on the ardu.
i have changed the code of the first functions as such:
#define PrescalerOverflowValue 4
ISR(TIMER2_OVF_vect)
{
if (Prescaler 7)
CurrentLED = 0;
}
void setPwmFrequency(int pin, int divisor) {
byte mode;
if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
switch(divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 64: mode = 0x03; break;
case 256: mode = 0x04; break;
case 1024: mode = 0x05; break;
default: return;
}
if(pin == 5 || pin == 6) {
TCCR0B = TCCR0B & 0b11111000 | mode;
} else {
TCCR1B = TCCR1B & 0b11111000 | mode;
}
} else if(pin == 3 || pin == 11) {
switch(divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 32: mode = 0x03; break;
case 64: mode = 0x04; break;
case 128: mode = 0x05; break;
case 256: mode = 0x06; break;
case 1024: mode = 0x7; break;
default: return;
}
TCCR2B = TCCR2B & 0b11111000 | mode;
}
}
int Red, Green, Blue;
int Hue1, Hue2, Hue3, Hue4, Hue5, Hue6, Hue7, Hue8, Saturation, Value;
void setup(void) {
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
digitalWrite(9, 1);
digitalWrite(10, 1);
digitalWrite(11, 1);
setPwmFrequency(9, 8);
setPwmFrequency(10, 8);
setPwmFrequency(11, 8);
analogWrite(9, 255);
analogWrite(10, 255);
analogWrite(11, 255);
//Start up the serial port
Serial.begin(19200);
//Signal the program start
Serial.println("RGB LED Controller");
// Enable Timer 2 interrupt (also used for PWM though)
// This interrupt is divided by a prescaler, and takes care of the multiplexing
TIMSK2 = 1<<TOIE2;
Hue1 = 0;
Hue2 = 10;
Hue3 = 20;
Hue4 = 30;
Hue5 = 40;
Hue6 = 50;
Hue7 = 60;
Hue8 = 70;
Saturation = 255;
Value = 255;
}
unsigned char incomingByte = 0;
unsigned char getNumPos = 0;
unsigned char tempNum = 0;
unsigned char LEDSetNum = 0;
unsigned char LEDSetColor = 0;
this is not working now. i'm trying to find what is wrong, my guess is that i have somthing wrong with the setup of the ISR timers. can you check my code?
thanks
Mor
@Pargit
Hi Pargit.
Have you pasted all the code in this comment, as it seems very incomplete to me?
The ISR fx isn’t doing the counting as supposed to.
The only thing it seems to be doing is setting the CurrentLED to 0 if Prescaler is 7, though Prescaler is never changed.
Regards Thomas
Hi Thomas,
i’m trying to paste the whole code but it keeps trimming the same part of the multiplex() function (which is the one i have alterd).
i paste it here: http://pastebin.com/z2qE1QWA you can se with the remarks the ways i tried to make it work. because i have trying to send to serial the number of led that is on and not seeing it in the console, my guess is that i’m not getting into the multiplex function at all.
hope you can help.
Pargit
@Pargit
Please make sure that you are using an ATMEGA168 or ATMEGA328 Arduino board, as I’m relying on Timer 2, used for the PWM generation on pin 11, to generate the Multiplex interrupts.
If not, try and move your test line, “DDRD = DDRD | B11111100;”, as the first line inside the ISR.
Merry christmas.
Regards Thomas
Why is the ULN2803a needed can’t you just add it to Ground?
I meant to say can’t you connect the the cathode to ground instead of the ULN2803a @Ron Jad
@Ron Jad
If you simply connect all of the cathodes to ground all the RGB LEDs will be lit at the same time and with the same color.
By doing this you will eliminate the multiplexing option and the option to set individual colors on each RGB LED.