To switch high-current AC or DC loads using a microcontroller, you use a 5V relay module (typically based on the Songle SRD-05VDC-SL-C) triggered via a digital GPIO pin through an optocoupler. The direct answer for most hobbyist builds: wire the module's VCC and GND to an external 5V power supply, connect the IN pin to Arduino Digital Pin 8, and remove the JD-VCC jumper for true optical isolation. This prevents the relay coil's inductive kickback and current draw from resetting your board.

⚠️ HIGH VOLTAGE WARNING: If your relay's Common (COM) and Normally Open (NO) terminals are switching mains voltage (120V/240V AC), de-energize the circuit at the breaker, verify dead with a tested multimeter, and use proper ferrule crimps. Never work on live mains. Local electrical codes (NEC/IEC) may require a licensed electrician for permanent mains wiring.

Relay Module Specifications and Power Budget

The most common relay module on the market uses the Songle SRD-05VDC-SL-C. While the contacts are rated for 10A at 250VAC, the coil is what dictates your microcontroller's power budget. A standard Arduino Uno R3 powered via USB can only safely supply about 400mA to 500mA total. A single relay coil draws roughly 70mA to 90mA. If you plug a 4-channel module directly into the Arduino's 5V pin and energize all four relays, you will pull nearly 300mA just for the coils, risking a brownout that resets the ATmega328P.

Table 1: Songle SRD-05VDC-SL-C 5V Module Specifications
Parameter 1-Channel Module 4-Channel Module Engineering Notes
Coil Voltage 5.0V DC 5.0V DC Will not trigger reliably at 3.3V without a level shifter.
Coil Resistance ~70 Ω ~70 Ω per relay Dictates the ~72mA current draw per active coil.
Max Contact Load 10A @ 250VAC / 30VDC 10A @ 250VAC / 30VDC Derate to 5A for inductive loads (motors, transformers).
Optocoupler Isolation PC817 (or clone) PC817 (or clone) Provides ~5kV galvanic isolation if JD-VCC jumper is removed.
Total Max Current Draw ~90mA ~320mA Exceeds safe USB limits if all 4 channels are active simultaneously.

Pin Mapping and Wiring Procedure

For this build, we are targeting the Arduino Uno R3 (ATmega328P). We will use Digital Pin 8 for the relay trigger. To achieve true optical isolation, you must power the relay coil from an external 5V source (like a buck converter or a dedicated 5V wall adapter) while feeding the optocoupler's LED side from the Arduino's 5V pin.

Table 2: Wiring Pinout (Isolated Configuration)
Arduino Uno R3 Pin Relay Module Pin Wire Color (Typical) Function
Digital 8 IN Green Logic trigger (drives optocoupler LED).
5V VCC Red Powers the optocoupler LED side only.
GND GND Black Logic ground reference.
External 5V (+) JD-VCC Orange Powers the relay coil. Remove jumper first!
External GND GND (Coil side) Brown Ground for the external 5V coil supply.

Numbered Wiring Steps:

  1. Remove the JD-VCC Jumper: Locate the blue plastic jumper connecting the VCC and JD-VCC pins on the module. Pull it off. This breaks the physical copper trace linking the Arduino's logic power to the relay coil power.
  2. Connect Logic Side: Wire Arduino 5V to module VCC, Arduino GND to module GND, and Digital Pin 8 to IN.
  3. Connect Coil Side: Wire your external 5V power supply positive to the JD-VCC pin, and the external ground to the module's GND pin (the one adjacent to JD-VCC, not the logic GND).
  4. Wire the Load: Connect your load's hot/live wire to the COM (Common) terminal. Connect the NO (Normally Open) terminal to the load's input. The neutral/ground wire bypasses the relay entirely.

Complete C++ Code with Error Handling

This sketch uses a non-blocking serial parser to control the relay. It includes a critical safety feature: a watchdog timeout. If the serial connection drops or the host PC crashes, the relay automatically disengages after 5 minutes to prevent a load from being left on indefinitely. This code targets the Arduino Uno R3 / Nano v3 (ATmega328P) and assumes an Active-LOW relay module (the most common variant, where pulling the IN pin LOW energizes the coil).

// Target Board: Arduino Uno R3 (ATmega328P) or Nano v3
// Relay Module: 5V Active-LOW (e.g., Songle SRD-05VDC-SL-C)
// Reference: https://docs.arduino.cc/hardware/uno-rev3/

const int RELAY_PIN = 8;
const int LED_STATUS = 13;
const unsigned long SAFETY_TIMEOUT_MS = 300000; // 5 minutes

unsigned long lastCommandTime = 0;
bool relayState = false;

void setup() {
  Serial.begin(9600);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(LED_STATUS, OUTPUT);

  // Initialize relay to OFF state (Active LOW means HIGH is off)
  digitalWrite(RELAY_PIN, HIGH);
  digitalWrite(LED_STATUS, LOW);
  lastCommandTime = millis();
  Serial.println("SYS: Relay Controller Ready. Send 'ON', 'OFF', or 'STATUS'.");
}

void loop() {
  // Safety timeout: turn off relay if no serial commands received
  if (millis() - lastCommandTime > SAFETY_TIMEOUT_MS && relayState) {
    setRelay(false);
    Serial.println("ERR: TIMEOUT. Relay deactivated for safety.");
  }

  if (Serial.available() > 0) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();
    cmd.toUpperCase();

    if (cmd == "ON") {
      setRelay(true);
      Serial.println("ACK: Relay ENGAGED.");
      lastCommandTime = millis();
    } 
    else if (cmd == "OFF") {
      setRelay(false);
      Serial.println("ACK: Relay DISENGAGED.");
      lastCommandTime = millis();
    } 
    else if (cmd == "STATUS") {
      Serial.print("STAT: Relay is ");
      Serial.println(relayState ? "ON" : "OFF");
      lastCommandTime = millis();
    } 
    else {
      // Exact error string handling for invalid serial inputs
      Serial.print("ERR: INVALID_CMD '");
      Serial.print(cmd);
      Serial.println("'. Expected ON, OFF, or STATUS.");
    }
  }
}

void setRelay(bool state) {
  relayState = state;
  // Active LOW logic: LOW turns relay ON, HIGH turns it OFF
  digitalWrite(RELAY_PIN, state ? LOW : HIGH);
  digitalWrite(LED_STATUS, state ? HIGH : LOW);
}

Debugging: First Three Things to Check When It Fails

When an electromechanical relay circuit fails, it usually comes down to power starvation, logic inversion, or serial parsing errors. Here is your ranked troubleshooting path.

1. The Arduino Randomly Resets When the Relay Clicks

The Cause: Power starvation and inductive kickback. When the relay coil de-energizes, the collapsing magnetic field generates a reverse voltage spike. If the module's built-in flyback diode (usually a 1N4148) is failing, or if you are powering a 4-channel module directly from the Arduino's USB 5V pin, the voltage dip triggers the ATmega328P's brownout detector.

The Fix: Verify the JD-VCC jumper is removed and the coil is on a dedicated 5V supply. If using a cheap clone module, inspect the SMD flyback diode across the coil pins with a multimeter in diode mode; it should read ~0.5V forward bias and OL in reverse. If it reads shorted, replace the diode.

2. The Relay Stays On (or Clicks Backwards)

The Cause: Logic inversion. Most optocoupler modules are Active-LOW. This means the optocoupler LED's anode is tied to VCC, and the cathode goes to the IN pin. Pulling the IN pin to GND (LOW) completes the circuit and turns the relay ON. If your code uses digitalWrite(RELAY_PIN, HIGH) to turn it on, it will do the exact opposite.

The Fix: Check the module silkscreen. If it says "Low Level Trigger", ensure your code writes LOW to engage and HIGH to disengage, exactly as implemented in the setRelay() function above.

3. Serial Monitor Outputs: ERR: INVALID_CMD

The Cause: Hidden carriage returns or encoding mismatches in the serial terminal. If you are using the Arduino IDE Serial Monitor, PuTTY, or a Python script, the line endings might be sending ON\r\n instead of just ON\n.

The Fix: The cmd.trim() function in the code above strips standard whitespace, but if you are writing your own parser, ensure you strip both \r (carriage return) and \n (newline). If you see ERR: INVALID_CMD 'ON' with seemingly correct text, check for invisible Unicode characters or ensure your baud rate matches exactly (9600 in this sketch).

How to Extend or Simplify the Build

Depending on your load requirements, a mechanical relay isn't always the right tool for the job. Here is how to pivot your design based on real-world constraints.

Simplify: Switch to a Logic-Level MOSFET for DC Loads

If you are only switching DC loads under 30V (like LED strips, 12V water pumps, or PC fans), ditch the relay entirely. Mechanical relays have a limited lifespan (usually ~100,000 cycles) and suffer from contact bounce. Instead, use an N-channel logic-level MOSFET like the IRLZ44N. It can handle up to 47A, switches silently at high PWM frequencies (allowing you to dim LEDs or control motor speed), and requires only a 10kΩ pull-down resistor on the gate and a 150Ω gate resistor to protect the Arduino GPIO pin.

Extend: Add a Solid State Relay (SSR) for High-Frequency AC

If you need to switch 120V/240V AC loads rapidly (like PID temperature control for a kiln or sous-vide using time-proportioning control), a mechanical relay will weld its contacts shut within hours. Extend your build by swapping the mechanical module for a Solid State Relay like the Omron G3MB-202P. SSRs use a triac to switch AC loads with zero moving parts, offering silent operation and millions of switching cycles. Note that SSRs generate heat proportional to the load current; any load over 2A requires mounting the SSR to a heatsink with thermal paste.

Maker's Tip: When switching highly inductive AC loads (like large transformers or AC motors) with any relay, the contacts will arc internally upon opening. To extend the life of your mechanical relay, 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 to absorb the arc energy.