Built-in project · Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
Hobby servo pin PWMArduino Uno R3 pin 13
Traffic light module pin RArduino Uno R3 pin 12
Traffic light module pin YArduino Uno R3 pin 11
Traffic light module pin GArduino Uno R3 pin 10
Relay board, two channels pin IN1Arduino Uno R3 pin 9
Relay board, two channels pin IN2Arduino Uno R3 pin 8
Ultrasonic ranger pin TRIGArduino Uno R3 pin 7
Ultrasonic ranger pin ECHOArduino Uno R3 pin 6
IR proximity switch, E18-D80NK pin OUTArduino Uno R3 pin 2
Character LCD with I2C backpack pin VCCArduino Uno R3 pin 5V
Character LCD with I2C backpack pin GNDArduino Uno R3 pin GND
Character LCD with I2C backpack pin SDAArduino Uno R3 pin A4
Character LCD with I2C backpack pin SCLArduino Uno R3 pin A5
Hobby servo pin GNDGround
Hobby servo pin VCC5 V
Traffic light module pin GNDGround
Relay board, two channels pin GNDGround
Relay board, two channels pin VCC5 V
DC motor pin 1Relay board, two channels pin NO1
Relay board, two channels pin COM15 V
Alarm siren, 12 V pin +Relay board, two channels pin NO2
Relay board, two channels pin COM25 V
DC motor pin 2Ground
Alarm siren, 12 V pin -Ground
Ultrasonic ranger pin VCC5 V
Ultrasonic ranger pin GNDGround
IR proximity switch, E18-D80NK pin VCC5 V
IR proximity switch, E18-D80NK pin GNDGround

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// 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.