Color Matcher
Works with
Any CircuitPython board with I2C
Point the sensor at anything — a crayon, a wall, a leaf — and the device reads the color, names it, shows the RGB values on a small OLED screen, and glows that exact color on a NeoPixel strip. It is a useful calibration tool, a satisfying toy, and a solid introduction to running two I2C devices on the same bus.
What you'll build
A handheld color identification device. The TCS34725 color sensor reads red, green, and blue light levels. The code maps those numbers to the nearest entry in a named color table and displays the name and RGB values on a 128x64 OLED. A NeoPixel strip glows the matched color so you can compare sensor output to real-world appearance side by side.
What you'll need
| Part | Notes |
|---|---|
| CircuitPython board with I2C | Feather, ItsyBitsy, Trinket M0, Circuit Playground, Pico |
| Adafruit TCS34725 color sensor breakout | Product page |
| SSD1306 128x64 OLED display | I2C version, product page |
| NeoPixel strip or ring | 8–30 pixels is plenty |
| Hookup wire or Stemma QT cables | |
| USB cable | Data-capable |
Wiring
All three devices share the I2C bus. The TCS34725 sits at address 0x29, and the SSD1306 at 0x3C (or 0x3D depending on the variant). NeoPixels use a single data pin — pick any available digital output.
graph LR
subgraph Board
B_3V3["3.3V"]
B_GND["GND"]
B_SDA["SDA"]
B_SCL["SCL"]
B_D5["D5 (NeoPixel data)"]
end
subgraph TCS34725["TCS34725 Color Sensor"]
T_VIN["VIN"]
T_GND["GND"]
T_SDA["SDA"]
T_SCL["SCL"]
end
subgraph SSD1306["SSD1306 OLED"]
O_VIN["VIN"]
O_GND["GND"]
O_SDA["SDA"]
O_SCL["SCL"]
end
subgraph NeoPixels["NeoPixel Strip"]
N_PWR["5V / 3.3V"]
N_GND["GND"]
N_DIN["DIN"]
end
B_3V3 --> T_VIN
B_GND --> T_GND
B_SDA --- T_SDA
B_SCL --- T_SCL
B_3V3 --> O_VIN
B_GND --> O_GND
B_SDA --- O_SDA
B_SCL --- O_SCL
B_3V3 --> N_PWR
B_GND --> N_GND
B_D5 --> N_DIN
Note
SDA and SCL on all I2C devices connect to the same two wires. You are not running separate buses — all devices share one pair of lines. Each device just has a unique address so the board knows who to talk to.
The code
import time
import board
import busio
import neopixel
import adafruit_tcs34725
import adafruit_ssd1306
from adafruit_display_text import label
import terminalio
# I2C bus shared by color sensor and OLED
i2c = busio.I2C(board.SCL, board.SDA)
# Sensor and display
sensor = adafruit_tcs34725.TCS34725(i2c)
sensor.integration_time = 50 # ms, longer = more accurate
sensor.gain = 4 # 1x, 4x, 16x, or 60x
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)
# NeoPixels
pixels = neopixel.NeoPixel(board.D5, 8, brightness=0.4)
# Small named color lookup table: name -> (r, g, b)
NAMED_COLORS = {
"Red": (220, 30, 30),
"Orange": (230, 120, 10),
"Yellow": (220, 200, 0),
"Green": ( 20, 180, 40),
"Cyan": ( 0, 200, 200),
"Blue": ( 10, 50, 220),
"Violet": (140, 10, 200),
"Pink": (230, 80, 150),
"White": (230, 230, 230),
"Gray": (130, 130, 130),
"Black": ( 20, 20, 20),
}
def nearest_color(r, g, b):
"""Return the name of the closest color in NAMED_COLORS."""
best_name = "Unknown"
best_dist = float("inf")
for name, (cr, cg, cb) in NAMED_COLORS.items():
dist = (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2
if dist < best_dist:
best_dist = dist
best_name = name
return best_name
def clamp(val, lo=0, hi=255):
return max(lo, min(hi, int(val)))
while True:
# Read raw color data and normalize to 0-255
r_raw, g_raw, b_raw, clear = sensor.color_raw
if clear == 0:
time.sleep(0.1)
continue
scale = 255 / clear
r = clamp(r_raw * scale)
g = clamp(g_raw * scale)
b = clamp(b_raw * scale)
name = nearest_color(r, g, b)
# Update NeoPixels
pixels.fill((r, g, b))
# Update OLED
oled.fill(0)
oled.text(name, 0, 0, 1)
oled.text(f"R:{r:3d} G:{g:3d} B:{b:3d}", 0, 16, 1)
oled.show()
time.sleep(0.2)
How it works
The TCS34725 sensor sits behind a grid of tiny red, green, blue, and clear photodiodes covered by matching optical filters. When light hits the sensor, each filtered photodiode reports how much energy it absorbed in its color band. The chip converts those currents into 16-bit integers you read over I2C. Integration time controls how long the sensor accumulates light before reporting — longer times give you more precision in dim environments, but the sensor becomes sluggish. Gain multiplies the raw signal, useful in very dark conditions.
Running multiple I2C devices on the same bus works because each device has a unique 7-bit address burned into its hardware. When the board sends a message, it starts with that address. Only the matching device responds. SDA and SCL are open-drain lines with pull-up resistors, so multiple devices can share the same two wires without conflict. The CircuitPython busio.I2C object handles all of this for you — you just pass the same i2c object to both the sensor and the display.
The color distance calculation treats RGB values as coordinates in a 3D space. The nearest named color is the one whose coordinates are closest to the measured color, using the standard Euclidean distance formula: the square root of the sum of squared differences across R, G, and B channels. Taking the square root is optional when you only care about which distance is smallest, so the code skips it to keep things fast. The lookup table is small and simple — you can expand it with as many named colors as you want.
Installing the libraries
Download the CircuitPython Library Bundle that matches your CircuitPython version. Copy these to the lib/ folder on your CIRCUITPY drive:
adafruit_tcs34725.mpyadafruit_ssd1306.mpyadafruit_displayio_ssd1306.mpy(if using displayio)adafruit_display_text/(entire folder)neopixel.mpyadafruit_bus_device/(entire folder)
Remix it
Remix idea
Log every color reading with a timestamp to Adafruit IO. Leave the sensor pointing at a window and watch how the color of daylight changes throughout the day. See Adafruit IO Basics for the WiFi and data logging pattern.
Remix idea
Add a servo and a hopper of small colored objects. When the sensor reads a color, the servo switches a gate to route the object into the matching bin. A color-sorting machine. See Servo Sweep to get a servo moving first.
Remix idea
Swap the TCS34725 for the AS7341, which measures 10 spectral channels instead of 3. You can distinguish between objects that look identical to the human eye. See the AS7341 reference for setup details.
Go deeper
- TCS34725 sensor reference
- Adafruit color sensors CircuitPython tutorial — Credit: Adafruit Learning System