Community project

Automated Soil Bagging System

Photo of Automated Soil Bagging System
Generated with AI

Krazy Tae

Published November 14, 2026

This project automates the process of bagging soil by weight using a load cell scale and servo-controlled gate. The system dispenses soil into bags to a precise target weight, displaying real-time measurements on an LCD screen while the Arduino manages the filling cycle from start to finish.

Builders will assemble a complete weighing and dispensing station using an HX711 load cell amplifier, a continuous rotation servo for gate control, and an LCD display for feedback. The guide includes a wiring diagram, complete parts list, calibration procedures, and step-by-step assembly instructions to get the system running supervised batches of accurately portioned soil.

Wiring diagram

Wiring diagram for Automated Soil Bagging System
Loading diagram…

Gather all the parts

QtyComponent
1

SparkFun HX711 Load Cell Amplifier

SparkFun breakout with separate VCC and VDD, both connected to 5 V for Uno logic per SparkFun hookup guide.

1

Load Cell

Four-wire strain-gauge straight-bar load cell, common 5 kg hobby scale sensor. It produces a small bridge signal and should be read through a load-cell ADC/amplifier such as HX711 or NAU7802 rather than directly by a microcontroller GPIO.

1

Standard LCD 16x2, white on blue

Standard HD44780-compatible character LCD module with 16 columns and 2 rows, white characters on a blue backlight. Suitable for simple status displays, menus, readouts, and breadboarded electronics projects.

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

Resistor

220 ohm

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

1

20 mm round SPST ON/OFF rocker switch

Non-illuminated two-terminal maintained dry contact used as a logic input, not a power connection.

1

20 mm round SPST ON/OFF rocker switch

Non-illuminated two-terminal maintained dry contact used as a logic input, not a power connection.

1

SG90-360 continuous rotation micro servo

4.8–6 V continuous rotation micro servo, not the positional SG90. Nominal neutral 1500 us; tune on the actual unit. Only for a lightweight 100 g demonstration gate.

1

USB-C 5V Adapter

regulated 5 V, 3 A dedicated servo supply

USB-C wall adapter delivering regulated 5 V to the board's USB or VBUS rail. Default wired power source for desktop / stationary projects.

1

Electrolytic Capacitor

1000 uF, 10 V

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

Breadboard 830 points

A full-size solderless breadboard with 830 tie points (MB-102 size), with socket grids on both sides of a centre channel and red and blue power rails along the long edges. LEDs, buttons, resistors, potentiometers, buzzers and jumper wires push into it without soldering. In a generated housing it rests in a screwless tray and carries those small parts inside the case.

Assemble it in 6 steps

1. Build a tiny supervised gate

Use a small hopper, thin freely sliding gate and short-travel rack-and-pinion linkage for the SG90-360 continuous rotation servo. Keep the hopper frame separate from the weighing platform. No limit switches are used. Provide a manual way to block soil flow. Begin with an empty hopper, then very small charges of dry sifted loam, never more than 200 g for initial trials. The target bag weight is 100 g.

  • Timed movement cannot detect position or a jam. This is only a supervised demonstration, not a reliable automatic packaging machine. Guard pinch points. Power loss may leave the gate open. Never drive against hard stops.

2. Connect the scale

Fix one end of the 5 kg cell to the base and the other to the platform with clearance to flex. The platform must not touch the hopper. Connect E+, E-, A+, A- to matching amplifier terminals. On the SparkFun breakout these are RED, BLK, GRN, WHT respectively; follow the cell maker labels rather than assumed wire colours. Leave YLW unused. Connect amplifier VCC and VDD to Uno 5 V, GND to ground, DAT to A0 and CLK to A1.

3. Wire the bare LCD

Connect LCD RS to D2, EN to D3, and D4–D7 to Uno D4–D7. Connect RW and GND to ground, VCC to 5 V. Leave LCD D0–D3 unused. Connect potentiometer outer terminals to 5 V and ground, centre to VO. Feed LED+ through the 220 ohm resistor from 5 V, and LED- to ground.

4. Connect rockers and servo power

Connect START T1 to D8 and STOP T1 to D10, with both T2 terminals to ground. Use non-illuminated two-terminal SPST rockers. Servo signal goes to D9. Power servo from the separate regulated 5 V 3 A supply and join its ground to Uno ground. Connect capacitor plus to servo 5 V and minus to ground. Keep servo power off the breadboard. Power Uno by USB. START must go OFF then ON for each action. STOP ON requests a timed closing movement during a cycle; keep it ON until motion ends, then turn OFF.

  • Do not connect the external positive supply to Uno 5 V. Observe capacitor polarity. STOP is a software closure request, not a safety-rated emergency stop or a guarantee of closure.

5. Tune travel before adding soil

With power disconnected manually place the gate closed without forcing the servo gears; uncouple the linkage if necessary. With the linkage uncoupled verify NEUTRAL_US stops the servo and OPEN_US and CLOSE_US turn in opposite correct directions. Tune neutral if it creeps. Reconnect only with power off. OPEN_MS and CLOSE_MS are initially 300 milliseconds, not measured travel times. Adjust these constants and Deploy again until the gate opens and closes without hitting hard stops. Check closure after every test. Opening may be interrupted early by weight or STOP, so timed closing can overrun; the mechanism must tolerate this without driving into a hard stop. Do not use a rigid end-stop mechanism with this switchless design.

6. Calibrate and run supervised batches

With the gate manually verified closed and platform empty, open the serial monitor at 115200 baud. Send z, add a known 1000 g mass, then send k to save calibration. Remove the mass. After reboot the first START OFF-to-ON action acknowledges that you manually checked closure and arms the system; it does not home the gate. Place an empty bag and flip START OFF then ON again to tare and dispense. Closure starts at 90 g; tune CUTOFF_G for falling soil and check final settled weight. Before each next batch visually check full closure and fit an empty bag. On an aborted cycle the gate is not guaranteed closed. Block the hopper manually, remove soil, inspect and reset before retrying.

  • Never add soil until empty-hopper travel tests pass. Timings vary with load, supply voltage and friction. If consistent automatic closure is needed, use position feedback or a suitable positional actuator instead.

Review all connections

1. Connections between "scale" and "Arduino"

FunctionscaleArduino
powerVCC5V
powerVDD5V
groundGNDGND
digitalDTGPIO 14
digitalSCKGPIO 15

2. Connections between "cell" and "Arduino"

FunctioncellArduino
powerE+ → SparkFun HX711 Load Cell Amplifier E+EXT
groundE- → SparkFun HX711 Load Cell Amplifier E-EXT
analogA+ → SparkFun HX711 Load Cell Amplifier A+EXT
analogA- → SparkFun HX711 Load Cell Amplifier A-EXT

3. Connections between "lcd" and "Arduino"

FunctionlcdArduino
powerVCC5V
groundGNDGND
analogVO → 10kΩ Potentiometer WIPEREXT
groundRWGND
digitalRSGPIO 2
digitalENGPIO 3
digitalD4GPIO 4
digitalD5GPIO 5
digitalD6GPIO 6
digitalD7GPIO 7
groundLED-GND

4. Connections between "contrast" and "Arduino"

FunctioncontrastArduino
powerVCC5V
groundGNDGND

5. Connections between "backlight" and "Arduino"

FunctionbacklightArduino
powerP15V
digitalP2 → Standard LCD 16x2, white on blue LED+EXT

6. Connections between "servo" and "Arduino"

FunctionservoArduino
pwmSIGNALGPIO 9
powerVCC5V
groundGNDGND

7. Connections between "supply" and "Arduino"

FunctionsupplyArduino
power+5V5V
groundGNDGND

8. Connections between "bulk" and "Arduino"

FunctionbulkArduino
power+5V
ground-GND

9. Connections between "start" and "Arduino"

FunctionstartArduino
digitalT1GPIO 8
groundT2GND

10. Connections between "stop" and "Arduino"

FunctionstopArduino
digitalT1GPIO 10
groundT2GND

Deploy the firmware

#include <Arduino.h>
#include <HX711.h>
#include <LiquidCrystal.h>
#include <Servo.h>
#include <EEPROM.h>
#include <math.h>
LiquidCrystal lcd(2,3,4,5,6,7);
HX711 scale;
Servo gate;
const int START=8, STOP=10;
const int NEUTRAL_US=1500, OPEN_US=1700, CLOSE_US=1300;
// Trial timings only: measure and tune with an EMPTY hopper. Never run against hard stops.
const unsigned long OPEN_MS=300, CLOSE_MS=300;
const float TARGET_G=100, CUTOFF_G=90; // tune for soil still falling during closure
struct Calibration {uint32_t magic; float factor;} cal;
enum State {LOCKED, READY, TARING, OPENING, FILLING, CLOSING, DONE, FAULT};
State state=LOCKED;
unsigned long entered=0,lastSample=0;
float raw=0,offset=0,grams=0,sum=0; uint8_t samples=0;
bool haveSample=false,previousStart=false,aborted=false;
char oldLine[2][17]={{0},{0}};
void line(uint8_t row,const char* s){char buf[17]; memset(buf,' ',16);buf[16]=0;memcpy(buf,s,min((size_t)16,strlen(s)));if(strcmp(buf,oldLine[row])){lcd.setCursor(0,row);lcd.print(buf);strcpy(oldLine[row],buf);}}
bool hit(int pin){return digitalRead(pin)==LOW;}
void setState(State s){state=s;entered=millis();}
void closeGate(bool abortCycle){aborted=abortCycle;setState(CLOSING);gate.writeMicroseconds(CLOSE_US);}
void fault(){gate.writeMicroseconds(NEUTRAL_US);setState(FAULT);}
void setup(){
 pinMode(START,INPUT_PULLUP);pinMode(STOP,INPUT_PULLUP);
 gate.attach(9);gate.writeMicroseconds(NEUTRAL_US);lcd.begin(16,2);Serial.begin(115200);
 scale.begin(A0,A1,false,false);EEPROM.get(0,cal);
 if(cal.magic!=0x534F494CUL || !isfinite(cal.factor) || fabs(cal.factor)<0.001){cal.magic=0;cal.factor=0;}
 Serial.println(F("Manually CLOSE gate before power-on. First START acknowledges closed gate; next START runs. Timed gate has NO position feedback. Calibration with gate CLOSED: empty platform send z; add known 1000 g mass send k; remove mass. Flip START OFF then ON to tare empty bag. STOP ON aborts. Reboot to clear a fault."));
}
void loop(){
 unsigned long now=millis();bool fresh=false;
 if(scale.is_ready()){raw=scale.read();lastSample=now;haveSample=true;fresh=true;if(cal.factor)grams=(raw-offset)/cal.factor;}
 bool pressed=hit(START); bool edge=pressed&&!previousStart;previousStart=pressed;
 if(Serial.available()){
 char c=Serial.read();
 if((state==LOCKED||state==READY||state==DONE)&&haveSample&&now-lastSample<500){
 if(c=='z'){offset=raw;Serial.println(F("Zero captured. Place known 1000 g mass, then send k."));}
 if(c=='k'){float f=(raw-offset)/1000.0f;if(fabs(f)>0.001){cal.magic=0x534F494CUL;cal.factor=f;EEPROM.put(0,cal);Serial.print(F("Saved counts/gram: "));Serial.println(f,5);}}
 }
 }
 if(hit(STOP)&&(state==TARING||state==OPENING||state==FILLING))closeGate(true);
 if((state==TARING||state==OPENING||state==FILLING)&&now-lastSample>1000)closeGate(true);
 switch(state){
 case LOCKED:
 if(edge&&!hit(STOP)&&cal.magic){setState(READY);}break;
 case READY:case DONE:
 if(edge&&!hit(STOP)&&cal.magic&&haveSample&&now-lastSample<500) {sum=0;samples=0;setState(TARING);}break;
 case TARING:
 if(fresh){sum+=raw;if(++samples>=10){offset=sum/samples;grams=0;gate.writeMicroseconds(OPEN_US);setState(OPENING);}}break;
 case OPENING:
 if(fresh&&grams>=CUTOFF_G)closeGate(false);
 else if(now-entered>=OPEN_MS){gate.writeMicroseconds(NEUTRAL_US);setState(FILLING);}
 else if(now-entered>5000)closeGate(true);break;
 case FILLING:
 if(fresh&&(grams>=CUTOFF_G||grams < -20))closeGate(grams< -20);
 else if(now-entered>60000)closeGate(true);break;
 case CLOSING:
 if(now-entered>=CLOSE_MS){gate.writeMicroseconds(NEUTRAL_US);setState(aborted?LOCKED:DONE);if(aborted&&cal.magic&&!hit(STOP))setState(READY);}
 else if(now-entered>5000)fault();break;
 case FAULT:break;
 }
 const char* names[]={"Check gate shut","Bag + START","Taring bag...","Opening gate","Filling soil","Closing gate","Check shut / bag","FAULT: manual"};
 line(0,names[state]); char text[17];
 if(!cal.magic)strcpy(text,"Serial: z then k");else if(!haveSample||now-lastSample>1000)strcpy(text,"Check HX711");else{snprintf(text,sizeof(text),"%ld g / 100 g",(long)grams);}line(1,text);
}

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