The Hardware: Arduino Uno R4 Minima Environmental Decision Node
The if/else statement in Arduino C++ evaluates boolean conditions to route execution. However, in embedded systems, naive if/else blocks cause relay chatter, blocking bugs, and state thrashing. To use them correctly, you must implement hysteresis and non-blocking checks. We will demonstrate this by building a dual-threshold climate controller that triggers a cooling fan based on precise temperature bands.
For this build, we are targeting the Arduino Uno R4 Minima. Unlike the legacy ATmega328P-based Uno R3, the R4 Minima uses a 32-bit ARM Cortex-M4 (Renesas RA4M1) running at 48 MHz. This matters for if/else logic because the 32-bit architecture handles floating-point math (like sensor thresholds) natively in hardware, eliminating the massive execution delay that software-emulated floats cause on older 8-bit boards.
Parts List & Exact Variants
| Component | Exact Model / Variant | Why This Part? |
|---|---|---|
| Microcontroller | Arduino Uno R4 Minima (ABX00080) | Native 32-bit float math; standard shield footprint. |
| Sensor | Adafruit AHT20 I2C Temp/Humidity (PID 4566) | Factory calibrated, no external pull-ups required on breakout. |
| Display | 128x64 I2C OLED (SSD1306 driver, 0x3C addr) | Low current draw (~20mA), clear text for debugging state. |
| Actuator | 5V 10A Relay Module (Opto-isolated, SRD-05VDC) | Opto-isolation protects the R4 Minima from inductive kickback. |
| Wiring | 22 AWG solid core hook-up wire | Standard for breadboard and screw terminal connections. |
Pin Mapping Table
| Component Pin | Arduino Uno R4 Minima Pin | Notes |
|---|---|---|
| AHT20 SDA | A4 (SDA) | Do not use D18; A4 is the primary I2C data line on R4. |
| AHT20 SCL | A5 (SCL) | Primary I2C clock line. |
| OLED SDA/SCL | A4 / A5 (Shared I2C Bus) | I2C allows multiple devices on the same two pins. |
| Relay IN (Signal) | D8 | Digital output; drives the opto-isolator LED. |
| Relay VCC / GND | 5V / GND | Ensure the R4's 5V rail can supply the relay coil (~70mA). |
Decision Tree: When to Use If/Else vs. Switch/Case vs. Lookup Tables
Beginners often default to if/else for every decision. On a microcontroller, choosing the wrong control structure wastes CPU cycles and flash memory. Use this decision path to select the right logic structure for your embedded project.
| Condition Type | Best Structure | Memory / Speed Impact | Example Use Case |
|---|---|---|---|
| Continuous ranges (e.g., temp > 25.5) | if / else if | Low flash, fast execution. Short-circuit evaluation saves cycles. | Sensor thresholds, PID control bands. |
| Discrete integer states (e.g., mode = 1, 2, 3) | switch / case | Compiler optimizes to jump tables. Faster than chained ifs for >4 states. | Menu navigation, state machines. |
| Complex multi-variable mapping | Lookup Tables (Arrays) | High RAM/Flash usage, but O(1) execution time. Zero branching penalties. | Thermistor Steinhart-Hart, LED gamma correction. |
if/else with hysteresis. It is the most readable, easily debuggable, and memory-efficient method for range-based hardware decisions.
The Code: Compilable If/Else Logic with Hysteresis and Error Handling
The most common mistake when using if/else for physical actuators is setting the turn-on and turn-off thresholds to the exact same value. If the fan turns on at 28.0°C, the temperature drops to 27.9°C, the fan turns off, the temp rises to 28.0°C, and the fan turns back on. This rapid cycling (chatter) will destroy a relay within hours. We solve this with a hysteresis band.
Board Target: Arduino Uno R4 Minima. Requires Adafruit AHTX0 and Adafruit SSD1306 libraries via the Library Manager.
#include <Wire.h>
#include <Adafruit_AHTX0.h>
#include <Adafruit_SSD1306.h>
// --- PIN DEFINITIONS ---
#define PIN_RELAY 8
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
// --- HYSTERESIS THRESHOLDS ---
#define TEMP_HIGH_THRESH 28.0 // Turn fan ON at or above this temp
#define TEMP_LOW_THRESH 26.0 // Turn fan OFF at or below this temp
Adafruit_AHTX0 aht;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool fanState = false; // Track physical state to avoid redundant GPIO writes
void setup() {
Serial.begin(115200);
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, LOW); // Ensure relay is off at boot
// Error Handling: Sensor Initialization
if (!aht.begin()) {
Serial.println("FATAL: AHT20 init failed. Check I2C wiring and pull-ups.");
while (1) { delay(10); } // Halt execution safely
}
// Error Handling: Display Initialization
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("FATAL: SSD1306 allocation failed"));
for(;;); // Halt execution
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.display();
}
void loop() {
sensors_event_t humidity, temp;
aht.getEvent(&humidity, &temp);
// Error Handling: Check for NaN (Not a Number) from sensor read failure
if (isnan(temp.temperature)) {
Serial.println("ERROR: Sensor read returned NaN. Resetting I2C bus.");
// Advanced recovery could re-initialize Wire here
delay(1000);
return;
}
// --- CORE IF/ELSE LOGIC WITH HYSTERESIS ---
if (temp.temperature >= TEMP_HIGH_THRESH && !fanState) {
digitalWrite(PIN_RELAY, HIGH);
fanState = true;
Serial.println("Decision: Temp HIGH -> Fan ON");
}
else if (temp.temperature <= TEMP_LOW_THRESH && fanState) {
digitalWrite(PIN_RELAY, LOW);
fanState = false;
Serial.println("Decision: Temp LOW -> Fan OFF");
}
else {
// Hysteresis band: Temp is between 26.0 and 28.0.
// Do nothing. Maintain current fanState.
}
// --- UI UPDATE ---
display.clearDisplay();
display.setCursor(0,0);
display.print("Temp: "); display.print(temp.temperature, 1); display.println(" C");
display.print("Hum: "); display.print(humidity.relative_humidity, 1); display.println(" %");
display.print("Fan: "); display.println(fanState ? "RUNNING" : "IDLE");
display.display();
delay(1000); // Non-ideal for production, but acceptable for 1Hz thermal sampling
}
Debugging: Syntax Errors and Logic Failures
When your if/else logic fails, it usually falls into two categories: compiler syntax errors or runtime logical failures. Here is how to debug the most common issues.
1. Compiler Error: error: expected primary-expression before 'else'
Ranked Causes:
- Trailing Semicolon: You wrote
if (temp > 28); { ... }. The semicolon terminates theifstatement immediately, making the subsequentelseorphaned and illegal. Fix: Remove the semicolon. - Missing Braces: You omitted curly braces on a multi-line
ifblock, causing the compiler to lose track of the scope before hittingelse. Fix: Always use{}even for single-line conditions. - Stray Characters: A hidden character or typo inside the condition parentheses. Fix: Retype the condition manually.
2. Compiler Error: error: 'else' without a previous 'if'
Ranked Causes:
- Premature Block Closure: You closed the
ifblock's curly brace too early, or added an extra}before theelse. Fix: Use your IDE's brace-matching highlighter (Ctrl+Shift+\ in Arduino IDE) to trace the scopes. - Macro Interference: A
#definemacro expanded into code that broke theif/elsechain. Fix: Check your preprocessor definitions.
3. Runtime Failure: Relay Chatter (Rapid Clicking)
If the code compiles but the relay clicks on and off rapidly when the temperature hovers around your threshold, your logic lacks hysteresis. Fix: Implement the dual-threshold if / else if structure shown in the code block above, ensuring a minimum 1.0°C gap between HIGH and LOW thresholds.
1. Semicolons after conditions: Scan every
if line for an accidental trailing ;.2. I2C Address Conflicts: Run an I2C scanner sketch. Both the AHT20 and SSD1306 must show up (usually 0x38 and 0x3C). If one is missing, check your SDA/SCL wiring.
3. Float Precision: If comparing floats, never use
==. Always use >= or <= due to floating-point rounding errors in C++.
Extending and Simplifying the Build
Once the core if/else hysteresis logic is stable, you can adapt the project for different environments.
How to Extend the Build
- Add Proportional Control: Replace the simple
if/elserelay trigger with a PWM output to a MOSFET. Usemap()inside theifblock to scale fan speed from 0-255 based on how far the temperature exceeds the threshold. - Implement Non-Blocking Timing: The
delay(1000)at the end of the loop blocks the CPU. For production firmware, replace it with amillis()based timer so the microcontroller can handle button inputs or WiFi MQTT publishing concurrently. - Deep Sleep Integration: If moving to a battery-powered ESP32, wrap the
if/elseevaluation in a wake routine, trigger the relay via a latching circuit, and return to deep sleep to achieve multi-year battery life.
How to Simplify the Build
- Drop the OLED: If you only need the physical actuation, remove the display code. This frees up roughly 20KB of flash memory and eliminates I2C bus contention.
- Use a Comparator IC: If you don't need a microcontroller at all, you can replace the Arduino entirely with an LM393 comparator circuit, using a potentiometer to set the physical voltage threshold. However, you lose the ability to easily program hysteresis without adding complex resistor feedback networks.
For deeper reading on C++ control structures in embedded environments, refer to the official Arduino Language Reference for Control Structures and the Adafruit AHT20 Integration Guide for I2C sensor best practices.






