Using an Arduino Mega
Centuries before the science of color temperature was understood, blacksmiths already knew how to read the temperature of a heated piece of metal by its color emissions. In this article, Jeff discusses the math and science behind color temperature. He then details his project that uses a color-light-to-digital sensor to measure the color temperature of a light source.
“White as snow,“ “white chocolate,“ “white wedding dress,“ “white lightning“ and “white diamonds“ are common descriptions of items in our world that include the descriptor “white.“ You can probably think of many others. Some of these describe a whitish hue, while others don’t connote a color at all. The most truthful might be “white hot.“ While today we might use this to describe someone that is “drop-dead gorgeous,“ according to Merriam Webster, this was first used as meaning “being at or radiating white heat.“ If you’ve ever sat by a campfire or gazed at a candle, you have experienced heat through radiation and colors associated with the flame.
Before we can discuss temperature and color, we need to begin with the “black-body radiator“ [1]. This is a theoretical object that can absorb all radiation that falls upon it. Radiation is the emission or transmission of energy in the form of waves (or particles) through some medium. This includes: electromagnetic radiation, such as radio waves, microwaves, infrared, visible light, ultraviolet, x-rays and gamma radiation (γ). At absolute zero—the coldest temperature possible—no heat is generated in a black body. Absolute zero is -273.15°C (Celsius) or -459.67°F (Fahrenheit). Science often uses the K (Kelvin) temperature scale to make more sense.
K = °C + 273.27
so absolute zero = 0K
… and freezing = 273.16K
As a black-body radiator’s temperature rises, it gains energy and will emit radiation beginning at the low end of the electromagnetic spectrum. Figure 1 is a good illustration comparing radiation type and temperature for the electromagnetic spectrum [2]. We experience this emission as color once its wavelength enters the visible spectrum: 400nm-700nm. A blacksmith heats a piece of steel in his forge to affect its malleability. While it’s not a perfect black body, the physical properties of the metal change with temperature, and he can estimate its temperature by its color emission alone (Figure 2) [3].


We can therefore define the color temperature of a light source in terms of the absolute temperature of a black-body radiator or “chromaticity.“ Chromaticity is the quality of color, independent its brightness or luminescence. Chromaticity consists of two independent parameters, often specified as hue (h) and as colorfulness (s). Colorfulness is alternatively called saturation or chroma. A color space chromaticity diagram (Figure 3) defines all visible color based on these parameters [4]. Note: Temperature color has been indicated within the color space. Green and violet fall outside the black body path or Planckian locus (see physicist Max Planck), with green (secondary) being a combination of primary yellow and blue, and violet (secondary) being a combination of primary red and blue.

ADDITIVE & SUBTRACTIVE COLOR
This has to do with the source of the color. If the source is projected, the three wavelengths used are RGB (red, green, blue) and are additive. If the source is reflected, then the pigments used are CMY (cyan, magenta, yellow) and are subtractive. Note: Many of us were taught early on that the three primary colors were RYB (red, yellow, blue). Figure 4 shows the computational effects of each system.
— ADVERTISMENT—
—Advertise Here—

These are the hues of chromaticity. The other aspect of chromaticity—colorfulness, saturation or chroma—has to do with its intensity. If our brains receive equal signal strengths from all three types of cones (color photoreceptors) in our eyes, then we would perceive white light. If these cone outputs were reduced from maximum to minimum, the resultant perception would be a fade to black. Likewise, if some other mix of cone outputs were perceived as, say, green, and these outputs were reduced to minimum while remaining in the same proportions, the perception would be a fade to gray.
We assume that a (white) source illuminating a subject is perfect. A perfect white source would give the expected result. The subject under observation would absorb/reflect the wavelengths, and we would perceive the expected colors. However, if the source were lacking some frequency, the resultant perception would be skewed. The missing frequency cannot be reflected, and so the result will be lacking. We have two options for a light source—the sun or an artificial source.
You’ve most likely noticed the temperature color is the opposite of the way we think of temperature and the visible color spectrum (as red = hot and blue = cold). With color temperature, red is of a lower temperature and therefore cooler than blue. The sun’s effective temperature, defined by the total radiative power per square unit, is about 5,800K. During sunrise, the tangential angle of light passes through more atmosphere, scattering the shorter-wavelength blue light from our view. As the path becomes a more direct angle, this scattering can be seen off-axis as blue skies.
Figure 5 shows how the source color temperature can vary between the sun and artificial lights. The color temperature value of a light source refers only to the visual appearance of the source. It does not necessarily describe the effect this source will have on photographs or digital images. The color temperature does not take into consideration the spectral distribution of a visible light source, and is not a reliable means of selecting suitable filters or creating look-up tables for color balance corrections. While different light sources may be described as having the same color temperature, without proper white balance, spectral output cannot determine the correct adjustments for color temperature balance. So, let’s look at one possible sensor that will allow us to get a feel for the spectral content of light exposed to it.

TCS3472
The TCS3472 is one of many sensors manufactured by ams, a global leader in the design and manufacture of advanced sensor solutions. It consists of three sets of four filtered sensors protected by an IR filter. Three of the four filtered sensors have color filter RGB. Sensor values and control registers are available via an I2C interface.
The amount of light falling on each set of silicon photodiodes is measured by an integrating analog-to-digital converter (ADC), one for each color. All ADCs operate in parallel, so all ADCs use the same integration time. The device covers a wide signal range. For low-level signals, higher programmable gains and longer integration times can be used. At the other extreme, higher-level signals will require lower gains and shorter integration times.
The integration time period is chosen as a multiple of 2.4ms. A saturated device will present a count of 1,024 for each period. So, you want to make sure the total count (for any sensor) / number of periods is less than 1,024. This is an analog saturation check. A sensor’s total value is held in a byte register pair, and therefore is limited to a maximum of 65,535. It is possible to have no analog saturation, but choose a high number of integration periods, where the total count is greater than 65,535. This is a digital saturation check. You would need to adjust gain and/or number of integration periods to keep the results high enough to provide good resolution, without going into analog or digital saturation.
The register set for the TCS3472 is presented in Figure 6. You’ll note that the COMMAND register is not really a physical register, but rather a pointer used to access registers 0x00-0x1B and alternately clear an interrupt. Besides pointer data, the COMMAND byte also enables/disables the auto increment of the pointer register function.

(Click to enlarge)
The device has an interrupt output that can be triggered by a clear channel conversion count that is less than or greater than minimum and maximum values you program into registers 0x04/5and 0x06/7. This interrupt can be further limited by requiring a number of consecutive out-of-range counts from 1 to 60, programmed into register 0x0C. The status of a conversion is found in register 0x13. Channel counts follow in registers 0x14-0x1B. The only interrupt that can be generated is the out-of-bounds interrupt, and it is not used in this project. Note that the external LED is controlled via this interrupt output, and it can be enabled and disabled via the AIEN bit in the ENABLE register (0x00).
— ADVERTISMENT—
—Advertise Here—
You must set a number of registers to control the operation of the TCS3472. There are timing and amplification areas of configuration. First is the general timing of the device. Assuming the device has been enabled (AEN, WEN, POW), it will continuously idle and sample the sensors. The idle or wait time is set by the wCycles
or WTIME. Each cycle adds 2.4ms to the operation. The cycle values are two’s complement values (inverts all bits and adds 1). Therefore, to set the minimum wCycles
equal to 1 (2.4ms), 0xFF must be written to the WTIME register (0x03). The CONFIGURATION register (0x0D) holds the WLONG bit that, when enabled, will multiply the WTIME by 12. The maximum WTIME will be approximately 7 seconds. This allows the device to remain in idle condition for a large percentage of time, lowering the average current of an active device by a factor of 5. However, device current is only 0.3mA to begin with.
The integration time has a similar register (ATIME 0x02). You set the integration time in periods of 2.4ms. If you wanted to set the integration time to 50ms, you would use an aCycle
count of 50ms / 2.4ms = 20.8 (or 21 periods). The two’s complement of 21 (0x15) is 235 (0xEB). There is no multiplier for the integration time. However, you can adjust the gain of the sensor amplifiers, which is similar to a multiplier, without actually adding any more time to the conversion.
You can see that if all the timing registers are set to maximum, one sample period would be WTIME = 0x00 (614ms), WLONG = 1 (times 12) 614ms × 12 = 7.4 seconds, plus ATIME = 0x00 (614ms) 7.4 seconds + 0.614 seconds = 8.014 seconds. Reading the resultant registers prior to this time would provide the data for the previous sample only.
USING THE SENSOR
Since the TSC3472 is an I2C device, it can be easily interfaced using a 3.3V Arduino (or a 5V Arduino with some level shifting on the I2C pins). I’ll begin this project with an Arduino MEGA. A library for the TCS3472 is available, but I won’t use it for this project. Instead I’ll break it all down into visible function calls.
Let’s begin with reading all of the registers. To do this we’ll need to write a command to the device. As we discussed previously, the COMMAND byte sets up the pointer to the register we want to communicate with (either to read from or to write to). It also instructs the device whether or not to increase this pointer automatically after each register access. Using a COMMAND value of 0xA0 will set the pointer to register 0x00 and enable auto increment. Since the device uses registers 0x00-0x1B, we will need to read 0x1C (28) registers values. Listing 1 shows the basic Arduino code needed to read all of the registers in the TSC3472x.
In Listing 1 we’ve collected 28 registers, and they are in BYTEARRAY[]
. Displaying these requires the majority of programming, and will therefore not be listed here. However, Listing 2 shows a typical display of the output from this function. There are actually two phases for each register. The first gives raw register info, register name and hex value found there. The second breaks this down—where necessary—indicating any special bit significance.
A check of the value in the ID
register (0x12) tells me the values are good. This device is actually a TSC34725 (a version that uses an I2CAddress
of 0x29 and runs off 3.3V). Note that some registers are not used (or at least have not been defined). With the displayAllRegisters()
function working, we can expand this program by adding a menu to allow us to choose alternate functions.
The next function that will be useful is to change a register’s contents. Only the first registers 0x00-0x0D are R/W (read/write) registers. Six of these are byte oriented, and four are word oriented. A sub menu will allow you choose which of these you want to change.
—-Change Register—-
0 – 0x00 Enable
1 – 0x01 Integration Time
2 – 0x03 Wait Time
3 – 0x04/5 Low Threshold
4 – 0x06/7 High Threshold
5 – 0x0C Persistence
6 – 0x0D Configuration
7 – 0x0F Control
Menu selection?
When a choice is made here, you get to enter a value for the byte or word register pairs. REGISTER
and REGISTERVALUE
variables are used to update a single register in the TSC3472x.
The Main menu below gives you total control of the device at your fingertips. We can stop, start and compare conversions to find the optimum settings for any light condition.
—-TCS3472 Probe—-
1 – Read and display all I2C registers
2 – Change an I2C register
3 – Get a raw sample
4 – Toggle external LED
5 – + Gain
6 – – Gain
7 – + Integrate Time
8 – – Integrate Time
9 – Sample and auto adjust
— ADVERTISMENT—
—Advertise Here—
Menu selection?
Note that while complete control of all registers is available, the main menu has shortcuts that allow adjustments of the two main parameters that you will want to change. The Gain sensor and its Integration Time can be altered directly via single-keys commands. I added a command to toggle the on-board LED light source that can be used for reflective measurements. Command 3 retrieves a sensor sample using the present configuration. The final Command, 9, makes fine tuning and sampling automatic.
AUTOMAGICAL
To make the magic happen, we need to make changes continuously to the Gain and Integration Time, and look for that point just before the system reaches saturation. Previous discussion showed both analog and digital saturation. It was identified that if the Integration Time exceeded a count of 63 (63 × 2.4ms = 151.2ms), unsaturated sample could occur up to the maximum counts of 65,535. For Integration Times less than 64 counts, the maximum counts allowed were substantially less. Using these rules, we can determine if saturation occurs for a conversion.
Since we want to determine the point just before saturation occurs, my algorithm begins with minimum Integration Time and maximum Gain. If saturation occurs, then we must turn the Gain down a notch, since the Integration Time is already at its minimum. Gain has four steps—60, 16, 4 and 1. Once we have a conversion with no saturation, the Gain is now valid, and we can ramp up the Integration Time until saturation again occurs.
The Integration Time has 256 steps 1-256 cycles (where 0=256). It could take a few seconds to try all possible steps. As each step increases the Integration Time, the conversion time also increases. While you could shorten the potential number of steps by using a different algorithm, I will keep this as simple as possible and just increase by a single step until saturation occurs, and then back off 1 step (count). We should now be at the maximum Gain and Integration Time without going into saturation.
LUX & COLOR TEMPERATURE
Lux and color temperature calculations can be performed on any unsaturated conversion. The previous configurations changes have been done to optimize the conversion values for the best possible accuracy when calculating the lux (intensity) and color temperature (surface temperature in Kelvin). The intensity of light hitting the sensors (RGBC) can be translated into a value that closely matches the human eye, by totaling the adjusted red, green and blue conversions and applying a device factor. You can see that as long as the color outputs stay in proportion to one another, the Lux has virtually no effect on the Color Temperature, which is essentially a ratio of blue to red values.
So far, we’ve needed a PC connected to the TSC3472x to get a conversion. Heltec makes a DIP-style Arduino with an integrated 4×20 graphic display for around $10 that is based on the Espressif Systems ESP8266. The ESP8266 has Wi-Fi, which we won’t be using here, but otherwise it is perfect for this project, as you will quickly see. I did use Adafruit’s support for its SSD1306 display, because Heltec’s library doesn’t have good text support. No special changes to the Arduino program are necessary, beyond that of the display support. However, I want to make this simple and the menus will have to go. Well … not really.
I want to use two functions in conjunction with the display. I want the user to be able to start an automatic conversion, and also have control over the LED. To do this, I need a couple of pushbuttons and a way to convert the button push into a simulated menu selection. This was done in the getUserInput()
routine that collects keyboard input. Normally, execution is waiting here for you to enter characters (menu choice). I added two digitalRead()
tests—one for each button. A button press (grounded input) forces exit of the getUserInput()
routine, returning the character “4“ (Toggle external LED) or “9“ (Sample and auto adjust). Other than how these buttons produce virtual keyboard input, nothing else changes.
Although the display is graphic in nature, you have control over every pixel on the screen. I am using it as a four-line-by-20 character display. Sign-on messages are produced on the first two lines, and the second two lines are used to show the progress of the conversion process. This should end up with a display as shown in Figure 7 when the conversion is complete. To produce text characters, the library routine is responsible for providing the character glyph (picture) of each character, as well as where to place the character within the display’s screen.

The SSD1306 uses separate display and buffer spaces. This allows the user to completely draw a screen in the buffer space before transferring it to the display space. Updates therefore look instantaneous. The buffer remains intact, and you may continue to add to it or clear it in preparation for new text (or graphics).
PORTABLE
Up to this point we’ve received power from the USB port, while attached to the PC. We added buttons to make it a stand-alone device, but we must do something about power! The Heltec device not only has a battery input, but also—because everything works on 3.3V—a single cell Li-Ion battery is a perfect mate. The Heltec Wi-Fi Kit 8 also has a Li-Ion battery charger built in to truly make all of this work with “no strings (or wires) attached.“
I mounted the Wi-Fi Kit 8 in a PacTec enclosure (Figure 8). I wanted to be able to aim the sensor independently from the display, and found an itty-bitty book light that looked like it would work well. The four-wire sensor cable had to be replaced with a couple of pieces of flat cable that I zipped down to two conductors wide. These could be fed through the sensor arm quite nicely, without any pinching to the wires.
To test this rig out, I got up before sunrise to make an early morning reading before the sun broke the horizon. Look back at Figure 7 for this sample. You’ll note the Lux value is less than 1 and the color temperature is 4,382K. This is just what I would suspect for sunrise. A clear, mid-day sky produced a high color temperature of 5,732K. You get a good sense of the many shades of white while driving at night. The range in color temperature for various styles of headlights is 3,000K to more than 12,000K!
Picking out a perfect shade of white is a nightmare come to life. Pure white? Off white? Simply white? Benjamin Moore has more than 35 shades of white! The shade of white (or other color) you apply to your walls will greatly influence the way you feel in that room.
Remember to wash your hands! Too much to do, too little time.
RESOURCES
References:
[1] https://www.olympus-lifescience.com/en/microscope-resource/primer/lightandcolor/colortemp/
[2] Inductiveload, NASA, 2007. File: EM Spectum Properties edit.svg. Wikimedia Commons. This file is licensed under the Creative CommonsAttribution-Share Alike 3.0 Unported license.
https://commons.wikimedia.org/wiki/File:EM_Spectrum_Properties_edit.svg
[3] Jeff Kubina, 2008. File:BlacksmithWorking.jpg. Wikimedia Commons. This file is licensed under the Creative CommonsAttribution-Share Alike 2.0 Generic license. https://commons.wikimedia.org/wiki/File:Blacksmith_working.jpg
[4] en:User:PAR – en:User:PAR, 2005. File:PlanckinaLocus.png. Wikimedia Commons. Public Domain.
https://commons.wikimedia.org/wiki/File:PlanckianLocus.png
Adafruit | www.adafruit.com
ams | www.ams.com
Espressif Systems | www.espressif.com
Heltec Automation | https://heltec.org
PacTec Enclosures | www.pactecenclosures.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • OCTOBER 2020 #363 – Get a PDF of the issue
Sponsor this ArticleJeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.