Tunable white (WW) SMD 3528 LED strip color temperature measurements
29 May 2021 | all notes
Rough empirical analysis of the effective correlated color temperature (CCT) and brightness (lux) of a double-white 12V LED strip at different total output levels and mixing of the two white LED types (high Kelvin aka ‘cold’ white vs. low Kelvin aka ‘warm’ white).
Setup: an ESP8266
controlled the current from a 12V 5A power supply
through two 30N06L
transistors. Looping through different total PWM
duty cycle lengths and mixing of the two LED types, lux and color
temperature measurements were obtained from a TCS34725
light sensor
using hideakitai
’s TCS34725
Arduino library (see this
post, or
scroll all the way to the bottom for the Arduino sketch).
Both the 1m long LED strip as well as the sensor were pointing at the plain white wall of an otherwise darkened room at a distance of 20cm and 40cm respectively.
Mean color temperatures of the pure LEDs are 3040K (sd=10) and 6381K (sd=39) respectively.
The variation of pure color temperature output per total output of the LED can be modeled as higher output => higher (‘colder’) color temperature in both cases:
##
## Pearson's product-moment correlation
##
## data: cct and ww
## t = 7.2571, df = 31, p-value = 3.651e-08
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.6185744 0.8933692
## sample estimates:
## cor
## 0.7933961
##
## Pearson's product-moment correlation
##
## data: cct and cw
## t = 7.0968, df = 41, p-value = 1.204e-08
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.5689985 0.8526650
## sample estimates:
## cor
## 0.7424596
It looks like, at the same PWM duty cycle length, maybe one of the LEDs contributes more lux than the other?
print(summary(lm(lux ~ cw + ww, d)))
##
## Call:
## lm(formula = lux ~ cw + ww, data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -32.890 -0.544 -0.290 0.156 62.513
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.271307 0.330690 3.844 0.000136 ***
## cw 0.117136 0.002107 55.582 < 2e-16 ***
## ww 0.120443 0.002106 57.196 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.653 on 505 degrees of freedom
## Multiple R-squared: 0.9424, Adjusted R-squared: 0.9422
## F-statistic: 4133 on 2 and 505 DF, p-value: < 2.2e-16
print(summary(lm(lux ~ cw + ww, subset(d, sum > 780))))
##
## Call:
## lm(formula = lux ~ cw + ww, data = subset(d, sum > 780))
##
## Residuals:
## Min 1Q Median 3Q Max
## -32.917 -19.797 -2.365 13.096 53.243
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -185.80200 88.16117 -2.108 0.05022 .
## cw 0.31534 0.09504 3.318 0.00407 **
## ww 0.30722 0.09504 3.233 0.00489 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 28.37 on 17 degrees of freedom
## Multiple R-squared: 0.3932, Adjusted R-squared: 0.3218
## F-statistic: 5.508 on 2 and 17 DF, p-value: 0.01431
How much extra does the lower temperature (‘warm’) LED add? Might not actually be a significant LED-specific pattern here.
print(summary(lm(lux ~ sum + ww, d)))
##
## Call:
## lm(formula = lux ~ sum + ww, data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -32.890 -0.544 -0.290 0.156 62.513
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.271307 0.330690 3.844 0.000136 ***
## sum 0.117136 0.002107 55.582 < 2e-16 ***
## ww 0.003307 0.003305 1.001 0.317476
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.653 on 505 degrees of freedom
## Multiple R-squared: 0.9424, Adjusted R-squared: 0.9422
## F-statistic: 4133 on 2 and 505 DF, p-value: < 2.2e-16
print(summary(lm(lux ~ sum + ww, subset(d, sum > 780))))
##
## Call:
## lm(formula = lux ~ sum + ww, data = subset(d, sum > 780))
##
## Residuals:
## Min 1Q Median 3Q Max
## -32.917 -19.797 -2.365 13.096 53.243
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.858e+02 8.816e+01 -2.108 0.05022 .
## sum 3.153e-01 9.504e-02 3.318 0.00407 **
## ww -8.118e-03 1.924e-02 -0.422 0.67838
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 28.37 on 17 degrees of freedom
## Multiple R-squared: 0.3932, Adjusted R-squared: 0.3218
## F-statistic: 5.508 on 2 and 17 DF, p-value: 0.01431
Arduino code
#include "TCS34725.h"
TCS34725 tcs;
float lux;
uint16_t cct;
void setup() {
pinMode(D5, OUTPUT);
digitalWrite(D5, LOW);
pinMode(D7, OUTPUT);
digitalWrite(D7, LOW);
Serial.begin(115200);
Wire.begin();
if (!tcs.attach(Wire)) {
Serial.println("Failed to attach Wire!");
ESP.deepSleep(0);
}
tcs.enableColorTempAndLuxCalculation(true);
// wait 10 seconds so I can turn off the laptop screen and leave the room
delay(10000);
// baseline (darkness) measurement
getLight();
Serial.print(cct);
Serial.print(",");
Serial.println(lux);
// LOW values -- up to PWM duty cycle length sum of 50/1024
for (uint16_t sum = 1; sum < 50; sum += 2) {
// need 15 steps per sum-level max
uint16_t diff = max(sum / 15, 1);
for (uint16_t i = 0; i <= sum; i += diff) {
analogWrite(D5, i);
analogWrite(D7, sum - i);
getLight();
Serial.print(sum - i);
Serial.print(",");
Serial.print(i);
Serial.print(",");
Serial.print(cct);
Serial.print(",");
Serial.println(lux);
if (diff > 1 && i + diff > sum) {
// make sure we catch the last one
i = diff - sum;
}
}
}
// HIGH values -- only try 5 mixing levels
for (uint16_t sum = 60; sum < 1024; sum += 60) {
for (uint16_t i = 0; i <= sum; i += sum/4) {
analogWrite(D5, i);
analogWrite(D7, sum - i);
getLight();
Serial.print(sum - i);
Serial.print(",");
Serial.print(i);
Serial.print(",");
Serial.print(cct);
Serial.print(",");
Serial.println(lux);
}
}
// over and out
ESP.deepSleep(0);
}
void loop() { }
// dynamically adjust gain+integration time so that CCT+lux calculations are reliable
#define REQUIRED_COUNT 10000
void getLight() {
tcs.gain(TCS34725::Gain::X01);
tcs.integrationTime(2.4);
byte gain = 1;
for (float integrationTime = 100; integrationTime <= 600; integrationTime += 100) {
while (!tcs.available()) {
delay(10);
}
TCS34725::RawData raw = tcs.raw();
if (raw.c >= REQUIRED_COUNT) {
break;
}
float fulfillmentPotentialAtCurrentGain = raw.c == 0 ? 60 : integrationTime * REQUIRED_COUNT / (600 * raw.c * gain);
if (fulfillmentPotentialAtCurrentGain >= 4) {
// 1, 4, 16, 60
if (fulfillmentPotentialAtCurrentGain >= 16) {
if (fulfillmentPotentialAtCurrentGain >= 60) {
tcs.gain(TCS34725::Gain::X60);
gain = 60;
} else {
tcs.gain(TCS34725::Gain::X16);
gain = 16;
}
} else {
tcs.gain(TCS34725::Gain::X04);
gain = 4;
}
}
tcs.integrationTime(integrationTime);
}
lux = tcs.lux();
cct = (uint16_t) tcs.colorTemperature();
}