A TCS34725 color sensor on an Arduino: a candy sorter
Intermediate
Or see every lesson.
- 503parts on the bench
- 44boards running now
- 1.00xreal time, on every board
A color sensor tells your Arduino what color something is: a candy, a bead, a card, a LEGO brick. The TCS34725 is the one most projects use. It sits on a small board with a white LED to light what it looks at, and it talks over I2C, so it needs only two signal wires.
The circuit above is an Uno, a TCS34725 and a servo, in a candy sorter like the ones people build. A feed disc brings each candy from the hopper under the sensor, the sketch works out which of six colors it is, and the servo swings the chute to that color’s bin before the candy drops onto it. Press Run and watch the candies land.
What you need
- An Arduino Uno
- A TCS34725 color sensor board (with the white LED)
- A micro servo (SG90)
- A breadboard and jumper wires
Wiring
| Part and pin | Goes to | Note |
|---|---|---|
| TCS34725 VIN | 5 V | The board regulates it down for the chip |
| TCS34725 GND | GND | |
| TCS34725 SDA | A4 | Address 0x29 |
| TCS34725 SCL | A5 | |
| TCS34725 LED | Nothing | The board pulls it high, so the LED is lit |
| Servo signal (orange) | Pin 9 | |
| Servo red and brown | 5 V and GND |
How the sensor sees color
Under the lens are many tiny light sensors, each behind a red, green, blue or clear filter. The chip counts the light falling on each kind for a set time and hands back four numbers: red, green, blue and clear (all the light). The white LED lights the object from the same spot every time, so room light matters less.
The library sets that counting time and the gain when you create it: TCS34725_INTEGRATIONTIME_50MS and TCS34725_GAIN_4X. A longer time or more gain gives bigger counts for dark things; too much and bright things max out.
Shares, not raw counts
Here is the trap. A dark red candy and a bright red candy give very different counts, and a red candy closer to the sensor gives bigger counts than the same candy farther away. Comparing raw counts mixes up "how red" with "how bright".
So the sketch turns the three colors into shares of their total, in thousandths. The red candy here reads R 2180, G 686, B 500. Its total is 3366, so in whole thousandths its shares are 647 red, 203 green and 148 blue: the red row of the table. A darker red, at half the brightness, reads R 1090, G 343, B 250: half the counts, the very same shares, and it still sorts as red.
The sketch has a table of six colors, measured once with the LED on, and picks the one whose shares are closest (the smallest sum of the squared differences). A low clear count, under 1500, means nothing is in the chute, and the sketch waits.
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},
};
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;
}The servo and the bins
Each color in the table also has an angle for the chute: red 15 degrees, orange 45, yellow 75, green 105, blue 135 and purple 165, thirty degrees apart. When the color changes, the sketch prints it, like "red -> bin 1 (R 2180 G 686 B 500 C 3366)", writes the angle to the servo and waits 300 ms for the chute to get there.
getRawData() waits for a fresh reading each time, about 50 ms with these settings, so the loop runs about twenty times a second without a delay.
Teaching it your own colors
The table is only right for these candies under this LED. To sort your own, print the raw counts for each one (add a Serial.print of rs, gs and bs), hold each candy in front of the sensor, and write down its shares. Put those in the table, with an angle for each bin.
The nearest-color method always picks something. A white candy is not in the table, so it lands in the purple bin. If odd things should go to a reject bin, keep the best distance too, and treat anything farther than a limit you pick as "unknown".
Common mistakes
Comparing raw counts instead of shares, so the same color sorts differently near and far.
No sensor found. With SDA pulled out the sketch prints "No TCS34725 found ... check your connections" and stops.
Calibrating in one light and sorting in another. The LED helps, but a shade over the sensor, so only its LED lights the candy, helps more.
Reading before the first count is done. Until the chip has finished one integration the counts are zero. The library waits for you; your own code has to as well.
Powering the servo from the Uno on a real build and getting resets when it moves. One SG90 is usually fine; anything bigger wants its own 5 V supply with the grounds joined.
Questions
Which is better, the TCS34725 or the TCS3200?
The TCS34725 talks I2C and gives clear numbers, with an IR-blocking filter that makes it better in mixed light. The older TCS3200 (or TCS230) outputs a square wave whose frequency you measure, and it needs more pins.
What is the TCS34725 I2C address?
0x29. It cannot be changed.
How do I turn off the white LED?
Wire the board’s LED pin to ground, or to a pin you set LOW. The board pulls it high, so left unwired it stays on.
Why does every color look the same?
Usually the counts are maxed out or tiny. Try a shorter integration time or less gain for bright things, more for dark ones, and keep the object a few millimeters from the lens.
Keep going
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.