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
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
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);
}
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.
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)
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
}
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.
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
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
}
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.
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
}
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.
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
}
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.
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
}
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)