Project analysis
This project focused on designing an autonomous system that sorts red, blue, green, and yellow balls into separate outputs without human intervention. The system handles a random number of balls and reliably identifies and directs each color correctly, requiring integration of mechanical design and control.
Sorter in use
The video demonstrates the sorter in operation, showing that after an initial calibration, the system autonomously retrieves a ball, detects its color using a sensor, and rotates the cup holder platform to direct the ball into the correct slot.
The system uses a Raspberry Pi to control a color sensor and two stepper motors to sort the balls automatically. First, the sensor measures the ball's color by reading red, green, and blue values and compares them to calibrated reference data to identify the color. Based on the detected color, one stepper motor rotates the cup holder to line up the correct bin, while the second stepper motor moves the ball through the system. After the ball is sorted, the platform returns to its starting position so the process can repeat for the next ball.
How it was built
This project uses two stepper motors driven by two H-bridge motor drivers to control the motion of the sorting mechanism and the rotation of the collection system. A Raspberry Pi serves as the main controller, handling motor control, logic, and system timing through custom code.
The physical structure of the machine is fabricated using a combination of woodworking techniques for the main frame and 3D printing for precise mechanical components such as mounts and guides. Electrical integration involves custom wiring, soldering, and proper power management to ensure reliable motor operation and communication between components.
View Python code — color detection & sorting control
import RPi.GPIO as GPIO
import time
import math
from RpiMotorLib import RpiMotorLib
GpioPins = [23, 24, 25, 8]
# Assign GPIO pin numbers to variables
s0 = 6
s1 = 5
s2 = 12
s3 = 14
sig = 7 #labeled "out" on your board
cycles = 10
# Setup GPIO and pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(s0, GPIO.OUT)
GPIO.setup(s1, GPIO.OUT)
GPIO.setup(s2, GPIO.OUT)
GPIO.setup(s3, GPIO.OUT)
GPIO.setup(sig, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# Set frequency scaling
GPIO.output(s0, GPIO.HIGH)
GPIO.output(s1, GPIO.LOW)
OUT1 = 21
OUT2 = 20
OUT3 = 26
OUT4 = 16
GPIO.setup(OUT1, GPIO.OUT)
GPIO.setup(OUT2, GPIO.OUT)
GPIO.setup(OUT3, GPIO.OUT)
GPIO.setup(OUT4, GPIO.OUT)
GPIO.output(OUT1, GPIO.LOW)
GPIO.output(OUT2, GPIO.LOW)
GPIO.output(OUT3, GPIO.LOW)
GPIO.output(OUT4, GPIO.LOW)
step_delay = 0.03
steps_90 = 13
steps_180 = 26
sequence = [
(1,0,1,0),
(0,1,1,0),
(0,1,0,1),
(1,0,0,1)
]
red_cal = []
green_cal = []
blue_cal = []
yellow_cal = []
# Declare an named instance of class pass a name and type of motor
mymotortest = RpiMotorLib.BYJMotor("MyMotorOne", "Nema")
time.sleep(0.5)
def calibrate():
start_cal = False
color_start = False
for color in range(4):
print(color)
while (not color_start):
color_start = input("Start Color Calibration (y/n)? ")
if (color_start == "y"):
color_start = True
else:
color_start = False
color_start = False
for x in range(5):
print(x)
while (not start_cal):
resp = input("Continue Calibration (y/n)? ")
if (resp == "y"):
start_cal = True
else:
start_cal = False
start_cal = False
# Detect red values
GPIO.output(s2, GPIO.LOW)
GPIO.output(s3, GPIO.LOW)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
red = cycles / duration
print("red value - ", red)
# Detect blue values
GPIO.output(s2, GPIO.LOW)
GPIO.output(s3, GPIO.HIGH)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
blue = cycles / duration
print("blue value - ", blue)
# Detect green values
GPIO.output(s2, GPIO.HIGH)
GPIO.output(s3, GPIO.HIGH)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
green = cycles / duration
print("green value - ", green)
if (color == 0):
red_cal.append([red, green, blue])
elif (color == 1):
green_cal.append([red, green, blue])
elif (color == 2):
blue_cal.append([red, green, blue])
else:
yellow_cal.append([red, green, blue])
def sense():
# Detect red values
GPIO.output(s2, GPIO.LOW)
GPIO.output(s3, GPIO.LOW)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
red = cycles / duration
print("red value - ", red)
# Detect blue values
GPIO.output(s2, GPIO.LOW)
GPIO.output(s3, GPIO.HIGH)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
blue = cycles / duration
print("blue value - ", blue)
# Detect green values
GPIO.output(s2, GPIO.HIGH)
GPIO.output(s3, GPIO.HIGH)
time.sleep(0.1)
start_time = time.time()
for count in range(cycles):
GPIO.wait_for_edge(sig, GPIO.FALLING)
duration = time.time() - start_time
green = cycles / duration
print("green value - ", green)
red_dis = 0
green_dis = 0
blue_dis = 0
yellow_dis = 0
for color in range(4):
distance = []
for x in range(5):
if (color == 0):
distance.append(math.sqrt(((red - red_cal[x][0]) ** 2) + ((green - red_cal[x][1]) ** 2) + ((blue - red_cal[x][2]) ** 2)))
elif (color == 1):
distance.append(math.sqrt(((red - green_cal[x][0]) ** 2) + ((green - green_cal[x][1]) ** 2) + ((blue - green_cal[x][2]) ** 2)))
elif (color == 2):
distance.append(math.sqrt(((red - blue_cal[x][0]) ** 2) + ((green - blue_cal[x][1]) ** 2) + ((blue - blue_cal[x][2]) ** 2)))
else:
distance.append(math.sqrt(((red - yellow_cal[x][0]) ** 2) + ((green - yellow_cal[x][1]) ** 2) + ((blue - yellow_cal[x][2]) ** 2)))
if (color == 0):
red_dis = sum(distance) / len(distance)
elif (color == 1):
green_dis = sum(distance) / len(distance)
elif (color == 2):
blue_dis = sum(distance) / len(distance)
else:
yellow_dis = sum(distance) / len(distance)
pos_color = min(red_dis, blue_dis, yellow_dis, green_dis)
if (pos_color == red_dis):
return("Red")
elif (pos_color == blue_dis):
return("Blue")
elif (pos_color == yellow_dis):
return("Yellow")
else:
return("Green")
def step_once(step):
GPIO.output(OUT1, step[0])
GPIO.output(OUT2, step[1])
GPIO.output(OUT3, step[2])
GPIO.output(OUT4, step[3])
time.sleep(step_delay)
def rotate_steps(steps, direction):
seq = sequence if direction == "CW" else sequence[::-1]
for _ in range(steps):
for st in seq:
step_once(st)
def go_and_return(steps, direction, ball):
rotate_steps(steps, direction)
time.sleep(2)
roll_ball(ball)
time.sleep(3)
back_dir = "CCW" if direction == "CW" else "CW"
rotate_steps(steps, back_dir)
time.sleep(1)
def roll_ball(ball_num):
if (ball_num == 1):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 2):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 3):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 4):
mymotortest.motor_run(GpioPins , 0.08, 5, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 5):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 6):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 7):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 8):
mymotortest.motor_run(GpioPins , 0.08, 5, False, False, "full", .2)
time.sleep(3)
elif (ball_num == 9):
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
time.sleep(3)
else:
mymotortest.motor_run(GpioPins , 0.08, 4, False, False, "full", .2)
try:
# Calibrate
calibrate()
ready = False
while (not ready):
next_ball = input("Calibration is complete. Continue (y/n)? ")
if (next_ball == "y"):
ready = True
else:
ready = False
time.sleep(2)
for ball in range(10):
# Sense ball
detect_color = sense()
if (detect_color == "Red"):
print("Red")
roll_ball(ball)
time.sleep(2)
elif (detect_color == "Blue"):
print("Blue")
go_and_return(steps_90, "CCW", ball)
elif (detect_color == "Green"):
print("Green")
go_and_return(steps_90, "CW", ball)
else:
print("Yellow")
go_and_return(steps_180, "CCW", ball)
except KeyboardInterrupt:
print("\nExiting Program")
finally:
GPIO.cleanup()
Reflection
This project resulted in a fully autonomous sorting system that works very well overall. All of the mechanical parts function smoothly and reliably, and the system is able to pick up balls, identify their color, and sort them into the correct locations without any human help. The motors, rotating platform, and overall structure performed as expected, showing that the mechanical design and control logic were successful.
The main area that could be improved is the color sensing. The sensor is not enclosed in a dark or shielded area, so outside light can sometimes affect how accurately it detects colors. Adding a black enclosure around the sensor would block ambient light and help the sensor read colors more consistently. With better light control and additional calibration, the system could achieve a higher accuracy and become a fully reliable, error-free autonomous sorter.
Gallery