Community project

Automated Bottle Filling Capping

Photo of Automated Bottle Filling Capping
Generated with AI

Hani Ka

Published November 2, 2026

This project automates the filling and capping of bottles using an Arduino Uno to coordinate a conveyor system, water pump, air valve, and capping motor. The guide provides a complete wiring diagram, parts list, and non-blocking firmware that safely manages bottle detection, timed filling, and capping operations with emergency stop capability.

Builders will learn how to integrate IR sensors for bottle positioning, stepper motor control for conveyor indexing, relay-driven pumps and valves, and a DC motor for the capping head. The assembly outline prioritizes safety by wiring power distribution first, followed by sensor integration and actuator control, with a dry-run testing phase before operating with actual bottles.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

TCRT5000 IR Line Tracking Sensor Module

Bottle sensor 1

Single-channel infrared reflective sensor module built around the Vishay TCRT5000 IR emitter/phototransistor pair. An onboard LM393 comparator with a sensitivity trim-pot drives a thresholded digital output (DO) for line-detection or obstacle-avoidance, while a separate analog pin (AO) exposes the raw reflected-IR reading. Runs on 3.3V-5V, senses reflective surfaces roughly 1-25mm away, and is the standard low-cost sensor on line-following and obstacle-avoiding robot kits. Sold as a generic 4-pin (VCC/GND/DO/AO) breakout under many brand names (HiLetgo, ACEIRMC, OSOYOO, etc.) rather than by a single official manufacturer.

1

TCRT5000 IR Line Tracking Sensor Module

Bottle sensor 2

Single-channel infrared reflective sensor module built around the Vishay TCRT5000 IR emitter/phototransistor pair. An onboard LM393 comparator with a sensitivity trim-pot drives a thresholded digital output (DO) for line-detection or obstacle-avoidance, while a separate analog pin (AO) exposes the raw reflected-IR reading. Runs on 3.3V-5V, senses reflective surfaces roughly 1-25mm away, and is the standard low-cost sensor on line-following and obstacle-avoiding robot kits. Sold as a generic 4-pin (VCC/GND/DO/AO) breakout under many brand names (HiLetgo, ACEIRMC, OSOYOO, etc.) rather than by a single official manufacturer.

1

TCRT5000 IR Line Tracking Sensor Module

Bottle sensor 3

Single-channel infrared reflective sensor module built around the Vishay TCRT5000 IR emitter/phototransistor pair. An onboard LM393 comparator with a sensitivity trim-pot drives a thresholded digital output (DO) for line-detection or obstacle-avoidance, while a separate analog pin (AO) exposes the raw reflected-IR reading. Runs on 3.3V-5V, senses reflective surfaces roughly 1-25mm away, and is the standard low-cost sensor on line-following and obstacle-avoiding robot kits. Sold as a generic 4-pin (VCC/GND/DO/AO) breakout under many brand names (HiLetgo, ACEIRMC, OSOYOO, etc.) rather than by a single official manufacturer.

1

Pololu A4988 Stepper Motor Driver Carrier, Black Edition

Pololu carrier board for the Allegro A4988 microstepping driver that runs one bipolar stepper motor at up to about 1.2 A per coil without cooling (2 A with extra cooling). A microcontroller drives it with STEP and DIR pins at 3-5.5 V logic, MS1-MS3 select full to 1/16 microstepping, and the motor supply is 8-35 V on VMOT with an onboard potentiometer setting the current limit.

1

Nema 17

NEMA 17-size bipolar stepper motor for motion axes. Typical hobby modules are 42mm-frame, 4-wire/two-phase motors with 1.8 degree step angle (200 steps/rev); use with a current-limited stepper driver such as TMC2209/A4988/DRV8825 rather than direct GPIO.

1

Electrolytic Capacitor

100 µF, 25 V electrolytic

Radial aluminium electrolytic capacitor bought by value and voltage rating, such as 10 µF, 100 µF or 1000 µF 10 V. It is polarised: the + lead goes to the higher voltage and the - lead, marked by the stripe on the sleeve, to the lower voltage or ground. It is mostly used for bulk filtering across a power rail, with a voltage rating comfortably above the rail.

1

5 V opto-isolated single-channel relay module

A preassembled opto-isolated 5 V relay board that switches the water pump's 12 V positive lead.

1

5 V opto-isolated single-channel relay module

A preassembled opto-isolated 5 V relay board that switches the air pump's 12 V positive lead.

1

5 V opto-isolated single-channel relay module

A preassembled opto-isolated 5 V relay board that switches the water valve's 12 V positive lead.

1

12V DC Submersible Mini Water Pump

Two-wire brushed DC submersible water pump for small irrigation or circulation projects. It is an external load and must be driven through a relay, MOSFET, or motor driver; do not connect directly to an MCU GPIO.

1

15A Single DC Motor Driver

Single-channel DC motor driver rated at 36V, 15A with UVLO and ESD protection. Provides a regulated 5V auxiliary output for connected logic devices. Compact footprint.

1

Piezo Buzzer

Small passive piezo buzzer (TDK PS1240) for beeps, tones and alerts. It has no built-in oscillator, so drive one lead with a 3-30 V square wave from a microcontroller pin using tone() or PWM and connect the other lead to ground. It is loudest around 4 kHz and works from about 2 to 10 kHz; driving both leads in antiphase roughly doubles the volume.

1

LED

green

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

Resistor

220 Ω

Through-hole resistor (current-limiting in series with an LED)

1

10kΩ Potentiometer

10 kΩ linear (B10K) rotary potentiometer in the common 16 mm panel style: metal can, M7 threaded bushing for mounting through a panel, a 6 mm knurled split shaft for push-on knobs, and three PCB pins in a row 5 mm apart. Connect the outer pins across 3.3 V or 5 V and ground; the middle wiper pin then gives an adjustable voltage for an ADC input over about 300° of rotation.

1

12 V DC diaphragm air pump

A two-wire 12 V air pump used to blow air during the optional purge cycle.

1

12 V DC normally-closed water solenoid valve

A two-wire valve that opens the water path only while its 12 V coil is powered.

1

12 V DC geared capping motor

A two-wire geared motor that turns the capping head while it is pressed onto a bottle.

1

12 V 10 A regulated DC power supply

An enclosed or DIN-rail regulated supply that provides the separate 12 V power needed by the motors, pumps, and valve.

1

latching mushroom emergency-stop switch, normally closed

A red twist-to-release emergency-stop switch that immediately interrupts the 12 V machine-load supply when pressed.

Assemble it in 8 steps

1. Mount the safety parts first

Mount the red emergency-stop button where it can be hit without reaching past moving parts. Put guards around the conveyor, capping head, and pinch points. Use a preassembled enclosed 12 V supply; do not expose its mains terminals.

  • Label the emergency-stop button clearly and keep the reset twist action accessible.
  • The emergency-stop switch must interrupt the fused 12 V load supply, not only a software signal; a software fault must not leave a moving machine powered.

2. Wire the 12 V load path

With all power unplugged, connect the supply +12 V through a fuse to emergency_stop LINE. Connect emergency_stop LOAD to index_driver VMOT, cap_driver VIN, and the COM screw terminal on water_relay, air_relay, and valve_relay. Connect every 12 V negative lead and each motor-driver ground back to machine_supply GND.

  • Use wire sized for the combined load and put the fuse close to the 12 V supply positive output.
  • Never connect a motor, pump, valve, or the A4988 VMOT pin directly to an Arduino pin; the current will damage the board.

3. Connect the conveyor motor and its driver

Connect the two wires of one NEMA 17 coil to index_driver 1A and 1B, then its other coil to 2A and 2B. Connect index_driver STEP to Uno D5 and DIR to Uno D6. Connect VDD to 5V (logic power), both GND pins to GND (ground), and join RST to SLP before taking SLP to 5V. Fit vmot_capacitor directly across VMOT positive and GND negative, with its marked negative stripe at GND.

  • Find each coil pair with a multimeter before connecting: the two wires that show a small resistance are one coil.
  • Set the A4988 current-limit adjustment for the motor before running the conveyor.
  • Do not plug or unplug the stepper motor while its driver has power; this can destroy the A4988. Swapped capacitor polarity can make the capacitor fail.

4. Wire the three bottle sensors

For each TCRT5000 sensor, connect VCC to Uno 5V (power) and GND to Uno GND (ground). Connect ir_infeed DO to D2 (arrival signal), ir_fill DO to A3 (filling-position signal), and ir_cap DO to D4 (capping-position signal).

  • Aim each sensor at the bottle feature or reflector it must see, then turn its tiny adjustment screw until its indicator changes cleanly as a bottle passes.
  • Keep the sensor leads away from motor and pump wires; electrical noise can cause a false bottle detection.

5. Wire the pump, air, and water valve

Connect each relay board VCC to Uno 5V (power) and GND to Uno GND (ground). Connect water_relay IN to D7 (water-pump control), air_relay IN to D8 (air-pump control), and valve_relay IN to D9 (water-valve control). Connect each relay NO terminal to the matching load positive lead: water_pump PUMP+, air_pump +, and fill_valve +. Connect each load negative lead to the 12 V supply GND (return).

  • Use normally-open (NO), not normally-closed (NC), so a powered-down controller leaves the water path closed.
  • Check the flow arrow on the water valve body before fitting tubing.
  • Water near electronics can cause shock, corrosion, and false signals. Keep all low-voltage electronics above the plumbing, use proper hose clamps, and check for leaks with the power off first.

6. Wire the capping drive

Connect cap_driver PWM to Uno D10 (speed signal), IN1 to D11 (direction signal), IN2 to D12 (direction signal), and COM to Uno GND (shared control ground). Connect OUT1 to cap_motor M- and OUT2 to cap_motor M+. Mount the capping head so it cannot rotate loose from the motor shaft.

  • If the head turns the wrong way during a low-power test, swap the two motor wires or reverse the direction logic in the code.
  • Keep hands, hair, sleeves, and loose wire ends away from the capping head; it can pinch or wrap them.

7. Add the knob and indicators

Connect fill_size_knob VCC to 5V (power), GND to GND (ground), and its middle WIPER pin to A0 (fill-size signal). Connect buzzer SIG to D3 (sound signal) and its other lead to GND. Connect Uno A2 through led_resistor to the long positive leg of status_led; connect the short LED leg to GND.

  • Turning the knob clockwise selects a longer fill time when its VCC outer terminal is on the right with the shaft facing you.
  • The green LED slowly pulses when the machine is ready for a bottle.
  • Make sure the LED long and short legs are not swapped, and keep the 220 Ω resistor in series; no resistor can damage the LED and Uno pin.

8. Test without water or caps

First raise the capping head or remove its belt, and leave water and air lines disconnected. Plug the Uno in by USB, release the emergency-stop button, then use the Deploy button. Confirm each sensor changes state, every relay is off when idle, the conveyor moves toward each station, and pressing the red button removes 12 V motion power.

  • Set the three sensor positions and the 200-step ejection value before adding bottles. Then calibrate the fill knob with water into a measuring jug.
  • If any motor or pump starts immediately after power-up, press the emergency-stop button and disconnect power. Change RELAY_ACTIVE_LOW in the firmware only after identifying the relay board's active level.

Review all connections

1. Connections between "ir_infeed" and "Arduino"

Functionir_infeedArduino
powerVCC → 5 V sensor power5V
groundGND → sensor groundGND
digitalDO → bottle-arrival signalGPIO 2

2. Connections between "ir_fill" and "Arduino"

Functionir_fillArduino
powerVCC → 5 V sensor power5V
groundGND → sensor groundGND
digitalDO → filling-position signalGPIO 17

3. Connections between "ir_cap" and "Arduino"

Functionir_capArduino
powerVCC → 5 V sensor power5V
groundGND → sensor groundGND
digitalDO → capping-position signalGPIO 4

4. Connections between "index_driver" and "Arduino"

Functionindex_driverArduino
groundGND → 12 V 10 A regulated DC power supply GNDEXT
power2B → Nema 17 B-EXT
power2A → Nema 17 B+EXT
power1A → Nema 17 A+EXT
power1B → Nema 17 A-EXT
powerVDD → 5 V driver logic power5V
groundGND_2 → logic groundGND
digitalRST → Pololu A4988 Stepper Motor Driver Carrier, Black Edition SLPEXT
powerSLP → driver enabled5V
digitalSTEP → index step pulsesGPIO 5
digitalDIR → index directionGPIO 6
powerVMOT → latching mushroom emergency-stop switch, normally closed LOADEXT

5. Connections between "vmot_capacitor" and "Arduino"

Functionvmot_capacitorArduino
power+ → Pololu A4988 Stepper Motor Driver Carrier, Black Edition VMOTEXT
ground- → Pololu A4988 Stepper Motor Driver Carrier, Black Edition GNDEXT

6. Connections between "water_relay" and "Arduino"

Functionwater_relayArduino
powerVCC → relay logic power5V
groundGND → relay logic groundGND
digitalIN → water pump relay controlGPIO 7
powerNO → 12V DC Submersible Mini Water Pump PUMP+EXT

7. Connections between "air_relay" and "Arduino"

Functionair_relayArduino
powerVCC → relay logic power5V
groundGND → relay logic groundGND
digitalIN → air pump relay controlGPIO 8
powerNO → 12 V DC diaphragm air pump +EXT
powerCOM → latching mushroom emergency-stop switch, normally closed LOADEXT

8. Connections between "valve_relay" and "Arduino"

Functionvalve_relayArduino
powerVCC → relay logic power5V
groundGND → relay logic groundGND
digitalIN → water valve relay controlGPIO 9
powerNO → 12 V DC normally-closed water solenoid valve +EXT
powerCOM → latching mushroom emergency-stop switch, normally closed LOADEXT

9. Connections between "water_pump" and "Arduino"

Functionwater_pumpArduino
dataPUMP- → 12 V 10 A regulated DC power supply GNDEXT

10. Connections between "air_pump" and "Arduino"

Functionair_pumpArduino
ground- → 12 V 10 A regulated DC power supply GNDEXT

11. Connections between "fill_valve" and "Arduino"

Functionfill_valveArduino
ground- → 12 V 10 A regulated DC power supply GNDEXT

12. Connections between "cap_driver" and "Arduino"

Functioncap_driverArduino
groundPGND → 12 V 10 A regulated DC power supply GNDEXT
pwmPWM → capping speed controlGPIO 10
digitalIN1 → capping direction 1GPIO 11
digitalIN2 → capping direction 2GPIO 12
groundCOM → shared control groundGND
powerOUT1 → 12 V DC geared capping motor M-EXT
powerOUT2 → 12 V DC geared capping motor M+EXT
powerVIN → latching mushroom emergency-stop switch, normally closed LOADEXT

13. Connections between "buzzer" and "Arduino"

FunctionbuzzerArduino
groundGND → buzzer groundGND
pwmSIG → position and completion beepsGPIO 3

14. Connections between "led_resistor" and "Arduino"

Functionled_resistorArduino
digitalP1 → LED current limitingGPIO 16
digitalP2 → LED ANODEEXT

15. Connections between "status_led" and "Arduino"

Functionstatus_ledArduino
groundGND → LED groundGND

16. Connections between "fill_size_knob" and "Arduino"

Functionfill_size_knobArduino
powerVCC → knob reference voltage5V
groundGND → knob groundGND
adcWIPER → selected fill-time inputGPIO 14

17. Connections between "machine_supply" and "Arduino"

Functionmachine_supplyArduino
groundGND → common 12 V and logic returnGND
power+12V → latching mushroom emergency-stop switch, normally closed LINEEXT

18. Connections between "emergency_stop" and "Arduino"

Functionemergency_stopArduino
powerLOAD → 5 V opto-isolated single-channel relay module COMEXT

Deploy the firmware

// Automated bottle filling and capping controller for Arduino Uno.
// All timing is non-blocking: the emergency/position inputs are checked every loop pass.

const byte IR_INFEED_PIN = 2;
const byte IR_FILL_PIN = A3;
const byte IR_CAP_PIN = 4;
const byte STEP_PIN = 5;
const byte DIR_PIN = 6;
const byte WATER_RELAY_PIN = 7;
const byte AIR_RELAY_PIN = 8;
const byte VALVE_RELAY_PIN = 9;
const byte CAP_PWM_PIN = 10;
const byte CAP_IN1_PIN = 11;
const byte CAP_IN2_PIN = 12;
const byte BUZZER_PIN = 3;
const byte LED_PIN = A2;
const byte FILL_SIZE_PIN = A0;

// Most opto-isolated relay boards turn on when their IN pin is LOW.
// Change this to HIGH only after safely testing your particular relay board.
const bool RELAY_ACTIVE_LOW = true;
const bool ENABLE_AIR_PURGE = true;

const unsigned long MIN_FILL_MS = 2000UL;
const unsigned long MAX_FILL_MS = 8000UL;
const unsigned long CAPPING_MS = 2500UL;
const unsigned long STEP_INTERVAL_US = 1600UL;
const unsigned long IDLE_PULSE_MS = 900UL;
const unsigned long MAX_INDEX_STEPS = 6000UL;
const byte CAPPING_PWM = 200;

enum MachineState { IDLE, MOVE_TO_FILL, FILLING, MOVE_TO_CAP, CAPPING, EJECT, FAULT };
MachineState state = IDLE;

unsigned long stateStartedMs = 0;
unsigned long lastStepUs = 0;
unsigned long stepsThisMove = 0;
unsigned long chosenFillMs = MIN_FILL_MS;
bool ledOn = false;

bool sensorActive(byte pin) {
  return digitalRead(pin) == LOW; // TCRT5000 comparator adjusted for active LOW.
}

void setRelay(byte pin, bool on) {
  digitalWrite(pin, (on == RELAY_ACTIVE_LOW) ? LOW : HIGH);
}

void allFluidLoadsOff() {
  setRelay(WATER_RELAY_PIN, false);
  setRelay(AIR_RELAY_PIN, false);
  setRelay(VALVE_RELAY_PIN, false);
}

void stopCapper() {
  analogWrite(CAP_PWM_PIN, 0);
  digitalWrite(CAP_IN1_PIN, LOW);
  digitalWrite(CAP_IN2_PIN, LOW);
}

void startCapper() {
  digitalWrite(CAP_IN1_PIN, HIGH);
  digitalWrite(CAP_IN2_PIN, LOW);
  analogWrite(CAP_PWM_PIN, CAPPING_PWM);
}

void beep(unsigned int frequency, unsigned long durationMs) {
  tone(BUZZER_PIN, frequency, durationMs);
}

void enterState(MachineState next) {
  state = next;
  stateStartedMs = millis();
  stepsThisMove = 0;

  if (next == MOVE_TO_FILL || next == MOVE_TO_CAP || next == EJECT) {
    digitalWrite(DIR_PIN, HIGH); // Reverse this level if the conveyor moves backwards.
  }

  if (next == FILLING) {
    chosenFillMs = map(analogRead(FILL_SIZE_PIN), 0, 1023, MIN_FILL_MS, MAX_FILL_MS);
    setRelay(VALVE_RELAY_PIN, true);
    setRelay(WATER_RELAY_PIN, true);
    setRelay(AIR_RELAY_PIN, ENABLE_AIR_PURGE);
  }

  if (next == CAPPING) {
    startCapper();
  }

  if (next == FAULT) {
    allFluidLoadsOff();
    stopCapper();
    beep(450, 600);
  }
}

void stepConveyor() {
  unsigned long nowUs = micros();
  if (nowUs - lastStepUs < STEP_INTERVAL_US) return;
  lastStepUs = nowUs;
  digitalWrite(STEP_PIN, HIGH);
  delayMicroseconds(4);
  digitalWrite(STEP_PIN, LOW);
  ++stepsThisMove;
}

bool indexTimedOut() {
  return stepsThisMove >= MAX_INDEX_STEPS;
}

void updateStatusLed() {
  if (state == IDLE) {
    bool newLedState = ((millis() / IDLE_PULSE_MS) % 2) == 0;
    if (newLedState != ledOn) {
      ledOn = newLedState;
      digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
    }
  } else if (state == FAULT) {
    bool newLedState = ((millis() / 150UL) % 2) == 0;
    if (newLedState != ledOn) {
      ledOn = newLedState;
      digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
    }
  } else if (ledOn) {
    ledOn = false;
    digitalWrite(LED_PIN, LOW);
  }
}

void setup() {
  pinMode(IR_INFEED_PIN, INPUT_PULLUP);
  pinMode(IR_FILL_PIN, INPUT_PULLUP);
  pinMode(IR_CAP_PIN, INPUT_PULLUP);
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(WATER_RELAY_PIN, OUTPUT);
  pinMode(AIR_RELAY_PIN, OUTPUT);
  pinMode(VALVE_RELAY_PIN, OUTPUT);
  pinMode(CAP_PWM_PIN, OUTPUT);
  pinMode(CAP_IN1_PIN, OUTPUT);
  pinMode(CAP_IN2_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);

  allFluidLoadsOff();
  stopCapper();
  digitalWrite(STEP_PIN, LOW);
  beep(1200, 150);
  stateStartedMs = millis();
}

void loop() {
  updateStatusLed();

  switch (state) {
    case IDLE:
      allFluidLoadsOff();
      stopCapper();
      if (sensorActive(IR_INFEED_PIN)) {
        beep(1500, 100);
        enterState(MOVE_TO_FILL);
      }
      break;

    case MOVE_TO_FILL:
      if (sensorActive(IR_FILL_PIN)) {
        beep(1200, 120);
        enterState(FILLING);
      } else if (indexTimedOut()) {
        enterState(FAULT);
      } else {
        stepConveyor();
      }
      break;

    case FILLING:
      if (millis() - stateStartedMs >= chosenFillMs) {
        allFluidLoadsOff();
        beep(1200, 150);
        enterState(MOVE_TO_CAP);
      }
      break;

    case MOVE_TO_CAP:
      if (sensorActive(IR_CAP_PIN)) {
        beep(1500, 100);
        enterState(CAPPING);
      } else if (indexTimedOut()) {
        enterState(FAULT);
      } else {
        stepConveyor();
      }
      break;

    case CAPPING:
      if (millis() - stateStartedMs >= CAPPING_MS) {
        stopCapper();
        beep(1800, 180);
        enterState(EJECT);
      }
      break;

    case EJECT:
      if (stepsThisMove >= 200UL) { // one full-step motor revolution; calibrate conveyor travel.
        beep(2100, 250);
        enterState(IDLE);
      } else {
        stepConveyor();
      }
      break;

    case FAULT:
      // Remove the obstructed bottle, then reset power to clear this protective stop.
      break;
  }

  delay(1);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik