Overview
The GY-31 TCS3200 is a programmable color light-to-frequency converter that lets your microcontroller "see" colors. The chip combines an 8x8 array of silicon photodiodes with a configurable current-to-frequency converter, outputting a square wave whose frequency is proportional to the intensity of red, green, blue, or unfiltered light reflected from the surface in front of it.
The GY-31 breakout adds 4 white LEDs to illuminate the target, mounting holes, and a clean 8-pin header. Pair it with an Arduino, ESP32, Raspberry Pi, or Pico to read RGB values, sort objects by color, build a line follower that distinguishes track colors, or trigger events when a specific hue is detected.
Because the output is a frequency rather than an analog voltage, the TCS3200 works on both 3.3V and 5V microcontrollers without level conversion — any digital input can read it.
At a Glance
Specifications
| Parameter | Value |
| Sensor IC | TAOS TCS3200 (or compatible TCS230) |
| Operating Voltage | 2.7V - 5.5V DC |
| Operating Current | ~20 mA (with LEDs on) |
| Photodiode Array | 8 x 8 (16 red, 16 green, 16 blue, 16 clear) |
| Color Filters | R, G, B, and Clear (no filter) |
| Output Type | Square wave, frequency proportional to light |
| Output Frequency Scaling | Power down / 2% / 20% / 100% (S0/S1) |
| Color Selection | Programmable via S2 / S3 pins |
| Communication | Direct digital (any GPIO that can read frequency) |
| On-board LEDs | 4 white, controllable via LED pin |
| Dimensions | ~28 x 28 mm |
Pinout Diagram

Wiring Guide
Arduino Wiring
The TCS3200 outputs a digital square wave, so any Arduino digital pin can read it. Powering at 5V gives the strongest LED illumination and matches Arduino's logic levels directly.
| TCS3200 Pin | Arduino Pin |
|---|---|
| VCC | 5V |
| GND | GND |
| S0 | D4 |
| S1 | D5 |
| S2 | D6 |
| S3 | D7 |
| OUT | D8 |
| LED | 5V (or any GPIO to dim/blink) |
ESP32 Wiring
The TCS3200's frequency output is digital and works perfectly with the ESP32's 3.3V GPIO. Power the module from the ESP32's 5V (VIN) pin so the on-board LEDs are bright enough.
| TCS3200 Pin | ESP32 Pin | Details |
|---|---|---|
| VCC | VIN (5V) | For brighter LEDs |
| GND | GND | |
| S0 | GPIO 18 | |
| S1 | GPIO 19 | |
| S2 | GPIO 21 | |
| S3 | GPIO 22 | |
| OUT | GPIO 23 | 3.3V output is safe |
| LED | VIN or any GPIO |
Raspberry Pi Wiring
The Pi's GPIO is 3.3V. To stay on the safe side, power the TCS3200 from 3.3V so the OUT signal also swings at 3.3V. Reading the frequency requires careful timing on Linux — see the Python example below.
| TCS3200 Pin | Raspberry Pi Pin |
|---|---|
| VCC | Pin 1 (3.3V) |
| GND | Pin 6 (GND) |
| S0 | Pin 11 (GPIO 17) |
| S1 | Pin 13 (GPIO 27) |
| S2 | Pin 15 (GPIO 22) |
| S3 | Pin 16 (GPIO 23) |
| OUT | Pin 18 (GPIO 24) |
| LED | Pin 1 (3.3V) |
Raspberry Pi Pico Wiring
The Pico runs on 3.3V GPIO. As with the Pi, power TCS3200 from 3.3V or use the Pico's VBUS (5V from USB) pin if you want brighter LEDs and a divider on OUT.
| TCS3200 Pin | Pico Pin |
|---|---|
| VCC | 3V3 (OUT) |
| GND | GND |
| S0 | GP10 |
| S1 | GP11 |
| S2 | GP12 |
| S3 | GP13 |
| OUT | GP14 |
| LED | 3V3 (OUT) |
time_pulse_us() function in MicroPython is a simple way to measure pulse width without writing your own counter routine.Code Examples
Arduino - Read R, G, B Frequencies
// GY-31 TCS3200 - Read raw red, green, blue frequencies on Arduino
// S0/S1 set frequency scaling. S2/S3 select which color filter is active.
// Lower frequency on a color = more of that color reflected.
const int S0 = 4;
const int S1 = 5;
const int S2 = 6;
const int S3 = 7;
const int OUT = 8;
void setup() {
Serial.begin(9600);
pinMode(S0, OUTPUT); pinMode(S1, OUTPUT);
pinMode(S2, OUTPUT); pinMode(S3, OUTPUT);
pinMode(OUT, INPUT);
// Set frequency scaling to 20% (S0=H, S1=L)
digitalWrite(S0, HIGH);
digitalWrite(S1, LOW);
}
unsigned long readColor(int s2, int s3) {
digitalWrite(S2, s2);
digitalWrite(S3, s3);
delay(50);
return pulseIn(OUT, LOW); // microseconds per low half-cycle
}
void loop() {
unsigned long red = readColor(LOW, LOW);
unsigned long blue = readColor(LOW, HIGH);
unsigned long green = readColor(HIGH, HIGH);
Serial.print("R="); Serial.print(red);
Serial.print(" G="); Serial.print(green);
Serial.print(" B="); Serial.println(blue);
delay(300);
}
ESP32 - Print Color Name (Calibrated)
// ESP32 + TCS3200: read R/G/B and guess a color name
// Calibrate MIN/MAX values by holding white and black under the sensor and
// noting the printed values, then update the constants below.
const int S0 = 18, S1 = 19, S2 = 21, S3 = 22, OUT = 23;
// Calibration -- replace with your own readings
const int R_MIN = 30, R_MAX = 250;
const int G_MIN = 35, G_MAX = 270;
const int B_MIN = 30, B_MAX = 240;
void setup() {
Serial.begin(115200);
pinMode(S0, OUTPUT); pinMode(S1, OUTPUT);
pinMode(S2, OUTPUT); pinMode(S3, OUTPUT);
pinMode(OUT, INPUT);
digitalWrite(S0, HIGH); digitalWrite(S1, LOW); // 20% scaling
}
int read(int s2, int s3) {
digitalWrite(S2, s2); digitalWrite(S3, s3);
delay(50);
return pulseIn(OUT, LOW);
}
int normalize(int v, int lo, int hi) {
int n = map(v, lo, hi, 255, 0);
return constrain(n, 0, 255);
}
void loop() {
int r = normalize(read(LOW, LOW), R_MIN, R_MAX);
int g = normalize(read(HIGH, HIGH), G_MIN, G_MAX);
int b = normalize(read(LOW, HIGH), B_MIN, B_MAX);
String name = "?";
if (r > 180 && g < 100 && b < 100) name = "RED";
else if (g > 150 && r < 120 && b < 120) name = "GREEN";
else if (b > 150 && r < 120 && g < 150) name = "BLUE";
else if (r > 200 && g > 200 && b > 200) name = "WHITE";
else if (r < 60 && g < 60 && b < 60) name = "BLACK";
Serial.printf("R=%3d G=%3d B=%3d -> %s\n", r, g, b, name.c_str());
delay(400);
}
Raspberry Pi Pico - MicroPython
# GY-31 TCS3200 on Raspberry Pi Pico (MicroPython)
# Wiring: S0=GP10 S1=GP11 S2=GP12 S3=GP13 OUT=GP14
from machine import Pin, time_pulse_us
import time
S0 = Pin(10, Pin.OUT); S1 = Pin(11, Pin.OUT)
S2 = Pin(12, Pin.OUT); S3 = Pin(13, Pin.OUT)
OUT = Pin(14, Pin.IN)
# 20% frequency scaling
S0.value(1); S1.value(0)
def read(s2, s3):
S2.value(s2); S3.value(s3)
time.sleep_ms(50)
return time_pulse_us(OUT, 0, 30000) # microseconds per low half-cycle
while True:
r = read(0, 0)
b = read(0, 1)
g = read(1, 1)
print("R={:4d} G={:4d} B={:4d}".format(r, g, b))
time.sleep(0.3)