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. |
| Stepper motor | Fixed to the base plate using M3 screws; motor shaft coupled to disc via a printed hub. |
| 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
#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);
}
}
Sketch 1 – System block diagram
Sketch 2 – Mechanical layout (side view)