Objective and use case
What you’ll build: You will build a standalone automated greenhouse vent controller that dynamically adjusts a physical ventilation flap via a servo motor based on ambient temperature. The system incorporates a manual hardware override and hysteresis logic to prevent mechanical oscillation.
Why it matters / Use cases
- Agricultural Automation: Regulate small-scale greenhouses autonomously, eliminating the need for constant human monitoring to prevent plant stress.
- Hysteresis Implementation: Utilize distinct upper and lower temperature thresholds (e.g., open flap at 26°C, close at 22°C) to stabilize the system and prevent servo wear from rapid toggling.
- Safety Overrides: Provide a hardware-based manual bypass (via limit switch) for immediate control during emergencies or routine maintenance.
Expected outcome
- A closed-loop system that drives a servo from 0° (closed) to 90° (open) with <50ms response latency when temperature thresholds are crossed.
- Smooth, jitter-free mechanical operation at temperature boundaries due to programmed hysteresis.
- Immediate mechanical response to the limit switch, preempting all automated sensor logic with near-zero latency.
Audience: Makers, agriculture tech students, and embedded developers; Level: Beginner to Intermediate
Architecture/flow: Temperature Sensor (Analog Input) ➔ Microcontroller (Hysteresis Logic & Hardware Interrupts) ➔ PWM Output ➔ Servo Motor Actuator
Educational validation note
Before publication, this case passed the Prometeo automated validation gate with status PASS. The validator checked the code blocks, article structure, copy/paste-safe commands and consistency with the supported device catalog.
Published validation evidence
- Automatic result: PASS.
- Parsed structure: 3 sections, 3 tables and 2 code blocks detected before publication.
- Checked code: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
- Supported catalog: the article text was checked against Prometeo’s validation-capable device profiles, and unsupported stacks block publication.
- Report findings: no blocking findings.
This validation confirms syntax and tool compatibility for the published material, but it does not replace physical testing on your exact hardware, wiring and runtime environment.
Educational safety note
This project is a low-voltage educational prototype, not a certified product. Before powering the setup, verify the wiring of your Arduino UNO R3, avoid shorting 5 V, GND or digital pins, disconnect power before changing connections, and use proper interface modules for relays, motors or external loads.
Prerequisites
To successfully complete this tutorial, you should have:
* A basic understanding of how to use the command line/terminal on your operating system.
* The Arduino CLI (Command Line Interface) installed on your computer.
* Basic familiarity with breadboarding and jumper wire connections.
* A standard USB Type-B cable to connect the Arduino UNO to your computer.
Materials
For this project, you must use EXACTLY this device model and component list:
* Microcontroller: Arduino UNO R3 (ATmega328P)
* Actuator: SG90 micro servo motor
* Sensor: LM35 analog temperature sensor (TO-92 package)
* Input Device: Limit switch (standard microswitch with a roller lever or basic push button)
* Accessories: 1x Solderless breadboard, assorted male-to-male jumper wires.
Note: No external resistors are required for the limit switch because we will utilize the ATmega328P’s internal pull-up resistors via software.
Setup/Connection
Proper wiring is critical for the stability of analog readings and servo movements. The LM35 provides a linear analog voltage output proportional to the temperature (10mV per degree Celsius). The SG90 servo is controlled via Pulse Width Modulation (PWM), and the limit switch uses a simple digital input.
Wiring Table
| Component | Pin / Wire Color | Arduino UNO R3 Pin | Function / Notes |
|---|---|---|---|
| LM35 | Pin 1 (Left, flat face up) | 5V | Power supply for the temperature sensor. |
| LM35 | Pin 2 (Middle) | A0 | Analog output signal (10mV/°C). |
| LM35 | Pin 3 (Right) | GND | Ground reference. |
| SG90 Servo | Red Wire | 5V | Power supply for the servo motor. |
| SG90 Servo | Brown / Black Wire | GND | Ground reference. |
| SG90 Servo | Orange / Yellow Wire | D9 | PWM signal to control servo angle. |
| Limit Switch | COM (Common) | GND | Ground reference for the switch. |
| Limit Switch | NO (Normally Open) | D2 | Digital input. Pulled HIGH internally; goes LOW when pressed. |
Connection Instructions
- Power Distribution: Connect the 5V pin from the Arduino to the positive rail of your breadboard. Connect the GND pin from the Arduino to the negative rail.
- LM35 Sensor: Insert the LM35 into the breadboard. With the flat side facing you, connect the left pin to the 5V rail, the right pin to the GND rail, and the center pin directly to Arduino analog pin A0.
- SG90 Servo: Connect the servo’s power wires (Red to 5V, Brown/Black to GND) to the breadboard rails. Connect the signal wire (Orange/Yellow) to Arduino digital pin D9.
- Limit Switch: Connect the Common (COM) terminal of the limit switch to the GND rail. Connect the Normally Open (NO) terminal to Arduino digital pin D2.
Validated Code
The following section contains the complete, compilable Arduino sketch and a Bash shell script used to automate the build and upload process using Arduino CLI.
Arduino Sketch: greenhouse_vent.ino
Create a directory named greenhouse_vent and save this code inside it as greenhouse_vent.ino.
Public preview of the validated file. The complete source is shown to members and in PDF/Print.
/*
* Greenhouse Vent Servo Controller
* Device: Arduino UNO R3 (ATmega328P) + SG90 servo + LM35 temperature sensor + limit switch
*
* Description: Reads temperature from LM35. Opens vent (servo 90 deg) if temp >= 28C.
* Closes vent (servo 0 deg) if temp <= 25C.
* A limit switch on D2 acts as a manual override to force the vent open.
*/
#include <Servo.h>
// Pin Definitions
const int lm35Pin = A0;
const int limitSwitchPin = 2;
const int servoPin = 9;
// Servo Object
Servo ventServo;
// Configuration Constants
const float TEMP_OPEN_THRESHOLD = 28.0;
const float TEMP_CLOSE_THRESHOLD = 25.0;
const int ANGLE_CLOSED = 0;
const int ANGLE_OPEN = 90;
// Timing Variables for non-blocking execution
unsigned long lastUpdateMillis = 0;
const unsigned long UPDATE_INTERVAL_MS = 1000;
// State Tracking
bool ventIsOpen = false;
void setup() {
// Initialize Serial Monitor
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect
}
// Configure Pins
// Internal pull-up ensures the pin reads HIGH when the switch is unpressed.
// When pressed, the switch connects the pin to GND, reading LOW.
pinMode(limitSwitchPin, INPUT_PULLUP);
// Attach and initialize servo to closed position
ventServo.attach(servoPin);
ventServo.write(ANGLE_CLOSED);
Serial.println("========================================");
Serial.println("Greenhouse Vent Controller Initialized");
Serial.println("========================================");
}
// .../*
* Greenhouse Vent Servo Controller
* Device: Arduino UNO R3 (ATmega328P) + SG90 servo + LM35 temperature sensor + limit switch
*
* Description: Reads temperature from LM35. Opens vent (servo 90 deg) if temp >= 28C.
* Closes vent (servo 0 deg) if temp <= 25C.
* A limit switch on D2 acts as a manual override to force the vent open.
*/
#include <Servo.h>
// Pin Definitions
const int lm35Pin = A0;
const int limitSwitchPin = 2;
const int servoPin = 9;
// Servo Object
Servo ventServo;
// Configuration Constants
const float TEMP_OPEN_THRESHOLD = 28.0;
const float TEMP_CLOSE_THRESHOLD = 25.0;
const int ANGLE_CLOSED = 0;
const int ANGLE_OPEN = 90;
// Timing Variables for non-blocking execution
unsigned long lastUpdateMillis = 0;
const unsigned long UPDATE_INTERVAL_MS = 1000;
// State Tracking
bool ventIsOpen = false;
void setup() {
// Initialize Serial Monitor
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect
}
// Configure Pins
// Internal pull-up ensures the pin reads HIGH when the switch is unpressed.
// When pressed, the switch connects the pin to GND, reading LOW.
pinMode(limitSwitchPin, INPUT_PULLUP);
// Attach and initialize servo to closed position
ventServo.attach(servoPin);
ventServo.write(ANGLE_CLOSED);
Serial.println("========================================");
Serial.println("Greenhouse Vent Controller Initialized");
Serial.println("========================================");
}
void loop() {
unsigned long currentMillis = millis();
// Execute control logic at defined intervals
if (currentMillis - lastUpdateMillis >= UPDATE_INTERVAL_MS) {
lastUpdateMillis = currentMillis;
// 1. Read Manual Override Limit Switch
// LOW means the switch is pressed (override active)
bool overrideActive = (digitalRead(limitSwitchPin) == LOW);
// 2. Read and Calculate Temperature from LM35
int rawADC = analogRead(lm35Pin);
// The Arduino UNO has a 10-bit ADC (0-1023) and operates at 5.0V.
// Voltage = (ADC Value / 1024.0) * 5.0
float voltage = rawADC * (5.0 / 1024.0);
// LM35 outputs 10mV per degree Celsius (0.01V/C)
// Temperature (C) = Voltage / 0.01 = Voltage * 100.0
float temperatureC = voltage * 100.0;
// 3. Determine Target Vent State
if (overrideActive) {
// Manual override forces the vent open
ventIsOpen = true;
} else {
// Temperature-based hysteresis control
if (temperatureC >= TEMP_OPEN_THRESHOLD) {
ventIsOpen = true;
} else if (temperatureC <= TEMP_CLOSE_THRESHOLD) {
ventIsOpen = false;
}
// If temperature is between 25.0 and 28.0, ventIsOpen remains unchanged.
}
// 4. Actuate Servo
if (ventIsOpen) {
ventServo.write(ANGLE_OPEN);
} else {
ventServo.write(ANGLE_CLOSED);
}
// 5. Log System State
Serial.print("Temp: ");
Serial.print(temperatureC, 1);
Serial.print(" C | Override: ");
Serial.print(overrideActive ? "ACTIVE " : "STANDBY");
Serial.print(" | Vent State: ");
Serial.println(ventIsOpen ? "OPEN (90 deg)" : "CLOSED (0 deg)");
}
}
Automation Script: build_and_upload.sh
Save this file in the parent directory of greenhouse_vent (or adjust paths accordingly). This script ensures the Arduino AVR core is installed, compiles the code, and uploads it to the board.
#!/bin/bash
# Define the FQBN for Arduino UNO R3
FQBN="arduino:avr:uno"
# Define the serial port (Change this to match your system, e.g., /dev/ttyACM0 or COM3)
PORT="/dev/ttyACM0"
# Define the sketch directory
SKETCH_DIR="greenhouse_vent"
echo "Updating Arduino CLI core index..."
arduino-cli core update-index
echo "Installing Arduino AVR core..."
arduino-cli core install arduino:avr
echo "Compiling sketch..."
arduino-cli compile --fqbn $FQBN $SKETCH_DIR
if [ $? -eq 0 ]; then
echo "Compilation successful. Uploading to $PORT..."
arduino-cli upload --fqbn $FQBN --port $PORT $SKETCH_DIR
if [ $? -eq 0 ]; then
echo "Upload complete! Open serial monitor at 9600 baud."
else
echo "Upload failed. Please check the PORT and connection."
fi
else
echo "Compilation failed. Please check the source code."
fi
Build/Flash/Run commands
To deploy the code to your Arduino UNO R3, you will use the Arduino CLI. Below are the exact commands used by the automation script, which you can also run manually.
Command Reference Table
| Action | Command |
|---|---|
| Update core index | arduino-cli core update-index |
| Install AVR core | arduino-cli core install arduino:avr |
| Compile sketch | arduino-cli compile --fqbn arduino:avr:uno greenhouse_vent |
| Upload to board | arduino-cli upload --fqbn arduino:avr:uno --port <PORT> greenhouse_vent |
| Monitor output | arduino-cli monitor --port <PORT> --config baudrate=9600 |
Numbered Workflow
- Connect your Arduino UNO R3 to your computer via the USB cable.
- Identify your serial port. On Linux, this is typically
/dev/ttyACM0or/dev/ttyUSB0. On Windows, it will be a COM port likeCOM3. You can find it by runningarduino-cli board list. - Open your terminal and navigate to the directory containing your
greenhouse_ventfolder. - Run the compilation command:
arduino-cli compile --fqbn arduino:avr:uno greenhouse_vent. - Run the upload command, replacing
<PORT>with your actual port:arduino-cli upload --fqbn arduino:avr:uno --port /dev/ttyACM0 greenhouse_vent. - Start the serial monitor to view the logs:
arduino-cli monitor --port /dev/ttyACM0 --config baudrate=9600.
Step-by-step Validation
Once the code is uploaded and the serial monitor is running, perform the following validation checkpoints to ensure the logic and hardware are functioning correctly.
Checkpoint 1: Initialization and Idle State
- Action: Observe the serial monitor immediately after uploading, with the room temperature below 25°C.
- Expected Observation: The serial monitor prints the initialization banner. The servo moves to the 0-degree position. Logs indicate a temperature below 25.0°C, Override: STANDBY, and Vent State: CLOSED.
- Pass Condition: Servo is physically at the zero position and logs reflect the closed state accurately.
Checkpoint 2: Heating Up (Crossing Upper Threshold)
- Action: Gently pinch the LM35 sensor between your fingers to raise its temperature. Watch the serial monitor.
- Expected Observation: The logged temperature will steadily rise. Once it hits 28.0°C or higher, the Vent State changes to OPEN.
- Pass Condition: The SG90 servo physically rotates 90 degrees immediately when the log shows a temperature $\ge$ 28.0°C.
Checkpoint 3: Hysteresis Verification (Cooling Down)
- Action: Let go of the LM35 and allow it to cool. Observe the logs as the temperature falls between 27.9°C and 25.1°C.
- Expected Observation: The temperature drops, but the Vent State remains OPEN. The servo does not move.
- Pass Condition: The system maintains its current state while in the hysteresis deadband, preventing rapid mechanical oscillation.
Checkpoint 4: Crossing Lower Threshold
- Action: Continue letting the sensor cool (or blow gently on it) until the temperature drops to 25.0°C or below.
- Expected Observation: The log updates the Vent State to CLOSED.
- Pass Condition: The servo physically rotates back to the 0-degree position.
Checkpoint 5: Manual Override Activation
- Action: While the temperature is below 25°C (vent normally closed), press and hold the limit switch.
- Expected Observation: The next serial log (within 1 second) shows Override: ACTIVE and Vent State: OPEN.
- Pass Condition: The servo immediately moves to 90 degrees despite the cold temperature. Releasing the switch should return the servo to 0 degrees on the next cycle.
Troubleshooting
If your prototype is not behaving as expected, consult the table below for common issues and their solutions.
| Symptom | Likely Cause | Fix |
|---|---|---|
| LM35 reads ~0°C or ~500°C constantly | Sensor wired backwards or floating ground. | Disconnect power immediately. Check the flat face of the LM35. Left is 5V, Right is GND, Center is A0. Re-seat the jumper wires. |
| Temperature readings fluctuate wildly (±5°C) |
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.




