TCS3200 Color Sensor Β· Astro Tech Blog

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.

TCS3200 Color Sensor

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 PinNameArduino Connection
VCCPower5V
GNDGroundGND
S0Frequency scaling input 0Digital Pin 4
S1Frequency scaling input 1Digital Pin 5
S2Photodiode filter input 0Digital Pin 6
S3Photodiode filter input 1Digital Pin 7
OUTFrequency outputDigital Pin 8

Frequency scaling (S0, S1)

S0S1Output Frequency Scaling
LOWLOWPower-down
LOWHIGH2%
HIGHLOW20%
HIGHHIGH100%

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)

S2S3Photodiode Filter
LOWLOWRed
LOWHIGHBlue
HIGHLOWNo filter (clear)
HIGHHIGHGreen

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 is 1 / pulseWidth. Using LOW instead of HIGH often 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

  1. Connect the TCS3200 module to the Arduino as shown in the schematic.
  2. Copy the code into the Arduino IDE.
  3. Select the correct board and port (Tools > Board and Tools > Port).
  4. Upload the sketch to the Arduino.
  5. Open the Serial Monitor (Tools > Serial Monitor, set baud rate to 9600).
  6. When prompted, place a white object (e.g., a white paper) in front of the sensor β€” the sketch calibrates against white first.
  7. When prompted, completely cover the sensor with something black (e.g., your hand or black cloth) for black calibration.
  8. 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 of LOW.
  • 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.