To decode infrared (IR) remote signals with a microcontroller, you need a 38kHz demodulating receiver (like the KY-022 module or bare TSOP38238 chip), a digital input pin, and the IRremote v4.x library. While hundreds of tutorials exist, most rely on outdated v2/v3 library syntax that throws compilation errors on modern IDEs, or they ignore the hardware filtering required to stop ambient light from drowning out your signal.
This guide targets the Arduino Uno R3 (ATmega328P) and the ESP32 DevKit V1. We will cover the exact hardware specs, the Vishay-recommended RC filter circuit for bare chips, fully compilable v4 code, and a decision tree for debugging the most common IR failures.
Hardware Specs & Pin Mapping
Not all IR receivers are created equal. The bare TSOP38238 chip requires external passive components to filter power supply noise, while hobbyist modules like the KY-022 integrate these onto a PCB. Choosing the wrong variant for your environment leads to phantom triggers.
IR Receiver Module Comparison
| Component / Module | Carrier Freq | Vcc Range | Icc (Typical) | Max Range | Built-in RC Filter | Best Use Case |
|---|---|---|---|---|---|---|
| KY-022 Module | 38 kHz | 2.7V - 5.5V | ~1.0 mA | 10 - 15m | Yes (Onboard) | Prototyping, Arduino Uno 5V logic |
| VS1838B Module | 38 kHz | 3.3V - 5.0V | ~1.2 mA | 8 - 12m | Yes (Onboard) | Budget builds, ESP32 3.3V logic |
| Bare TSOP38238 | 38 kHz | 2.5V - 5.5V | 0.7 mA | Up to 35m* | No (Requires external) | Custom PCBs, high-reliability products |
| TSOP4838 | 38 kHz | 2.5V - 5.5V | 0.7 mA | Up to 35m* | No (Requires external) | High-noise environments (AGC3 filter) |
*Range dependent on IR LED transmitter power and ambient lighting. Data sourced from the Vishay TSOP382 Datasheet.
Microcontroller Pin Mapping
The IRremote library can use any digital pin with an interrupt, but standardizing your pinout saves debugging time. Note the logic level differences between the Uno and ESP32.
| Function | Arduino Uno R3 (5V Logic) | ESP32 DevKit V1 (3.3V Logic) | Notes |
|---|---|---|---|
| IR Receiver Data (OUT) | Pin 11 | GPIO 15 | Any interrupt-capable digital pin works. |
| IR Transmitter Data (IN) | Pin 3 | GPIO 4 | Hardcoded to Pin 3 on Uno for PWM timer in IRremote. |
| Vcc | 5V | 3.3V | Do not feed 5V to an ESP32 GPIO pin. |
| GND | GND | GND | Ensure common ground with transmitter circuit. |
Wiring the Arduino IR Circuit
If you are using a pre-assembled module like the KY-022, wiring is trivial: Vcc to 5V, GND to GND, and OUT to Pin 11. However, if you are using a bare TSOP38238 chip on a breadboard or custom PCB, you must include the decoupling network. Without it, switching noise from your microcontroller or nearby power supplies will trigger phantom IR decodes.
According to Vishay's application notes, a bare TSOP38238 requires a 100Ω resistor in series with the Vcc line, and a 4.7µF electrolytic capacitor placed in parallel across the Vcc and GND pins (after the resistor). This suppresses power line ripple and prevents the internal AGC (Automatic Gain Control) from falsely triggering on continuous noise.
Step-by-Step Wiring (KY-022 Module to Uno R3)
- Power Down: Disconnect the USB cable from your Arduino Uno R3.
- Ground Connection: Connect a 22 AWG black jumper wire from the
GNDpin on the KY-022 to anyGNDpin on the Uno. - Power Connection: Connect a 22 AWG red jumper wire from the
VCC(or+) pin on the KY-022 to the5Vpin on the Uno. - Signal Connection: Connect a 22 AWG yellow jumper wire from the
OUT(orS) pin on the KY-022 toDigital Pin 11on the Uno. - Verify: Use a multimeter in continuity mode to ensure there are no shorts between Vcc and GND before applying power.
Compilable Code with Error Handling
The code below targets the Arduino Uno R3 and uses the modern IRremote v4.x API. Older tutorials use IRrecv irrecv(PIN), which will fail to compile on v4.x. This script includes robust error handling for unknown protocols and repeat codes.
Prerequisite: Install the "IRremote" library by shirriff/z3t0 via the Arduino Library Manager. Ensure you select version 4.0.0 or higher.
#include <IRremote.hpp>
// --- PIN DEFINITIONS ---
const int IR_RECV_PIN = 11;
// --- GLOBAL VARIABLES ---
unsigned long lastDecodedValue = 0;
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port (needed for Leonardo/Micro, safe for Uno)
// Initialize the IR receiver (v4.x syntax)
// ENABLE_LED_FEEDBACK blinks the Uno's onboard LED when an IR signal is received
IrReceiver.begin(IR_RECV_PIN, ENABLE_LED_FEEDBACK);
Serial.println("IR Receiver initialized on Pin 11.");
Serial.println("Point your remote at the sensor and press a button.");
}
void loop() {
// Check if new IR data is available
if (IrReceiver.decode()) {
// 1. Handle Repeat Codes (e.g., holding down a volume button)
if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT) {
Serial.print("REPEAT: ");
Serial.println(lastDecodedValue, HEX);
}
// 2. Handle Unknown Protocols or Noise
else if (IrReceiver.decodedIRData.protocol == UNKNOWN) {
Serial.print("UNKNOWN PROTOCOL / NOISE detected. Raw data length: ");
Serial.println(IrReceiver.decodedIRData.rawDataPtr->rawlen);
// You can print the raw buffer here for custom decoding
}
// 3. Handle Valid Decoded Signals
else {
lastDecodedValue = IrReceiver.decodedIRData.decodedRawData;
Serial.print("Protocol: ");
// printIRResultMinimal prints the protocol name automatically
IrReceiver.printIRResultMinimal(&Serial);
Serial.print(" | Hex Value: 0x");
Serial.println(lastDecodedValue, HEX);
// Example: Trigger an action for a specific NEC button (e.g., Power button)
if (IrReceiver.decodedIRData.protocol == NEC && lastDecodedValue == 0x10EFD827) {
Serial.println(">>> ACTION: Power button pressed!");
// Add relay toggle or LED logic here
}
}
// CRITICAL: Resume receiving after processing
IrReceiver.resume();
}
}
Debugging Common Arduino IR Failures
When an IR circuit fails, the issue is almost always a mismatch between library versions, ambient light saturation, or power noise. If your serial monitor is blank or throwing errors, check these three things first.
1. The First Three Things to Check
- Library Version Syntax: The IRremote library underwent a massive rewrite at v3.0 and v4.0. If you are copying code from a 2018 tutorial, it will not compile on the current library.
- Ambient Light Saturation: Direct sunlight or certain CFL/LED bulbs emit broadband IR or 100/120Hz flicker that blinds the 38kHz sensor. Cup your hand over the receiver; if it starts working, you have an ambient light problem.
- Carrier Frequency Mismatch: Most consumer remotes use 38kHz. If you are trying to decode an older Bang & Olufsen remote (455kHz) or a specific Sony device (40kHz), a standard TSOP38238 will physically filter out the signal before the Arduino ever sees it.
Ranked Error Strings and Fixes
error: 'IRrecv' does not name a typeCause: You are using v2.x syntax (
IRrecv irrecv(RECV_PIN);) with the v4.x library installed.Fix: Delete the object instantiation. Replace
irrecv.enableIRIn(); with IrReceiver.begin(RECV_PIN, ENABLE_LED_FEEDBACK); and use IrReceiver.decode() in the loop.
UNKNOWN or random hex values continuouslyCause: The receiver is saturated by 100Hz/120Hz ripple from cheap LED drivers or fluorescent ballasts, or the AGC is amplifying thermal noise due to missing decoupling capacitors.
Fix: Move the receiver away from lighting fixtures. If using a bare chip, verify the 100Ω resistor and 4.7µF capacitor are present. Switch to a TSOP4838, which uses a more aggressive AGC3 filter designed specifically to suppress continuous noise.
0xFFFFFFFF or REPEAT but you need the actual hex codeCause: The remote is sending a dedicated repeat frame (common in NEC protocol) rather than re-sending the original payload.
Fix: This is correct hardware behavior. Store the last valid decoded hex value in a global variable (as shown in the code above) and reference that variable when a repeat flag is detected.
Extending and Simplifying Your IR Build
Once you have reliable decoding, you can scale the project up for home automation or scale it down to eliminate custom C++ coding entirely.
How to Extend: Add an IR Blaster
To control a TV or AC unit, you need to transmit. Wire a 940nm IR LED (like the TSAL6200) to Pin 3 on the Uno R3. Do not connect the LED directly to the GPIO pin; the ATmega328P can only source ~20mA per pin, which limits your range to a few inches. Instead, use an NPN transistor (like a 2N2222) to switch the LED from the 5V rail, allowing you to push 100mA pulses for multi-meter range. Use IrSender.sendNEC(lastDecodedValue, 32); in your code to re-transmit captured signals.
How to Simplify: Skip C++ with ESPHome
If your end goal is integrating an IR remote into Home Assistant, writing custom Arduino C++ to bridge MQTT is tedious. Instead, use an ESP32 DevKit V1 and flash it with ESPHome. ESPHome handles the 38kHz demodulation, protocol decoding, and WiFi MQTT bridging natively via YAML configuration. You map the decoded hex strings directly to Home Assistant entities without writing a single line of C++.
For deeper protocol analysis and raw timing dumps, the Arduino-IRremote GitHub repository remains the definitive reference for supported protocols and edge-case timing tolerances.






