Introduction: Color Calibrating RGB LEDs With an Arduino

Most people think LEDs and Arduino’s relations end with the Blink sketch. Thereafter you have to go towards motors, smart homes, and other ‘serious’ stuff. Well, maybe I’m slow or maybe it’s just that I’m a graphic artist, but I do like my LEDs and my Arduinos. The problem is, these LEDs are awfully unpredictable. One bunch has a very dark blue tint, the other one has blue that looks almost as white… Well, there IS a problem with these guys, as they are not like your monitor pixels: they actually produce colors (not just tint the white backlight) and are not calibrated.

For some time, I used a simple sketch with Serial Monitor input to get the values relatively right. But once I started to make some more or less serious stuff it became insufficient. I had to find a better way to color-calibrate RGB LEDs.

It may seem an easy task: just connect three pots (trimmers, potentiometers, trimpots) to your Arduino to adjust red, green and blue. Well, practice shows it’s not that easy (just try doing it). So here’s the instructable that will help you make your RGB LEDs display exactly (at least, as possible) the colors you need. You won’t need to have any programming or engineering experience to use it unless you want to experiment. The sketches are provided.

To run these sketches you’ll need:

An Arduino with USB connection (Serial Monitor working)


6x 220 Ohm resistors

3x potentiometers (trimmers, trimpots). I use 10K ones.

4x pushbuttons

1x 100nF capacitor

breadboard and wires

Additionally recommended:

External 5v power supply OR a second Arduino with separate power supply

Step 1: Problem and Solution

You won’t be able to get good calibration off a simple Arduino with trimmers for this reason: there’s no Low Pass filter on the AVCC input on Arduino boards, genuine or not. The only Arduino variation I found that has it is described in this instructable.

On usual Arduinos, the AVCC input is connected directly to the normal VCC input, which means voltage fluctuations when the Arduino is doing something else while checking analog inputs. More specifically, this leads to a lot of noise in the analogRead readings when the test LEDs are on.

As you won’t be able to install an inductor on the AVCC input on the Arduino, you’ll have to use some other bypass. There are three easy ways to do it.

1. Put a 100nF capacitor between AREF pin and GND. In fact, this approach is clearly defined in the Atmega datasheet, but somehow it didn’t make it to the Arduino docs. It is a good idea to keep the cap there if you’re using ADC (analogReads) in your projects, as it makes readings more reliable. However, ‘more reliable’ doesn’t mean ‘exact’ – you’ll still have some noise, but not a critical one. Most notably, you still won’t be able to get zeros and 1023s in the output, and, in fact, these errors will vary each time you plug the Arduino in.

2. Use a separate 5v power supply for the ADC inputs. Namely, connect it to the rail that the trimmers are getting their 5v from; don’t forget to connect the GNDs of both power supplies together. Make sure you connect the external power AFTER turning on the Arduino, just in case. This method will provide even better readings, however, some errors are possible due to the difference between exact voltages of two supplies (like 4.98v on Arduino and 5.23v on the external supply). This method is more reliable, you will be able to see zeros and 1023s, but there will be noise (check the screenshot above).

It is possible to connect this external voltage to the AREF pin, setting analogReference() to EXTERNAL (thus changing the ADMUX register accordingly). A word of warning: I don’t recommend it as it is possible to damage the Atmega chip if you do something wrong. Check the instructions here.

3. The best results are achieved if two separate Arduinos are used, one for ADC stuff, the second for test LEDs. It may look like an overkill, but in fact, it is the best option for two reasons. Firstly, most of us Arduino hobbyists always have a bunch of cheap Chinese Nanos lying around, right? Secondly, and more importantly, the practice of dedicating different controllers to different tasks is actually a recommended one. Especially if these tasks are so different as in our case: one trying to get exact results from a rather weak Atmega ADC, the other running power-hungry LEDs. Check the steady output on the screenshot above.

Step 2: Before We Begin

The calibration thingy works as follows. Three trimmers correspond to red, green and blue channels. You have two LEDs, one shows the color being set up with the trimmers, the other shows a reference color that can be selected with two buttons. You can check the current readings and the current PWM values of the LED under test in the Serial Monitor. Once you are OK with the result, you press the button and start calibrating the next color. Once all colors are done, you get the results in the Serial Monitor window as a ready C++ array: just copy-paste it into your sketch.

If you chose the recommended way of two Arduinos, first get this sketch , then upload it into the Arduino that’s connected to the trimmers (slave device). This Arduino doesn’t have to be connected to your PC thereafter, just provide it with power from an external source.

The calibration sketch can be downloaded here.

It’s big, but, in fact, pretty simple. The size is due to me gluing a bunch of separate sketches into one with compiler directives. I think it is easier to have one sketch and select its modus operandi with a single word in the setup string than to search for the needed one in a library. If you want to edit the separate sketches (as you’re welcome to), they are clearly defined by comment blocks.

To get the big sketch running, though, you’ll have to change its setup section and get some idea about the different calibration modes it provides.

Step 3: General Setup

The first line of the Setup section lets you select an external ADC device. If you’re using two Arduinos, you have to uncomment it:


The second line lets you specify the Calibration Mode. These modes will be described below, use the names provided in this instructable.

The third line deals with power consumption. It is a good idea to keep the power consumption of your RGB LEDs at bay, so I generally make sure that the sum of all three RGB values does not exceed the value of a single LED. Namely, if you have Red at (255,0,0), you have to make Yellow (127,127,0), not (255,255,0). Not only does it help not to exceed the maximum power ratings of your hardware, it also keeps the different colors you compose at more-or-less the same level of brightness. More on this in my previous instructable.

Thus, the calibration sketch always makes sure to keep the values at bay. If the sum of all three readings exceeds the value set in the POWER_RESTRICTION line, these values will be adjusted proportionally. When this correction happens, the LED on pin 13 (overflow LED) of the Arduino will light up.

I actually added this #define for the benefit of this article only, it wasn’t there beforehand, as I always use the same value. However, you may want to get rid of this feature; no problem, just change the number here. The base value for a single LED is 1023 (meaning the sum of three PWM values won’t exceed 255). If you raise the value over 3072, there will be no correction.

Then comes a section with button #defines, as well as a #define for the overflow LED (explained just before). You may wish to change them according to your setup.

Finally, there is the outputPins[] array that contains Arduino pins that the RGB LEDs are connected to. First three values are R, G and B pins for the test LED, second three values are R, G, B for reference LED. After that you’ll see the setRGBpoint function: it is configured for common anode LEDs, if you use common cathode ones, remove three ‘255-’ bits from it.

The rest of the setup deals with particular modes and will be explained soon.

Step 4: Practical Modes

The first two modes, ROUGH and FINE, are the most practical ones, as they allow you to set up particular colors on a particular RGB LED for use in your projects. The resulting array of calibrated values is printed in the Serial Monitor window and can be copy-pasted into your project sketch.


As the name suggests, this mode lets you setup your colors without exact precision, although it may be enough for most. In this mode the trimmers are providing absolute data, which means that R, G and B brightness corresponds to the trimmers current position; you’ll have to manually adjust them for each color.

The sketch is set up to calibrate colors for my OnePixel clocks.

To adjust it to your project needs first set the number of colors you intend to calibrate in

#define tableSize XX

Then edit the RGBready array accordingly (do not rename it though). At this time, you should put some generic values in it, roughly corresponding to the colors you intend to achieve.

Lastly, edit the comment[] array – these will be displayed in the Serial Monitor and in the resulting table, they help to keep in touch with what you’re doing.

Upload the sketch and open the Serial Monitor. It will show the current state of affairs: the color number, absolute readings from the trimmers (first three columns), current RGB values (second three columns), and comment line. Adjust the values by rotating the trimmers; the result will be immediately visible on the first (test) LED. The second LED, reference, will show initial value set in the RGBready array (until you change it by pressing SET). The reference LED can be switched between all the colors in the RGBready array by pressing COMPAREUP and COMPAREDOWN buttons. The PRINT button can be used to display the results at any time; press it again to resume calibration.

Once satisfied with the results on the particular color, press the SET button. It will save the current values in the RGBready array and move the program on to the next color. Once all the colors are done, the resulting array will be printed in the Serial Monitor window. You may then press the PRINT button to start over.


In FINE mode, the trimmers are used to get relative data, which means you won’t have to set up each color, only adjust the already set ones.

First, copy the resulting RGBready array from the ROUGH mode and paste it in place of the previous one in the sketch. You may also want to set up the precision of fine-tuning:

#define divider X

The divider is used to divide trimmer readings, so bigger number means more precision (although you won’t need more than 10).

Don’t forget to change the CALIBRATION_MODE to FINE. Upload the sketch and open the Serial Monitor. Three first columns now show the trimmers positions relative to their state at the start of the sketch and can go into negative.

The PRINT button becomes HOLD button: it can be used to adjust trimmers if you run out of space. When it is pressed their position is not being read, and the new base position will be defined once you release the button.

The rest is the same: use SET to save colors, get the results once all the colors are set.

Step 5: Evaluation Modes

The rest of the modes are good for evaluation and experimentation; I mostly use them to test new bunches of LEDs. They all work similar to the ROUGH mode, meaning that the absolute positions of the trimmers are used.


Similar to ROUGH, this mode lets you setup a pre-defined array of basic colors: red, green, blue, yellow, cyan, magenta and white. Initial colors sit in CMYWready[] array, comment array is below it. After you set all seven colors, the program will print the array, as well as relative maximum values for three base colors and a set of ‘multipliers’ showing their relative strengths. These values are calculated based on the way you set each of the 7 basic colors. Again, I use these values just as a reference when evaluating new LEDs, but maybe you’ll find some better use for them. Once all colors are set, the two LEDs will go into rainbow mode: the test one will show the rainbow based on these new values, the reference LED will show a synthetic default rainbow, you may be able to spot the difference. Check the

void showTwoFinalRainbows()

function, there are commented routines to show this rainbow in different ways (sine wave included).

COMPAREUP and DOWN buttons can be used to change the speed of the rainbow animation.


Same as above, but limited to red, green and blue. Try to lower green value (it is almost always brighter than the other two colors), and check the result. May be useful for resistor selection if your LEDs setup needs them on each leg.


Same as above, but only cyan, magenta and yellow. The reference LED will show red, green and blue as they are good comparison points to set up these colors.


The ‘White Balance’ mode lets you set up only white. The difference here is that reference LED will show cyan, magenta and yellow with the changes already applied.


This mode lets you trim three base colors on the live rainbow animation. The test LED shows adjusted colors, the comparison LED shows default colors. The second LED can be turned off and on with the SET button. Press PRINT button to get results and switch between HSV and Sine Wave rainbow animations.

And that's it! Comments and questions welcome.