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

ComponentExact Model / VariantWhy This Part?
MicrocontrollerArduino Uno R4 Minima (ABX00080)Native 32-bit float math; standard shield footprint.
SensorAdafruit AHT20 I2C Temp/Humidity (PID 4566)Factory calibrated, no external pull-ups required on breakout.
Display128x64 I2C OLED (SSD1306 driver, 0x3C addr)Low current draw (~20mA), clear text for debugging state.
Actuator5V 10A Relay Module (Opto-isolated, SRD-05VDC)Opto-isolation protects the R4 Minima from inductive kickback.
Wiring22 AWG solid core hook-up wireStandard for breadboard and screw terminal connections.

Pin Mapping Table

Component PinArduino Uno R4 Minima PinNotes
AHT20 SDAA4 (SDA)Do not use D18; A4 is the primary I2C data line on R4.
AHT20 SCLA5 (SCL)Primary I2C clock line.
OLED SDA/SCLA4 / A5 (Shared I2C Bus)I2C allows multiple devices on the same two pins.
Relay IN (Signal)D8Digital output; drives the opto-isolator LED.
Relay VCC / GND5V / GNDEnsure 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 TypeBest StructureMemory / Speed ImpactExample Use Case
Continuous ranges (e.g., temp > 25.5)if / else ifLow flash, fast execution. Short-circuit evaluation saves cycles.Sensor thresholds, PID control bands.
Discrete integer states (e.g., mode = 1, 2, 3)switch / caseCompiler optimizes to jump tables. Faster than chained ifs for >4 states.Menu navigation, state machines.
Complex multi-variable mappingLookup Tables (Arrays)High RAM/Flash usage, but O(1) execution time. Zero branching penalties.Thermistor Steinhart-Hart, LED gamma correction.
Default Pick: For analog sensor thresholds and hardware actuation (like turning on a fan based on temperature), use 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:

  1. Trailing Semicolon: You wrote if (temp > 28); { ... }. The semicolon terminates the if statement immediately, making the subsequent else orphaned and illegal. Fix: Remove the semicolon.
  2. Missing Braces: You omitted curly braces on a multi-line if block, causing the compiler to lose track of the scope before hitting else. Fix: Always use {} even for single-line conditions.
  3. 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:

  1. Premature Block Closure: You closed the if block's curly brace too early, or added an extra } before the else. Fix: Use your IDE's brace-matching highlighter (Ctrl+Shift+\ in Arduino IDE) to trace the scopes.
  2. Macro Interference: A #define macro expanded into code that broke the if/else chain. 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.

The First 3 Things to Check When the Build Fails:
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/else relay trigger with a PWM output to a MOSFET. Use map() inside the if block 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 a millis() 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/else evaluation 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.