Automatic Plant Watering System with Arduino & Soil Sensor
Build a self-watering plant system using Arduino Uno, a capacitive soil moisture sensor, a 5V relay, and a mini submersible pump. Includes calibration routine and a moisture-level OLED display.
Why Capacitive Over Resistive?
Older resistive soil sensors corrode within weeks because they pass current through the soil. Capacitive sensors measure the dielectric constant of the soil instead — no exposed metal contacts in the soil, so they last years. The module outputs an analog voltage between 0 V (submerged) and ~3 V (bone dry) which maps neatly to Arduino's 10-bit ADC.
The relay acts as a high-current switch to run the 5V pump from a separate power rail, so the Arduino never sources pump current directly.
⚠️ Warning
Power the submersible pump from a dedicated 5V 1A power supply — NOT from the Arduino's 5V pin, which is limited to 500 mA from USB. Sharing power rails will brown-out the board and cause erratic ADC readings.
Components required
Wiring
Soil sensor: VCC → 3.3V, GND → GND, AOUT → A0.
(Using 3.3V prevents the ADC from saturating near 0 when submerged)
Relay module: VCC → 5V (Arduino), GND → GND, IN → D7.
NO terminal of relay → pump positive; COM terminal → external 5V supply positive.
Pump negative → external 5V GND (share GND with Arduino).
OLED (I2C): VCC → 3.3V, GND → GND, SDA → A4, SCL → A5.
// ── Automatic Plant Watering System ──────────────────────────────────────────
// Capacitive moisture sensor + 5V relay + OLED display
// ─────────────────────────────────────────────────────────────────────────────
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SENSOR_PIN A0
#define RELAY_PIN 7
#define SCREEN_W 128
#define SCREEN_H 32
#define OLED_RESET -1
Adafruit_SSD1306 oled(SCREEN_W, SCREEN_H, &Wire, OLED_RESET);
// ── Calibration values — run calibration sketch first ────────────────────────
// AIR_VALUE: raw ADC reading when sensor is in open air (bone dry)
// WATER_VALUE: raw ADC reading when sensor tip is submerged in water
const int AIR_VALUE = 620; // adjust after calibration
const int WATER_VALUE = 300; // adjust after calibration
// ── Thresholds ────────────────────────────────────────────────────────────────
const int DRY_THRESHOLD = 40; // % moisture → trigger pump
const int WET_THRESHOLD = 70; // % moisture → stop pump
const unsigned long PUMP_MAX_MS = 5000; // Safety: max pump run time
// ── State ─────────────────────────────────────────────────────────────────────
bool pumpActive = false;
unsigned long pumpStart = 0;
int readMoisturePercent() {
int raw = analogRead(SENSOR_PIN);
int pct = map(raw, AIR_VALUE, WATER_VALUE, 0, 100);
return constrain(pct, 0, 100);
}
void drawOLED(int pct, bool pumping) {
oled.clearDisplay();
oled.setTextSize(1);
oled.setTextColor(SSD1306_WHITE);
oled.setCursor(0, 0);
oled.print("Moisture: ");
oled.print(pct);
oled.print("%");
// Progress bar
int barW = map(pct, 0, 100, 0, 120);
oled.drawRect(0, 12, 120, 8, SSD1306_WHITE);
oled.fillRect(0, 12, barW, 8, SSD1306_WHITE);
oled.setCursor(0, 24);
oled.print(pumping ? "Pump: ON " : "Pump: OFF ");
oled.print(pct < DRY_THRESHOLD ? "[ DRY ]" : pct > WET_THRESHOLD ? "[ WET ]" : "[ OK ]");
oled.display();
}
void setup() {
Serial.begin(9600);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH); // Relay is active-LOW; HIGH = pump off
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init failed");
while (true);
}
oled.clearDisplay();
oled.display();
Serial.println("Auto-watering system ready");
}
void loop() {
int moisture = readMoisturePercent();
Serial.print("Moisture: ");
Serial.print(moisture);
Serial.println("%");
// ── Pump logic ─────────────────────────────────────────────────────────────
if (!pumpActive && moisture < DRY_THRESHOLD) {
pumpActive = true;
pumpStart = millis();
digitalWrite(RELAY_PIN, LOW); // Turn pump ON
Serial.println("Pump ON — soil dry");
}
if (pumpActive) {
bool soilWet = moisture >= WET_THRESHOLD;
bool timeout = (millis() - pumpStart) >= PUMP_MAX_MS;
if (soilWet || timeout) {
pumpActive = false;
digitalWrite(RELAY_PIN, HIGH); // Turn pump OFF
Serial.println(timeout ? "Pump OFF (timeout)" : "Pump OFF — soil wet");
}
}
drawOLED(moisture, pumpActive);
delay(1000);
}Live simulation
Steps
- 1Install libraries: Adafruit GFX and Adafruit SSD1306 via Library Manager
- 2Wire the sensor to 3.3V (not 5V) to prevent ADC saturation
- 3Power the pump from a separate 5V 1A supply through the relay
- 4Run the sketch and open Serial Monitor — note the raw ADC value in open air (AIR_VALUE) and when submerged (WATER_VALUE)
- 5Update both calibration constants in the sketch and re-upload
- 6Insert sensor into dry soil — pump should trigger below 40% moisture and stop at 70%
- 7Adjust DRY_THRESHOLD and WET_THRESHOLD to suit your plant species