Built-in project · Arduino Uno R3

Color sorter on an Arduino Uno

A TCS34725 color sensor looks at each candy the feed disc brings and a servo swings the chute to its bin before it drops: six colors, and an empty disc it waits on. The whole build, an Arduino Uno and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $30. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino Color Sensor: TCS34725 Candy Sorter

The circuit runs here once the page has loaded.

What you'll learn

  • I2C sensors and their libraries
  • Color as ratios, not raw brightness
  • Nearest match against a lookup table
  • Positioning a servo by angle
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 3

How it works

A TCS34725 color sensor reads four counts for each candy: red, green, blue and clear. It talks to the Uno over I2C on A4 and A5.

The sketch turns red, green and blue into shares of their total, so the answer does not depend on how bright the light is. It compares those shares with a table of six known colors and picks the closest one.

Each color has a chute angle. The servo on pin 9 swings the chute to the matching bin. A low clear count means the chute is empty, so nothing moves.

Parts list

3 parts, plus the jumper wires. They retail for about $30; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Candy color sorter pin SERVOArduino Uno R3 pin 9
Candy color sorter pin VCCArduino Uno R3 pin 5V
Candy color sorter pin GNDArduino Uno R3 pin GND
Candy color sorter pin SDAArduino Uno R3 pin A4
Candy color sorter pin SCLArduino Uno R3 pin A5

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Color sorter: a TCS34725 looks at the candy in front of it and a servo
// swings the chute to that color's bin, as in the candy and bead sorters
// people build.
//
//   TCS34725   SDA -> A4   SCL -> A5   VIN -> 5 V   GND -> GND   (LED left high: lit)
//   servo      signal -> 9   red -> 5 V   brown -> GND
//
// The sensor gives four counts: red, green, blue and clear. Their shares of
// the total say what color it is whatever the brightness, so the sketch
// compares them with a table of the six colors it knows, measured once with
// the LED on, and picks the nearest. A low clear count is an empty chute.
//
// In the simulator the sorter's feed disc brings the candies one at a time:
// watch the chute move, and the candy land in its bin.

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

Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);
Servo chute;

const int SERVO_PIN = 9;
const uint16_t EMPTY_BELOW = 1500;  // clear count of the empty chute

struct Reference {
  const char *name;
  uint16_t red, green, blue;  // shares of the total, in thousandths
  uint8_t angle;              // where the chute points for this bin
};

const Reference COLORS[] = {
  {"red", 647, 203, 148, 15},
  {"orange", 566, 342, 90, 45},
  {"yellow", 475, 431, 92, 75},
  {"green", 192, 524, 283, 105},
  {"blue", 137, 313, 549, 135},
  {"purple", 324, 243, 431, 165},
};
const uint8_t COUNT = sizeof(COLORS) / sizeof(COLORS[0]);

int8_t last = -2;

void setup() {
  Serial.begin(9600);
  chute.attach(SERVO_PIN);
  chute.write(90);
  if (!tcs.begin()) {
    Serial.println("No TCS34725 found ... check your connections");
    while (1) delay(10);
  }
  Serial.println("Color sorter ready");
}

// The nearest color in the table, or -1 for an empty chute.
int8_t classify(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
  if (c < EMPTY_BELOW) return -1;
  uint32_t total = (uint32_t)r + g + b;
  int32_t rs = (uint32_t)r * 1000 / total;
  int32_t gs = (uint32_t)g * 1000 / total;
  int32_t bs = (uint32_t)b * 1000 / total;
  int8_t best = 0;
  int32_t bestDistance = 0x7fffffff;
  for (uint8_t i = 0; i < COUNT; i++) {
    int32_t dr = rs - COLORS[i].red;
    int32_t dg = gs - COLORS[i].green;
    int32_t db = bs - COLORS[i].blue;
    int32_t d = dr * dr + dg * dg + db * db;
    if (d < bestDistance) {
      bestDistance = d;
      best = i;
    }
  }
  return best;
}

void loop() {
  uint16_t r, g, b, c;
  tcs.getRawData(&r, &g, &b, &c);
  int8_t color = classify(r, g, b, c);
  if (color == last) return;
  last = color;
  if (color < 0) {
    Serial.println("chute empty");
    return;
  }
  Serial.print(COLORS[color].name);
  Serial.print(" -> bin ");
  Serial.print(color + 1);
  Serial.print("  (R ");
  Serial.print(r);
  Serial.print(" G ");
  Serial.print(g);
  Serial.print(" B ");
  Serial.print(b);
  Serial.print(" C ");
  Serial.print(c);
  Serial.println(")");
  chute.write(COLORS[color].angle);
  delay(300);
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Starts serial at 9600, attaches the servo on pin 9 and centers it at 90 degrees. Then it starts the sensor. If tcs.begin() fails, it prints a message and stops there.

void setup() {
  Serial.begin(9600);
  chute.attach(SERVO_PIN);
  chute.write(90);
  if (!tcs.begin()) {
    Serial.println("No TCS34725 found ... check your connections");
    while (1) delay(10);
  }
  Serial.println("Color sorter ready");
}

classify sketch.ino

Returns -1 if the clear count is under EMPTY_BELOW (1500). Otherwise it converts red, green and blue to thousandths of their total. It then finds the table entry with the smallest squared distance and returns its index.

// The nearest color in the table, or -1 for an empty chute.
int8_t classify(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
  if (c < EMPTY_BELOW) return -1;
  uint32_t total = (uint32_t)r + g + b;
  int32_t rs = (uint32_t)r * 1000 / total;
  int32_t gs = (uint32_t)g * 1000 / total;
  int32_t bs = (uint32_t)b * 1000 / total;
  int8_t best = 0;
  int32_t bestDistance = 0x7fffffff;
  for (uint8_t i = 0; i < COUNT; i++) {
    int32_t dr = rs - COLORS[i].red;
    int32_t dg = gs - COLORS[i].green;
    int32_t db = bs - COLORS[i].blue;
    int32_t d = dr * dr + dg * dg + db * db;
    if (d < bestDistance) {
      bestDistance = d;
      best = i;
    }
  }
  return best;
}

loop sketch.ino

Reads the sensor and classifies the result. It acts only when the answer changes. It prints the color and bin, moves the chute to that color's angle, then waits 300 ms.

void loop() {
  uint16_t r, g, b, c;
  tcs.getRawData(&r, &g, &b, &c);
  int8_t color = classify(r, g, b, c);
  if (color == last) return;
  last = color;
  if (color < 0) {
    Serial.println("chute empty");
    return;
  }
  Serial.print(COLORS[color].name);
  Serial.print(" -> bin ");
  Serial.print(color + 1);
  Serial.print("  (R ");
  Serial.print(r);
  Serial.print(" G ");
  Serial.print(g);
  Serial.print(" B ");
  Serial.print(b);
  Serial.print(" C ");
  Serial.print(c);
  Serial.println(")");
  chute.write(COLORS[color].angle);
  delay(300);
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The monitor says "No TCS34725 found" This project
    Check that SDA goes to A4 and SCL goes to A5, and that the sensor has 5V and ground.
  • It picks the wrong color This project
    The table values were measured with the sensor LED on. Check that the LED is lit, then adjust the numbers in COLORS if you use different candies.
  • The chute never moves This project
    Check that the servo signal wire is on pin 9. Also check that it is not reading as empty, which happens when the clear count is below EMPTY_BELOW.
  • Candies land in the wrong bin. Candy color sorter
    Check the color readings at the sensor in good, steady light, and tune the color thresholds. Then adjust the servo angle for each bin and how long the chute takes to move.
  • The servo jitters or the board resets. Candy color sorter
    Power the servo from a separate supply that can supply enough current, with the grounds joined. Check the TCS34725 is on I2C address 0x29, and give it time to integrate each reading.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.