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();
}

Comments