RGB LED Lighting Programmer

LED mood lighting controller

Check out the accompanying videos at https://www.youtube.com/playlist?list=PLUJdy4sTWIrpdgpVsYgxAaxRtH6WdTTs-
So, I was looking at some RGB lights and thought they might be interesting to have a play with.
I went to a friends house and she had spent quite a bit of money on some LED RGB lights.
We were both a bit disappointed with the amount of light from them and the fact that the controller
would only set static light levels. I am sure there are controllers that do a lot more but it starts to get
expensive. I recalled a few years ago Philips launching a TV set with ambient lighting https://www.techradar.com/news/should-i-buy-a-philips-ambilight-tv
that matched multiple colours of the scene on the TV and lit LEDs to match this at the rear of the TV,
it was a pleasing effect and I wondered if we could combine these ideas. Also in the 1980’s I designed
a lamp disco/party lighting controller to sequence 100 watt mains lamps, either at a constant rate,
or to the beat of music. I still like these ideas and with Low Voltage 12 Volt LED strips we can try
out many projects pretty safely.


The completed test set up

The Arduino is perfect for this sort of project, it is cheap enough that you can play with it and not sulk
too much if you blow it up (it may happen) and also if you embed one into a project they are cheap
enough to buy another. I have acquired some genuine UNO boards for around 10 GBP on Ebay
including a base and a prototype board and as little as 5 GBP for just the Arduino compatible boards.


I also managed to locate an LED strip, there are plenty to choose from and we require an RGB one for
this project. I went for the 5050 IP21 style and that is also available in a IP65 rated case so you can light
your pond or fish tank externally safely.


IP ratings - the first digit indicates intrusion protection and the second moisture protection
IP 65 means there is some protection from moisture, for full details go here http://www.enclosurecompany.com/ip-ratings-explained.php


This project is a baseline to demonstrate the principle, it is up to you how you employ and adapt it.
I have rated the transistors for the full LED strip length, they will handle 10 Amps (with a heatsink) but
the full 5050 LED strip fully illuminated draws just over 3 Amps, as the transistors are driven by PWM
they are either fully on, or fully off so they will not dissipate much heat. In analog mode the ratio of on
time to off time is being varied to adjust the power, the human eye just averages it out, it is being
switched at 490 - 980 Hz (depending on model). So I recommend a 12V 3Amp power supply
(The max current is only drawn for a second or two). If you reduce the length of the LED strip then
you can get away with a proportionately smaller PSU current.


Digital or Analogue?


So the basic idea starts with lighting just the red, green or blue LEDs in sequence. This is okay but
rather unappealing aesthetically, so we’ll add some variations to create more colours.


Red
Red + Green = Yellow
Green
Green + Blue = Cyan (Turquoise)
Blue
Red + Green + Blue = White (ish)


I say whiteish because LEDs produce different amounts of light in each colour and they do not combine
to give a true white light. We will deal with this later on in AnalogueFader.


The code to do this looks like this -


// digital sequencer
void digitalSequencer()
{
// slide the fade across the leds by rewriting, have a delay after each step
// set each LEDs status to LOW or HIGH in CAPITALS


digitalWrite(led[0], HIGH);//red
digitalWrite(led[1], LOW);
digitalWrite(led[2], LOW);
delay(incTime);


digitalWrite(led[0], HIGH);//yellow (red + green)
digitalWrite(led[1], HIGH);
digitalWrite(led[2], LOW);
delay(incTime);


digitalWrite(led[0], LOW);
digitalWrite(led[1], HIGH);//green
digitalWrite(led[2], LOW);
delay(incTime);


digitalWrite(led[0], LOW);
digitalWrite(led[1], HIGH);//cyan (green + blue)
digitalWrite(led[2], HIGH);
delay(incTime);


digitalWrite(led[0], LOW);
digitalWrite(led[1], LOW);
digitalWrite(led[2], HIGH);//blue
delay(incTime);


digitalWrite(led[0], HIGH);// purple (red + blue)
digitalWrite(led[1], LOW);
digitalWrite(led[2], HIGH);
delay(incTime);


digitalWrite(led[0], HIGH);// white (red + green + blue)
digitalWrite(led[1], HIGH);
digitalWrite(led[2], HIGH);
delay(incTime);
}


This is basic stuff, we turn on each LED and turn off the ones that we do not want lit (as they may be
lit in a previous step)  This also means that you can change the sequence by transposing the 4 lines
of code and know that only the LEDs that you want lit will be lit.
The comments // are not executed by the program, but they add clarity as to what is happening, very
handy when you come back to code that you wrote over six months ago. This is a best practice of
programming and when combined with consistent indenting and spacing this makes for very readable c
ode, especially when other people need to work on it.


Analogue fader
We have chosen specific pins to use for our LED outputs that can be used for either Digital or Analogue
(PWM) outputs. Okay they are not true analogue pins but they are switched on and off with a variable
ratio which emulates a variable voltage. This works because most devices and the human eye have
persistence. The LEDs will be flashing between 480 and 980 times a second (Hz), but you will only see
the average level. This is also true when controlling motors and other components.


To get a nice fade for one LED is pretty easy, you change a value from 0 to 255 in steps of 1 and when
it gets to 255 you step -1 until you get back to 0. But we want to control 3 LEDs, this took quite a bit of
trial and error. Imagine a 3 spoke cycle wheel rotating slowly and where the spokes meet the wheel rim
are the brightness of the LEDs when we measure from a horizontal line at the base of the wheel to each
spoke.


If our maximum brightness was 15 (I’ll explain soon why we do not use 255)  we could start the LEDs
off at intervals like so


int brightness[] = { 4, 7, 0 }; // start the LEDs off at 1/3 of cycle


Now we cannot just increment them or they will not obey the cycle wheel model. So we must
decrement the Red channel and increment the Green and Blue channels. As each one gets to it 0
value we will switch from decrementing to incrementing and vice versa when they reach a maximum
value of 15.


Okay so why 15 and not 255?


The human senses are funny things, our hearing is logarithmic, our sense of brightness is even weirder.
I did some research and to emulate true brightness changes requires some pretty fancy formula. I did
think this might be a it much for a 16 Mhz microcontroller to juggle 3 channels.


Here is the sort of maths involved https://jared.geek.nz/2013/feb/linear-led-pwm


So I tried an approximation, a square law. 15 x 15 = 225, hey that’s close to the maximum value and
as it is not linear, nobody will really notice. So if we step 0 - 15 and square it we will get values that are
close to the ideal curve (don’t forget we are lighting LEDs not launching rockets into space). I was really
impressed with the result and I hope that you will be too as it makes for simple maths, we just multiply
the value by itself .


‘n’ is the led number
analogWrite(led[n], brightness[n] * brightness[n]);


The best code is simple code, right?


So we spoke earlier about not getting a very good white when all 3 leds where fully lit, well it gets worse
when you fade them as they are all at a % brightness. We get a very greeny yellow, a bluish cyan and
the white is too blue. The Blue led os the most sensitive, followed by the Green, then Red. What if we
weighted the values with an adjustment factor?


float adjustLevel[] = { 1.0, 0.6, 0.4 }; // alternative weighted values for my LEDs, try experimenting with
these. I have added a variable array adjustLevel[] to allow you to calibrate the LEDs better to achieve a
nicer white, you can adjust these value from 0 to 1.0, Red is the least sensitive colour LED so leave that
at 1.0,Green is the next least sensitive so give it 0.6, then Blue is the most sensitive so give it 0.4. In the
analogueWrite line we will apply this adjustment factor.


analogWrite(led[n], brightness[n] * brightness[n] * adjustLevel[n]);


Let’s try it out -


In the reset() method (function) we set the delay() in the reset method to 5000 it will fully light all 3 LED
colours for 5 seconds (more if you like), also change


analogWrite(led[i], 0);


To


analogWrite(led[i], 255  * adjustLevel[i]);


Adjust the values and upload to the Arduino over and over until you are happy.


Once you are happy with the white effect change the lines in reset() from


analogWrite(led[i], 255  * adjustLevel[i]);
To
analogWrite(led[i], 0);


And the delay back to delay(100) which is 0.1 seconds


If you are not confident doing this just leave the values as I set them, it’ll be pretty close.


Here is the debug output from the analogue fade showing the brightness of each LED ‘cycling’ through
0-15 as I described.


This was produced by adding some extra lines to return values back to the host PC. more about
debugging later.


I added these lines to the bottom of analogFader()


// debugging
Serial.print("LED0 ");
Serial.print(brightness[0]);
Serial.print(" LED1 ");
Serial.print(brightness[1]);
Serial.print(" LED2 ");
Serial.println(brightness[2]);


This is the debug output that allowed me to make subtle changes to get the effect I wanted. This is a
full cycle which then repeats indefinitely.


Opening port
Port open
reset ran!
LED0 3 LED1 8 LED2 1
LED0 2 LED1 9 LED2 2
LED0 1 LED1 10 LED2 3
LED0 0 LED1 11 LED2 4
LED0 1 LED1 12 LED2 5
LED0 2 LED1 13 LED2 6
LED0 3 LED1 14 LED2 7
LED0 4 LED1 15 LED2 8
LED0 5 LED1 14 LED2 9
LED0 6 LED1 13 LED2 10
LED0 7 LED1 12 LED2 11
LED0 8 LED1 11 LED2 12
LED0 9 LED1 10 LED2 13
LED0 10 LED1 9 LED2 14
LED0 11 LED1 8 LED2 15
LED0 12 LED1 7 LED2 14
LED0 13 LED1 6 LED2 13
LED0 14 LED1 5 LED2 12
LED0 15 LED1 4 LED2 11
LED0 14 LED1 3 LED2 10
LED0 13 LED1 2 LED2 9
LED0 12 LED1 1 LED2 8
LED0 11 LED1 0 LED2 7
LED0 10 LED1 1 LED2 6
LED0 9 LED1 2 LED2 5
LED0 8 LED1 3 LED2 4
LED0 7 LED1 4 LED2 3
LED0 6 LED1 5 LED2 2
LED0 5 LED1 6 LED2 1
LED0 4 LED1 7 LED2 0
LED0 3 LED1 8 LED2 1


Which version to run?


By removing the comments // we can select which version to run
Below we have analogFader() selected


// the main program loop routine runs over and over again forever
void loop() {
// here we will select which effect(s) to apply
analogFader();
// digitalSequencer();
}


Here we have digitalSequencer() selected


// the main program loop routine runs over and over again forever
void loop() {
// here we will select which effect(s) to apply
// analogFader();
digitalSequencer();
}


We can make more methods to  run different display sequences and lighting options, sat static red,
green, or blue and maybe make it selectable by buttons. We can do the same for the delay speed,
make it speed up or slow down at the press of a button.


Electronics


Okay, so this will only work if we attach some LEDs, at the most basic (and slightly risky) level we
could hook LEDs and resistors directly onto the Arduino pins.


Better to use some breadboard or prototyping board like so -




Here is the circuit diagram for the test of the LEDs


So we connect 3 resistors of identical values (100 ohms to 1k ohms) to the analog pins that we will
configure as OUTPUT in our code which are 3, 6, 11. Of course you could use alternatives but you
would have to change this line in the code -


const int led[] = { 3, 6, 11 };   // an Array to store the ~ PWM output pins the LEDs are  attached to.
Power the Arduino just from USB for now, this will make 5 Volts available from the three outputs when
they are turned on or PWM sent to them.


Upload the program by pressing Ctrl + U in the Arduino IDE. If there are no errors the LEDs on the
board should blink to show data transfer and then the other LEDs should start sequencing or fading
according to what method you selected.


If all is well you will need to acquire an RGB LED strip and a 12 Volt 3 Amp power supply. I am using
my home made bench supply for now. Change the wiring to the diagram below.


Here is my breadboard layout, you may notice I substituted the LEDs for the TIP41C transistors by
virtue of some clever forwards planning.



The four connection on the bottom right hand side will line up with the 4 way plug on my RGB LEDs,
see the videos. The orange wire is the incoming 12 Volts from my power supply and the Black one
on the rear right hand side is 0 Volts to my power supply. At this stage we still need the USB to power
the Arduino, we’ll fix this later.


Here is the completed wiring with the RGB LED strip attached -


We will arrange something more permanent once we have the software all coded, customized and
tested.
Debugging code


Everything I originally coded can be done using the Arduino IDE (Integrated Development Environment)
from Arduino.cc. This is how I originally coded it.


You can use the Web Editor at https://create.arduino.cc/
Or better still download the Arduino IDE software at https://www.arduino.cc/en/Main/Software


Any project can be built and launched to an Arduino from these software. But I did have some trouble
with the AnalogueFader, so I decided we needed a debugger to see what was happening. The only
thing that you cannot do with Arduino IDE is to interrogate values used in your program or stop and
run it line by line, this process is called debugging and is a powerful tool used by professional
developers every day. I was delighted to find that we can use some professional tools with the Arduino
as you can for many other devices.  


A number of debugging tools are available to do this https://www.circuito.io/blog/arduino-debugging/


Visual Micro


I plan to use Visual Micro as it integrates with Microsoft Visual Studio 2019 Community Edition which
I already have installed.


If you are an experienced programmer you will already be aware of the power of Microsoft Visual
Studio. This is the first thing we need to to install. It is a slow process but you only need do it once
and the configuration can be changed later with ease.


Microsoft Visual Studio Community Edition 2019 is free to download and use. First create an Outlook
or Hotmail account as this integrates the software with other packages and tools like Azure hosting
and GIT. It is a very powerful package and you will need a recent PC with plenty of hard drive space,
at least 6GB and preferably an SSD. Then login at https://my.visualstudio.com/Downloads/Featured


You will be offered quite a few options when you install it, if you are a beginner just install general
C# web development and Python. These will be useful one day and all of the  options can be
changed later. The screen should look similar to this - (You don’t need Azure deployment or Node.js
unless you know what these mean). Asp.Net programming is something you might aspire to if you
intend to become a professional programmer (like me) and use C# and VB.Net and other code
like HTML and CSS and Javascript.




Now, Visual Studio is a great platform but natively it cannot program or debug an Arduino. No worries,
we can extend its functionality by installing Visual Micro.ArduinoIDE for Visual Studio.


Visual Micro is free to download and evaluate for up to 90 days
This is a trial version that you can use for a period before you have to pay for it, this extends VS2019
to Arduino and C++ programming and debugging. It is awesome!


At its simplest we can  build our code, Ctrl, Shift + b  and see any errors, then run and upload it by
pressing F5 and any debugging data will appear in a pop-up window. You will need to read the
documentation to reach the full power of this IDE but a few carefully placed lines like
Serial.println("reset ran!");


// debugging
Serial.print("LED0 ");
Serial.print(brightness[0]);
Serial.print(" LED1 ");
Serial.print(brightness[1]);
Serial.print(" LED2 ");
Serial.println(brightness[2]);


Will do wonders, Serial.println prints a new line after the output, Serial.print does not. Read the
documentation to see how much more this can do. It also allows you into the Arduino’s internals in
the output windows -


    Millis is currently 1665459.
11:23:59.126 [up:27m:47s perf:2499ms]   RGB_LED_Programmer.ino, line 43 millis()=1667963
    Millis is currently 1667963.
11:24:01.554 [up:27m:50s perf:2499ms]   RGB_LED_Programmer.ino, line 43 millis()=1670468
    Millis is currently 1670468.
11:24:04.083 [up:27m:52s perf:2500ms]   RGB_LED_Programmer.ino, line 43 millis()=1672973
    Millis is currently 1672973.
11:24:06.613 [up:27m:55s perf:2499ms]   RGB_LED_Programmer.ino, line 43 millis()=1675478
    Millis is currently 1675478.
11:24:09.065 [up:27m:57s perf:2499ms]   RGB_LED_Programmer.ino, line 43 millis()=1677982
    Millis is currently 1677982.
11:24:11.597 [up:28m:00s perf:2500ms]   RGB_LED_Programmer.ino, line 43 millis()=1680487
    Millis is currently 1680487.


Keep a copy of the original program and hack away (I hate that word!), okay, change some variables
and see what they really do and change the program flow so that you understand how it works and
why it doesn’t. Soon you will be writing your own code from scratch. It is great fun if you are patient
and like problem solving and it can lead to a rewarding career in software or web development and
you might end up at Facebook, Amazon or Google!


Further improvements


It is not very convenient to have to change the method called in the loop() every time that we want a
different effect. After all the Arduino should be able to stand alone and be user-friendly. So we need
to add a couple of buttons and connect them to digital inputs. We will use one button to control what
program is run and it will change each time that the button is pressed. We will also have a speed
button that will double the speed (half the delay()) each time it is pressed to give us some control.




Now if we interrogate the buttons in loop() there will be a delay before it is responded to and that
could be a while. So rather that have to wait and maybe record double clicks we will use an interrupt.
An interrupt is a method outside the main program loops and it will periodically check if a button is
pressed, then it will make some decisions based on the button pressed and jump to the correct light
program method, cool! I took this info from
https://www.allaboutcircuits.com/technical-articles/using-interrupts-on-arduino/
So full credit to them for this.




We will set up some new global variables


First I need to change the Red LED pin as the interrupts on the Arduino UNO only work on pins 2 and 3


const int led[] = { 5, 6, 11 }; //(changed to pin 5)


// set up button inputs and variables
const int programButtonPin = 2;
const int speedButtonPin = 3;
int programMode = 0;
int speedButtonState = 0;
int progButtonState = 0;
int const progMax = 5; // the maximum number of program modes


We need to initialize the pins in setup() by adding these lines


// set up switches input pins
pinMode(programButtonPin, INPUT);
pinMode(speedButtonPin, INPUT);
// Attach 2 interrupts to the ISR vectors
attachInterrupt(digitalPinToInterrupt(programButtonPin), program_ISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(speedButtonPin), speed_ISR, CHANGE);
Serial.println("reset ran!");


And add two new methods called program_ISR and speed_ISR (ISR means Interrupt Service Routine)


// handles the program button interrupt
void program_ISR()
{
// get button states
programButtonState = digitalRead(programButtonPin);
// what to do when program button pressed?
if (programButtonState == 1) {
programMode += 1;
if (programMode >= progMax) { programMode = 0; }
delay(100); // prevent double registration
}
// debugging
Serial.print("programMode=");
Serial.println(programMode);
}


// handles the speed button interrupt
void speed_ISR()
{
// get button states
speedButtonState = digitalRead(speedButtonPin);
// what to do when speed button pressed?
if (speedButtonState == 1) {
incTime = incTime / 2; // speed the effect up
if (incTime <= 100) { incTime = 5000; }// then slow it right down
delay(100); // prevent double registration
}
// debug
Serial.print("incTime=");
Serial.println(incTime);
}


We will then use the value of programMode to determine which method to run to generate the
LED patterns


void loop() {
// here we will select which effect(s) to apply
switch (programMode) {
case 1:
// statements
analogFader();
break;


case 2:
// statements
justRed();
break;


case 3:
// statements
justGreen();
break;


case 4:
// statements
justBlue();
break;


// add new options here and change progMax to that number


default: // case 0:
// statements
digitalSequencer();
break;
}
}


So if we press the button connected to pin 2 the if statement will detect it and increment programMode.
If the button attached to pin 4 is pressed the if statement will detect it and  speed will be doubled by
halfing incTime.


The full version is available to download and is called RGC_LED_MultiProgrammer, I have included
Visual Studio solution and .ino files




The .SLN zipped folders are for Visual Studio and Visual Micro, to use with Arduino IDE just download
the .ino file and open it, allow it to create a containing folder.


You are welcome to modify the source code as you see fit but please leave the credits comments
intact.If possible please share the full article.
The underside of the LED Controller




The upperside of the controller


The Videos of the project are at
youtube.com

Comments

My photo
Ralph Beardmore
Derbyshire, United Kingdom
I am full of life and rarely still. I have forsaken TV for YouTube and other channels. I love creating and empowering others to do the same. You CAN do it!