To decode an Arduino infrared remote, you need a 38kHz demodulating receiver (like the VS1838B or TSOP38238) wired to a digital interrupt pin, running the IRremote v4.x library. The receiver strips the 38kHz carrier wave, leaving the baseband pulse train for the microcontroller to measure and map against known protocol timings like NEC or RC5.
This guide provides the exact hardware specifications, protocol timing data, and production-ready code to get your decoder running, along with bench-tested debugging steps for when the Serial Monitor spits out garbage data.
Project Specs & Required Hardware
Target Board Variant: Arduino Uno R3 or Nano v3 (ATmega328P). Note: The code uses Timer2, which is standard on the ATmega328P. If using an Arduino Mega 2560, the IRremote library automatically shifts to Timer2, but pin mappings for PWM will differ.
Parts List
- Microcontroller: Arduino Uno R3 (ATmega328P)
- IR Receiver: VS1838B module (38kHz, 3-pin DIP with integrated RC filter) or bare Vishay TSOP38238.
- Resistors: 10kΩ pull-up (only required if using a bare TSOP sensor, not needed for the VS1838B module).
- Wiring: 3x male-to-female or male-to-male jumper wires.
Infrared Protocol Timings & Carrier Specs
Before writing code, it helps to understand what the microcontroller is actually measuring. Infrared remotes don't send serial bytes directly; they pulse an LED at a specific carrier frequency (usually 38kHz) and turn that carrier on and off to represent binary 1s and 0s. The SB Projects IR Knowledge Base remains the definitive reference for these low-level timings.
Below is the reference data for the most common consumer protocols you will encounter.
| Protocol | Carrier Freq | Bit Length | Leader Pulse (µs) | Space / Gap (µs) | Common Use Case |
|---|---|---|---|---|---|
| NEC | 38 kHz | 32 bits | 9000 µs | 4500 µs | Most generic TV/audio remotes |
| RC5 | 36 kHz | 14 bits | N/A (Start bits) | 889 µs (Half-bit) | Philips, older European AV gear |
| RC6 | 36 kHz | 20+ bits | 2666 µs | 889 µs | Xbox 360, modern Philips |
| Sony SIRC | 40 kHz | 12/15/20 bits | 2400 µs | 600 µs | Sony Bravia, PlayStation |
| Samsung | 38 kHz | 32 bits | 4500 µs | 4500 µs | Samsung TVs and soundbars |
Wiring the Receiver & Pin Mapping
The most common point of failure in Arduino infrared remote projects is wiring the receiver backward. Cheap clone modules often have contradictory silkscreen labels.
Pin Mapping Table
| VS1838B Module Pin | Arduino Uno R3 Pin | Notes & Warnings |
|---|---|---|
| OUT (Signal) | D2 (Digital Pin 2) | Must be an interrupt-capable pin on the Uno. |
| GND | GND | Connect to any ground rail. |
| VCC | 5V | WARNING: Do not use 3.3V. The internal IC requires 4.5V-5.5V for stable demodulation. |
Wiring Steps
- Verify the Pinout: Look at the physical receiver component on the module. The pin directly connected to the large internal die is usually OUT. The middle is GND, and the right is VCC. Ignore the printed text on the plastic shell if it conflicts with the PCB traces.
- Connect the OUT pin to Arduino Digital Pin 2.
- Connect GND to the Arduino GND rail.
- Connect VCC to the Arduino 5V pin.
- Keep the receiver away from direct sunlight and CFL/LED bulbs, which emit broadband IR noise that can saturate the sensor's automatic gain control (AGC).
Complete IRremote v4 Decoder Code
This code targets the Arduino-IRremote library (v4.x). Earlier versions (v2.x) used irrecv.decode(&results), which is now deprecated and will throw compiler errors on modern installations.
#include <IRremote.hpp>
// Pin Definitions
const int IR_RECV_PIN = 2;
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port on Leonardo/Micro, harmless on Uno
// Initialize the IR receiver
// ENABLE_LED_FEEDBACK blinks the onboard LED (Pin 13) when IR is received
IrReceiver.begin(IR_RECV_PIN, ENABLE_LED_FEEDBACK);
Serial.println(F("IR Receiver Initialized. Waiting for signals..."));
}
void loop() {
if (IrReceiver.decode()) {
// Error Handling: Check for buffer overflow
if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_WAS_OVERFLOW) {
Serial.println(F("ERROR: IRDATA_FLAGS_WAS_OVERFLOW. Try increasing RAW_BUFFER_LENGTH."));
IrReceiver.resume();
return;
}
// Error Handling: Check for unknown protocols
if (IrReceiver.decodedIRData.protocol == UNKNOWN) {
Serial.print(F("Protocol: UNKNOWN | Hash: 0x"));
Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
// Print raw timings for debugging
IrReceiver.printIRResultMinimal(&Serial);
}
else {
// Successful decode
Serial.print(F("Protocol: "));
Serial.println(IrReceiver.decodedIRData.protocol);
Serial.print(F("Hex Code: 0x"));
Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
Serial.print(F("Bits: "));
Serial.println(IrReceiver.decodedIRData.numberOfBits);
// Handle repeat frames (e.g., holding down a button)
if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT) {
Serial.println(F("[REPEAT FRAME]"));
}
}
Serial.println(F("------------------------"));
// CRITICAL: Resume receiving after processing
IrReceiver.resume();
}
}
Debugging: "UNKNOWN" Protocols & Hardware Failures
When you point your remote at the sensor and the Serial Monitor outputs Protocol: UNKNOWN or fails to print anything entirely, do not immediately blame the library. Run through these first three checks.
The First Three Things to Check
- VCC/GND Swap (The Magic Smoke Test): If the Serial Monitor prints absolutely nothing, or the receiver gets hot to the touch, you have likely swapped VCC and GND. The VS1838B internal IC will instantly short and fail. Desolder it and test with a multimeter's diode mode; a dead receiver will show a short across the power rails.
- Ambient 38kHz Noise Saturation: If your output is a continuous stream of random
UNKNOWNhashes without you pressing any buttons, your environment is noisy. Compact Fluorescent Lamps (CFLs) and cheap LED drivers emit switching noise in the 30-50kHz range. Cup your hand over the sensor to block ambient light. If the phantom signals stop, you need to move the project away from the light source or add a physical IR-pass optical filter. - Baud Rate & Buffer Overflow: If you see the exact error string
ERROR: IRDATA_FLAGS_WAS_OVERFLOW, the microcontroller's RAM buffer filled up before it could process the interrupt. This usually happens if you haveSerial.print()statements blocking the loop at a low baud rate. Ensure your Serial Monitor is set to 115200 baud to match the code, or disable serial printing entirely for production.
Ranked Causes for "Protocol: UNKNOWN"
If the hardware is verified and the environment is quiet, but you still get Protocol: UNKNOWN, rank your troubleshooting by these causes:
- Cause 1: Non-Standard Proprietary Protocol. Many modern AC units and ceiling fans use custom 64-bit or 128-bit pulse trains that
IRremotedoesn't recognize natively. Fix: Use the raw timing dump to replicate the exact pulse train rather than relying on hex decoding. - Cause 2: RC5/RC6 Toggle Bit Confusion. RC5 and RC6 protocols flip a specific "toggle bit" every time a button is pressed to distinguish a long hold from two rapid presses. This results in two different hex codes for the exact same physical button. Fix: Mask the toggle bit in your
switch/caselogic using bitwise AND operations. - Cause 3: Weak Battery in the Remote. A dying CR2032 or AAA battery causes the remote's internal oscillator to drag, stretching the microsecond timings just enough to fail the strict tolerance checks in the
IRremotedecoder. Fix: Swap the remote battery.
Extending or Simplifying the Build
Once you have reliable hex codes printing to the Serial Monitor, you can adapt the project to your specific needs.
How to Extend: Adding Relay Control
To turn the decoder into a functional smart-home bridge, map the decoded hex values to digital output pins driving a relay module. Add this logic inside the else block of the successful decode section:
// Define Relay Pin
const int RELAY_PIN = 8;
// Inside the successful decode block:
switch (IrReceiver.decodedIRData.decodedRawData) {
case 0x18: // Example NEC Power Button Hex
digitalWrite(RELAY_PIN, !digitalRead(RELAY_PIN)); // Toggle state
Serial.println(F("Relay Toggled"));
break;
case 0x52: // Example NEC Volume Up Hex
// Trigger specific automation
break;
}
Note: Always use an opto-isolated relay module. Driving an inductive relay coil directly from an ATmega328P GPIO pin will induce back-EMF voltage spikes that will permanently destroy the microcontroller's output register.
How to Simplify: Raw Timing Dumps
If you are dealing with an air conditioner remote that sends complex, multi-byte state packets (temperature, fan speed, swing mode all encoded into one 200-bit burst), protocol decoding is useless. Simplify your approach by abandoning hex decoding entirely and capturing the raw microsecond timings.
Replace the decode logic with a raw dump approach using IrReceiver.printIRResultMinimal(&Serial); or by accessing the IrReceiver.decodedIRData.rawData[] array. You can then store these raw arrays in PROGMEM and use IrSender.sendRaw() to blast the exact sequence back out via a 940nm IR LED, bypassing the need to reverse-engineer the manufacturer's proprietary checksum algorithms.






