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.
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:
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.
“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.
“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 neededPractice questions
Three quick questions from the lessons. Pick an answer to check it.
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.
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.
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.
“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.
Related topics
- What is hysteresis, and how does it stop flicker?On below one line, off above another: the gap that stops a noisy reading flickering.
- How to measure temperature with a thermistor and ArduinoAn NTC thermistor, a divider and two ways to turn readings into degrees.
- How to use a photoresistor (LDR) with ArduinoLight in, number out: an LDR, a divider and analogRead.
- Bang-bang (on-off) control vs PIDFully on or fully off: the thermostat's controller, its band, and its limits.