BeginnerArduinoArduino UnoSoil MoistureRelay

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.

Circuit Hub20 min read3 views

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

Arduino Uno R3
×1Buy
Capacitive Soil Moisture Sensor v1.2
×1Buy
5V Single-Channel Relay Module
×1Buy
Mini Submersible Water Pump (3–6V)
×1Buy
Silicone Tube (4 mm ID, 30 cm)
×1
0.96" I2C OLED Display (SSD1306)
×1Buy
5V 1A USB Power Supply (for pump)
×1
Breadboard + Jumper Wires
×1

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.

C++
// ── 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

  1. 1Install libraries: Adafruit GFX and Adafruit SSD1306 via Library Manager
  2. 2Wire the sensor to 3.3V (not 5V) to prevent ADC saturation
  3. 3Power the pump from a separate 5V 1A supply through the relay
  4. 4Run the sketch and open Serial Monitor — note the raw ADC value in open air (AIR_VALUE) and when submerged (WATER_VALUE)
  5. 5Update both calibration constants in the sketch and re-upload
  6. 6Insert sensor into dry soil — pump should trigger below 40% moisture and stop at 70%
  7. 7Adjust DRY_THRESHOLD and WET_THRESHOLD to suit your plant species

Related projects