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
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Candy color sorter
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 pin | Connects to |
|---|---|
| Candy color sorter pin SERVO | Arduino Uno R3 pin 9 |
| Candy color sorter pin VCC | Arduino Uno R3 pin 5V |
| Candy color sorter pin GND | Arduino Uno R3 pin GND |
| Candy color sorter pin SDA | Arduino Uno R3 pin A4 |
| Candy color sorter pin SCL | Arduino 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.
// 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 inCOLORSif 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 belowEMPTY_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.