A standard 5V Arduino relais module uses an optocoupler and a switching transistor to let a low-current microcontroller GPIO pin safely switch a high-current AC or DC load. The most common variant on the bench is the blue 2-channel board featuring Songle SRD-05VDC-SL-C relays. While they are cheap and ubiquitous, they have a specific quirk—the JD-VCC jumper—that frequently leads to fried microcontrollers or erratic resets if misunderstood.
This guide provides the exact wiring procedure for true optical isolation, a robust non-blocking control sketch for the Arduino Uno R3, and a hardware debugging matrix for when the relay clicks but your load stays dead.
Spec Sheet and Pin Mapping Reference
Before wiring anything, you need to know exactly what you are driving. The relay coil is an inductive load that draws a surprising amount of current. Here are the real-world bench measurements and datasheet values for the standard 2-channel 5V module.
| Parameter | Value / Rating | Bench Notes & Constraints |
|---|---|---|
| Coil Voltage (Nominal) | 5.0V DC | Will reliably pull in down to ~3.8V; drops out at ~1.5V. |
| Coil Resistance | ~70 Ω | Draws ~71mA per coil. Do not drive directly from an ATmega328P pin (40mA max limit). |
| Contact Rating (Resistive) | 10A @ 250VAC / 30VDC | Derate to 5A for inductive loads (motors, transformers) without a snubber. |
| Logic Trigger | Active LOW | GPIO must sink current to ground to energize the coil. HIGH = relay off. |
| Optocoupler | PC817 (or equivalent) | Provides galvanic isolation ONLY if JD-VCC jumper is removed. |
| Flyback Protection | 1N4148 Diode | Built-in across the coil to suppress back-EMF when the transistor switches off. |
Module Pinout and Jumper Configuration
The low-voltage side has a 6-pin header. The high-voltage side has three screw terminals per channel.
| Pin / Terminal | Function | Connection Target |
|---|---|---|
| DC+ (or VCC) | Logic power for optocoupler LEDs | Arduino 5V pin |
| DC- (or GND) | Logic ground reference | Arduino GND pin |
| IN1 / IN2 | Control signal (Active LOW) | Arduino Digital Pin 8 / 9 |
| JD-VCC | Relay coil power source | External 5V supply (if isolated) or bridged to VCC |
| COM | Common contact (Mains Hot) | AC Line (Hot) incoming |
| NO | Normally Open | Load Hot (turns ON when triggered) |
| NC | Normally Closed | Load Hot (turns OFF when triggered) |
Parts List and True Isolation Wiring
To build this safely, we are configuring the module for true optical isolation. By default, these modules ship with a plastic jumper connecting VCC and JD-VCC. This ties the relay coil power directly to your Arduino's 5V rail. When the relay coil collapses, back-EMF spikes can reset your microcontroller or degrade the ATmega328P over time. Removing the jumper and powering the coils separately fixes this.
Required Materials
- Microcontroller: Arduino Uno R3 (ATmega328P, DIP or SMD variant)
- Relay Module: 2-Channel 5V Relay Module (Songle SRD-05VDC-SL-C)
- External Power: 5V 1A USB buck converter or bench supply (for JD-VCC)
- Wiring (Logic): 22 AWG solid core jumper wires
- Wiring (Mains): 18 AWG stranded THHN or appliance wire, ferrule crimps
- Tools: Multimeter (CAT III), wire strippers, small flathead screwdriver
Wiring Steps (Numbered)
- Remove the JD-VCC Jumper: Use tweezers to pull the black plastic jumper cap off the
VCCandJD-VCCpins on the module. - Wire Logic Side: Connect Arduino
5Vto moduleDC+ (VCC). Connect ArduinoGNDto moduleDC- (GND). - Wire Control Pins: Connect Arduino Digital Pin
8toIN1, and Pin9toIN2. - Wire Coil Power (Isolated): Connect your external 5V supply's positive lead to
JD-VCC. Connect the external supply's ground to the module'sGND(or a dedicatedDC-pin if the board breaks it out separately, but usually, coil ground shares the logic ground plane on cheap modules—verify with a multimeter continuity test). - Wire Mains Load: With power OFF, connect the incoming AC Hot wire to the
COMscrew terminal. Connect the Load's Hot wire to theNOterminal. The AC Neutral bypasses the relay and connects directly to the load. - Verify: Set your multimeter to continuity. With the Arduino off, probe
COMandNO. It should read open (OL). Briefly jumperIN1toGND(simulating an Active LOW trigger); it should beep (closed).
Complete Control Code (Arduino Uno R3)
This sketch targets the Arduino Uno R3. It avoids the delay() function, which blocks the processor and prevents safety monitoring. Instead, it uses a non-blocking millis() timer (see the Arduino BlinkWithoutDelay reference) and includes a failsafe timeout. If the main loop hangs or the control variable gets corrupted, the relay automatically drops out after a set duration to prevent a stuck-on mains hazard.
/*
* Arduino Relais Module Control with Failsafe Timeout
* Target Board: Arduino Uno R3 (ATmega328P)
* Module: 2-Channel 5V Active LOW Relay
*/
// --- Pin Definitions ---
#define RELAY_1_PIN 8
#define RELAY_2_PIN 9
#define STATUS_LED_PIN 13
// --- Timing Constants ---
#define TOGGLE_INTERVAL 5000UL // Toggle every 5 seconds (ms)
#define FAILSAFE_TIMEOUT 15000UL // Max time relay can stay ON (ms)
// --- State Variables ---
bool relay1State = false;
unsigned long previousMillis = 0;
unsigned long relayEngagedTime = 0;
void setup() {
// Relays are Active LOW, so we set them HIGH (OFF) immediately
pinMode(RELAY_1_PIN, OUTPUT);
digitalWrite(RELAY_1_PIN, HIGH);
pinMode(RELAY_2_PIN, OUTPUT);
digitalWrite(RELAY_2_PIN, HIGH);
pinMode(STATUS_LED_PIN, OUTPUT);
digitalWrite(STATUS_LED_PIN, LOW);
Serial.begin(9600);
Serial.println(F("Relay Module Initialized. Safe State: OFF"));
}
void loop() {
unsigned long currentMillis = millis();
// 1. Failsafe Check: Prevent stuck relays if logic hangs
if (relay1State && (currentMillis - relayEngagedTime >= FAILSAFE_TIMEOUT)) {
forceRelayOff();
Serial.println(F("ERROR: Failsafe timeout triggered. Relay forced OFF."));
// Simple error handling: halt or blink LED to indicate fault
blinkErrorPattern();
return;
}
// 2. Non-blocking Toggle Logic
if (currentMillis - previousMillis >= TOGGLE_INTERVAL) {
previousMillis = currentMillis;
relay1State = !relay1State;
if (relay1State) {
// Turn ON (Active LOW)
digitalWrite(RELAY_1_PIN, LOW);
digitalWrite(STATUS_LED_PIN, HIGH);
relayEngagedTime = currentMillis;
Serial.println(F("Relay 1 ENGAGED"));
} else {
// Turn OFF (Active HIGH)
digitalWrite(RELAY_1_PIN, HIGH);
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println(F("Relay 1 DISENGAGED"));
}
}
}
void forceRelayOff() {
digitalWrite(RELAY_1_PIN, HIGH);
digitalWrite(RELAY_2_PIN, HIGH);
digitalWrite(STATUS_LED_PIN, LOW);
relay1State = false;
}
void blinkErrorPattern() {
for (int i = 0; i < 5; i++) {
digitalWrite(STATUS_LED_PIN, HIGH);
delay(100);
digitalWrite(STATUS_LED_PIN, LOW);
delay(100);
}
}
Debugging: Compilation Errors and Hardware Failures
When working with relay modules, failures usually fall into two categories: IDE compilation errors from missing definitions, or hardware failures where the module clicks but the load doesn't respond. Here is how to diagnose both.
IDE Error: 'RELAY_1_PIN' was not declared in this scope
If you see this exact error string in the Arduino IDE output console:
error: 'RELAY_1_PIN' was not declared in this scope
Ranked Causes:
- Missing
#defineblock: You copied theloop()but forgot the header constants at the top of the sketch. - Typo in variable name: You defined
RELAY1_PINbut calledRELAY_1_PINin thedigitalWrite()function. - Scope issue: You declared the pin variable inside
setup()(e.g.,int RELAY_1_PIN = 8;) making it local, but tried to use it inloop(). Always use global#defineor declare constants beforesetup().
Hardware Failure: Relay Clicks But Load Stays Dead
The module LED illuminates, you hear the mechanical click of the armature, but your multimeter reads infinite resistance across COM and NO, or the AC load remains off.
The First 3 Things to Check When It Fails:
- Check for Welded or Pitted Contacts: If you previously switched an inductive load (like a motor or a large transformer) without a snubber circuit, the inrush current or back-EMF arc likely pitted or welded the internal contacts. Open the relay plastic cover (if possible) or replace the module. Fix: Always use a snubber network (RC circuit) or a flyback diode for DC inductive loads.
- Verify Screw Terminal Torque and Wire Prep: 18 AWG stranded wire splayed out under the screw terminal can cause a high-resistance connection that fails under load. Fix: Strip exactly 8mm of insulation, twist the strands tightly (or use a ferrule crimp), and torque the screw until the wire cannot be pulled out with a firm tug.
- Measure Voltage Drop Under Load: Use your multimeter to measure AC voltage directly across the
COMandNOterminals while the load is connected and the relay is engaged. If you read 120V (or 230V) across the relay terminals, the contacts are open internally despite the click. If you read 0V across the relay but the load is dead, your load or neutral wiring is faulty.
How to Extend or Simplify the Build
Depending on your end goal, the standard 5V Arduino relais module might be overkill, or it might lack the connectivity you need.
Simplifying the Build (DC Loads Only)
If you are only switching a 12V DC LED strip or a small pump, drop the relay module entirely. Relays are slow, mechanically noisy, and waste power in the coil. Instead, use a Logic-Level N-Channel MOSFET (like the IRLZ44N or STP16NF06L).
Why? A MOSFET switches in nanoseconds, has no moving parts to wear out, and draws virtually zero continuous current from your Arduino GPIO pin. Wire the gate to your digital pin (with a 10kΩ pull-down resistor to GND), the source to GND, and the drain to the load's negative terminal.
Extending the Build (IoT and Remote Control)
The Arduino Uno R3 lacks native networking. To extend this into a smart-home or remote-monitoring node:
Option A (Minimal hardware swap): Replace the Uno R3 with an ESP32-DevKitC V4. The ESP32 has built-in WiFi and Bluetooth. You can use the exact same relay module wiring, but you must change the GPIO pins in your code (avoiding ESP32 strapping pins like GPIO 0, 2, and 12).
Option B (Keep the Uno): Add a W5500 Ethernet Shield or an ESP-01S module communicating via UART (using NEC-compliant low-voltage data cabling) to push MQTT state updates to a Home Assistant server.






