Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Mango Ripeness Checker: the finished build
ESP32-C3 SuperMiniStudent projects

Mango ripeness checker with a TCS34725 colour sensor (ESP32-C3 SuperMini)

Turn skin colour into a number — sort raw, turning and ripe mangoes the same way every time.

Difficulty
Easy
Build time
1.5 h
Parts cost
₹1,020 · $12
Verified — core esp32 3.3.12, Adafruit TCS34725 1.4.4, 3 Nov 2026Compile-only

Community draft — report a mistake

Parts list

PartQtyApprox.Buy
ESP32-C3 SuperMini1₹230Buy ESP32-C3 SuperMini
TCS34725 colour sensor module (with white LED)1₹350Buy TCS34725 colour sensor module (with white LED)
0.96" SSD1306 I2C OLED1₹220Buy 0.96" SSD1306 I2C OLED
12 mm push button1₹10Buy 12 mm push button
Black tube or bottle cap around the sensor (blocks room light)1₹10Buy Black tube or bottle cap around the sensor (blocks room light)
USB power bank or 5 V adapter + cable1₹200Buy USB power bank or 5 V adapter + cable
Total₹1,020

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

ESP32-C3 SuperMiniOnly the pins used are shown · 3.3 V logicTCS34725 RGB colour sensorSSD1306 OLED 0.96″ (128×64, I2C)Push button (6 mm tactile switch)GND3V35 · 56 · 67 · 710 · 10LEDINT (not connected)SDASCL3V3 (not connected)GNDVINGNDVCCSCLSDA12
Every connection
ESP32-C3 SuperMiniPart · pinWireNotes
5TCS34725 RGB colour sensor · SDAblueGPIO5 — shared I2C bus, off strapping pins 8/9
6TCS34725 RGB colour sensor · SCLyellowGPIO6
7TCS34725 RGB colour sensor · LEDwhiteGPIO7 — HIGH lights the white LED only while measuring
3V3TCS34725 RGB colour sensor · VINredVIN, not 3V3 — the 3V3 pin is the module’s regulator output
GNDTCS34725 RGB colour sensor · GNDblack
5SSD1306 OLED 0.96″ (128×64, I2C) · SDAblue
6SSD1306 OLED 0.96″ (128×64, I2C) · SCLyellow
3V3SSD1306 OLED 0.96″ (128×64, I2C) · VCCred
GNDSSD1306 OLED 0.96″ (128×64, I2C) · GNDblack
10Push button (6 mm tactile switch) · 1greenGPIO10 — press to measure, hold 3 s on white paper to white-balance
GNDPush button (6 mm tactile switch) · 2black

Board quirks: The sensor needs the same light every time. Fit a short black tube (a bottle cap with a hole works) around it so only its own white LED lights the skin, and press it flat against the fruit. White-balance first: hold the button for 3 s with the sensor on plain white paper. The gains are saved in flash and correct for the LED’s bluish light and the sensor’s uneven red/green/blue sensitivity. Colour is only one sign of ripeness. Alphonso, Kesar, Banganapalli and Totapuri turn yellow-orange, but Langra and some Dasheri stay green when ripe — for those, smell and softness still win. Set <code>GREEN_HUE</code> and <code>RIPE_HUE</code> from a raw and a ripe fruit of your variety. Both I2C parts share GPIO5/6. The TCS34725 answers at 0x29 and the OLED at 0x3C, so they don’t clash.

Code

mango-ripeness-checker.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Mango ripeness checker — ESP32-C3 SuperMini + TCS34725 colour sensor + SSD1306 OLED.
// Hold the sensor's window flat against the fruit's skin and press the button. The white LED
// lights the skin, the sensor reads red/green/blue, and the C3 turns that into a hue angle:
// green skin ~100 degrees, yellow ~55, orange ~35. The hue is mapped onto a 0-100 % ripeness
// scale between GREEN_HUE and RIPE_HUE — set both for your variety (see the page).
// I2C (both parts): SDA 5, SCL 6 — off strapping pins 8/9. TCS34725 LED pin: GPIO7 (HIGH = on).
// White balance: hold the button 3 s with the sensor on plain white paper (saved in flash).
// Button: GPIO10 to GND. Board option: USB CDC On Boot.
#include <Wire.h>
#include <Adafruit_TCS34725.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>

constexpr int SDA_PIN = 5, SCL_PIN = 6, LED_PIN = 7, BTN = 10;
float GREEN_HUE = 100;  // raw, unripe skin of your variety
float RIPE_HUE = 45;    // fully ripe skin

Adafruit_TCS34725 tcs(TCS34725_INTEGRATIONTIME_154MS, TCS34725_GAIN_4X);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
bool sensorOk = false;
Preferences prefs;
float gainR = 1, gainG = 1, gainB = 1;  // white-balance gains

float hueOf(float r, float g, float b) {  // standard RGB -> hue, 0-360 degrees
  float mx = max(r, max(g, b)), mn = min(r, min(g, b)), d = mx - mn;
  if (d <= 0) return 0;
  float h = mx == r ? fmod((g - b) / d, 6.0f) : mx == g ? (b - r) / d + 2 : (r - g) / d + 4;
  h *= 60;
  return h < 0 ? h + 360 : h;
}

void show(const char* l1, const char* l2, const char* l3) {
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.print(l1);
  oled.setTextSize(2);
  oled.setCursor(0, 18);
  oled.print(l2);
  oled.setTextSize(1);
  oled.setCursor(0, 48);
  oled.print(l3);
  oled.display();
}

void setup() {
  Serial.begin(115200);
  pinMode(BTN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  Wire.begin(SDA_PIN, SCL_PIN);
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) Serial.println("OLED not found at 0x3C");
  sensorOk = tcs.begin();
  if (!sensorOk) {
    Serial.println("TCS34725 not found at 0x29 - check SDA 5 / SCL 6");
    show("Colour sensor", "missing", "check wiring");
    return;
  }
  show("Mango ripeness", "Ready", "Hold on skin, press");
  prefs.begin("mango", false);
  gainR = prefs.getFloat("gr", 1);
  gainG = prefs.getFloat("gg", 1);
  gainB = prefs.getFloat("gb", 1);
  Serial.println("hue,ripeness_pct,r,g,b,clear");
}

void loop() {
  if (!sensorOk || digitalRead(BTN) == HIGH) return;
  digitalWrite(LED_PIN, HIGH);
  delay(200);  // one integration period with the LED on
  float hSum = 0;
  uint16_t r, g, b, c;
  for (int i = 0; i < 3; i++) {  // average three readings
    tcs.getRawData(&r, &g, &b, &c);
    hSum += hueOf(r * gainR, g * gainG, b * gainB);
  }
  uint32_t pressed = millis();
  while (digitalRead(BTN) == LOW && millis() - pressed < 3000) delay(10);
  if (millis() - pressed >= 3000 && r && g && b) {  // held 3 s: white balance on white paper
    float avg = (r + g + b) / 3.0f;
    gainR = avg / r, gainG = avg / g, gainB = avg / b;
    prefs.putFloat("gr", gainR), prefs.putFloat("gg", gainG), prefs.putFloat("gb", gainB);
    digitalWrite(LED_PIN, LOW);
    show("White balance", "Saved", "Now test a fruit");
    Serial.printf("White balance gains %.2f %.2f %.2f\n", gainR, gainG, gainB);
    while (digitalRead(BTN) == LOW) delay(10);
    return;
  }
  digitalWrite(LED_PIN, LOW);
  float hue = hSum / 3;
  char l1[24], l2[16];
  if (c < 200) {  // almost no light came back: not on a fruit
    show("Too dark", "--", "Press the window on");
    return;
  }
  int ripe = constrain((int)((GREEN_HUE - hue) / (GREEN_HUE - RIPE_HUE) * 100), 0, 100);
  const char* verdict = hue > GREEN_HUE + 20 ? "Not a mango?" : ripe < 25 ? "Raw" : ripe < 60 ? "Turning" : ripe < 90 ? "Ripe" : hue < RIPE_HUE - 15 ? "Overripe?" : "Fully ripe";
  snprintf(l1, sizeof l1, "Hue %.0f deg", hue);
  snprintf(l2, sizeof l2, "%d%%", ripe);
  show(l1, l2, verdict);
  Serial.printf("%.1f,%d,%u,%u,%u,%u\n", hue, ripe, r, g, b, c);
  while (digitalRead(BTN) == LOW) delay(10);
}

Compiled for c3supermini on esp32 3.3.12 with Adafruit TCS34725 1.4.4 · 3 Nov 2026

How it works

Traders, farmers and households judge mangoes by eye, and two people rarely agree on “ready”. A colour sensor turns the skin colour into a number, so you can sort a crate of fruit the same way every time — or run a neat school experiment on how fruit ripens over a week.

The TCS34725 has red, green, blue and clear photodiodes behind an infrared-blocking filter, plus a white LED to light the surface. When you press the button, the C3 switches the LED on, takes three readings, and converts each red/green/blue triple into a hue angle — where the colour sits on the colour wheel. Raw green mango skin is around 100°, yellow about 55°, orange-red about 35°.

The hue is placed on a 0–100 % ripeness scale between two reference values for your variety, GREEN_HUE and RIPE_HUE, and the OLED shows the hue, the percentage and a word: Raw, Turning, Ripe or Fully ripe. If almost no light comes back, it says “Too dark” instead of guessing. Every reading also goes to the Serial Monitor as CSV.

The ESP32-C3 SuperMini keeps it pocket-sized and runs from a power bank. Both I2C parts share GPIO5/6, set with Wire.begin(5, 6) because the board’s default I2C pins are strapping pins.

Troubleshooting

“Too dark” on every fruit

The LED isn’t lighting. Check the LED wire on GPIO7; on some modules the LED pin is labelled LED or LEDON.

White paper reads as green or blue

Do the 3-second white balance. Read the gains in the Serial Monitor — they should be between about 0.6 and 1.6.

Readings change a lot on the same fruit

Room light is leaking in or the sensor isn’t flat. Use the black tube and measure the same spot (the shoulder near the stalk ripens first).

Upgrades

  • Measure three spots and average them before giving a verdict.
  • Save reference hues for several fruits (banana, tomato, papaya) and pick with a second button.
  • Build a sorting chute with a servo, like the colour sorting machine project.

↑↓ to move · Enter to open