Build an automatic mini greenhouse

A greenhouse controller that waters dry soil with a relay, opens a roof vent with a servo when it gets hot, and shows everything on the LCD.
  • Level:Intermediate
  • Duration: 1 h 50 min
  • Ages 12+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build an automatic mini greenhouse – EEMENTOR STEM™ lesson

Commercial greenhouses are run by controllers that measure the air and the soil and then act: they water, open windows and switch on fans. In this capstone you build a small version. A soil sensor decides when to water, a DHT11 decides when to open the roof vent, a relay stands in for the water pump and a servo moves the vent. The LCD shows what the controller measures and what it's doing. You can run it on your desk, or build it into a shoebox or a clear plastic box with a real plant.

What you'll learn

  • How to turn sensor readings into automatic actions
  • Why controllers use two limits instead of one (hysteresis)
  • How to limit an action with timers so it can never run away
  • How to share one 5 V supply between a servo, a relay and a screen

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • 1602 LCD display, 10 kΩ potentiometer and 1 resistor, 220 Ω (UNO R3 Starter Kit)
  • DHT11 temperature and humidity sensor (UNO R3 Starter Kit)
  • Servo motor (UNO R3 Starter Kit)
  • Soil moisture module with probe (45-in-1 Sensor Kit)
  • Relay module (45-in-1 Sensor Kit)
  • A plant in a pot, and a glass of water for calibration
  • Optional: cardboard and tape for a roof vent
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Before you start

The LCD has 16 connections in a row. If yours has no pins, solder 16 pins from the 40-pin male header in the kit first, so it plugs into the breadboard. Ask an adult to help if you're new to soldering.

Safety: the relay is used here only as a switch that you can hear and see. If you later connect a pump, use a small battery-powered or USB-powered pump (3–6 V). Never connect mains electricity to the relay, and keep water away from the electronics.

Wiring

Breadboard wiring diagram: Build an automatic mini greenhouse
Breadboard view. Check each connection against the wiring table below.
From To
LCD VSS, RW and K GND
LCD VDD 5V
LCD V0 Middle pin (W) of the potentiometer
LCD RS UNO pin 12
LCD E UNO pin 11
LCD D4, D5, D6, D7 UNO pins 5, 4, 3, 2
LCD A (backlight +) 220 Ω resistor, then 5V
Potentiometer outer pins 5V and GND
DHT11 S (signal) UNO pin 7
DHT11 + and − 5V and GND
Soil module VCC UNO pin 8 (powers the sensor only while measuring)
Soil module GND GND
Soil module AO UNO pin A0
Soil module DO Not used
Relay module S UNO pin 6
Relay module + and − 5V and GND
Servo orange / yellow wire UNO pin 9
Servo red wire 5V
Servo brown / black wire GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

Build it in stages

This is a bigger build than the earlier lessons, so don't wire everything at once. Add one part, test it, then add the next. When something stops working, you know it's the part you just added.

  1. Screen and air sensor. Wire the LCD, the potentiometer and the DHT11, and upload the sketch. The top line shows temperature and humidity.
  2. Soil sensor. Add the soil module and calibrate it (see below).
  3. Roof vent. Add the servo. Warm the DHT11 gently between your fingers or with warm breath. To test without waiting, lower VENT_OPEN_AT for a moment to just above room temperature.
  4. Watering. Add the relay module. Take the probe out of the soil: after the first minute the relay clicks on for three seconds, and the screen shows "Watering".

The sketch

Install the DHT sensor library by Adafruit. The LiquidCrystal and Servo libraries are built in.

#include <LiquidCrystal.h>
#include <DHT.h>
#include <Servo.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
DHT dht(7, DHT11);
Servo vent;

const int SOIL_POWER = 8;
const int SOIL_PIN = A0;
const int RELAY_PIN = 6;
const int SERVO_PIN = 9;

// Calibrate these two for your own sensor (see "Calibrate the soil sensor")
const int DRY_READING = 1010;   // probe in dry air
const int WET_READING = 400;    // probe in a glass of water

const int WATER_BELOW = 35;                  // water when the soil is drier than this (%)
const unsigned long WATER_TIME = 3000;       // the pump runs for 3 seconds
const unsigned long SOAK_TIME = 60000;       // then wait 1 minute for the water to spread
const float VENT_OPEN_AT = 28.0;             // open the vent at this temperature
const float VENT_CLOSE_AT = 26.0;            // close it again at this one

bool ventOpen = false;
bool watering = false;
unsigned long lastRead = 0;
unsigned long waterStart = 0;
unsigned long lastWater = 0;
int waterCount = 0;

int readSoil() {
  digitalWrite(SOIL_POWER, HIGH);            // power on only while measuring
  delay(100);
  int reading = analogRead(SOIL_PIN);
  digitalWrite(SOIL_POWER, LOW);
  Serial.print(F("Soil reading: "));
  Serial.println(reading);
  return constrain(map(reading, DRY_READING, WET_READING, 0, 100), 0, 100);
}

void moveVent(bool open) {
  vent.attach(SERVO_PIN);
  vent.write(open ? 90 : 0);
  delay(600);                                // time to get there
  vent.detach();                             // no power to the motor while it rests
  ventOpen = open;
}

void setup() {
  Serial.begin(9600);
  pinMode(SOIL_POWER, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  lcd.begin(16, 2);
  dht.begin();
  lcd.print(F("Greenhouse"));
  moveVent(false);                           // start with the vent closed
}

void loop() {
  unsigned long now = millis();

  // Stop the pump on time, whatever else happens
  if (watering && now - waterStart >= WATER_TIME) {
    digitalWrite(RELAY_PIN, LOW);
    watering = false;
    lastWater = now;
  }

  if (now - lastRead < 2000) return;         // measure every 2 seconds
  lastRead = now;

  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  int soil = readSoil();

  if (isnan(temperature) || isnan(humidity)) {
    lcd.clear();
    lcd.print(F("Sensor error"));
    return;
  }

  // Roof vent: two limits, so it doesn't flap open and shut
  if (!watering) {
    if (temperature >= VENT_OPEN_AT && !ventOpen) moveVent(true);
    if (temperature <= VENT_CLOSE_AT && ventOpen) moveVent(false);
  }

  // Watering: a short burst, then a pause
  if (!watering && soil < WATER_BELOW && now - lastWater >= SOAK_TIME) {
    watering = true;
    waterStart = now;
    waterCount++;
    digitalWrite(RELAY_PIN, HIGH);
  }

  lcd.clear();
  lcd.print(F("T:"));
  lcd.print(temperature, 1);
  lcd.print(F("C H:"));
  lcd.print(humidity, 0);
  lcd.print(F("%"));
  lcd.setCursor(0, 1);
  if (watering) {
    lcd.print(F("Watering #"));
    lcd.print(waterCount);
  } else {
    lcd.print(F("Soil:"));
    lcd.print(soil);
    lcd.print(ventOpen ? F("% V:open") : F("% V:shut"));
  }
}

Calibrate the soil sensor

  1. Open the Serial Monitor at 9600 baud. It prints the soil reading every two seconds.
  2. Hold the probe in the air and note the reading. Put it in DRY_READING.
  3. Dip the probe in a glass of water up to the line where the metal ends and note the reading. Put it in WET_READING.
  4. Upload again and push the probe into the soil next to your plant.

How it works

Every two seconds the controller measures, decides and acts. That loop of measure, decide, act is how all automatic control works, from a room thermostat to a factory.

The vent uses two limits. It opens at 28 °C but closes only when the air is back down to 26 °C. If it used a single limit, the vent would open and close again and again while the temperature hovered around that number. The gap between the two limits is called hysteresis.

Watering is protected by two timers. The pump runs for only three seconds, and then the controller waits a full minute before it's allowed to water again. Water needs time to spread through the soil and reach the probe. Without the pause, the controller would keep pumping until the pot overflowed. Notice that the line that stops the pump is at the very top of loop(), before anything that could return early, so a sensor error can never leave the pump running.

The servo, the relay and the LCD backlight all share the UNO's 5 V supply. To keep the load low, the servo is switched off with detach() as soon as it has moved, and the vent never moves while the pump is on.

Try this next

  • Add the photoresistor module and switch on an LED "grow light" when it gets dark.
  • Show a warning on the LCD when the controller has watered more than five times: the water tank may be empty.
  • Open the vent halfway at 27 °C and fully at 30 °C.
  • Log the readings to an SD card with the code from the weather station capstone.

Troubleshooting

  • The screen lights up but shows only blocks or nothing: turn the potentiometer slowly from one end to the other.
  • The soil value is always 0 % or 100 %: calibrate again; your readings may be outside the default numbers.
  • The relay is on when it should be off: some relay modules switch on with LOW. Swap HIGH and LOW in the two relay lines and in setup().
  • The UNO restarts or the screen flickers when the servo moves: the USB port can't supply enough current. Use a different USB port or a powered hub, and check that the servo isn't blocked.
  • The vent never opens: the DHT11 reacts slowly. Check the temperature on the LCD and compare it with VENT_OPEN_AT.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit