Measuring lux and color temperature (CT) with the TCS34725 on Arduino

05 May 2021 | all notes

Sensor basics

The sensor measures the input to 4 different types of photodiodes (tuned to red, green, blue and clear/broad spectrum light), all photodiodes are behind the same IR filter. The documentation states that the sensor’s dynamic range is 3,800,000:1 BUT this range can only be achieved by adjusting two sensor parameters (see below). In fact the 4 color channel readings only have a resolution of 2 bytes each, and the different channels are of course highly correlated. So without dynamically adjusting the two parameters it’s not actually possible to distinguish more than 65535 different levels, and depending on your application you will have to tweak the following 2 parameters to avoid overflow and underflow effects in your measurements:

Based on the raw values and the integration time, gain etc. one can compute lux and color temperature according to the very detailed official ‘DN40’ documentation. However, not every library does a good job at this.


Limits to Lux calculation

Based on DN40:

Before calculating lux, it is important to understand device saturation. There are two conditions for device saturation: analog saturation and digital saturation. Analog saturation is when the analog input is greater than what can be accumulated with the light-to-frequency conversion. Digital saturation is when the digital accumulator is overflowing before the analog saturates.

The full scale value for analog saturation depends upon the integration time programmed into the device. In saturation, the device accumulates 1024 counts for each 2.4 ms (in many devices) of integration time up to a maximum of 65,535 counts. Analog saturation will occur up to an integration time of 154 ms.

If the ALS integration time is greater than 154 ms (ATIMEx ≤ 64), digital saturation will occur before analog saturation. Digital saturation occurs when the count reaches 65,535.

An important coefficient is counts per lux, CPL = (AGAINx * ATIME_ms) / (GA * DF) where for the TCS472 DF=310 and GA is the glass attenuation factor (1 unless your sensor is covered by dark glass). In the case of white light the maximum lux that can be measured with a given gain and integration time is MaxLux = 65k / (CPL * 3).

Based on the table below, if you want to be able to measure accurate lux values in direct outdoor sunlight (up to 100k lux), you might be limited to an integration time of 50 or 100ms tops. Because the digitization error of the lux calculation is in the range of (+/- 2) / CPL, this will result in errors of +-12 and +-6 lux respectively, which seems fair given such high lux values (and sensor noise).

Maximal measurable lux for different integration times at gain 1 (this is with absolutely no more headroom!):

 ms    luxmax       der
 50 134333.33   += 12.4
100  67166.67   += 6.2
150  44777.78   += 4.13
200  33583.33   += 3.1
250  26866.67   += 2.48
300  22388.89   += 2.07
350  19190.48   += 1.77
400  16791.67   += 1.55
450  14925.93   += 1.38
500  13433.33   += 1.24
550  12212.12   += 1.13
600  11194.44   += 1.03
# calculate/plot in R
d <- data.frame(ms=50*1:12)
d$luxmax <- 65000 / (3 * d$ms / 310)
d$der <- paste('+=', 2 / (d$ms / 310))

curve(65000 / (3 * x / 310), 50, 600, n=12, type="p", xlab='integration time (ms)', ylab='maximal measurable lux at gain 1 (no headroom!)', ylim=c(0, 150000))

Extending the lux sensitivity range

Determine if measurement is saturated (and therefore unreliable for lux/CT calculation):

M = max (R”, G”, B”)
m = min (R”, G”, B”)
Saturation = (M – m) / M

where R”, G”, B” are the non-raw measurements from the sensor.

For saturated light, M – m is large and if (M – m) / M > 0.75, the light source is starting to saturate.

The documentation describes an algorithm that can extend the lux range up to 3x by monitoring the 75% saturation point and then applying the algorithm, but this doesn’t seem to be implemented in any of the libraries.