1. Project Overview and Parts List
The system works on a simple principle: read the soil moisture level, and if it falls below a threshold, run the pump for a fixed number of seconds. Then wait. This prevents the pump from running continuously if the sensor reading is slow to update after watering, and it limits total water delivery per cycle.
Here is everything you need:
- Arduino Uno — the brain of the system.
- Capacitive soil moisture sensor v2 — reads soil dryness without corroding.
- Mini submersible DC water pump (3–6 V) — moves water from reservoir to pot.
- PVC flexible hose, 6 mm × 8 mm — routes water from pump to soil.
- 18650 battery shield — makes the whole system portable.
- One NPN transistor (2N2222 or similar) or a small relay module.
- One 1N4007 flyback diode (if using a transistor to drive the pump).
- One 1 kΩ resistor (base resistor for the transistor).
- Jumper wires and a solderless breadboard.
- A small water reservoir (a plastic container or jar works perfectly).
The total build time is around 60–90 minutes for a first attempt. No soldering is required if you use a breadboard and the pump’s existing wire leads.
2. Capacitive vs Resistive Soil Sensors
Soil moisture sensors come in two types, and the difference matters for long-term use.
Resistive Sensors
Resistive sensors measure moisture by passing a small current through two exposed metal probes inserted into the soil and reading the resistance between them. Wet soil conducts electricity better than dry soil, so lower resistance means more moisture. They are cheap and easy to understand — but there is a significant problem. Passing current continuously through metal probes in moist, slightly acidic soil drives an electrolytic reaction. Within weeks, the probes corrode visibly and the readings drift until the sensor becomes useless.
Capacitive Sensors
The capacitive soil moisture sensor v2 works differently. It measures the dielectric permittivity of the soil — essentially, how much the soil changes the capacitance of a circuit embedded in the sensor probe. Water has a dramatically higher dielectric constant than air, so wet soil measurably shifts the capacitance. Because no current flows through the soil between exposed electrodes, there is no electrolytic corrosion. The sensor is also coated in a protective layer that resists the slightly acidic environment inside compost-rich soil.
For any project that will run unsupervised for weeks, always use a capacitive sensor. The small price difference is trivial compared to the cost of a dead plant caused by a corroded probe giving false “wet” readings.
3. Reading the Soil Sensor and Calibration
The capacitive sensor outputs an analogue voltage between 0 V and VCC (3.3–5 V) on its AOUT pin. Connect this to analogue pin A0 on the Arduino. Connect VCC to the Arduino’s 3.3 V or 5 V pin (the v2 sensor accepts both) and GND to GND.
Run this calibration sketch before writing any control logic:
void setup() {
Serial.begin(9600);
}
void loop() {
int reading = analogRead(A0);
Serial.println(reading);
delay(500);
}
Open the Serial Monitor at 9600 baud and note two values:
- Dry value: Hold the sensor in open air. The reading will be high — typically 620–680 on a 5 V system.
- Wet value: Submerge the sensor tip in a glass of water. The reading will be low — typically 280–340.
Write both values down. These become the endpoints for your moisture scale and will vary between individual sensors and supply voltages. Calibrating to your specific sensor is important — the default numbers in tutorials online may not match your hardware.
You can map these raw values to a 0–100% scale using Arduino’s map() function:
// Replace 680 and 300 with your own calibrated values
int moisturePercent = map(analogRead(A0), 680, 300, 0, 100);
moisturePercent = constrain(moisturePercent, 0, 100);
The constrain() call prevents the value going below 0 or above 100 if the raw
reading goes slightly outside your calibrated range — which can happen in extremely wet or
extremely dry conditions.
4. Controlling the Pump Safely
The mini submersible pump draws around 100–220 mA at 5 V — far more current than an Arduino digital output pin can supply directly (each pin is limited to 40 mA maximum, with a recommended maximum of 20 mA). Connecting the pump directly to a GPIO pin will damage the Arduino. You need a transistor or relay to act as a switch between the Arduino’s low-power output and the pump’s higher current demand.
Using an NPN Transistor (Recommended for This Pump)
A 2N2222 or BC547 NPN transistor handles this pump’s current easily. Wire it as follows:
- Base → through a 1 kΩ resistor → Arduino digital pin 7.
- Collector → pump negative lead.
- Emitter → GND.
- Pump positive lead → 5 V (or your battery supply positive).
- 1N4007 diode → across the pump terminals, cathode (banded end) to positive. This flyback diode absorbs the voltage spike the pump motor generates when it is switched off suddenly, protecting the transistor and the Arduino.
When Arduino pin 7 goes HIGH, it forward-biases the transistor, which saturates (turns on) and connects the pump’s negative lead to GND, completing the circuit. When pin 7 goes LOW, the transistor turns off and the pump stops. This is clean, reliable, and requires no additional power supply.
Using a Relay Module (Alternative)
A 5 V relay module works equally well and is easier to understand conceptually. Connect the relay IN pin to Arduino pin 7, VCC to 5 V, GND to GND, and wire the pump through the relay’s Normally Open (NO) and Common (COM) contacts. Relay modules include their own flyback protection, so no external diode is needed.
5. Wiring Everything Together
With the transistor approach, the complete wiring is:
- Sensor VCC → Arduino 5 V (or 3.3 V if your sensor is 3.3 V only).
- Sensor GND → Arduino GND.
- Sensor AOUT → Arduino A0.
- Arduino pin 7 → 1 kΩ resistor → transistor base.
- Transistor emitter → GND rail.
- Transistor collector → pump negative lead.
- Pump positive lead → 5 V rail.
- 1N4007 diode across pump terminals, cathode to positive lead.
- Pump submerged in water reservoir, with PVC hose on the pump outlet, routed to the plant pot.
Place the water reservoir higher than the pump outlet if possible — or ensure the hose run is short enough that the pump’s head pressure (120 L/h, roughly 0.5 m lift) can push water to the plant. Route the hose so its open end sits at the base of the plant’s stem, pointing down into the soil rather than at the surface, so water reaches the roots rather than running off.
6. Full Arduino Code With Comments
// ── Automatic Plant Watering System ──────────────────────────
// Reads soil moisture every 30 minutes. If soil is too dry,
// runs the pump for 3 seconds, then waits before checking again.
const int SENSOR_PIN = A0;
const int PUMP_PIN = 7;
// Calibrated sensor endpoints — adjust to your own readings
const int DRY_VALUE = 680; // Raw reading in open air
const int WET_VALUE = 300; // Raw reading submerged in water
// Tunable parameters
const int DRY_THRESHOLD = 40; // Water when moisture falls below 40%
const unsigned long PUMP_ON_TIME = 3000UL; // Pump runs for 3 seconds
const unsigned long CHECK_INTERVAL = 1800000UL; // Check every 30 minutes
unsigned long lastCheckTime = 0;
unsigned long pumpStartTime = 0;
bool pumpRunning = false;
void setup() {
pinMode(PUMP_PIN, OUTPUT);
digitalWrite(PUMP_PIN, LOW); // Ensure pump is off at startup
Serial.begin(9600);
Serial.println("Plant watering system started.");
}
void loop() {
unsigned long now = millis();
// ── Turn pump off after PUMP_ON_TIME has elapsed ──────────
if (pumpRunning && (now - pumpStartTime >= PUMP_ON_TIME)) {
digitalWrite(PUMP_PIN, LOW);
pumpRunning = false;
Serial.println("Pump off.");
}
// ── Check soil moisture at the set interval ────────────────
if (!pumpRunning && (now - lastCheckTime >= CHECK_INTERVAL)) {
lastCheckTime = now;
int raw = analogRead(SENSOR_PIN);
int moisture = map(raw, DRY_VALUE, WET_VALUE, 0, 100);
moisture = constrain(moisture, 0, 100);
Serial.print("Soil moisture: ");
Serial.print(moisture);
Serial.println("%");
if (moisture < DRY_THRESHOLD) {
Serial.println("Soil dry — running pump.");
digitalWrite(PUMP_PIN, HIGH);
pumpRunning = true;
pumpStartTime = now;
} else {
Serial.println("Moisture OK — no watering needed.");
}
}
}
The code uses millis() throughout rather than delay(). This is
important: delay() blocks the entire program for its duration, preventing the
pump from being turned off on time if the check interval and pump duration overlap.
The millis() approach keeps both timers running independently.
7. Setting and Tuning the Moisture Threshold
The DRY_THRESHOLD constant (set to 40% in the code above) is the most important
value to tune for your specific plant. It is the moisture percentage below which the system
decides to water. Getting it right depends on the plant species, pot size, and how quickly
your particular soil drains.
A practical tuning method:
- Water the plant thoroughly by hand until the soil is evenly moist but not waterlogged.
- Insert the sensor and note the percentage reading. This is roughly your “just watered” value — the system should never need to trigger at this level.
- Let the soil dry naturally over several days, checking the reading each day.
- The day before you would normally water the plant by hand is your threshold. Set
DRY_THRESHOLDto the reading you see on that day.
As a rough starting guide by plant type:
- Succulents and cacti: 15–20% (water infrequently; they prefer dry soil).
- Herbs (basil, mint, coriander): 45–55% (they like consistent moisture).
- Tropical houseplants (pothos, philodendron): 35–45%.
- Vegetable seedlings: 50–60% (young roots need consistent access to water).
8. Adding a Timer to Prevent Overwatering
The code already includes two overwatering safeguards that deserve explicit explanation, because they solve a real problem with naive moisture-sensor designs.
Fixed Pump Duration
The pump runs for exactly PUMP_ON_TIME milliseconds (3 seconds by default),
regardless of how dry the sensor reads. A design that runs the pump until the sensor reads
“wet” will almost always overwater, because water takes time to percolate through soil. If
you run the pump until the sensor at the top of the pot reads wet, the lower half of the
pot will be waterlogged by the time the water moves downward. A fixed, conservative pump
duration avoids this.
Calibrate your pump duration by timing how long it takes to deliver roughly 100–150 mL of
water (a reasonable watering amount for a medium pot), and set PUMP_ON_TIME to that duration in milliseconds.
Minimum Check Interval
The CHECK_INTERVAL (30 minutes by default) prevents the system from re-checking
immediately after watering and triggering a second pump cycle before the first watering has
had time to register. After a watering event, the system will not check again for 30 minutes.
For most plants, this interval can be extended to 2–6 hours without any issue — the soil does
not go from saturated to dangerously dry in 30 minutes.
9. Making It Portable with a Battery
Mains power through a USB adapter is fine for a permanent windowsill installation. For a balcony, greenhouse, or anywhere without a convenient socket, the 18650 battery shield converts one or more 18650 lithium cells into a regulated 5 V supply via USB — exactly what the Arduino Uno’s USB input needs.
Estimating battery life for this project:
- Arduino Uno active current draw: ~50 mA.
- Capacitive sensor current draw: ~5 mA.
- Pump current draw: ~150–200 mA when running.
- A single 18650 cell at 2,500 mAh provides roughly 2,500 ÷ 55 mA ≈ 45 hours of continuous runtime, ignoring pump cycles.
- With a 30-minute check interval and a 3-second pump cycle, the pump overhead is negligible.
- Two 18650 cells in the shield extend this to approximately 90 hours — nearly four days.
To significantly extend battery life beyond this, you would need to put the Arduino into a
low-power sleep mode between readings — a worthwhile next step once the basic system is
working, using the LowPower library.
Closing Thoughts
This project is one of the most satisfying first builds in Arduino because it does something
real. The skills it covers — reading an analogue sensor, calibrating it, controlling a
higher-current load safely, using millis() for non-blocking timing, and tuning
a threshold for a real-world condition — transfer directly to dozens of other projects. Once
the system is running, the natural next steps are adding an OLED display to show moisture
readings, sending alerts over WiFi with an ESP32, or expanding to multiple plants with
multiple sensors and a valve manifold.
Everything you need to get started is available in one place: the capacitive soil moisture sensor, the mini submersible pump, PVC hose, the Arduino Uno, and the 18650 battery shield for portable operation. Build it once, calibrate it carefully, and you may never kill a plant by forgetting to water it again.
