Project Overview & Difficulty Rating

Decoding the color resistor code by eye is a rite of passage, but it becomes tedious when sorting bulk bins of 5% carbon film or 1% metal film resistors. This project bridges fundamental component theory with embedded systems by building an automated color resistor code reader. Using an ESP32 and a TCS34725 RGB light-to-digital converter, we will read the physical color bands, map them to the IEC 60062 standard, and display the result on an I2C OLED screen.

AttributeSpecification
DifficultyIntermediate (I2C wiring, C++ color mapping logic)
Time to Build2-3 hours (including 3D printed sensor shroud)
Estimated Cost$18 - $24 USD (based on 2026 component pricing)
Target BoardESP32-DevKitC V4 (ESP32-WROOM-32 module)

The Theory: IEC 60062 Color Resistor Code Standards

Before writing firmware, we must understand the standard we are digitizing. The international standard IEC 60062 defines the color resistor code mapping. For a standard 4-band resistor:

  • Band 1 & 2: Significant digits (Black=0, Brown=1, Red=2, Orange=3, Yellow=4, Green=5, Blue=6, Violet=7, Grey=8, White=9).
  • Band 3: Multiplier (10^n).
  • Band 4: Tolerance (Gold=±5%, Silver=±10%, Brown=±1%).

Worked Example: A resistor with Yellow-Violet-Red-Gold bands translates to 4 (Yellow), 7 (Violet), x100 (Red), ±5% (Gold). The math: 47 × 100 = 4,700Ω, or 4.7kΩ. Precision 5-band resistors simply add a third significant digit band before the multiplier.

Hardware Bill of Materials & Pin Mapping

This build relies on the ESP32's hardware I2C bus. While the ESP32-WROOM-32 has internal weak pull-ups (~45kΩ), the TCS34725 requires strong 4.7kΩ external pull-ups on the SDA and SCL lines for reliable 400kHz Fast Mode communication. Do not skip the pull-up resistors.

ComponentExact Variant / Part NumberQty
MicrocontrollerESP32-DevKitC V4 (ESP32-WROOM-32)1
Color SensorAdafruit TCS34725 Breakout (with IR blocking filter)1
Display128x64 SSD1306 I2C OLED (0.96 inch)1
Pull-up Resistors4.7kΩ 1/4W Metal Film (for I2C lines)2
Wiring22 AWG solid core jumper wires~10

ESP32 Pin Mapping Table

ESP32-DevKitC V4 PinTarget ModuleModule PinNotes
3V3TCS34725 & OLEDVIN / VCCSensor operates at 3.3V logic
GNDTCS34725 & OLEDGNDCommon ground required
GPIO 21 (SDA)TCS34725 & OLEDSDAAttach 4.7kΩ pull-up to 3V3
GPIO 22 (SCL)TCS34725 & OLEDSCLAttach 4.7kΩ pull-up to 3V3

Step-by-Step Wiring & Assembly

  1. Prepare the I2C Bus: Insert the ESP32 into a solderless breadboard. Connect GPIO 21 to the SDA rail and GPIO 22 to the SCL rail.
  2. Install Pull-ups: Bridge a 4.7kΩ resistor between the 3V3 rail and the SDA rail. Repeat for the SCL rail. This ensures clean square wave edges on the I2C clock.
  3. Wire the Sensor: Connect the TCS34725 VIN to 3V3, GND to GND, SDA to SDA, and SCL to SCL. Leave the INT and LED pins disconnected for this baseline build.
  4. Wire the OLED: Connect the SSD1306 VCC to 3V3, GND to GND, SCL to SCL, and SDA to SDA. Both devices share the same I2C bus but use different addresses (0x29 for TCS, 0x3C for OLED).
  5. Fabricate a Light Shroud: The TCS34725 is highly sensitive to ambient room lighting. Cut a small shroud from black heat-shrink tubing or 3D print a TPU boot that fits snugly over the sensor and the resistor body to block external light bleed.

Complete ESP32 Firmware & Color Mapping Logic

The following C++ code targets the ESP32-DevKitC V4 in the Arduino IDE (ensure you have the Espressif ESP32 board manager installed, along with the Adafruit_TCS34725 and Adafruit_SSD1306 libraries). It includes robust I2C initialization error handling and a baseline RGB-to-color mapping function.

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

// Pin Definitions for ESP32-DevKitC V4
#define SDA_PIN 21
#define SCL_PIN 22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define TCS_ADDRESS 0x29

// Initialize sensor with 50ms integration time and 4x gain
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  
  // Error Handling: TCS34725 Init
  if (!tcs.begin(TCS_ADDRESS, &Wire)) {
    Serial.println("[FATAL] TCS34725 I2C Init Failed - Check SDA/SCL pull-ups");
    while (1) { delay(1000); } // Halt execution
  }
  
  // Error Handling: OLED Init
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println("[FATAL] SSD1306 I2C Init Failed");
    while (1) { delay(1000); }
  }
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("Color Reader Ready");
  display.display();
  delay(1000);
}

void loop() {
  uint16_t r, g, b, c;
  tcs.getRawData(&r, &g, &b, &c);
  
  String colorName = mapColor(r, g, b);
  
  display.clearDisplay();
  display.setCursor(0,0);
  display.print("Detected: ");
  display.println(colorName);
  display.print("R:"); display.print(r);
  display.print(" G:"); display.print(g);
  display.print(" B:"); display.println(b);
  display.display();
  
  delay(500);
}

String mapColor(uint16_t r, uint16_t g, uint16_t b) {
  float total = r + g + b;
  if (total == 0) return "Black";
  
  float rPct = (r / total) * 100;
  float gPct = (g / total) * 100;
  float bPct = (b / total) * 100;
  
  // Baseline thresholds (requires calibration for specific lighting)
  if (rPct > 55 && gPct < 25 && bPct < 20) return "Red";
  if (rPct > 40 && rPct < 55 && gPct < 30 && bPct < 20) return "Brown";
  if (gPct > 50 && rPct < 30) return "Green";
  if (rPct > 45 && gPct > 40 && bPct < 15) return "Yellow";
  if (bPct > 50 && rPct < 25) return "Blue";
  if (rPct > 30 && gPct > 30 && bPct > 30) return "White/Grey";
  
  return "Unknown";
}

Debugging: First Three Things to Check When It Fails

If your serial monitor outputs the exact error string [FATAL] TCS34725 I2C Init Failed - Check SDA/SCL pull-ups, or if the sensor reads wildly inaccurate colors, follow this ranked troubleshooting path:

  1. Missing or Incorrect I2C Pull-up Resistors (Most Likely): The ESP32's internal pull-ups are too weak for the TCS34725's capacitive load at 400kHz. If you omitted the external 4.7kΩ resistors on SDA and SCL, the I2C bus will hang during the tcs.begin() handshake. Measure the SDA/SCL lines with a multimeter; they should read ~3.3V when idle.
  2. Ambient Light Bleed (Color Misreads): If the sensor initializes but outputs 'Unknown' or confuses Red with Brown, ambient room light is washing out the sensor's IR-blocking filter. You must physically shroud the sensor. A simple fix is sliding a piece of opaque black heat-shrink over the sensor and the resistor body, leaving only the LED illumination path.
  3. I2C Address Collision or Wiring Swap: Verify you haven't accidentally swapped SDA (GPIO 21) and SCL (GPIO 22). While some ESP32 core versions allow software remapping, the hardware I2C peripheral expects strict pin assignments for reliable DMA transfers. Run an I2C scanner sketch to confirm addresses 0x29 and 0x3C appear.
Bench Tip: Brown and Red are notoriously difficult to separate optically because brown is essentially dark red/orange. If your color resistor code reader struggles here, increase the TCS34725 integration time in the code from TCS34725_INTEGRATIONTIME_50MS to TCS34725_INTEGRATIONTIME_154MS to gather more photon data and improve color resolution.

Extending and Simplifying the Build

To Simplify: If you are debugging on a bench and don't need a standalone tool, remove the SSD1306 OLED entirely. Rely on the Serial.println() outputs in the Arduino IDE Serial Monitor. This frees up I2C bus capacitance and reduces code complexity.

To Extend: Turn this into an automated sorting machine by adding an MG996R servo motor and a 3D-printed tilt tray. When the mapColor() function identifies a band, trigger the servo to tilt the resistor into the corresponding physical bin. You can also implement the full IEC 60062 math by prompting the user via a rotary encoder to scan Band 1, Band 2, and Band 3 sequentially, calculating the final ohm value on the fly.

Frequently Asked Questions

How do I read a 5-band color resistor code for precision resistors?

A 5-band color resistor code is used for high-precision (usually 1% or better) metal film resistors. The first three bands represent significant digits, the fourth band is the multiplier, and the fifth band is the tolerance. For example, Brown-Black-Black-Red-Brown translates to 100 × 100 = 10,000Ω (10kΩ) with a ±1% tolerance. To adapt the ESP32 project for this, you would need to mechanically index the resistor to scan three distinct color zones sequentially.

What is the difference between a 4-band and 6-band color resistor code?

The 6-band color resistor code includes all the information of the 5-band version but adds a sixth band indicating the Temperature Coefficient of Resistance (TCR), measured in ppm/°C. This sixth band tells you how much the resistance will drift as the component heats up during operation. Black denotes 250 ppm/°C, while Brown denotes 100 ppm/°C. This is critical in precision analog circuits like transimpedance amplifiers or reference voltage dividers.

Why does my color resistor code reader confuse red and brown bands?

Optically, brown is just a low-luminance, desaturated red. The TCS34725 sensor measures raw light intensity. If your integration time is too short, or if your 3.3V LED drive current is too high, the brown band will reflect enough red light to trigger the 'Red' threshold in the C++ mapping logic. Lower the sensor gain to TCS34725_GAIN_1X and rely on the shroud to eliminate ambient white light, which artificially inflates the blue and green channels and skews the RGB percentage ratios.

Can I use an Arduino Uno instead of an ESP32 for this color resistor code project?

Yes, but with hardware caveats. The Arduino Uno (ATmega328P) operates at 5V logic, while the TCS34725 breakout requires 3.3V. You must use a bidirectional logic level converter on the SDA and SCL lines to prevent bricking the sensor's I2C pull-up array. Furthermore, the Uno's SRAM is limited (2KB), so if you plan to extend the project with large lookup tables for 5-band decoding, you will hit memory limits much faster than on the ESP32's 520KB SRAM.