Built-in project · Arduino Uno R3

Soil moisture meter on an Arduino Uno

An FC-28 fork in the soil, powered from a pin only while it is read so it does not corrode, and the moisture on a 16x2 I2C LCD: dry, just right or wet. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Beginner About 15 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $35. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino FC-28 Soil Moisture Sensor: Wiring and Code

The circuit runs here once the page has loaded.

What you'll learn

  • How a resistive soil sensor works
  • Powering a sensor from a pin to save its probes
  • Analog and digital outputs from the same board
  • Turning a reading into a verdict with thresholds
  • Calibrating for your own soil
  • Difficulty: Beginner
  • Time: About 15 minutes
  • Board: Arduino Uno R3
  • Parts: 4

How it works

The FC-28 probe is two bare prongs. Wet soil between them conducts well and dry soil barely does. The board turns that into a voltage on A0: 1023 in the air, falling as the soil gets wetter. It also has a digital output on pin 2 that goes LOW once the soil is wet enough.

The sensor is powered from pin 7, not from 5V, and only for a short time. Once a second the sketch turns it on, waits 10 ms, reads both outputs and turns it off. Constant current through wet soil wears the prongs away. The LCD shows the number and a verdict: over 750 is Dry, 500 to 750 is Just right and under 500 is Wet.

Parts list

4 parts, plus the jumper wires. They retail for about $35; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Soil moisture sensor, resistive FC-28 pin VCCArduino Uno R3 pin 7
Soil moisture sensor, resistive FC-28 pin DOArduino Uno R3 pin 2
Character LCD with I2C backpack pin VCCArduino Uno R3 pin 5V
Soil moisture sensor, resistive FC-28 pin GNDArduino Uno R3 pin GND
Character LCD with I2C backpack pin GNDArduino Uno R3 pin GND
Soil moisture sensor, resistive FC-28 pin AOArduino Uno R3 pin A0
Character LCD with I2C backpack pin SDAArduino Uno R3 pin A4
Character LCD with I2C backpack pin SCLArduino Uno R3 pin A5

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Soil moisture meter: an FC-28 resistive soil sensor, powered from a pin
// only while it is read, and the moisture on a 16x2 I2C LCD.
//
//   pin 7   the sensor board's VCC (powered from the pin, see below)
//   GND     the sensor board's GND
//   A0      the board's AO          pin 2   the board's DO
//   LCD     SDA -> A4    SCL -> A5    VCC -> 5 V    GND -> GND
//
// The fork is two bare prongs, and the soil between them is a resistor: dry
// soil barely conducts, wet soil conducts well. The board puts the fork under
// a 10 k pull-up, so the reading is 1023 in the air and falls as the soil gets
// wetter. Its LM393 compares AO with the blue trimmer and pulls DO low, with
// its second lamp lit, once the soil is wet enough.
//
// # Why VCC is on a pin
//
// A current through wet soil all day is electrolysis: the prongs plate away
// in weeks. So the sensor is powered for a few milliseconds a reading, and
// off the rest of the time. Pin 7 can supply the few milliamps the board takes.
//
// # The numbers
//
// What a fork reads depends on the soil and the salts in it as much as on the
// water, so these bands are a starting point, from the tutorials: over 750 is
// dry, 500 to 750 is about right, under 500 is wet. Calibrate yours in dry and
// in just-watered soil.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const int POWER = 7;
const int AO = A0;
const int DO = 2;
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: soil moisture meter");
  pinMode(POWER, OUTPUT);
  digitalWrite(POWER, LOW);
  pinMode(DO, INPUT);
  lcd.init();
  lcd.backlight();
}

void loop() {
  // Power the sensor, let it settle, read it, and switch it off again.
  digitalWrite(POWER, HIGH);
  delay(10);
  int moisture = analogRead(AO);
  bool wet = digitalRead(DO) == LOW;
  digitalWrite(POWER, LOW);

  const char *verdict;
  if (moisture > 750) verdict = "Dry, water me";
  else if (moisture >= 500) verdict = "Just right";
  else verdict = "Wet";

  lcd.setCursor(0, 0);
  lcd.print("Soil: ");
  lcd.print(moisture);
  lcd.print(wet ? "  DO wet " : "  DO dry ");
  lcd.setCursor(0, 1);
  lcd.print(verdict);
  lcd.print("        ");

  Serial.print("Moisture: ");
  Serial.print(moisture);
  Serial.print("  DO: ");
  Serial.print(wet ? "LOW" : "HIGH");
  Serial.print("  ");
  Serial.println(verdict);
  delay(1000);
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Starts serial and sets POWER (pin 7) as an output, held LOW so the sensor is off. Pin 2 becomes an input for the board's digital output. Then it starts the LCD and its backlight.

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: soil moisture meter");
  pinMode(POWER, OUTPUT);
  digitalWrite(POWER, LOW);
  pinMode(DO, INPUT);
  lcd.init();
  lcd.backlight();
}

loop sketch.ino

Sets pin 7 HIGH, waits 10 ms, takes analogRead(AO), reads whether DO is LOW, and sets pin 7 LOW again. It compares the number with 750 and 500 for the verdict. The LCD shows the number, whether DO says wet or dry, and the verdict. It prints to serial and waits one second.

void loop() {
  // Power the sensor, let it settle, read it, and switch it off again.
  digitalWrite(POWER, HIGH);
  delay(10);
  int moisture = analogRead(AO);
  bool wet = digitalRead(DO) == LOW;
  digitalWrite(POWER, LOW);

  const char *verdict;
  if (moisture > 750) verdict = "Dry, water me";
  else if (moisture >= 500) verdict = "Just right";
  else verdict = "Wet";

  lcd.setCursor(0, 0);
  lcd.print("Soil: ");
  lcd.print(moisture);
  lcd.print(wet ? "  DO wet " : "  DO dry ");
  lcd.setCursor(0, 1);
  lcd.print(verdict);
  lcd.print("        ");

  Serial.print("Moisture: ");
  Serial.print(moisture);
  Serial.print("  DO: ");
  Serial.print(wet ? "LOW" : "HIGH");
  Serial.print("  ");
  Serial.println(verdict);
  delay(1000);
  // ... 1 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The verdict does not match your soil. This project
    The 750 and 500 limits are only a starting point. Soil type and salts change the reading, so test dry and just-watered soil and change the limits.
  • The reading is always 1023. This project
    That is the reading in the air. Push the prongs into the soil, and check VCC is wired to pin 7 and AO to A0.
  • Readings are inconsistent or the probe wears out fast. Soil moisture sensor
    The resistive probe corrodes when current flows all the time. Power the board only when you take a reading, such as from a digital pin, then switch it off.
  • The wetness value is the wrong way around, or DO never changes. Soil moisture sensor
    AO falls as the soil gets wetter. DO goes low when it passes the threshold set by the pot, so test for LOW. Calibrate dry and wet readings yourself, since the numbers vary by soil.
  • The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
    The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears.
  • The backlight is on but there is no text, or text is garbled. Character LCD with I2C backpack
    Wire SDA and SCL to the right pins (A4 and A5 on an Uno), power it from 5 V, and call lcd.init() and the backlight call in setup.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.