Skip to content

Embedded Systems

Build an Arduino greenhouse controller with hysteresis

An Arduino greenhouse controller reads a light sensor and a thermistor, and switches a grow light and a heater. The trick that makes it work is hysteresis: each output has an on threshold and a separate off threshold, and between them it keeps doing whatever it was doing. The grow light comes on below a reading of 300 and goes off above 600; the heater comes on below 18 °C and goes off above 22 °C.

2345678910111213A0A1A2A3A4A55VGNDLLearnEE Coregrow light: pin 9heater: pin 10light: A0−t°warmth: A1Serial monitorlight onheat on…
The greenhouse: a light sensor and a thermistor in, a grow light and a heater out, and a monitor that says each change

The parts

  • An LDR in a divider on A0: low readings mean dark
  • An NTC thermistor in a divider on A1: higher readings mean warmer
  • The grow light on pin 9 and the heater on pin 10, LEDs standing in for the real loads

Degrees from the thermistor

Two known points turn the raw count into degrees: 396 at 15 °C and 620 at 35 °C. map() draws a straight line between them:

map(455, 396, 620, 15, 35) = 15 + (455 − 396) × 20 over 224 ≈ 20 °C

A thermistor isn't quite a straight line, so this is good to a degree or two near the middle and worse beyond the two points. Always compare degrees with degrees: a raw reading is in the hundreds and would never be below 18.

Live experiment

Grow, little plant

A light sensor on A0 and a thermistor on A1. Pip is the grow light on pin 9, on below 300 and off above 600; the heater on pin 10 comes on below 18 °C and goes off above 22 °C. Make it night, then cold, then bring the day back, and watch the serial monitor.

Try it yourself: free lesson

“Project: Greenhouse” is the project that ends Signal Shore, Embedded Systems World 3. Free with an account. Lessons unlock in order, so you’ll do the 20 before it first.

The sketch

bool lightOn = false, heatOn = false;
bool lightWas = false, heatWas = false;

void setup() {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int light = analogRead(A0);
  int c = map(analogRead(A1), 396, 620, 15, 35);

  if (light < 300) lightOn = true;   // dark: on
  if (light > 600) lightOn = false;  // bright: off
  if (c < 18) heatOn = true;         // cold: on
  if (c > 22) heatOn = false;        // warm: off

  if (lightOn != lightWas) {         // say each change once
    Serial.println(lightOn ? "light on" : "light off");
    lightWas = lightOn;
  }
  if (heatOn != heatWas) {
    Serial.println(heatOn ? "heat on" : "heat off");
    heatWas = heatOn;
  }

  digitalWrite(9, lightOn);
  digitalWrite(10, heatOn);
}

Each output is a bool kept above setup(), so it remembers its state between passes. A reading inside the gap fires neither rule, so the state holds. At dusk, a reading of 390 keeps a light that was on lit until the morning pushes it past 600.

Say it once

Printing on every pass floods the serial monitor thousands of times a second. Keeping a copy of each bool from the last pass, and printing only when they differ, reports each change exactly once: light on, heat on, light off, heat off.

Free lesson

Now build it yourself

Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.

Try it yourself: free lesson

“Project: Greenhouse” is the project that ends Signal Shore, Embedded Systems World 3. Free with an account. Lessons unlock in order, so you’ll do the 20 before it first.

Or start from the very first lesson, no account needed

Practice questions

Three quick questions from the lessons. Pick an answer to check it.

  1. Question 1 of 3The grow light comes on below 300 and goes off above 600. Dusk reads 390 and the light was on. What happens?
    Show the answer

    Answer: It stays on. 390 is inside the gap, so neither rule fires and the bool keeps its value. The light stays on until the reading climbs past 600.

    • Why not “It turns off”? It turns off only above 600. At 390 neither rule fires, so the light keeps its last state, on.
    • Why not “It flickers”? The gap between 300 and 600 is there to stop flicker: inside it, nothing changes.
    • Why not “It turns off then on”? Neither rule fires at 390, so nothing switches at all, off or on.
  2. Question 2 of 3The light is on and the reading climbs to 500. The rules: below 300 set on = true; above 600 set on = false. What is on now?
    Show the answer

    Answer: true: 500 is below 600, so nothing changed. 500 fires neither rule, so on keeps its old value, true. Only a reading above 600 turns it off.

    • Why not “It depends on the previous reading”? It depends on the stored state, not the previous reading, and the stored state was true.
    • Why not “The program can't tell”? It can: on was true, and 500 fires neither rule, so it stays true.
    • Why not “false: 500 is above 300”? Being above 300 doesn't turn it off; only rising above 600 does. At 500 neither rule fires.
  3. Question 3 of 3Which comparison belongs in the thermostat?
    Show the answer

    Answer: if (c < 20). c is the temperature in degrees; reading is the raw count in the hundreds. Compare degrees to degrees. A reading is never below 20, so the other options heat forever or never.

    • Why not “if (c < 396)”? c is in degrees, around 15 to 35, so it's always below 396 and the heater would never switch off.
    • Why not “if (reading < 15)”? reading is a raw count in the hundreds, never below 15, so the heater would never come on.
    • Why not “if (reading < 20)”? reading is the raw count, in the hundreds, so it's never below 20 and the heater would never come on.
Keep practising: free lesson

“Project: Greenhouse” is the project that ends Signal Shore, Embedded Systems World 3. Free with an account. Lessons unlock in order, so you’ll do the 20 before it first.

Questions people ask

Why use two thresholds instead of one?

With one line, a reading that wobbles across it switches the output on and off many times a second, which flickers a light and wears out a relay. With a gap, the output keeps its state until the reading has clearly moved, so it switches once.

How wide should the gap be?

Wider than the noise and the normal wobble, narrower than you can tolerate. A 4 °C band for a greenhouse heater is gentle on the relay; for a fish tank you might want 1 °C, and a smoothed reading to go with it.

How do I switch a real heater or grow light?

Not from the pin. A small DC load can go through a logic-level MOSFET; a mains heater needs a relay module rated for its current, with the mains side built and enclosed by someone qualified.

Every topic