Reject station on an Arduino Uno
A quality check that tracks each part: an HC-SR04 measures the part's height at the gate, the good or bad verdict waits in a queue while the part rides the conveyor, and a servo pusher kicks only the bad ones into the bin two seconds later. The whole build, an Arduino Uno and 8 more parts, 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 30 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $50. On Mokxi they're free.
What you'll learn
- Sensors
- Motors
- Displays
- Moving a servo to an angle
- Difficulty: Intermediate
- Time: About 30 minutes
- Board: Arduino Uno R3
- Parts: 15
How it works
A quality check that tracks each part: an HC-SR04 measures the part's height at the gate, the good or bad verdict waits in a queue while the part rides the conveyor, and a servo pusher kicks only the bad ones into the bin two seconds later.
Arduino Uno R3 runs the sketch below. The circuit around it: Hobby servo, Traffic light module, Relay board, two channels, DC motor, Alarm siren, 12 V, Ultrasonic ranger. pinMode sets GATE_PIN as an input with its pull-up on. digitalWrite drives TRIG_PIN HIGH. digitalRead reads whether GATE_PIN is HIGH or LOW. delay waits 20 milliseconds. Serial.begin opens the serial port at 9600 baud.
Parts list
15 parts, plus the jumper wires. They retail for about $50; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
- 6 × Power, 5 V
- 1 × Traffic light module
- 1 × Relay board, two channels
- 1 × DC motor
- 1 × Alarm siren, 12 V
- 1 × Ultrasonic rangerIn Halloween Prop Kit.
- 1 × IR proximity switch, E18-D80NK
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
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 |
|---|---|
| Hobby servo pin PWM | Arduino Uno R3 pin 13 |
| Traffic light module pin R | Arduino Uno R3 pin 12 |
| Traffic light module pin Y | Arduino Uno R3 pin 11 |
| Traffic light module pin G | Arduino Uno R3 pin 10 |
| Relay board, two channels pin IN1 | Arduino Uno R3 pin 9 |
| Relay board, two channels pin IN2 | Arduino Uno R3 pin 8 |
| Ultrasonic ranger pin TRIG | Arduino Uno R3 pin 7 |
| Ultrasonic ranger pin ECHO | Arduino Uno R3 pin 6 |
| IR proximity switch, E18-D80NK pin OUT | Arduino Uno R3 pin 2 |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
| Hobby servo pin GND | Ground |
| Hobby servo pin VCC | 5 V |
| Traffic light module pin GND | Ground |
| Relay board, two channels pin GND | Ground |
| Relay board, two channels pin VCC | 5 V |
| DC motor pin 1 | Relay board, two channels pin NO1 |
| Relay board, two channels pin COM1 | 5 V |
| Alarm siren, 12 V pin + | Relay board, two channels pin NO2 |
| Relay board, two channels pin COM2 | 5 V |
| DC motor pin 2 | Ground |
| Alarm siren, 12 V pin - | Ground |
| Ultrasonic ranger pin VCC | 5 V |
| Ultrasonic ranger pin GND | Ground |
| IR proximity switch, E18-D80NK pin VCC | 5 V |
| IR proximity switch, E18-D80NK pin GND | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Reject station: a quality check on a conveyor. Each part is measured as
// it passes the gate, and a pusher further down the belt kicks the bad ones
// into the reject bin when they get there.
//
// 2 E18-D80NK, OUT (brown on 5 V, blue on GND): low while a part
// stands at the gate
// 7/6 HC-SR04 TRIG and ECHO, looking down at the belt from 100 mm up
// 9 relay board IN1, the conveyor motor's contactor
// 8 relay board IN2, the reject horn (a small siren on the contacts)
// 13 the pusher, a servo (red on 5 V, brown on GND)
// 12/11/10 the stack light: red, yellow, green
// LCD SDA -> A4 SCL -> A5 VCC -> 5 V GND -> GND
//
// The idea is tracking. The gate and the pusher are in different places,
// so the sensor and the actuator act at different times:
//
// 1. A part arrives at the gate. The ultrasonic sensor measures its height
// and the sketch decides, right then, good or bad.
// 2. The decision goes into a queue with the time the part arrived. The
// part rides on down the belt.
// 3. TRAVEL_MS later the part is at the pusher. The sketch takes the oldest
// entry off the queue: a bad part gets kicked, a good part rides on.
//
// Several parts can be on the belt between the gate and the pusher at once,
// and the queue keeps them in order. The loop never waits with delay(), so
// the gate is watched the whole time the pusher is working.
//
// The stack light: green while all is well, yellow while a bad part is on its
// way to the pusher, red while the pusher is out.
//
// Press Run, set the ultrasonic sensor's range (60 mm is a good 40 mm part),
// then drag the E18-D80NK's target close and away again for each part.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "mokxi_servo.h"
const int GATE_PIN = 2;
const int TRIG_PIN = 7;
const int ECHO_PIN = 6;
const int CONVEYOR_PIN = 9;
const int HORN_PIN = 8;
const int PUSHER_PIN = 13;
const int RED_PIN = 12;
const int YELLOW_PIN = 11;
const int GREEN_PIN = 10;
const int MOUNT_MM = 100; // the sensor's height above the belt
const int TARGET_MM = 40; // a good part's height
const int TOLERANCE_MM = 3; // plus or minus
const unsigned long TRAVEL_MS = 2000; // gate to pusher at belt speed
const unsigned long KICK_MS = 400; // pusher out
const unsigned long BEEP_MS = 150;
const unsigned long CLEAR_MS = 50; // the gate must be clear between parts
const int PUSHER_HOME = 0;
const int PUSHER_OUT = 90;
// The relay board switches on a low input.
const int ON = LOW;
const int OFF = HIGH;
// The queue: one entry for every part between the gate and the pusher,
// oldest first, in a ring of QUEUE_SIZE slots.
const int QUEUE_SIZE = 8;
unsigned long arrivedAt[QUEUE_SIZE];
bool isBad[QUEUE_SIZE];
int head = 0; // the oldest entry
int count = 0; // entries in the queue
int badOnBelt = 0; // bad parts among them
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo pusher(PUSHER_PIN);
unsigned int parts = 0;
unsigned int good = 0;
unsigned int bad = 0;
bool present = false;
unsigned long lastBlocked = 0;
unsigned long kickUntil = 0;
unsigned long beepUntil = 0;
bool kicking = false;
// One reading from the HC-SR04, in millimetres; 0 when there is no echo.
int distanceMm() {
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000UL);
// Sound covers 0.343 mm a microsecond, there and back.
return (int)((us * 343UL + 1000UL) / 2000UL);
}
void show() {
lcd.setCursor(0, 0);
lcd.print("Good ");
lcd.print(good);
lcd.print(" Bad ");
lcd.print(bad);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Reject ");
lcd.print(parts ? (unsigned long)bad * 100UL / parts : 0UL);
lcd.print("% ");
}
void light() {
bool red = kicking;
bool yellow = !red && badOnBelt > 0;
digitalWrite(RED_PIN, red);
digitalWrite(YELLOW_PIN, yellow);
digitalWrite(GREEN_PIN, !red && !yellow);
}
// Step 1 and 2: measure, decide, and remember the decision.
void inspect(unsigned long now) {
parts++;
int distance = distanceMm();
int height = distance > 0 ? MOUNT_MM - distance : 0;
bool reject = height < TARGET_MM - TOLERANCE_MM || height > TARGET_MM + TOLERANCE_MM;
if (reject) bad++; else good++;
Serial.print("part ");
Serial.print(parts);
Serial.print(": ");
Serial.print(height);
Serial.println(reject ? " mm, bad" : " mm, good");
// Eight slots: parts closer than a quarter of a second apart would be
// more than the belt can carry anyway.
if (count < QUEUE_SIZE) {
int tail = (head + count) % QUEUE_SIZE;
arrivedAt[tail] = now;
isBad[tail] = reject;
count++;
if (reject) badOnBelt++;
}
show();
}
// Step 3: the oldest part has reached the pusher.
void atPusher(unsigned long now) {
unsigned int n = parts - count + 1; // the queue holds the newest parts in order
bool reject = isBad[head];
head = (head + 1) % QUEUE_SIZE;
count--;
if (reject) {
badOnBelt--;
kicking = true;
kickUntil = now + KICK_MS;
digitalWrite(HORN_PIN, ON);
beepUntil = now + BEEP_MS;
}
Serial.print(reject ? "kick" : "pass");
Serial.print(" part ");
Serial.println(n);
}
void setup() {
pinMode(GATE_PIN, INPUT_PULLUP);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(CONVEYOR_PIN, OFF);
digitalWrite(HORN_PIN, OFF);
pinMode(CONVEYOR_PIN, OUTPUT);
pinMode(HORN_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
Serial.begin(9600);
Serial.println("Reject station");
pusher.begin();
pusher.write(PUSHER_HOME);
lcd.init();
lcd.backlight();
show();
digitalWrite(CONVEYOR_PIN, ON); // ready: the belt runs from here on
light();
}
void loop() {
unsigned long now = millis();
// A new part at the gate: the beam was clear a moment ago, long enough.
bool blocked = digitalRead(GATE_PIN) == LOW;
if (blocked) {
if (!present && now - lastBlocked >= CLEAR_MS) inspect(now);
present = true;
lastBlocked = now;
} else {
present = false;
}
// The oldest part on the belt reaches the pusher TRAVEL_MS after the gate.
if (count > 0 && now - arrivedAt[head] >= TRAVEL_MS) atPusher(now);
if (kicking && (long)(now - kickUntil) >= 0) kicking = false;
if (beepUntil != 0 && (long)(now - beepUntil) >= 0) {
digitalWrite(HORN_PIN, OFF);
beepUntil = 0;
}
light();
pusher.write(kicking ? PUSHER_OUT : PUSHER_HOME);
delay(20);
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Runs once when the board starts. It gets the pins and libraries ready.
void setup() {
pinMode(GATE_PIN, INPUT_PULLUP);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(CONVEYOR_PIN, OFF);
digitalWrite(HORN_PIN, OFF);
pinMode(CONVEYOR_PIN, OUTPUT);
pinMode(HORN_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
Serial.begin(9600);
Serial.println("Reject station");
pusher.begin();
pusher.write(PUSHER_HOME);
lcd.init();
lcd.backlight();
show();
digitalWrite(CONVEYOR_PIN, ON); // ready: the belt runs from here on
light();
}loop sketch.ino
Runs over and over for as long as the board has power. This is where the project does its work.
void loop() {
unsigned long now = millis();
// A new part at the gate: the beam was clear a moment ago, long enough.
bool blocked = digitalRead(GATE_PIN) == LOW;
if (blocked) {
if (!present && now - lastBlocked >= CLEAR_MS) inspect(now);
present = true;
lastBlocked = now;
} else {
present = false;
}
// The oldest part on the belt reaches the pusher TRAVEL_MS after the gate.
if (count > 0 && now - arrivedAt[head] >= TRAVEL_MS) atPusher(now);
if (kicking && (long)(now - kickUntil) >= 0) kicking = false;
if (beepUntil != 0 && (long)(now - beepUntil) >= 0) {
digitalWrite(HORN_PIN, OFF);
beepUntil = 0;
}
light();
pusher.write(kicking ? PUSHER_OUT : PUSHER_HOME);
delay(20);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The servo jitters, twitches or resets the board when it moves. Hobby servo
Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply. - The servo hums at the end of its travel or will not reach full angle. Hobby servo
Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo. - One color never lights, or all three light together. Traffic light module
Check each wire goes to its own pin labeled R, Y, G, and that GND is connected. A pin driven high lights its LED. Resistors are already on the board, so do not add more. - Lights seem dim or the wrong color comes on in a sequence. Traffic light module
Confirm the pins in your code match the wires, and that the module gets the right supply voltage. Keep each state's delay long enough to see, and turn the old color off before the next. - A relay is on at boot or the logic is inverted. Relay board
These boards are active low, so LOW on IN turns the relay on. Set the pin HIGH before setting it as an output so it does not click at start. - The relay clicks but the load does nothing, or the board resets. Relay board
Use the correct contact: COM with NO for normally off. Power the relay coil with enough current, ideally from a separate 5 V supply, with grounds joined. Mains wiring is dangerous, so be careful. - The motor does not spin, or the pin gets hot or the board resets when it is wired straight to a GPIO pin. DC motor
A pin gives about 20 mA and a motor needs far more. Drive it through a transistor or motor driver, with its own supply and a shared ground. - The transistor or pin is damaged by spikes when the motor stops. DC motor
Put a flyback diode across the motor, cathode to the positive side. A motor is a coil and kicks back voltage when switched off. Add a 0.1 uF capacitor across the motor terminals to cut noise.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.