DEV Community

張旭豐
張旭豐

Posted on

5 Soil Moisture Sensor Projects That Keep Your Plants Alive

5 Soil Moisture Sensor Projects That Keep Your Plants Alive

Build automated plant care systems: self-watering planter, greenhouse control, vertical garden, herb window box, and agricultural field monitor

Soil Moisture Sensor Hero

The soil moisture sensor is one of the most practical sensors for home automation — it directly translates to saving plants, saving time, and reducing water waste. Most hobbyist sensors (like the resistive YL-69) are inexpensive and work well for soil types without highly mineralized content. In this guide, we'll build five automated plant care projects that water only when needed, monitor multiple zones, and keep your plants healthy even when you're away.

Topics covered: Analog sensor reading, threshold calibration, relay control, hysteresis logic, multi-zone monitoring, LCD I2C display integration, power management.


What You'll Need

  • Soil moisture sensor (YL-69 or capacitive v1.2, ×1-5 depending on project)
  • Arduino Nano or Uno (×1)
  • 5V relay module (×1-3)
  • 5V submersible water pump (for irrigation projects)
  • 16×2 LCD I2C display (for monitoring projects)
  • Jumper wires and breadboard
  • USB cable for programming
  • Power supply (5V 2A for pump projects)

How the Sensor Works

The resistive soil moisture sensor works by measuring the resistance between two probes inserted in the soil. Dry soil has high resistance; wet soil has low resistance. The Arduino's analog input reads this as a raw value from 0 (very wet) to 1023 (very dry air).

Arduino          Soil Sensor (YL-69)
  A0    ──────  A0 (signal)
  5V    ──────  VCC
  GND   ──────  GND
Enter fullscreen mode Exit fullscreen mode

Important: Resistive sensors corrode over time when powered constantly. Only power the sensor when taking a reading, then cut power immediately.

// WF1 Run #038 - Basic Moisture Reading with Power Control
#define SENSOR_POWER  8
#define SENSOR_PIN    A0

void setup() {
  pinMode(SENSOR_POWER, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);  // Sensor OFF
  Serial.begin(115200);
}

int readMoisture() {
  // Only power the sensor when reading
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);  // Allow sensor to stabilize
  int value = analogRead(SENSOR_PIN);
  digitalWrite(SENSOR_POWER, LOW);  // Power down immediately

  return value;  // 0 = wet, 1023 = dry
}

void loop() {
  int moisture = readMoisture();
  Serial.print("Moisture: ");
  Serial.println(moisture);
  delay(1000);
}
Enter fullscreen mode Exit fullscreen mode

Project 1: Self-Watering Planter

Goal: A ceramic planter that automatically waters your plant when the soil gets too dry, and stays off when it's already moist.

Self-Watering Planter

Hardware

  • Soil moisture sensor (YL-69)
  • Arduino Nano
  • 5V submersible pump
  • 5V relay module
  • Small water reservoir (container under the pot)
  • Tubing for water delivery

Wiring

Soil Sensor:   A0 → Arduino A0,  VCC → Pin 8 (power control),  GND → GND
Relay:          Signal → Pin 7,   VCC → 5V,  GND → GND
Pump:           Positive → COM (relay NO),  5V external → NO (relay)
Enter fullscreen mode Exit fullscreen mode

Code

// WF1 Run #038 - Project 1: Self-Watering Planter
#define SENSOR_POWER   8
#define SENSOR_PIN     A0
#define RELAY_PIN      7

#define DRY_THRESHOLD  700   // Water when reading is above this
#define WET_THRESHOLD  550   // Stop watering when below this
#define WATER_TIME_MS   5000  // Water for 5 seconds per cycle

bool isWatering = false;

void setup() {
  pinMode(SENSOR_POWER, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);
  digitalWrite(RELAY_PIN, LOW);  // Pump OFF
  Serial.begin(115200);
}

int readMoisture() {
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);
  int value = analogRead(SENSOR_PIN);
  digitalWrite(SENSOR_POWER, LOW);
  return value;
}

void waterPlant() {
  Serial.println("Watering...");
  digitalWrite(RELAY_PIN, HIGH);  // Pump ON
  delay(WATER_TIME_MS);
  digitalWrite(RELAY_PIN, LOW);   // Pump OFF
  Serial.println("Done watering.");
}

void loop() {
  int moisture = readMoisture();
  Serial.print("Moisture: ");
  Serial.println(moisture);

  if (moisture > DRY_THRESHOLD && !isWatering) {
    waterPlant();
  }

  delay(3600000);  // Check once per hour
}
Enter fullscreen mode Exit fullscreen mode

Project 2: Greenhouse Irrigation Control

Goal: Monitor multiple plant beds in a greenhouse and control individual irrigation valves based on each bed's moisture level.

Greenhouse Control

Hardware

  • 3× soil moisture sensors
  • Arduino Mega (more analog inputs) or 3× analog muxes
  • 3× 5V relay modules
  • 3× solenoid valves (12V with separate power)
  • 16×2 LCD I2C display
  • 5V power supply

Wiring

Bed 1 Sensor:  A0
Bed 2 Sensor:  A1
Bed 3 Sensor:  A2
Valve 1 Relay: Pin 7
Valve 2 Relay: Pin 8
Valve 3 Relay: Pin 9
LCD:           SDA → A4,  SCL → A5
Enter fullscreen mode Exit fullscreen mode

Code

// WF1 Run #038 - Project 2: Greenhouse Control (simplified 3-bed version)
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define SENSOR_POWER  8
#define SENSOR_PINS  {A0, A1, A2}
#define RELAY_PINS   {7, 8, 9}
#define DRY_THRESHOLD   650
#define WET_THRESHOLD   500

LiquidCrystal_I2C lcd(0x27, 16, 2);

int sensorPins[] = {A0, A1, A2};
int relayPins[] = {7, 8, 9};
bool valveStates[] = {false, false, false};

void setup() {
  lcd.begin();
  lcd.backlight();
  pinMode(SENSOR_POWER, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);
  for (int i = 0; i < 3; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], LOW);
  }
  Serial.begin(115200);
}

int readMoisture(int pin) {
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);
  int value = analogRead(pin);
  digitalWrite(SENSOR_POWER, LOW);
  return value;
}

void loop() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Greenhouse Status");

  for (int bed = 0; bed < 3; bed++) {
    int moisture = readMoisture(sensorPins[bed]);
    bool isDry = moisture > DRY_THRESHOLD;
    bool isWet = moisture < WET_THRESHOLD;

    // Hysteresis: open valve when dry, close when wet enough
    if (isDry && !valveStates[bed]) {
      digitalWrite(relayPins[bed], HIGH);
      valveStates[bed] = true;
      Serial.print("Bed ");
      Serial.print(bed + 1);
      Serial.println(": Valve OPEN");
    } else if (isWet && valveStates[bed]) {
      digitalWrite(relayPins[bed], LOW);
      valveStates[bed] = false;
      Serial.print("Bed ");
      Serial.print(bed + 1);
      Serial.println(": Valve CLOSED");
    }

    lcd.setCursor(0, bed + 1);
    lcd.print("B");
    lcd.print(bed + 1);
    lcd.print(":");
    lcd.print(moisture);
    lcd.print(valveStates[bed] ? " WET" : " DRY");

    delay(50);  // Small delay between sensor reads
  }

  delay(1800000);  // Check every 30 minutes
}
Enter fullscreen mode Exit fullscreen mode

Project 3: Vertical Garden Wall Monitor

Goal: A tall vertical garden wall where each pocket has its own moisture sensor, with a central display showing which plants need water.

Vertical Garden

Hardware

  • 4× soil moisture sensors
  • Arduino Nano with multiplexer (or Capacitive Soil Sensor v1.2 for less corrosion)
  • 4× 5V micro pumps (one per plant)
  • 4× RGB LEDs for status indication
  • 16×2 LCD I2C display

Code

// WF1 Run #038 - Project 3: Vertical Garden Monitor
#include <LiquidCrystal_I2C.h>

#define SENSOR_POWER   8
#define SENSOR_PIN     A0
#define MUX_A          2
#define MUX_B          3
#define MUX_C          4
#define DRY_THRESHOLD  680

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Plant labels
const char* plantNames[] = {"Basil", "Mint", "Rosemary", "Thyme"};

// RGB LED pins for status: R=10, G=9, B=11
const int LED_R = 10;
const int LED_G = 9;
const int LED_B = 11;

void selectMuxChannel(int channel) {
  digitalWrite(MUX_A, channel & 1);
  digitalWrite(MUX_B, (channel >> 1) & 1);
  digitalWrite(MUX_C, (channel >> 2) & 1);
}

void setup() {
  lcd.begin();
  lcd.backlight();
  pinMode(SENSOR_POWER, OUTPUT);
  pinMode(MUX_A, OUTPUT);
  pinMode(MUX_B, OUTPUT);
  pinMode(MUX_C, OUTPUT);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);
  Serial.begin(115200);
}

int readMoisture(int muxChannel) {
  selectMuxChannel(muxChannel);
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);
  int value = analogRead(SENSOR_PIN);
  digitalWrite(SENSOR_POWER, LOW);
  return value;
}

void setLED(int r, int g, int b) {
  digitalWrite(LED_R, r);
  digitalWrite(LED_G, g);
  digitalWrite(LED_B, b);
}

void loop() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Vertical Garden");

  for (int plant = 0; plant < 4; plant++) {
    int moisture = readMoisture(plant);
    bool isDry = moisture > DRY_THRESHOLD;

    lcd.setCursor(0, plant + 1);
    lcd.print(plantNames[plant]);
    lcd.print(":");
    lcd.print(isDry ? "DRY!" : "OK ");

    // LED color: green=OK, yellow=low, red=critical dry
    if (isDry) {
      if (moisture > 800) setLED(HIGH, LOW, LOW);      // Red
      else setLED(HIGH, HIGH, LOW);                      // Yellow
    } else {
      setLED(LOW, HIGH, LOW);                           // Green
    }

    delay(50);
  }

  delay(3600000);  // Check every hour
}
Enter fullscreen mode Exit fullscreen mode

Project 4: Herb Kitchen Window Box

Goal: A windowsill herb garden with moisture monitoring and a small pump that tops up water automatically, keeping herbs fresh without daily attention.

Herb Kitchen

Hardware

  • Soil moisture sensor
  • Arduino Nano
  • 5V mini submersible pump
  • 5V relay module
  • Small LCD 16×2 I2C display showing moisture percentage
  • Plastic tubing

Code

// WF1 Run #038 - Project 4: Herb Window Box
#include <LiquidCrystal_I2C.h>

#define SENSOR_POWER   8
#define SENSOR_PIN     A0
#define RELAY_PIN      7

#define DRY_THRESHOLD  650
#define WET_THRESHOLD  520
#define WATER_TIME_MS  3000

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  pinMode(SENSOR_POWER, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);
  digitalWrite(RELAY_PIN, LOW);
  lcd.begin();
  lcd.backlight();
  Serial.begin(115200);
}

int readMoisture() {
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);
  int value = analogRead(SENSOR_PIN);
  digitalWrite(SENSOR_POWER, LOW);
  return value;
}

int moistureToPercent(int raw) {
  // Map 1023 (dry) to 0%, 0 (wet) to 100%
  return map(raw, 1023, 0, 0, 100);
}

void loop() {
  int moisture = readMoisture();
  int percent = moistureToPercent(moisture);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Herb Garden");
  lcd.setCursor(0, 1);
  lcd.print("Moisture: ");
  lcd.print(percent);
  lcd.print("%");

  if (moisture > DRY_THRESHOLD) {
    lcd.setCursor(14, 1);
    lcd.print("WATER");

    // Auto-water
    digitalWrite(RELAY_PIN, HIGH);
    delay(WATER_TIME_MS);
    digitalWrite(RELAY_PIN, LOW);

    delay(60000);  // Wait 1 minute before rechecking
  } else if (moisture < WET_THRESHOLD) {
    lcd.setCursor(14, 1);
    lcd.print("OK  ");
  }

  delay(1800000);  // Check every 30 minutes
}
Enter fullscreen mode Exit fullscreen mode

Project 5: Agricultural Field Monitor

Goal: An outdoor field station that monitors soil moisture across multiple rows, displays data on an LCD, and logs readings for analysis.

Agricultural Field Monitor

Hardware

  • 3× soil moisture sensors (with waterproof encapsulation)
  • Arduino Mega (or multiple Nanos with serial)
  • 16×2 LCD I2C display
  • SD card module for data logging
  • DS3231 RTC module for timestamps
  • Solar panel with USB charging (for continuous outdoor operation)
  • IP65 enclosure

Code

// WF1 Run #038 - Project 5: Agricultural Field Monitor
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <SD.h>

#define SENSOR_POWER   8
#define SENSOR_PINS    {A0, A1, A2}
#define DRY_THRESHOLD  620

LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS3231 rtc;
File logFile;

int sensorPins[] = {A0, A1, A2};
const char* rowLabels[] = {"Row A", "Row B", "Row C"};

void setup() {
  pinMode(SENSOR_POWER, OUTPUT);
  digitalWrite(SENSOR_POWER, LOW);

  lcd.begin();
  lcd.backlight();

  Serial.begin(115200);

  // Initialize SD card
  if (!SD.begin(10)) {
    lcd.setCursor(0, 0);
    lcd.print("SD Init Failed");
    while(1);
  }

  // Initialize RTC
  if (!rtc.begin()) {
    lcd.setCursor(0, 0);
    lcd.print("RTC Init Failed");
    while(1);
  }

  // Create log file with date-based name
  DateTime now = rtc.now();
  char filename[16];
  sprintf(filename, "LOG_%02d%02d%02d.TXT", now.year()%100, now.month(), now.day());

  logFile = SD.open(filename, FILE_WRITE);

  lcd.setCursor(0, 0);
  lcd.print("Field Monitor OK");
  delay(1000);
}

int readMoisture(int pin) {
  digitalWrite(SENSOR_POWER, HIGH);
  delay(10);
  int value = analogRead(pin);
  digitalWrite(SENSOR_POWER, LOW);
  return value;
}

int moistureToPercent(int raw) {
  return map(raw, 1023, 0, 0, 100);
}

void loop() {
  DateTime now = rtc.now();

  // Display current readings on LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  char timeStr[9];
  sprintf(timeStr, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
  lcd.print(timeStr);

  for (int row = 0; row < 3; row++) {
    int moisture = readMoisture(sensorPins[row]);
    int percent = moistureToPercent(moisture);

    lcd.setCursor(0, row + 1);
    lcd.print(rowLabels[row]);
    lcd.print(":");
    lcd.print(percent);
    lcd.print("%");

    if (moisture > DRY_THRESHOLD) {
      lcd.print(" DRY");
    }

    // Log to SD card
    if (logFile) {
      logFile.print(now.timestamp());
      logFile.print(",");
      logFile.print(rowLabels[row]);
      logFile.print(",");
      logFile.println(percent);
    }

    delay(50);
  }

  if (logFile) {
    logFile.flush();
  }

  delay(3600000);  // Log every hour
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Problem Cause Fix
Sensor readings jump wildly Mineral deposits on probes Clean probes with vinegar; switch to capacitive sensor
Always reads dry even in wet soil Poor ground contact Reinsert probes fully into soil
Pump won't turn off Diode protection missing on relay Add flyback diode across pump terminals
LCD shows garbage characters I2C address mismatch Scan for correct I2C address (default 0x27 or 0x3F)
SD card won't initialize SPI pin conflict or card format Use FAT32 format; check SD pin 10 (CS) is defined

Calibration note: Every sensor and soil type is slightly different. After building your project, test with both dry soil (known dry, watered the day before) and wet soil (just watered, water draining freely). Set your thresholds between those two values for reliable operation.


Start Here

Affiliate disclosure: As an Amazon Associate, I earn from qualifying purchases.

The right parts make the difference:

Soil Moisture Sensor YL-69 — Inexpensive and works well for most soil types.

Capacitive Soil Sensor v1.2 — Lasts much longer without corrosion.

Arduino Mega 2560 — More analog inputs for multi-zone monitoring.

5V Submersible Pump — Quiet and reliable for indoor planter projects.

16x2 LCD I2C Display — Essential for monitoring and status display.

5V Relay Module — Controls pump or valve from Arduino signal.


Next Step: From Scene to Sensor, Without Writing Code

If this guide gave you ideas for your own setup — but you're not sure which sensors and outputs work best for your specific space — I can help you map that out.

I offer a personalized interactive device design guide at Fiverr:

👉 https://www.fiverr.com/phd_hfchang/generate-an-arduino-interactive-prototypef

What you get:

  • A custom guide based on your actual scene (not generic recommendations)
  • Sensor selection matched to user behavior and physical constraints
  • Interaction logic without needing to write code from scratch
  • Testing methodology with pass/fail criteria for each output

Tags: Arduino Soil Moisture Plant Care Automation IoT Agriculture Smart Garden Arduino Projects

Top comments (0)