The color-sorting machine is fully assembled and running. Mixed Perler beads are poured into the 3D-printed funnel, indexed on the rotating disc, read by a TCS34725 sensor, and routed by two SG90 servos into colour-coded bins inside a laser-cut plywood electronics box. Full documentation continues on the Final Project page and in Electronics.
Completed machine on the bench with yellow, green, and red beads already sorted into their bins. Servo2 positions the white chute over the active bin; the wooden box hides the XIAO ESP32-C3, AMS1117 regulator, and jumper wiring.
White PLA parts were printed on a Bambu Lab printer, then press-fit onto the laser-cut base. The funnel feeds beads onto the disc; the pivoting chute directs each sorted bead into a recessed bin.
Power comes from a 7.2 V battery pack through a rocker switch and AMS1117 5 V regulator. The XIAO ESP32-C3, TCS34725 (I²C on GPIO6/7), and both servos share a common ground; servo power is not taken from the XIAO 3.3 V pin.
Servo1 (GPIO2) advances the turntable; Servo2 (GPIO3) rotates the sort chute to 30° / 60° / 90° for red, green, and yellow. The TCS34725 sits in a printed holder ~5 mm above the bead path.
Wi-Fi web control test — start/stop from phone while machine sorts (Week 11).
Servo motion test — Servo1 feed cycle and Servo2 chute angles during integration debugging.
The built machine differs from the early stepper-motor plan below — integration used two SG90 servos instead of a 28BYJ-48 stepper:
Early planning document from before the physical build. The sketches and flowchart below show the intended architecture; photos above document what was actually built.
My final project is a rotating-disc color sorting machine that detects the color of small objects like beads, and sorts them into different bins according to the color. The core components:
The result is a self-contained desktop sorter: no loose wires, no exposed moving belts.
| Component | Packaging / Mounting |
|---|---|
| XIAO ESP32 | Mounted on a small PCB breakout with M2 screws; placed inside a printed electronics box under the base. |
| Voltage regulator (AMS1117) | Mounted inside the laser-cut electronics box; converts the 7.2 V battery pack to a stable 5 V supply for the XIAO, servos, and color sensor. |
| Color sensor | Inserted into a printed holder that keeps it 5mm above the disc surface. |
| Servo | Screwed into a bracket that supports the sliding chute. |
| Limit switch | Mounted on the base with an adjustable screw actuator; triggered by a tab on the disc (home position). |
| Wiring | All wires routed through channels in the base; using cable ties and a single cable exit. |
| Overall enclosure | A laser-cut acrylic cover with ventilation slots protects the electronics and moving parts. |
Hardware Integration
Draft firmware below targeted a stepper-driven disc. The as-built machine uses dual SG90 servos instead; see working code in Week 11 and Final Project — Electronics.
#include <Stepper.h>
#include <Wire.h>
#include <Adafruit_TCS34725.h>
#include <ESP32Servo.h>
// Pins
#define STEPPER_IN1 D0
#define STEPPER_IN2 D1
#define STEPPER_IN3 D2
#define STEPPER_IN4 D3
#define SERVO_PIN D4
#define SWITCH_PIN D5
#define SDA_PIN D6
#define SCL_PIN D7
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);
Servo chuteServo;
Stepper myStepper(2048, STEPPER_IN1, STEPPER_IN3, STEPPER_IN2, STEPPER_IN4);
int currentPos = 0;
const int stepsPerPocket = 341; // 2048/6
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
if (!tcs.begin()) {
Serial.println("Sensor error");
while (1);
}
chuteServo.attach(SERVO_PIN);
pinMode(SWITCH_PIN, INPUT_PULLUP);
myStepper.setSpeed(15);
// Home: rotate until switch is pressed
while(digitalRead(SWITCH_PIN)) {
myStepper.step(10);
delay(2);
}
currentPos = 0;
Serial.println("Ready");
}
String classifyColor(uint16_t r, uint16_t g, uint16_t b) {
if (r > g && r > b && r > 200) return "RED";
if (g > r && g > b && g > 180) return "GREEN";
if (b > r && b > g && b > 180) return "BLUE";
return "OTHER";
}
void setChute(String color) {
if (color == "RED") chuteServo.write(0);
else if (color == "GREEN") chuteServo.write(45);
else if (color == "BLUE") chuteServo.write(90);
else chuteServo.write(135);
delay(500); // chute settles
}
void loop() {
// Wait for base switch press to start
if (digitalRead(SWITCH_PIN) == LOW) {
delay(50); // debounce
// Move disc to bring a pocket from funnel to sensor
myStepper.step(stepsPerPocket);
currentPos = (currentPos + 1) % 6;
delay(300);
// Read color
uint16_t r, g, b, c;
tcs.getRawData(&r, &g, &b, &c);
String color = classifyColor(r, g, b);
Serial.println(color);
// Set chute angle
setChute(color);
// Move disc to release position (e.g., next step)
myStepper.step(stepsPerPocket);
currentPos = (currentPos + 1) % 6;
delay(500);
// Return chute to neutral (optional)
chuteServo.write(90);
// Wait for user to press switch again
while(digitalRead(SWITCH_PIN) == LOW);
delay(100);
}
}
Early system block diagram and mechanical layout sketches (before the as-built photos above).
Sketch 1 – System block diagram
Sketch 2 – Mechanical layout (side view)
Final Project — full documentation · Electronics & firmware · 2D & 3D design