Difficulty: Intermediate (Mains voltage involved) | Time: 45 Minutes | Cost: ~$15 USD

If you are using a 5V microcontroller like the Arduino Uno, Nano, or Mega, the concrete pick for your project is a 5V Opto-isolated Relay Module featuring the Songle SRD-05VDC-SL-C. If you are using a 3.3V board like the ESP32 or Raspberry Pi Pico, you must use a 3.3V-specific relay module or drive a 5V module via a logic-level MOSFET. Cheap generic 5V relay boards will not trigger reliably from a 3.3V GPIO pin due to the forward voltage drop of the internal optocoupler LED.

This guide walks through the exact hardware selection, safe mains wiring, and robust C++ code required to switch AC loads without frying your microcontroller or encountering silent hardware failures.

The Right Arduino Relay Board for Your Logic Level

The most common mistake makers make is buying a standard 5V relay module and trying to drive it directly from an ESP32. The internal optocoupler requires roughly 1.1V to 1.2V forward voltage drop, plus the voltage needed to push current through the current-limiting resistor. A 3.3V pin simply cannot source enough current to trigger the coil. Use this decision path to select your hardware:

Microcontroller Logic Load Requirements Concrete Hardware Pick
5V (Uno, Mega, Nano) < 10A, < 250VAC (Resistive) Default: 5V Opto-isolated Module (Songle SRD-05VDC-SL-C)
3.3V (ESP32, Pi Pico) < 10A, < 250VAC (Resistive) Default: 3.3V Opto-isolated Module OR 5V Module + IRLZ44N MOSFET driver
Any (5V or 3.3V) > 10A, or Inductive (Motors) Default: Solid State Relay (SSR) module (e.g., Omron G3MB-202P) or Mechanical Contactor
Mains Safety Warning: Switching voltages above 50V AC or 120V DC can be lethal. Always de-energize the circuit, verify dead with a known-working multimeter, and ensure all AC connections are housed in a grounded, non-conductive enclosure per NFPA 70 (NEC) guidelines. If you are unsure about mains wiring, consult a licensed electrician.

Hardware Spec Sheet and Pin Mapping

The code and wiring below target the Arduino Uno R3 paired with a standard 2-Channel 5V Opto-isolated Relay Module. This module uses an EL817 optocoupler to electrically isolate your microcontroller's sensitive logic from the relay coil's inductive kickback.

Songle SRD-05VDC-SL-C Relay Module Specifications
Parameter Value / Rating Practical Implication
Coil Voltage 5V DC Must be supplied from Arduino 5V pin or external 5V source.
Coil Resistance ~70 Ω Draws ~70mA when active. Exceeds Arduino GPIO 20mA limit; requires onboard transistor.
Contact Rating (Resistive) 10A @ 250VAC / 30VDC Do not exceed 8A continuous for safety margin. Derate for inductive loads.
Trigger Logic Active LOW GPIO must be pulled to GND (LOW) to energize the coil. HIGH turns it off.

Pin Mapping Table

Arduino Uno R3 Pin Relay Module Pin Wire Color (Recommended) Function
5V VCC Red Logic power for optocoupler LEDs
GND GND Black Common ground reference
Digital Pin 8 IN1 Yellow Channel 1 Trigger (Active LOW)
Digital Pin 9 IN2 Green Channel 2 Trigger (Active LOW)
The JD-VCC Jumper Explained: Most 2-channel and 4-channel boards have a jumper labeled JD-VCC. Leaving it on powers the relay coils from the Arduino's 5V rail. For true optical isolation and to protect your Uno's voltage regulator from coil noise, remove the jumper. Connect an external 5V power supply to the JD-VCC pin and its dedicated GND pin on the relay side, while keeping the Arduino 5V connected only to the VCC pin on the logic side.

Step-by-Step Wiring and Compilable Code

This sketch implements a non-blocking, Serial-controlled relay switch. It includes explicit pin definitions, safe boot states (relays default to OFF), and error handling for invalid Serial commands.

Numbered Wiring Steps

  1. Disconnect all power. Ensure the Arduino is unplugged and the AC load is disconnected from the mains.
  2. Wire the logic side. Connect Arduino 5V to VCC, GND to GND, Pin 8 to IN1, and Pin 9 to IN2.
  3. Wire the AC load. Cut the Live (Hot) wire of your AC appliance. Connect the incoming Live wire to the COM (Common) terminal of Relay 1. Connect the outgoing Live wire to the NO (Normally Open) terminal. Do not touch the NC terminal.
  4. Verify continuity. Use a multimeter in continuity mode. With the Arduino off, COM and NO should read open (OL).
  5. Upload the code below, then apply AC power and test via the Serial Monitor.

Complete C++ Code (Targets Arduino Uno R3)


/*
 * Arduino 2-Channel Relay Serial Controller
 * Target: Arduino Uno R3 (5V Logic)
 * Hardware: 5V Opto-isolated Relay Module (Active LOW)
 */

// --- Pin Definitions ---
#define RELAY_1_PIN 8
#define RELAY_2_PIN 9
#define BAUD_RATE 115200

// Relay states (Active LOW means HIGH = OFF, LOW = ON)
#define RELAY_OFF HIGH
#define RELAY_ON LOW

void setup() {
  Serial.begin(BAUD_RATE);
  
  // Initialize pins as outputs
  pinMode(RELAY_1_PIN, OUTPUT);
  pinMode(RELAY_2_PIN, OUTPUT);
  
  // CRITICAL: Set safe default state (OFF) immediately upon boot
  digitalWrite(RELAY_1_PIN, RELAY_OFF);
  digitalWrite(RELAY_2_PIN, RELAY_OFF);
  
  Serial.println("System Ready. Commands: '1ON', '1OFF', '2ON', '2OFF'");
}

void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    command.trim(); // Remove whitespace and newline characters
    command.toUpperCase();
    
    processCommand(command);
  }
}

void processCommand(String cmd) {
  if (cmd == "1ON") {
    digitalWrite(RELAY_1_PIN, RELAY_ON);
    Serial.println("ACK: Relay 1 ENGAGED");
  } 
  else if (cmd == "1OFF") {
    digitalWrite(RELAY_1_PIN, RELAY_OFF);
    Serial.println("ACK: Relay 1 DISENGAGED");
  } 
  else if (cmd == "2ON") {
    digitalWrite(RELAY_2_PIN, RELAY_ON);
    Serial.println("ACK: Relay 2 ENGAGED");
  } 
  else if (cmd == "2OFF") {
    digitalWrite(RELAY_2_PIN, RELAY_OFF);
    Serial.println("ACK: Relay 2 DISENGAGED");
  } 
  else {
    // Error handling for invalid commands
    Serial.print("ERR: Invalid command '");
    Serial.print(cmd);
    Serial.println("'. Use 1ON, 1OFF, 2ON, 2OFF.");
  }
}

Note: For more on configuring GPIO behavior, refer to the official Arduino pinMode() documentation.

Debugging: Exact Errors and Ranked Causes

When working with mechanical relays, failures usually fall into two categories: compilation errors from sloppy C++ macros, or hardware failures caused by misunderstanding the module's internal schematic.

Software Error: Compilation Failure

Exact Error String: exit status 1: 'RELAY_1_PIN' was not declared in this scope

Ranked Causes:

  1. Missing or misspelled #define: You typed RELAY1_PIN in the code but defined it as RELAY_1_PIN at the top. C++ macros are strictly case-sensitive and underscore-sensitive.
  2. Scope violation: You placed the #define statement inside the setup() function instead of at the global level at the top of the sketch.
  3. Library conflict: A third-party library you included is undefining or clashing with your macro name. Rename your pin definitions to something highly specific like PIN_RELAY_CH1.

Hardware Symptom: "Relay Clicks But Load Doesn't Switch"

You hear the mechanical snap of the Songle relay, the LED on the module lights up, but your AC lamp or motor remains dead. Here are the first three things to check, in order of likelihood:

  1. Wrong Terminal Selection (COM vs NC vs NO): The most common error. If you wired your load to NC (Normally Closed), the circuit is closed when the Arduino is OFF, and opens when you trigger the relay. Move the load wire to the NO (Normally Open) terminal. Verify with a multimeter in continuity mode: COM to NO should only beep when the relay is actively triggered.
  2. Pitted or Welded Contacts: If you previously used this relay to switch a high-inrush inductive load (like a large motor or a transformer) without a snubber circuit, the internal contacts may have arced and pitted, creating a high-resistance connection. The relay moves, but current cannot pass. Fix: Replace the relay module.
  3. Active LOW Logic Inversion: If your relay clicks immediately upon powering the Arduino, and turns off when you send the "ON" command, your code is inverted. Ensure your setup() writes HIGH to the pin to keep it off, and LOW to turn it on. (Handled correctly in the code block above).

Extending the Build: Snubbers and Solid State Upgrades

Once you have the basic resistive load (like a desk lamp) working, you will eventually want to switch inductive loads like AC fans, solenoids, or water pumps. This requires modifying the hardware to prevent voltage spikes from destroying your module.

How to Extend: Add an RC Snubber for Inductive Loads

When a relay opens an inductive circuit, the collapsing magnetic field generates a massive voltage spike (flyback voltage) that can arc across the relay contacts, degrading them over time and emitting electromagnetic interference (EMI) that resets your Arduino. To prevent this, wire an RC snubber network (typically a 100Ω resistor in series with a 0.1µF X2-rated capacitor) directly across the COM and NO terminals of the relay. This absorbs the inductive spike. For a deep dive into the physics of inductive kickback, review the Flyback Diode and Snubber theory.

How to Simplify: Switch to a Solid State Relay (SSR)

If the mechanical clicking noise is unacceptable, or if you need to switch a load rapidly (like PWM dimming an AC heating element), abandon the mechanical Songle relay entirely.

The Concrete Pick for Upgrades: Buy an Omron G3MB-202P Solid State Relay module. It switches silently, has no moving parts to wear out, and features built-in zero-cross detection to prevent EMI. It is wired identically on the logic side (Active LOW) but requires a heat sink if you plan to draw more than 1A continuous current. Ensure you buy the AC-DC version (switches AC loads with a DC logic signal), not the DC-DC version.