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.
But 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?
Taking 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
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.
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

TCS34725

White OLED

LEDs
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.
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
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 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:
I 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
#include
#include
#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.