Reading tiny, faded resistor bands under poor bench lighting is a universal pain point for electronics hobbyists and production technicians alike. While you can always fall back on a multimeter, building an automated code color resistance scanner is a fantastic embedded systems project that forces you to master I2C communication, sensor calibration, and color space mapping.

This guide walks through building a scanner using an ESP32 and a TCS34725 RGB color sensor. We will cover the exact wiring, provide complete compilable code, and deeply debug the most common I2C and color-drift errors you will encounter on the bench.

Project Spec Sheet & Parts List

Difficulty: Intermediate (Requires I2C debugging and basic C++ logic)
Estimated Time: 90 minutes
Estimated Cost: $18 - $25 USD

To ensure the code compiles and the I2C timing behaves exactly as written below, use these specific board and module variants:

ComponentExact Variant / ModelNotes
MicrocontrollerESP32-DevKitC V4 (ESP32-WROOM-32E)38-pin layout, native 3.3V logic
Color SensorAdafruit TCS34725 Breakout (Product ID: 1334)Includes onboard 3.3V regulator and I2C pull-ups
Resistors (Test)Standard 1/4W 5% Carbon Film KitUsed for calibration targets
Wiring24 AWG Silicone Jumper WiresFlexible, high-strand count for breadboarding
Bench Tip: The Adafruit TCS34725 breakout (ID: 1334) has built-in 10kΩ pull-up resistors on the SDA and SCL lines. If you are using a generic, unbranded clone from an online marketplace, you must add external 4.7kΩ pull-up resistors between the SDA/SCL lines and 3.3V, or the I2C bus will float and fail to initialize.

Pin Mapping & Wiring Steps

The ESP32-WROOM-32E uses GPIO 21 and GPIO 22 for its default I2C0 bus. We will route these directly to the sensor.

ESP32-DevKitC V4 PinTCS34725 Breakout PinWire Color (Suggested)
3V3VINRed
GNDGNDBlack
GPIO 21 (SDA)SDABlue
GPIO 22 (SCL)SCLYellow

Follow these numbered steps to wire the circuit safely:

  1. De-energize the bus: Ensure the ESP32 is unplugged from your PC or USB power supply before making I2C connections.
  2. Connect Power: Route the 3.3V pin to the sensor's VIN. Do not use the 5V pin; while the Adafruit breakout has a regulator, feeding 5V to generic clones can instantly fry the TCS34725 die.
  3. Connect Ground: Tie the ESP32 GND to the sensor GND. A missing ground is the #1 cause of erratic RGB readings.
  4. Connect I2C Data: Wire GPIO 21 to SDA and GPIO 22 to SCL. Keep these wires under 30cm (12 inches) to prevent capacitive loading on the I2C bus.
  5. Verify with a Multimeter: Before applying power, use your multimeter in continuity mode to verify there are no shorts between VCC and GND on the breadboard rails.

The Code: Reading and Decoding Color Bands

This code targets the ESP32-DevKitC V4 using the Arduino IDE (ESP32 Core v2.0.x or v3.0.x). It relies on the Adafruit TCS34725 library. Install it via the Arduino Library Manager before compiling.

The script initializes the sensor, reads the raw RGB values, calculates the color temperature to isolate the band color, and maps it to standard code color resistance digits (Black through White).

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

// --- Pin Definitions for ESP32-DevKitC V4 ---
#define I2C_SDA 21
#define I2C_SCL 22

// Initialize sensor with 50ms integration time and 4x gain
// 50ms is fast enough for bench scanning; 4x gain helps with dark bands (brown/black)
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);

// Resistor Color Code Mapping
const char* colorNames[] = {"Black", "Brown", "Red", "Orange", "Yellow", "Green", "Blue", "Violet", "Gray", "White"};

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow serial monitor to connect
  
  Serial.println("Initializing Code Color Resistance Scanner...");
  
  // Explicitly define I2C pins for ESP32
  Wire.begin(I2C_SDA, I2C_SCL);
  
  if (tcs.begin()) {
    Serial.println("Sensor initialized successfully.");
  } else {
    Serial.println("ERROR: Failed to find TCS34725");
    Serial.println("Check I2C wiring and pull-up resistors.");
    while (1) {
      delay(1000); // Halt execution on fatal hardware error
    }
  }
}

void loop() {
  float red, green, blue;
  
  // Read normalized RGB values
  tcs.getRGB(&red, &green, &blue);
  
  // Calculate Color Temperature (Kelvin) and Lux
  uint16_t colorTemp = tcs.calculateColorTemperature(red, green, blue);
  uint16_t lux = tcs.calculateLux(red, green, blue);
  
  Serial.print("RGB: [");
  Serial.print((int)red); Serial.print(", ");
  Serial.print((int)green); Serial.print(", ");
  Serial.print((int)blue); Serial.print("] ");
  Serial.print("Temp: "); Serial.print(colorTemp); Serial.print("K ");
  Serial.print("Lux: "); Serial.print(lux);
  
  // Map Color Temperature to Resistor Band
  // Note: These thresholds require calibration for your specific ambient lighting
  String bandColor = mapColorTemp(colorTemp, lux);
  
  Serial.print(" -> Detected Band: ");
  Serial.println(bandColor);
  
  delay(500); // Debounce scanning rate
}

String mapColorTemp(uint16_t temp, uint16_t lux) {
  // Ignore readings in total darkness
  if (lux < 5) return "No Target / Too Dark";
  
  if (temp < 2200) return colorNames[0];      // Black
  if (temp < 2800) return colorNames[1];      // Brown
  if (temp < 3500) return colorNames[2];      // Red
  if (temp < 4200) return colorNames[3];      // Orange
  if (temp < 5000) return colorNames[4];      // Yellow
  if (temp < 5800) return colorNames[5];      // Green
  if (temp < 6500) return colorNames[6];      // Blue
  if (temp < 7200) return colorNames[7];      // Violet
  if (temp < 8000) return colorNames[8];      // Gray
  return colorNames[9];                       // White
}

Debugging: I2C Errors and Color Drift

When working with optical sensors on the I2C bus, things rarely work perfectly on the first flash. If your serial monitor outputs the exact error string ERROR: Failed to find TCS34725, the microcontroller cannot handshake with the sensor at its default I2C address (0x29).

Ranked Causes for I2C Initialization Failure

  1. Missing or Incorrect Pull-Up Resistors: The I2C bus is open-drain. Without pull-ups to 3.3V, the SDA/SCL lines float, causing the ESP32 to read garbage data. (Fix: Add 4.7kΩ resistors to 3.3V if using a bare sensor module).
  2. SDA and SCL Swapped: Unlike UART, I2C lines are not cross-referenced. GPIO 21 must go to SDA, and GPIO 22 must go to SCL. (Fix: Swap the blue and yellow jumper wires).
  3. Sensor in Sleep State / Power Starvation: The TCS34725 draws roughly 60µA in sleep, but spikes to 300µA during integration. If your USB cable has high voltage drop, the ESP32's 3.3V regulator might brown out. (Fix: Use a high-quality, short USB-C data cable and check voltage at the sensor VIN pin with a multimeter; it should read >3.1V).

The First Three Things to Check When It Fails

Before rewriting code or replacing components, execute this physical verification sequence:

  1. Run an I2C Scanner Sketch: Flash a standard I2C scanner script. If it returns 0x29, your wiring is perfect and the issue is a software library conflict. If it returns nothing, you have a hardware/wiring fault.
  2. Measure Continuity on GND: Put your multimeter in continuity mode. Probe the ESP32 GND pin and the metal shield of the USB port. Then probe the sensor GND pin and the USB shield. If it doesn't beep, your ground wire is broken.
  3. Verify Logic Levels: Measure the voltage on the SDA and SCL lines while idle. They should sit at a steady 3.3V. If they sit at 0V or 1.5V, your pull-up resistors are missing or wired to the wrong voltage rail.

For deeper I2C timing analysis, refer to the official Espressif I2C API documentation, which details how to adjust the I2C clock speed if bus capacitance is causing signal degradation.

Color Drift Gotcha: If the scanner initializes but reads "Orange" when scanning a "Red" band, your ambient lighting is polluting the sensor. The TCS34725 is highly sensitive to 50Hz/60Hz mains flicker from overhead fluorescent or LED room lights. Increase the integration time in the code to TCS34725_INTEGRATIONTIME_154MS to average out the AC flicker cycle.

Extending and Simplifying the Build

Depending on your end goal, you might want to strip this project down or scale it up for production use.

How to Simplify:
If you don't need WiFi or heavy processing, swap the ESP32-DevKitC V4 for an Arduino Nano V3 (ATmega328P). You will need to change the I2C pins in the code to A4 (SDA) and A5 (SCL). To make it standalone, wire a 0.96" SSD1306 I2C OLED display to the same bus and print the code color resistance value directly to the screen, eliminating the need for a serial monitor.

How to Extend:
To turn this into a reliable bench tool, ambient light is your enemy. Design a 3D-printed shroud or "jig" that holds the resistor exactly 5mm from the sensor aperture. Integrate a WS2812B RGB LED ring inside the shroud, driven by the ESP32, to provide a consistent, calibrated 5000K daylight white illumination source. This eliminates the color drift caused by moving your hand over the sensor and allows you to reliably read 5-band precision resistors.

FAQ: Resistor Color Code Automation

How do I calibrate the sensor for faded resistor color codes?

Faded resistors (especially older carbon composition types) lose their saturation, making Brown look like Black, and Red look like Orange. To calibrate, place a known, high-quality 1% metal film resistor of the target color under the sensor. Record the raw RGB and Color Temperature values printed to the serial monitor. Adjust the threshold boundaries in the mapColorTemp() function to center around your specific faded component's readings. Always calibrate with your 3D-printed light shroud in place.

Can this code color resistance setup read 5-band precision resistors?

Yes, but not by simply waving the sensor over the component. 5-band resistors have tightly packed bands, and the TCS34725's optical aperture is roughly 2mm wide. To read 5-band codes, you must build a mechanical sled that moves the resistor past the sensor at a constant speed, taking readings every 2mm. You will then need to write a state-machine algorithm in C++ that identifies the spacing between peaks in the reflected light to separate the bands programmatically.

Why does my code color resistance read change when I move my hand over the sensor?

The TCS34725 lacks an integrated optical filter for infrared (IR) rejection in its default configuration, and it is highly susceptible to ambient light pollution. When you move your hand over the breadboard, you are casting a shadow that changes the ratio of ambient room light to the light bouncing off the resistor band. Furthermore, your skin reflects IR radiation, which can skew the red channel. Building an opaque enclosure around the sensor and the resistor is mandatory for stable, repeatable readings.