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:
- gain (1x, 4x, 16x, 60x)
- integration time (2.4ms to 614ms)
- 50ms or a multiple of 50ms should be chosen for accurate derivations of color temperature that are not influenced by 50/60Hz ripple
- readings are actually returned slightly faster than the specified integration time (e.g. every 197ms when specifying 200ms integration time), not sure if this is a problem
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.
Libraries
- Adafruit TCS34725 library
- basic reading works fine, but for some reason the integration times can only be specified at a small number of fixed lengths
- the implementation for lux and color temperature calculations is inaccurate/incomplete
- hideakitai’s TCS34725 library
- integration time can be specified freely (float parameter)
- lux and color temperature calculations are accurate, and can also take into account glass attenuation (GA)
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.