ESP32-C3 Color Detector with TCS34725, Real-Time RGB Detection & Web Interface

This ESP32-C3 Color Detector demonstrates how accurate and efficient color recognition can be achieved using a dedicated TCS34725 optical sensor. With real-time RGB detection, instant LED feedback, and a wireless smartphone interface, the project provides a practical and low-cost solution for a wide range of embedded electronics applications.

Color detection is a fundamental task in many embedded systems – from industrial sorting machines to smart home lighting and DIY electronics. When using a microcontroller (MCU) like the ESP32, there are two common approaches:
1. Camera‑based detection
2. and Optical sensors with integrated color filters
The first system uses a camera module to capture an image, then the MCU or a connected computer runs algorithms. This way consumes much more processing power, memory, and energy. Optical sensor contain photodiodes with red, green, blue, and clear filters, plus an analog-to-digital converter. The MCU reads the intensity of each channel via I²C.

This method is fast, low‑power, and works well, and no complex image processing is required. The project presented in this video uses a second, more efficient approach, using a dedicated color sensor to achieve real-time, accurate readings with minimal resources.

This project is sponsored by PCBWay . From July 1st to July 31st, 2026, PCBWay is organizing 12th anniversary campaig. Grab your discount and unlock more rewards in the form of Exclusive Coupons, PCB Order for Just $5, Turnkey Electronic Design Services, Up to 50% off for 3D printing & CNC Machining, and Special Sales in PCBWay Store. Next, Join the fun with interactive games and receive your exclusive Engineer Color Personality, Click the Box to Participate in the Lucky Draw, Show Your Colorful Projects, and Vote for the Most Popular Badge.

The hardware implemented in this project is:
- ESP32‑C3 – a compact, Wi‑Fi enabled RISC‑V chip. It handles sensor reading, PWM generation for the RGB LED, and runs a web server.
- Color sensor board TCS3472 which should be close to the target object to exclude ambient light. The sensor has a built-in white LED to illuminate the sample.
- A common‑cathode RGB LED connected to three PWM pins. The LED mirrors the detected colour, providing immediate visual feedback.
- And Smartphone connected to Wi-Fi Acces point created by ESP32C3

The components are mounted in a suitable housing and the sensor is protected from external light influence. As for the code, it consists of two parts. Calibration code and functional code.

The calibration code is used in a way that a white paper is placed in front of the sensor, and the values ​​for R, G and B are recorded on the Serial Monitor. Then these values ​​are placed in the main code at the beginning where the constants are defined. The main code has a part for controlling the sensor and the local RGB LED, as well as generating a web interface for displaying the results on a smartphone.
Now let's see how the device works in real conditions. By turning on the device, the white LED inside the sensor is activated, whose function is to illuminate the object in order to read its color. The detected color is immediately displayed on the RGB LED placed close to the sensor.

The device also has the option to display the result on a smartphone. For this purpose, we need to connect the smartphone to the Wi-Fi network with the name "ESP32_Color_Detector" and the password "12345678". Now we need to enter the address of the access point generated by ESP32 in a web browser, which is 192.168.4.1. Here a graphical interface opens where we can see the read color as well as the individual values ​​of the Red, Green and Blue colors. in a range from 0 to 255.

On the lower half of the display we also have a graphical display of these values ​​and their representation in terms of wavelength.
And finally a short conclusion. This efficient ESP32-C3 color detector utilizes a dedicated TCS3472 optical sensor to provide fast, real-time, and accurate color readings without demanding heavy processing power. With its instant local RGB LED feedback and an interactive smartphone web interface, the project serves as a highly practical and low-power solution for embedded color detection.

CODE
// By mircemk June, 2026

#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <WiFi.h>
#include <WebServer.h>
#include "esp_wifi.h"

// ========== RGB LED PINS ==========
#define LED_RED   10
#define LED_GREEN 5    // Change according to your board
#define LED_BLUE  6

// ========== Wi-Fi ACCESS POINT ==========
const char* password = "12345678";
WebServer server(80);

// ========== SENSOR ==========
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_101MS,
                                          TCS34725_GAIN_16X);

// ========== CALIBRATION MAXIMUMS ==========
const int MAX_R = 3400;
const int MAX_G = 4300;
const int MAX_B = 3600;

// ========== GAMMA TABLE ==========
byte gammaTable[256];

// ========== GLOBAL CURRENT COLORS ==========
byte currentR = 0, currentG = 0, currentB = 0;

// ========== HTML ==========
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head><meta name=viewport content="width=device-width,initial-scale=1.0,user-scalable=yes"><title>Color Detector</title><style>
*{margin:0;padding:0;box-sizing:border-box}body{background:#1e1e2f;height:100vh;display:flex;flex-direction:column;overflow:hidden}.top{height:40vh;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;border-bottom:3px solid white;background:#000;transition:background 0.05s}.top h1{font-size:2rem;margin-bottom:8px}.vals{font-size:1.5rem;font-family:monospace;line-height:1.3}.bottom{height:60vh;overflow-y:auto;padding:12px;background:#0a0a12;display:flex;flex-direction:column}#mount{width:100%;background:#00000020;border-radius:16px;margin-bottom:12px;min-height:220px;height:auto}#spec{width:100%;height:100px;border-radius:12px;margin-bottom:8px}.labels{display:flex;justify-content:space-between;padding:8px 4px;color:#ddd;font-size:12px;font-weight:bold;font-family:monospace;background:#0a0a12;border-top:1px solid #555;margin-top:4px}
</style></head><body><div class=top id=top><h1>COLOR DETECTOR</h1><div class=vals id=vals><div id=r>R: --</div><div id=g>G: --</div><div id=b>B: --</div></div></div><div class=bottom><canvas id=mount></canvas><canvas id=spec></canvas><div class=labels><span>400nm</span><span>450nm</span><span>500nm</span><span>550nm</span><span>600nm</span><span>650nm</span><span>700nm</span></div></div><script>
function fetchColor(){fetch('/data').then(r=>r.json()).then(d=>{let top=document.getElementById('top');top.style.backgroundColor=`rgb(${d.r},${d.g},${d.b})`;document.getElementById('r').innerHTML='R: '+d.r;document.getElementById('g').innerHTML='G: '+d.g;document.getElementById('b').innerHTML='B: '+d.b;let br=d.r*0.299+d.g*0.587+d.b*0.114;top.style.color=br>140?'#000':'#fff';drawMount(d.r,d.g,d.b);})}
function drawMount(r,g,b){let c=document.getElementById('mount'),ctx=c.getContext('2d'),w=c.clientWidth,h=c.clientHeight;c.width=w;c.height=h;let xb=0.2*w,xg=0.5*w,xr=0.8*w;let yb=h-(b/255)*h*0.85,yg=h-(g/255)*h*0.85,yr=h-(r/255)*h*0.85;ctx.clearRect(0,0,w,h);ctx.beginPath();ctx.moveTo(0,h);ctx.lineTo(xb,yb);ctx.lineTo(xg,yg);ctx.lineTo(xr,yr);ctx.lineTo(w,h);ctx.closePath();let grad=ctx.createLinearGradient(0,0,w,0);grad.addColorStop(0,`rgb(0,0,${b})`);grad.addColorStop(0.35,`rgb(0,${g},0)`);grad.addColorStop(0.7,`rgb(${r},0,0)`);ctx.fillStyle=grad;ctx.fill();ctx.beginPath();ctx.moveTo(xb,yb);ctx.lineTo(xg,yg);ctx.lineTo(xr,yr);ctx.strokeStyle="#fff";ctx.lineWidth=3;ctx.stroke();ctx.fillStyle="white";ctx.beginPath();ctx.arc(xb,yb,6,0,2*Math.PI);ctx.fill();ctx.beginPath();ctx.arc(xg,yg,6,0,2*Math.PI);ctx.fill();ctx.beginPath();ctx.arc(xr,yr,6,0,2*Math.PI);ctx.fill();ctx.fillStyle="#fff";ctx.font="bold 14px monospace";ctx.fillText(`${b}`,xb-12,yb-8);ctx.fillText(`${g}`,xg-12,yg-8);ctx.fillText(`${r}`,xr-12,yr-8);}
function drawSpec(){let c=document.getElementById('spec'),ctx=c.getContext('2d'),w=c.clientWidth,h=c.clientHeight;c.width=w;c.height=h;let grad=ctx.createLinearGradient(0,0,w,0);grad.addColorStop(0,'#8b00ff');grad.addColorStop(0.16,'#00f');grad.addColorStop(0.33,'#0ff');grad.addColorStop(0.5,'#0f0');grad.addColorStop(0.66,'#ff0');grad.addColorStop(0.83,'#f80');grad.addColorStop(1,'#f00');ctx.fillStyle=grad;ctx.fillRect(0,0,w,h);ctx.strokeStyle="#fff8";ctx.lineWidth=1;for(let x=0;x<=1;x+=0.1666){ctx.beginPath();ctx.moveTo(x*w,0);ctx.lineTo(x*w,h);ctx.stroke();}}
window.onload=()=>{drawSpec();setInterval(fetchColor,300);};window.addEventListener('resize',()=>{drawSpec();fetchColor();});
</script></body></html>
)rawliteral";

void handleRoot() { server.send(200, "text/html", index_html); }
void handleData() {
  String json = "{\"r\":" + String(currentR) + ",\"g\":" + String(currentG) + ",\"b\":" + String(currentB) + "}";
  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(8, 9);   // SDA=9, SCL=8 (check if it matches your board)
  
  if (!tcs.begin()) { 
    Serial.println("No sensor"); 
    while(1); 
  }
  tcs.setInterrupt(true);
  
  pinMode(LED_RED, OUTPUT); 
  pinMode(LED_GREEN, OUTPUT); 
  pinMode(LED_BLUE, OUTPUT);
  
  for (int i=0; i<256; i++) {
    float x = (float)i/255.0;
    x = pow(x, 2.2);
    gammaTable[i] = (byte)(x*255.0);
  }

  // ========== Wi-Fi Access Point (FIXED) ==========
  // Ресетирај го Wi-Fi стекот
  WiFi.disconnect(true, true);
  delay(100);
  WiFi.mode(WIFI_OFF);
  delay(100);
  
  // Create a unique SSID (with a random number)
  randomSeed(analogRead(0));
  String ssid = "ESP32_Color_" + String(random(100, 999));
  Serial.print("SSID: ");
  Serial.println(ssid);
  
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid.c_str(), password);
  WiFi.setSleep(false);
  esp_wifi_set_ps(WIFI_PS_NONE);
  WiFi.softAPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0));
  
  // Set channel 6 and maximum transmit power
  esp_wifi_set_channel(6, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_max_tx_power(84);
  
  WiFi.softAPsetHostname("ESP32-Color");
  
  Serial.print("IP: "); 
  Serial.println(WiFi.softAPIP());
  
  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
  Serial.println("Ready.");
}

void loop() {
  // Memory check
  static unsigned long lastMemCheck = 0;
  if (millis() - lastMemCheck > 5000) {
    lastMemCheck = millis();
    int freeHeap = ESP.getFreeHeap();
    Serial.print("Free heap: ");
    Serial.println(freeHeap);
    if (freeHeap < 15000) {
      Serial.println("CRITICAL: restarting...");
      ESP.restart();
    }
  }
  
  uint16_t r_raw, g_raw, b_raw, c_raw;
  tcs.setInterrupt(false);
  delay(60);
  tcs.getRawData(&r_raw, &g_raw, &b_raw, &c_raw);
  tcs.setInterrupt(true);

  byte r_norm = constrain(map(r_raw, 0, MAX_R, 0, 255), 0, 255);
  byte g_norm = constrain(map(g_raw, 0, MAX_G, 0, 255), 0, 255);
  byte b_norm = constrain(map(b_raw, 0, MAX_B, 0, 255), 0, 255);

  currentR = gammaTable[r_norm];
  currentG = gammaTable[g_norm];
  currentB = gammaTable[b_norm];

  analogWrite(LED_RED, currentR);
  analogWrite(LED_GREEN, currentG);
  analogWrite(LED_BLUE, currentB);

  server.handleClient();

  delay(100);
}






CODE CALIBRATION:  //kalibracija so BEL list

#include <Wire.h>
#include "Adafruit_TCS34725.h"
Adafruit_TCS34725 tcs(TCS34725_INTEGRATIONTIME_100MS, TCS34725_GAIN_16X);
void setup() {
  Serial.begin(115200);
  Wire.begin(8, 9);
  tcs.begin();
  tcs.setInterrupt(true);
}
void loop() {
  uint16_t r,g,b,c;
  tcs.setInterrupt(false);
  delay(60);
  tcs.getRawData(&r,&g,&b,&c);
  tcs.setInterrupt(true);
  Serial.print("R:"); Serial.print(r); Serial.print(" G:"); Serial.print(g); Serial.print(" B:"); Serial.println(b);
  delay(500);
}
License
All Rights
Reserved
licensBg
0