TCS3200 Color Sensor
The TCS3200 is a programmable color light-to-frequency converter. It contains a 4x4 array of photodiodes with red, green, blue, and clear (no filter) filters β 16 of each type. By selecting a filter group via two control pins, the sensor outputs a square wave whose frequency is proportional to the intensity of that color component. This makes it ideal for color recognition, sorting, and calibration applications.
For this interfacing you need the following components:
- Arduino board (Uno, Nano, Mega, etc.)
- TCS3200 color sensor module
- Breadboard and jumper wires
- USB cable to connect Arduino to your computer
- (Optional) White LED for consistent illumination
Schematic
Connect the TCS3200 module to the Arduino as follows:
TCS3200 Module Arduino
-------------- -------
VCC --> 5V
GND --> GND
S0 --> Digital Pin 4
S1 --> Digital Pin 5
S2 --> Digital Pin 6
S3 --> Digital Pin 7
OUT --> Digital Pin 8
The module operates at 5V and includes built-in pull-up resistors on the output pin. No external components are required.
Pin Map
| Module Pin | Name | Arduino Connection |
|---|---|---|
| VCC | Power | 5V |
| GND | Ground | GND |
| S0 | Frequency scaling input 0 | Digital Pin 4 |
| S1 | Frequency scaling input 1 | Digital Pin 5 |
| S2 | Photodiode filter input 0 | Digital Pin 6 |
| S3 | Photodiode filter input 1 | Digital Pin 7 |
| OUT | Frequency output | Digital Pin 8 |
Frequency scaling (S0, S1)
| S0 | S1 | Output Frequency Scaling |
|---|---|---|
| LOW | LOW | Power-down |
| LOW | HIGH | 2% |
| HIGH | LOW | 20% |
| HIGH | HIGH | 100% |
For most Arduino applications, use 20% scaling (S0 = HIGH, S1 = LOW). The 100% setting can output up to 600 kHz, which exceeds the reliable pulseIn() range on slower Arduino boards.
Filter selection (S2, S3)
| S2 | S3 | Photodiode Filter |
|---|---|---|
| LOW | LOW | Red |
| LOW | HIGH | Blue |
| HIGH | LOW | No filter (clear) |
| HIGH | HIGH | Green |
Install necessary Library
No external library is required. The TCS3200 uses digitalWrite(), digitalRead(), and pulseIn() functions built into Arduino.
For convenience, you may install the TCS3200 library by Abdullah Alzhrani via the Library Manager. This tutorial uses the direct approach for full transparency.
Code with complete explanation
This sketch reads the RGB values from the TCS3200 sensor, calibrates them against white and black references, and prints the detected color name to the Serial Monitor.
// Frequency scaling pins
const int S0 = 4;
const int S1 = 5;
// Filter selection pins
const int S2 = 6;
const int S3 = 7;
// Frequency output pin
const int sensorOut = 8;
// Calibration values (set via calibration routine)
int redMin = 25;
int redMax = 230;
int greenMin = 30;
int greenMax = 220;
int blueMin = 30;
int blueMax = 225;
// Raw frequency readings
int redFreq, greenFreq, blueFreq;
// Mapped RGB values (0β255)
int redVal, greenVal, blueVal;
void setup()
{
Serial.begin(9600);
pinMode(S0, OUTPUT);
pinMode(S1, OUTPUT);
pinMode(S2, OUTPUT);
pinMode(S3, OUTPUT);
pinMode(sensorOut, INPUT);
// Set frequency scaling to 20%
digitalWrite(S0, HIGH);
digitalWrite(S1, LOW);
Serial.println("TCS3200 Color Sensor Test");
Serial.println("Place a white object in front of the sensor");
Serial.println("Calibrating in 3 seconds...");
delay(3000);
calibrate();
Serial.println("Calibration done!");
Serial.println("Now place colored objects to detect them.");
Serial.println();
}
void loop()
{
readColor();
Serial.print("RGB: ");
Serial.print(redVal);
Serial.print(", ");
Serial.print(greenVal);
Serial.print(", ");
Serial.print(blueVal);
Serial.print(" -> ");
Serial.println(detectColor());
delay(500);
}
// Read raw RGB frequencies and map to 0β255
void readColor()
{
// Read red
digitalWrite(S2, LOW);
digitalWrite(S3, LOW);
redFreq = pulseIn(sensorOut, LOW);
redVal = map(redFreq, redMin, redMax, 255, 0);
redVal = constrain(redVal, 0, 255);
// Read green
digitalWrite(S2, HIGH);
digitalWrite(S3, HIGH);
greenFreq = pulseIn(sensorOut, LOW);
greenVal = map(greenFreq, greenMin, greenMax, 255, 0);
greenVal = constrain(greenVal, 0, 255);
// Read blue
digitalWrite(S2, LOW);
digitalWrite(S3, HIGH);
blueFreq = pulseIn(sensorOut, LOW);
blueVal = map(blueFreq, blueMin, blueMax, 255, 0);
blueVal = constrain(blueVal, 0, 255);
}
// Auto-calibrate by reading white and black references
void calibrate()
{
int redRead, greenRead, blueRead;
// --- White calibration ---
Serial.println("Reading white...");
delay(500);
redRead = readFiltered(LOW, LOW);
greenRead = readFiltered(HIGH, HIGH);
blueRead = readFiltered(LOW, HIGH);
redMin = redRead - 30;
greenMin = greenRead - 30;
blueMin = blueRead - 30;
// --- Black calibration ---
Serial.println("Cover the sensor completely (black)");
Serial.println("Calibrating in 3 seconds...");
delay(3000);
redRead = readFiltered(LOW, LOW);
greenRead = readFiltered(HIGH, HIGH);
blueRead = readFiltered(LOW, HIGH);
redMax = redRead + 30;
greenMax = greenRead + 30;
blueMax = blueRead + 30;
// Print calibration values
Serial.print("Red range: ");
Serial.print(redMin);
Serial.print(" - ");
Serial.println(redMax);
Serial.print("Green range: ");
Serial.print(greenMin);
Serial.print(" - ");
Serial.println(greenMax);
Serial.print("Blue range: ");
Serial.print(blueMin);
Serial.print(" - ");
Serial.println(blueMax);
Serial.println();
}
// Helper: read frequency for a given filter selection
int readFiltered(int s2State, int s3State)
{
digitalWrite(S2, s2State);
digitalWrite(S3, s3State);
return pulseIn(sensorOut, LOW);
}
// Simple color detection based on dominant channel
String detectColor()
{
if (redVal > 180 && greenVal > 160 && blueVal > 140)
{
return "WHITE";
}
if (redVal < 50 && greenVal < 50 && blueVal < 50)
{
return "BLACK";
}
if (redVal > greenVal && redVal > blueVal)
{
return "RED";
}
if (greenVal > redVal && greenVal > blueVal)
{
return "GREEN";
}
if (blueVal > redVal && blueVal > greenVal)
{
return "BLUE";
}
if (redVal > 100 && greenVal > 100)
{
return "YELLOW";
}
return "UNKNOWN";
}
Code breakdown
pulseIn(sensorOut, LOW)β measures the pulse width of the output signal when held LOW. The frequency is1 / pulseWidth. UsingLOWinstead ofHIGHoften gives more stable readings on the TCS3200.map(freq, min, max, 255, 0)β converts the raw frequency to an 0β255 RGB value. The arguments are reversed (255, 0) because higher frequency = more light = lower mapped value for non-dominant colors.constrain(value, 0, 255)β clamps the mapped value to the valid 0β255 range.calibrate()β reads white and black references to set the min/max range for each channel. This is essential because ambient light, sensor distance, and component tolerances vary between setups.detectColor()β simple threshold-based color classification. For more accuracy, use a lookup table or machine learning approach with multiple samples.
Reading with HIGH pulse (alternative)
Some TCS3200 modules work better when measuring the HIGH pulse:
int readColorChannel(int s2, int s3)
{
digitalWrite(S2, s2);
digitalWrite(S3, s3);
return pulseIn(sensorOut, HIGH);
}
If your readings are unstable or inverted, try switching between LOW and HIGH in pulseIn().
Using a library (alternative)
The TCS3200 library by Abdullah Alzhrani simplifies the code:
#include <TCS3200.h>
TCS3200 colorSensor(S0, S1, S2, S3, sensorOut);
void setup()
{
Serial.begin(9600);
colorSensor.begin();
colorSensor.setScaling(SCALING_20_PERCENT);
colorSensor.calibrate();
}
void loop()
{
int red = colorSensor.readRed();
int green = colorSensor.readGreen();
int blue = colorSensor.readBlue();
Serial.print("R:");
Serial.print(red);
Serial.print(" G:");
Serial.print(green);
Serial.print(" B:");
Serial.println(blue);
delay(500);
}
Steps to perform this interfacing
- Connect the TCS3200 module to the Arduino as shown in the schematic.
- Copy the code into the Arduino IDE.
- Select the correct board and port (
Tools > BoardandTools > Port). - Upload the sketch to the Arduino.
- Open the Serial Monitor (
Tools > Serial Monitor, set baud rate to 9600). - When prompted, place a white object (e.g., a white paper) in front of the sensor β the sketch calibrates against white first.
- When prompted, completely cover the sensor with something black (e.g., your hand or black cloth) for black calibration.
- After calibration, place colored objects in front of the sensor and observe the RGB values and detected color name.
Manual calibration
If auto-calibration doesnβt work well, set the min/max values manually. Observe raw frequency readings for each channel with a white surface and a black cover, then update the redMin, redMax, etc. values in the code:
// Uncomment to see raw frequencies
// Serial.print("R freq: "); Serial.print(redFreq);
// Serial.print(" G freq: "); Serial.print(greenFreq);
// Serial.print(" B freq: "); Serial.println(blueFreq);
Caution
- Consistent lighting: The TCS3200 reads reflected light, so readings are highly dependent on ambient lighting conditions. For consistent results, use the sensor in a controlled environment with a white LED pointing at the target. Avoid fluorescent or incandescent lights β they flicker at 50/60 Hz and introduce noise into the readings.
- Distance matters: The sensor-to-object distance dramatically affects readings. Maintain a consistent distance (typically 5β15 mm) between the sensor and the colored surface. Even a 1 mm difference can change frequency values significantly.
- Calibration is required: The sensor must be calibrated for each setup. Ambient light, object distance, and even the sensorβs warm-up state affect readings. Always run the calibration routine before taking measurements.
- Frequency scaling: On 16 MHz Arduino boards, the 100% frequency scaling (S0=HIGH, S1=HIGH) can produce frequencies above 500 kHz, which
pulseIn()cannot measure reliably. Use 20% scaling (S0=HIGH, S1=LOW) for best results. - Reading order: The sensor measures one color channel at a time (selected by S2, S3). The total read cycle for all three channels takes approximately 5β15 ms. For rapidly moving objects, this delay may cause misregistration between channels.
- Output polarity: Some TCS3200 modules invert the output signal. If all readings are reversed or nonsensical, try
pulseIn(sensorOut, HIGH)instead ofLOW. - Warm-up: Allow the sensor 1β2 seconds after power-up for the readings to stabilize. The internal oscillator takes a short time to reach its stable operating frequency.