Prototype RGB Light Sensor

DIY RGB Light Meter

I decided to grow tomatoes indoors during the Canadian winter months.  I prepared a little spot where I could control humidity, temperature and light.  I purchased some “burple” lights, so-called due to their dominance of Red and Blue LED lights and set up my own indoor garden.

I was quite overjoyed to see my tomato plants sprout, then grow their first leaves. Then, I watched in awe as they grew taller, eventually producing their first tomatoes.

Growing in my first artificial environment was a challenge. I had to control humidity, which in the winter months means running a cold-mist humidifier non-stop. The temperature wasn’t a problem since I was in the house. 

I decided to purchase a few sensors and build a microprocessor-controlled garden to measure soil moisture, humidity, temperature, pressure and light levels.

ViparSpectra Par ChartBut I had questions about where to place my lights and how bright they should be.  Manufacturers always specify the proper height of the lights depending on the stage of growth your plant is in. But this doesn’t account for different types of plants that need different amounts of light.  And the burning question I always had; how close to actual sunlight can I get?

I’ll have more to say on soil moisture and environmental measurements in an upcoming article.

Why Measure Light Levels?

ViparSpectra Par ChartTaking measurements from under my lights tells me how much fall-off I have and how much overlap I need with another light.

The brightest section of the garden canopy is always directly under the light and tapers off toward the edges. So how can you tell if the sides of your garden are getting as much light as the centre?  With a light meter, you can take measurements and compare.  If you find too little light at the edges, you can make changes to maximize your crop.

Most Importantly, measuring light lets us repeat things we’ve found to work.  If you keep a log of your light readings, you can refer to it to ensure you have the right amount of light.

Luckily for us, measuring light is cheap and easy with an RGB light sensor from AMS. So, with a little elbow grease and some programming, we can make our own light meter for only a couple of bucks.

AMS TCS34725 RGB Sensor

  • 16-bit resolution with gain control and variable integration time
  • Uses the I2C bus.
  • Can trigger an Interrupt when a range is exceeded
  • Temperature operating range: -30°C to +70°C
AMS TCS34725 RGB Light Sensor
Click for Datasheet

The AMS TCS34725 sensor can measure light intensity in Red, Green and Blue channels individually.  In addition, it has a clear sensor to take non-colour filtered measurements. Furthermore, it features built-in I2C output and an IR filter to closely match the human eye’s light response.

TCS34725 Break-out Modules

Companies such as Adafruit sell a module built around the AMS TCS34725 sensor.  The module adds features such as:

  • Break-out board with 2.54mm pin headers (breadboard compatible)
  • 4150°K LED for Reflective measurements
  • Voltage Regulator to accept 5V power supplies
  • Level-shifted I2C pins for 5V or 3.3V operation

The modules provide a bright 4150°K LED, which you can see in the pictures as a yellow and white rectangle.  The LED automatically illuminates when the module is powered so that you can take a reflective light measurement.  However, we’re measuring ambient light levels, so we don’t need the built-in LED.

Kelvin Chart
https://lightingdesignstudio.co.uk/colour-temperature/

To defeat the built-in LED, connect the module’s LED pin directly to GND, as you’ll see in the design.

Designing The Project

esp32 node mcu

ESP32

Microcontroller
Check Prices
TCS34725 Break-out Board

TCS34725

Break-out Board
Check Prices
OLED 0.96 127x64 White

White OLED

0.96" 128X64
Check Prices
Clear LED

LEDs

Red, Green, Blue
Check Prices

I will use an ESP32 microcontroller to read the light sensor and update the OLED display. Both will use the same I2C bus.

Furthermore, there will be three LEDs, one Red, one Green and one Blue. Their brightness will be controlled by altering the duty cycle of a PWM signal and will reflect the respective brightness measured by the RGB light sensor.

The ESP32 will continuously monitor the RGB light sensor during normal operation and update the OLED and LEDs.

I will build the prototype on a standard breadboard. I added two optional 470uF capacitors across the power rails (3.3V and GND) to fill in any voltage gaps coming from the power supply – which in this case is the ESP32.

Schematic Diagram

Prototype Build

Programming the ESP32

I decided to program the ESP32 in C++ under PlatformIO, VSCode and the Arduino framework.  PlatformIO takes care of the toolchain. It automatically downloads all the tools I need to compile and upload firmware to the ESP32.  VSCode is an excellent code editor, and the Arduino framework is a well-known open-source framework that makes programming your microprocessors easier.

All of these tools are free, and I’ve written about them previously.

TCS34725 Library

Adafruit provides a TCS34725 library for PlatformIO, which can be installed by adding a single line to the lib_deps section in your platform.ini file:

				
					
lib_deps
    adafruit/Adafruit TCS34725 @ ^1.3.6
				
			
Here is the lib_deps section from this project’s platform.ini file so you can see how to add it to your existing file:
				
					
lib_deps =
   adafruit/Adafruit TCS34725 @ ^1.3.6
   adafruit/Adafruit SSD1306 @ ^2.4.6
   adafruit/Adafruit GFX Library @ ^1.10.10
   adafruit/Adafruit BusIO @ ^1.8.3
				
			

Once the library has been installed, we can instantiate a new sensor object:

				
					
#include <Adafruit_TCS34725.h>
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_101MS,TCS34725_GAIN_4X);
				
			

And start it up using the default I2C interface…

				
					if (tcs.begin()){
    Serial.println("Found sensor");
  } else {
    Serial.println("No TCS34725 found ... check your connections");
    while (1);
}
				
			

Take a reading and make some calculations:

				
					uint16_t r, g, b, c;
uint16_t colorTemp, lux;
tcs.getRawData(&r, &g, &b, &c);
colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
lux = tcs.calculateLux(r, g, b);
				
			

Red, Green, Blue and LUX range from 0 to 65535.

Colour temperature is a standard colour temperature.

A couple of Notes:

Accuracy:

This sensor’s measurements are uncalibrated. This means your readings may not match a professional piece of equipment without first tuning your readings to match a known standard.  But, that may not matter at all if you’re only using the sensor to detect differences in the spectrum. For example, if you’re tuning an LED light, you may want the RED and BLUE channels to be balanced.  This uncalibrated sensor accomplishes that.

You could easily calibrate it to a known standard by standing in the 12pm sun and writing down the measurements. After that, you can compare all measurements you make to the bright sun.

Its Gain and Integration Time are Adjustable:

TCS34725 Gain Options Screen CaptureI chose an integration time of 101mS as a balance between quick and accurate.  The longer times you use, the more the sensor ignores fluctuations in light, such as that caused by fluorescent lights.

I initialized the sensor with a 4X gain because I was in a dim room when writing this. So you can choose a gain of 1X,  4X, 16X and 60X.

Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_101MS,TCS34725_GAIN_4X);

The OLED and TCS34725 Module use the same I2C bus:

The ESP32 has two hardware I2C busses usually available on GPIO pins 21/22 and 4/5. I say “usually” because the ESP32 can remap GPIO pins to nearly any function.  This works in my particular case with a random ESP32 development board.

Since each device uses a unique I2C address, sharing a bus is no problem.

The Software

To make the sensor work, you can begin a new project in PlatformIO and insert the following code into main.cpp and platform.ini:

main.cpp

				
					/*
  RGB Light Sensor by Andrew Shirley 2021
  
  Connect your OLED and TCS34725 Module to the ESP32's I2C interface found here:

  GPIO 21 : SDA
  GPIO 22 : SCL


*/

#include <Adafruit_TCS34725.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


#define SCREEN_WIDTH 128     // OLED display width, in pixels
#define SCREEN_HEIGHT 64     // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3c  // My OLED uses 0x3c, yours may use 0x3d


#define GPIO_LED_RED 25
#define GPIO_LED_GREEN 26
#define GPIO_LED_BLUE 27
#define ScreenUpdateTime 200        // 200 mS

// Choose an Integration time that's not too long, but long enough to capture enough signal
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_101MS,TCS34725_GAIN_4X);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

void setup() {
  Serial.begin(115200);

  // Initialize the OLED screen

  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)){
      Serial.println("OLED Failed Initialization");
      while(1);
  }

  Serial.println("Initialized the OLED");
  display.clearDisplay();         // Clear the display buffer
  display.display();              // Sends the display buffer to the OLED


  if (!tcs.begin()) {
    Serial.println("TCS34725 Failed Initialization");
    while (1);
  }
  
  Serial.println("Initialized RGB sensor");


  // Initialize the LEDs PWM channels
  ledcAttachPin(GPIO_LED_RED, 0);     // assign Red led pin to PWM channel 0
  ledcAttachPin(GPIO_LED_GREEN, 1); 
  ledcAttachPin(GPIO_LED_BLUE, 2);

  ledcSetup(0, 4000, 8);              // Start PWM, Channel 0, 4KHz, 8 bits
  ledcSetup(1, 4000, 8);
  ledcSetup(2, 4000, 8);
}

long FutureTime = 0;
void loop() {
  uint16_t r, g, b, c;
  uint16_t colorTemp, lux;

  tcs.getRawData(&r, &g, &b, &c);
  colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
  lux = tcs.calculateLux(r, g, b);

  int   RED = 100 * ( (r-242) / 4000.0);
  int GREEN = 100 * ( (g-242) / 4000.0);
  int  BLUE = 100 * ( (b-242) / 4000.0);

  RED =     RED > 100 ? 100 :   RED < 0 ? 0 : RED;          // Contain between 0 and 100
  GREEN = GREEN > 100 ? 100 : GREEN < 0 ? 0 : GREEN;        
  BLUE =   BLUE > 100 ? 100 :  BLUE < 0 ? 0 : BLUE; 

  ledcWrite(0,RED);
  ledcWrite(1,GREEN);
  ledcWrite(2,BLUE);

  if(millis() > FutureTime)
  {

    // Update the OLED SCREEN
    display.clearDisplay();
    display.setTextColor(1);
    display.setCursor(0,0);
    display.printf("   RED  GREEN  BLUE");

    display.setCursor(0,12);
    display.printf("%6d%7d%6d",r,g,b);

    display.setCursor(0,32);
    display.printf("   Temp (K)    LUX");
    display.setCursor(8,44);
    display.printf("%8d%9d",colorTemp,lux);

    display.display();

    // Print RGBC, LUX and Temp to the serial port
    Serial.print("Color Temp: "); Serial.print(colorTemp, DEC); Serial.print(" K - ");
    Serial.print("Lux: "); Serial.print(lux, DEC); Serial.print(" - ");
    Serial.print("R: "); Serial.print(r, DEC); Serial.print(" ");
    Serial.print("G: "); Serial.print(g, DEC); Serial.print(" ");
    Serial.print("B: "); Serial.print(b, DEC); Serial.print(" ");
    Serial.print("C: "); Serial.print(c, DEC); Serial.print(" ");
    Serial.println(" ");

    FutureTime = millis() + ScreenUpdateTime;        // Update the screen 
  }
}



				
			

platform.ini

				
					[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino

upload_port = COM10
upload_speed = 921600
monitor_speed = 115200
lib_deps = 
    adafruit/Adafruit TCS34725 @ ^1.3.6
    adafruit/Adafruit SSD1306 @ ^2.4.6
    adafruit/Adafruit GFX Library @ ^1.10.10
    adafruit/Adafruit BusIO @ ^1.8.3

				
			

The Adafruit TCS34725 library depends on Adafruit BusIO.

I also included the Adafruit SSD1306 and GFX library to control the OLED display.

Testing the RGB Light Meter