The if...else statement in Arduino C++ executes specific code blocks based on boolean conditions. However, when you move from blinking LEDs to controlling physical hardware like relays, contactors, or motors, a naive if/else threshold check will destroy your components through rapid switching (chatter). To build robust embedded systems, you must combine the if else statement in Arduino with state-tracking variables and hysteresis bands.
In this guide, we will build a dual-threshold temperature controller using an Arduino Nano V3 and a DHT22 sensor. We will cover the exact hardware, the compilable code with error handling, and a debugging framework for when your conditional logic fails on the bench.
Why Simple If/Else Logic Fails in Physical Projects
If you write if (temp > 25.0) { relayOn(); } else { relayOff(); }, you are creating a mechanical nightmare. If the ambient temperature hovers at 25.0°C, sensor noise (often ±0.2°C on cheap thermistors) will cause the Arduino to evaluate the condition as true, then false, dozens of times per second. This arcs your relay contacts, welds them shut, or burns out the optocoupler LED.
The solution is hysteresis: introducing a deadband between your turn-on and turn-off thresholds, tracked by a state variable. Instead of asking "Is it hot?", the logic asks "Is it hot enough to turn on, given its current state?"
Hardware BOM and Pin Mapping for the Nano V3
This build targets the Arduino Nano V3.0 (ATmega328P variant). Do not use the older ATmega168 variant, as it lacks the flash memory for robust string handling and some modern sensor libraries. The total BOM cost in 2026 is roughly $14.
| Component | Exact Variant / Spec | Est. Price | Notes |
|---|---|---|---|
| Microcontroller | Arduino Nano V3.0 (ATmega328P, 16MHz) | $6.00 | Ensure it has the CH340 or FT232RL USB chip for driver compatibility. |
| Sensor | DHT22 / AM2302 (Bare 4-pin module) | $4.50 | Superior to DHT11; reads down to 0.1°C and 0.1% RH. |
| Actuator | 5V Relay Module (1-Channel, Optocoupler) | $2.00 | Must be Active-LOW triggered (standard blue modules). |
| Resistor | 10kΩ (1/4W, 5% tolerance) | $0.10 | Required pull-up for the DHT22 data line if not on a pre-pulled PCB. |
Pin Mapping Table
| Component Pin | Arduino Nano Pin | Configuration |
|---|---|---|
| DHT22 VCC | 5V | Power |
| DHT22 Data | D2 | INPUT_PULLUP (with external 10kΩ to 5V) |
| DHT22 GND | GND | Common Ground |
| Relay VCC | 5V | Power (draws ~70mA when active) |
| Relay IN | D8 | OUTPUT (Active-LOW logic) |
| Relay GND | GND | Common Ground |
Complete Compilable Code with State-Change Handling
This code uses the Adafruit DHT Sensor Library. It implements non-blocking timing via millis() to respect the DHT22's mandatory 2-second polling interval, and uses a boolean state flag to enforce hysteresis.
#include <DHT.h>
// --- PIN DEFINITIONS ---
#define DHTPIN 2
#define DHTTYPE DHT22
#define RELAY_PIN 8
// --- THRESHOLDS (Hysteresis Band) ---
#define TEMP_HIGH 26.0 // Turn relay ON at 26.0°C
#define TEMP_LOW 24.0 // Turn relay OFF at 24.0°C
// --- TIMING ---
const unsigned long POLL_INTERVAL = 2000; // DHT22 max read rate is 0.5Hz
unsigned long lastReadTime = 0;
// --- STATE VARIABLES ---
bool relayState = false; // Tracks physical relay state to prevent redundant writes
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
// Initialize Relay Pin
pinMode(RELAY_PIN, OUTPUT);
// Active-LOW relay: HIGH means OFF, LOW means ON
digitalWrite(RELAY_PIN, HIGH);
// Initialize Sensor
dht.begin();
Serial.println("System Initialized. Monitoring temperature...");
}
void loop() {
unsigned long currentMillis = millis();
// Non-blocking delay for sensor polling
if (currentMillis - lastReadTime >= POLL_INTERVAL) {
lastReadTime = currentMillis;
float temp = dht.readTemperature();
// Error Handling: Check if sensor read failed
if (isnan(temp)) {
Serial.println("[ERROR] Failed to read from DHT22! Check wiring and pull-up.");
return; // Exit loop iteration early, do not actuate relay on bad data
}
Serial.print("Current Temp: ");
Serial.print(temp);
Serial.println(" °C");
// --- HYSTERESIS IF/ELSE LOGIC ---
if (temp >= TEMP_HIGH && relayState == false) {
relayState = true;
digitalWrite(RELAY_PIN, LOW); // Active LOW: Trigger relay
Serial.println("[ACTION] Threshold exceeded. Relay ENGAGED.");
}
else if (temp <= TEMP_LOW && relayState == true) {
relayState = false;
digitalWrite(RELAY_PIN, HIGH); // Active LOW: Release relay
Serial.println("[ACTION] Temp dropped. Relay DISENGAGED.");
}
// Implicit 'else': Temp is inside the deadband (24.0 < temp < 26.0).
// We do nothing, maintaining the current state.
}
}
Debugging: The First Three Things to Check When Logic Fails
When your if/else logic misbehaves, it is rarely a compiler issue and almost always a hardware-timing or syntax trap. Here is the ranked troubleshooting path.
1. The "Lvalue Required" or Silent Failure Trap
Exact Error String: error: lvalue required as left operand of assignment
Cause: You used a single equals sign = (assignment) instead of a double equals sign == (comparison) inside your if condition. For example: if (temp = 25.0). The compiler tries to assign 25.0 to temp, evaluates the result as "true" (non-zero), and permanently locks your logic into the if block.
Fix: Change to if (temp == 25.0). Better yet, use Yoda conditions (if (25.0 == temp)) which will throw a compile error if you accidentally use a single equals sign.
2. Sensor NaN (Not a Number) Ghosting
Symptom: The serial monitor prints nan and the relay never triggers, or triggers erratically.
Cause: The DHT22 requires a strict 2-second minimum between reads. If you put dht.readTemperature() directly in the loop() without a millis() gate, you are polling it 100,000 times a second. The sensor locks up and returns NaN.
Fix: Ensure your if (currentMillis - lastReadTime >= 2000) wrapper is intact. Additionally, verify the 10kΩ pull-up resistor is physically present on the DHT22 data line; the internal INPUT_PULLUP on the ATmega328P (approx 20kΩ-50kΩ) is often too weak for long wire runs.
3. Relay Chatter (Rapid Clicking)
Symptom: The relay clicks on and off rapidly when the temperature is near the threshold.
Cause: You are using a simple if (temp > 25) { on(); } else { off(); } without a state-tracking variable, or your hysteresis band (TEMP_HIGH minus TEMP_LOW) is smaller than the sensor's noise floor.
Fix: Implement the state-flag logic shown in the code block above. Widen the gap between TEMP_HIGH and TEMP_LOW to at least 2.0°C.
Extending the Build: Adding a Failsafe Timeout
A common flaw in basic if/else hardware projects is assuming the sensor will always return valid data. If the DHT22 data wire breaks, the code above safely ignores the NaN value. But what if the sensor gets stuck reporting a frozen value of 15.0°C while the actual room is overheating?
To extend this build, add a watchdog timeout. Track the last time the relay state changed. If the relay has been ON for more than 4 hours (14400000 ms), force it OFF and trigger a secondary alarm LED. This prevents catastrophic failure in applications like terrarium heaters or server room cooling fans.
Decision Path: Choosing the Right Conditional Structure
Do not default to nested if statements for every scenario. Use this decision matrix to select the correct structure for your embedded logic.
| Condition Type | Use Case | Code Structure | Concrete Example |
|---|---|---|---|
| Binary Action | Single threshold, no mechanical wear (e.g., toggling an LED). | Simple if |
if (button == LOW) { toggleLED(); } |
| Mutually Exclusive States | Two distinct outcomes based on one variable (e.g., battery charging vs discharging). | if / else |
if (voltage > 12.6) { charge(); } else { discharge(); } |
| Multi-Band Thresholds | Categorizing a sensor reading into discrete ranges (e.g., Cold, Warm, Hot). | if / else if / else |
if (t < 10) {...} else if (t < 20) {...} else {...} |
| Hysteresis / Stateful | Controlling physical actuators where chatter must be prevented. | if / else if with State Flags |
The exact code block provided in this guide. |
Final Recommendation: For any project involving relays, solenoids, or motors, bypass simple if/else structures entirely. Default immediately to the Hysteresis / Stateful pattern using boolean state flags and a minimum 1.5°C to 2.0°C deadband. It requires three extra lines of code but increases the physical lifespan of your actuator by orders of magnitude.






