One-sentence summary
We build an Arduino prototype that uses a soil moisture sensor to measure whether the soil is dry and, when it is, runs a small pump for a short time to water the plant.
Why does it matter?
Watering a plant regularly sounds easy, but it is very easy to forget when you are away on holiday or having a busy week at school. Overwatering is just as harmful as underwatering. This is where computers do their best work: patiently measuring a situation and deciding based on a rule.
This project brings together parts we learned separately in earlier lessons into a single system: measuring with a sensor, making a decision with a condition, and controlling an output (the pump). The same idea is used in real greenhouses, smart farming and home gardens. We are building a small, safe copy of it.
This content is at a beginner level, and because it combines water and electronics it must always be done together with an adult.
How does the system work?
Three-stage thinking
Almost every automation system has three stages:
- Measure (input): The soil moisture sensor reads how wet the soil is.
- Decide (process): The Arduino compares the reading with a threshold.
- Act (output): If the soil is dry, the pump runs briefly and delivers water.
This loop repeats again and again. An example from everyday life: a thermostat that heats a room in winter works the same way. It measures the temperature, turns on the heater if it is below the target, and turns it off once the target is reached. In our system, "temperature" becomes "soil moisture" and "heater" becomes "pump".
The moisture value and the threshold
The sensor sends the Arduino a number between 0 and 1023. With the sensor we use, the value is high when the soil is dry and low when it is wet. We decide which number counts as "dry"; this is called the threshold.
The way to find the threshold is by testing: we dip the sensor first into dry soil, then into watered soil, and read the values on the serial monitor. We pick a number in between. This is like the everyday decision "how often should laundry be washed" — there is no exact number, you adjust it by observation.
Materials and wiring
Materials list
- Arduino Uno (connected to the computer by USB)
- Soil moisture sensor (with analog output)
- Small 5 V submersible water pump
- 1 NPN transistor (e.g. 2N2222) or a ready-made motor/relay driver module
- 1 diode, 1N4007 (reverse-current protection for the pump)
- 1 resistor, 1 kΩ (for the transistor base)
- 1 LED + 220 Ω resistor (watering indicator)
- A separate low-voltage power source (e.g. a 4-cell battery pack)
- Breadboard and jumper wires, a cup of water, a small plant pot
Wiring logic
- Moisture sensor: VCC → 5 V, GND → GND, output (AO) → A0.
- LED: long leg → pin 9 through a 220 Ω resistor, short leg → GND.
- The pump is not connected directly to an Arduino pin. A separate battery pack powers the pump; the Arduino only controls the transistor's base (through a 1 kΩ resistor on pin 8). The transistor acts like a switch.
- The diode is connected across the pump terminals in reverse and absorbs the harmful current that appears when the motor stops.
- Common GND: The negative terminal of the battery pack must be joined to the Arduino GND, otherwise the transistor switching will not work.
Full sketch
First, the basic version that measures, prints to the serial monitor, and runs the pump briefly if the soil is dry:
const int moisturePin = A0; // moisture sensor
const int pumpPin = 8; // transistor base
const int ledPin = 9; // watering indicator
const int dryThreshold = 600; // above this value = dry
const unsigned long waterTime = 3000; // 3 seconds
void setup() {
Serial.begin(9600);
pinMode(pumpPin, OUTPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(pumpPin, LOW); // pump off at start
}
The main loop measures, decides, and waters when needed:
void loop() {
int moisture = analogRead(moisturePin);
Serial.print("Moisture value: ");
Serial.println(moisture);
if (moisture > dryThreshold) { // is the soil dry?
digitalWrite(ledPin, HIGH);
digitalWrite(pumpPin, HIGH); // pump on
delay(waterTime); // water briefly
digitalWrite(pumpPin, LOW); // pump off
digitalWrite(ledPin, LOW);
}
delay(5000); // wait 5 seconds, then measure again
}
The logic is simple: read, compare, water briefly if needed, wait. Keeping the waterTime value small matters for safety; the pump never stays on for a long time.
Test and verify
| Test | Condition | Expected behaviour | Observed result | Next decision |
|---|---|---|---|---|
| Normal | Standard input and complete connection | The core task is completed | Fill in during testing | Keep it or make a small improvement |
| Boundary | Lowest or highest accepted value | The system remains stable | Fill in during testing | Review the threshold or rule |
| Failure | Missing, incorrect or unexpected input | A safe and understandable response | Fill in during testing | Add error handling |
| Repeat | At least three trials under the same condition | Similar results | Fill in during testing | Investigate the source of inconsistency |
- Before putting the pump in water, test only the sensor and the code. Open the serial monitor (
Serial Monitor, 9600) and watch the values. - Dip the sensor into dry soil: the value should be above the threshold. Dip it into a cup of water: the value should drop.
- Look at the values and adjust the
dryThresholdnumber for your own soil. - Place the pump in a cup of water, point the outlet hose into the pot, and fix the electronic parts high up and away from water.
- Watch a short watering, and confirm that the LED blinks and the pump stops after 3 seconds.
One bug and its fix
On my first attempt the pump ran but would not stop. The moisture value on the serial monitor stayed above the threshold. I looked for the problem step by step:
Goal: Water for 3 seconds when the soil is dry
Problem: The pump stays on all the time
Check: The sensor and pump were in the same cup of water
Reason: Water affected the electronics, corrupting the reading
Fix: Put the sensor in the pot, the pump in a separate water container
The lesson was this: if water and measurement mix, the system makes wrong decisions. The sensor should measure the soil, and the pump should draw water from a separate container.
Mini practice
Make the system a bit smarter. Right now the code cannot do anything while it runs delay(waterTime). Instead, build a non-blocking wait with millis() and require at least one minute between two waterings. That way the system will not water again before the soil has absorbed the water.
Starting idea:
unsigned long lastWatering = 0;
const unsigned long waitTime = 60000; // 60 seconds
void loop() {
int moisture = analogRead(moisturePin);
if (moisture > dryThreshold && millis() - lastWatering > waitTime) {
digitalWrite(pumpPin, HIGH);
delay(3000); // short watering
digitalWrite(pumpPin, LOW);
lastWatering = millis(); // record the time
}
}
Extra task: count the number of waterings in a variable and print it to the serial monitor. Observe how many times a day the plant is watered.
Common mistakes
Connecting the pump directly to an Arduino pin
An Arduino pin can only supply a very small current. The pump draws more and can permanently damage the pin. The pump is always powered separately; the Arduino only switches it on and off through a transistor.
Forgetting the common GND
If the battery pack and the Arduino GND are not connected, the transistor switching will not work. The "zero point" of the two power sources must be shared.
Writing a threshold without testing
Every soil and every sensor is different. If you choose dryThreshold without looking at real readings, the system will either never water or water non-stop.
Setting a long watering time
Keeping waterTime large can overflow the pot. A short watering plus a waiting time is safer.
Safety note
- Water and electronics stay apart. The Arduino, breadboard, batteries and wires must sit high up and away from water. Put a towel anywhere a splash could reach.
- Use low voltage only: USB or a battery pack. A water pump is never run from mains (wall socket) electricity.
- Do not power the pump directly from an Arduino pin. Motors and pumps need a driver/transistor and a separate power source.
- Do not touch the circuit with wet hands.
- Start the pump at low flow and for short times; watch for any overflow.
- This project is done under adult supervision. Have an adult check the connections before powering the circuit.
Lesson summary
- An auto-watering system works in three stages: measure, decide, act.
- The moisture sensor gives an analog value; we set the threshold that decides "dry" and adjust it by testing.
- The pump is never run directly from an Arduino pin, but with a separate power source and a transistor/driver.
- A short watering time, a waiting time, and non-blocking timing with
millis()make the system safe and smart. - Keeping water and electronics apart is essential for both correct measurement and safety.
Check questions
- What are the three basic stages of an auto-watering system?
- With the sensor we use, is the moisture value high or low when the soil is dry?
- Why do we not connect the pump directly to an Arduino pin?
- How do we correctly set the
dryThresholdvalue? - What is the advantage of using
millis()instead ofdelay()?
Answers
- Measure (input), decide (process), and act/output (run the pump).
- High; the value drops in wet soil. That is why the condition
moisture > dryThresholdindicates dryness. - The pump draws far more current than an Arduino pin can supply; it damages the pin. A separate power source and a transistor are needed.
- By reading the sensor in dry and wet soil on the serial monitor and picking a number between the two values — that is, by testing.
millis()does not stop the program while waiting; the Arduino can keep measuring and doing other work during that time.
Source and verification note
For “Project: Mini Auto-Watering Prototype”, verification focuses on whether the relationship between How does the system work? and The moisture value and the threshold remains consistent across examples. Pin, voltage and current limits can differ between Arduino-compatible boards. Compiling code does not guarantee a safe circuit; loads such as motors and servos require a suitable driver and external power where appropriate.
Next lesson
Robotic Systems module: We begin designing larger robot systems that combine sensing, decision-making and movement.