If you want to repurpose an old TV remote or build a custom wireless trigger, decoding infrared (IR) signals is the most reliable starting point. This guide walks through building a universal arduino remote control receiver that maps decoded hex codes to a 4-channel relay bank. The code and pinouts below specifically target the Arduino Nano v3 (ATmega328P, 16MHz) due to its compact footprint and native 5V logic, which perfectly matches standard IR receiver modules without needing logic level shifters.
By the end of this build, you will have a working decoder that toggles relays based on specific remote buttons, complete with serial debugging and state-tracking. We will also cover the exact library migration errors that trip up most builders when moving from legacy IR libraries to the modern v4 standard.
Component Spec Sheet & IR Protocol Data
Before wiring, verify your exact hardware variants. The VS1838B is the industry standard for 38kHz carrier demodulation, but it requires a clean 5V rail to maintain its internal Automatic Gain Control (AGC). The relay module specified is active-LOW, which is critical for the code logic provided later.
| Component | Exact Variant / Model | Operating Voltage | Current Draw | Est. Cost (USD) |
|---|---|---|---|---|
| Microcontroller | Arduino Nano v3 (ATmega328P, 16MHz) | 5V (USB or VIN 7-12V) | ~19mA (base) | $6.00 - $12.00 |
| IR Receiver | VS1838B (38kHz, through-hole or module) | 2.7V - 5.5V DC | 1.5mA (typ) | $1.00 - $3.00 |
| Relay Bank | 4-Channel 5V Relay Module (Active-LOW, Optocoupled) | 5V DC (Coil) | ~70mA per coil | $4.00 - $8.00 |
| Decoupling Cap | 100µF Electrolytic Capacitor (16V+) | N/A | N/A | $0.20 |
| Pull-up Resistors | 10kΩ (1/4W Carbon Film) x4 | N/A | N/A | $0.10 |
Not all remotes speak the same language. When you point an IR remote at the VS1838B, the module strips the 38kHz carrier wave and outputs the raw baseband envelope. The microcontroller then measures the pulse widths to determine the protocol. Here is what the decoder will be looking for:
| Protocol | Carrier Freq | Data Length | Lead Pulse / Encoding | Common Brands |
|---|---|---|---|---|
| NEC | 38 kHz | 32 bits | 9ms pulse / 4.5ms space | Samsung, LG, generic Chinese |
| RC5 (Philips) | 36 kHz | 14 bits | Bi-phase (Manchester) | Philips, older European audio |
| Sony SIRC | 40 kHz | 12 / 15 / 20 bits | 2.4ms lead pulse | Sony TV, Bravia, PlayStation |
| Samsung | 38 kHz | 32 bits | 4.5ms lead pulse | Samsung (older non-NEC models) |
Wiring the Receiver and Relay Bank
Follow the official Arduino Nano pinout to ensure you are using hardware-interrupt-capable pins for the receiver. On the ATmega328P, Pins 2 and 3 support external interrupts (INT0 and INT1), which the IRremote library requires for accurate microsecond timing.
When multiple relay coils energize simultaneously, they can draw over 250mA, causing a voltage sag on the Nano's 5V rail. This triggers a brownout reset, wiping your relay state. Always solder a 100µF electrolytic capacitor directly across the relay module's VCC and GND terminals to buffer the transient current spike.
- Power the Rails: Connect the Nano's 5V pin to the breadboard positive rail and GND to the negative rail. Use 22 AWG solid core hook-up wire.
- Wire the VS1838B: Connect the module's VCC to 5V, GND to GND, and the OUT (data) pin directly to Nano Pin D2.
- Wire the Relay Module: Connect the relay VCC to 5V and GND to GND. Connect the control inputs (IN1, IN2, IN3, IN4) to Nano Pins D4, D5, D6, and D7 respectively.
- Install the Decoupling Cap: Place the 100µF capacitor across the relay module's power input terminals. Observe polarity (stripe to GND).
- Verify Connections: Use a multimeter in continuity mode to verify there are no shorts between 5V and GND before plugging in the USB cable.
Complete IRremote v4 Decoder Code
The Arduino-IRremote library underwent a massive architectural rewrite between v2.x and v4.x. The code below uses the modern v4 object-oriented syntax. It includes state-tracking arrays so each remote button acts as a toggle switch rather than a momentary trigger.
#include <IRremote.hpp>
// --- PIN DEFINITIONS ---
#define IR_RECEIVE_PIN 2
#define RELAY_1_PIN 4
#define RELAY_2_PIN 5
#define RELAY_3_PIN 6
#define RELAY_4_PIN 7
// --- IR HEX CODES (Replace with your remote's actual codes) ---
// Use the Serial Monitor to find your specific remote's codes
#define IR_CODE_BTN1 0x18E7A05F
#define IR_CODE_BTN2 0x18E7609F
#define IR_CODE_BTN3 0x18E7E01F
#define IR_CODE_BTN4 0x18E7906F
const int relayPins[4] = {RELAY_1_PIN, RELAY_2_PIN, RELAY_3_PIN, RELAY_4_PIN};
const uint32_t irCodes[4] = {IR_CODE_BTN1, IR_CODE_BTN2, IR_CODE_BTN3, IR_CODE_BTN4};
bool relayStates[4] = {false, false, false, false};
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port on native USB boards
// Initialize IR Receiver on Pin 2 with LED feedback disabled to save power
IrReceiver.begin(IR_RECEIVE_PIN, DISABLE_LED_FEEDBACK);
// Initialize Relay Pins
for (int i = 0; i < 4; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], HIGH); // HIGH = OFF for active-LOW relays
}
Serial.println(F("Arduino Remote Control Decoder Ready."));
Serial.println(F("Press buttons on your remote to map codes."));
}
void loop() {
if (IrReceiver.decode()) {
// Error Handling: Check for buffer overflow or repeat frames
if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT) {
// Ignore repeat signals (holding down the button)
IrReceiver.resume();
return;
}
uint32_t receivedCode = IrReceiver.decodedIRData.decodedRawData;
// Print raw hex to serial for mapping new remotes
Serial.print(F("Received Hex: 0x"));
Serial.println(receivedCode, HEX);
// Match received code to our relay bank
for (int i = 0; i < 4; i++) {
if (receivedCode == irCodes[i]) {
relayStates[i] = !relayStates[i]; // Toggle state
// Write to pin (Invert logic for active-LOW relay modules)
digitalWrite(relayPins[i], relayStates[i] ? LOW : HIGH);
Serial.print(F("Relay "));
Serial.print(i + 1);
Serial.print(F(" is now "));
Serial.println(relayStates[i] ? F("ON") : F("OFF"));
break;
}
}
// Clear buffer and prepare for next signal
IrReceiver.resume();
}
}
Debugging: Fixing 'decode_results' and Signal Drops
When migrating older tutorials to modern IDE environments, builders frequently hit a wall during compilation. If your build fails, address these specific failure modes.
The Exact Error String
If your IDE throws this exact error:
error: 'decode_results' was not declared in this scope
error: 'IRrecv' does not name a type
Ranked Causes:
- Library Version Mismatch (95% of cases): You are compiling v2.x syntax (using
IRrecvanddecode_results) against the v4.x library installed in your IDE. The v4 library replaced these with theIrReceiverobject anddecodedIRDatastruct. Use the code block provided above to fix this. - Corrupted Library Cache: The IDE is pulling from an old cached zip file. Delete the
IRremotefolder from yourDocuments/Arduino/librariesdirectory and reinstall via the Library Manager. - Conflicting Libraries: You have both
IRremoteandIRremoteESP8266installed for a standard AVR board, causing namespace collisions. Remove the ESP8266 variant if compiling for the Nano.
The First Three Things to Check When It Fails to Trigger
If the code compiles and uploads, but the serial monitor shows no output when you press remote buttons, check these three physical layer issues:
- Ambient Light Interference: Compact Fluorescent (CFL) bulbs and cheap LED drivers emit broadband IR noise that swamps the VS1838B's AGC. Fix: Cup your hand over the receiver to block room light. If it suddenly works, you need to add a physical shroud or move the receiver away from ceiling fixtures.
- Interrupt Pin Mapping: Did you accidentally wire the receiver OUT pin to D4 instead of D2? The ATmega328P requires hardware interrupts for the 10-microsecond timing resolution needed to read IR pulses. D2 and D3 are your only options on the Nano.
- Carrier Frequency Mismatch: The VS1838B is tuned strictly to 38kHz. If you are trying to decode an old Sony remote that transmits at 40kHz, the module's internal bandpass filter will attenuate the signal to the point of failure. Fix: Use a TSOP4040 (40kHz) receiver for Sony SIRC devices.
Extending or Simplifying the Build
Depending on your end goal, this baseline arduino remote control decoder can be scaled up for smart home integration or stripped down for ultra-compact embedded applications.
How to Simplify (The ATtiny85 Route)
If you only need to trigger a single latching relay or a MOSFET for a car audio accessory, the Arduino Nano is overkill. You can port this exact logic to an ATtiny85 microcontroller. The ATtiny85 costs under $1.50, fits in a DIP-8 socket, and has Pin 2 (PB2) mapped to INT0 for the IR receiver. You will need to use the ATTinyCore board package in the Arduino IDE and reduce the serial debugging outputs, as the ATtiny85 lacks a native hardware UART (requiring SoftwareSerial, which can interfere with IR timing).
How to Extend (The ESP32 + MQTT Route)
To integrate this into a modern smart home (like Home Assistant), swap the Nano for an ESP32-WROOM-32 dev board.
Migration Steps:
- Install the
IRremoteESP8266library (the standard AVR IRremote library has timing conflicts with the ESP32's Wi-Fi stack). - Add the
PubSubClientlibrary to connect to an MQTT broker (e.g., Mosquitto). - Instead of toggling local relays, publish the decoded hex string to an MQTT topic like
home/livingroom/ir/raw. - This allows Home Assistant to listen for the remote press and trigger complex automations, like dimming Hue lights or arming a security system, entirely wirelessly.






