The IRremote library for Arduino is the definitive standard for decoding and transmitting infrared signals in embedded projects. However, the library underwent a massive architectural overhaul in version 3.0 and 4.0, deprecating the old IRrecv and IRsend class structures. If you are copying code from older tutorials, it will fail to compile. This guide provides the modern v4.x implementation, exact hardware specifications, and a debugging matrix for the most common compiler and runtime errors.
Hardware Specs and Protocol Timings
Before wiring your circuit, you must understand the carrier frequencies and timing tolerances of the protocol you intend to use. The IRremote library handles the 38kHz (or 36/40kHz) carrier wave generation via hardware timers, but knowing the base timings is critical when debugging signal drops or building custom raw IR arrays.
| Protocol | Carrier Freq | Header Mark (µs) | Header Space (µs) | IRremote v4 Constant | Bit Length |
|---|---|---|---|---|---|
| NEC | 38 kHz | 9000 | 4500 | NEC |
32 bits |
| Sony (SIRC) | 40 kHz | 2400 | 600 | SONY |
12/15/20 bits |
| RC5 | 36 kHz | N/A (Bi-phase) | N/A (Bi-phase) | RC5 |
14 bits |
| Samsung | 38 kHz | 4500 | 4500 | SAMSUNG |
32 bits |
| LG | 38 kHz | 8500 | 4250 | LG |
28 bits |
For a deep dive into the physics of these pulse-distance and bi-phase encoding schemes, the SB Projects IR Knowledge Base remains the most authoritative reference on the web.
Parts List and Pin Mapping
A frequent mistake in IR transmission circuits is driving the IR LED directly from the microcontroller's GPIO pin. The ATmega328P on the Arduino Uno R3 can safely source only 20mA per pin. A high-power IR LED like the TSAL6200 requires 100mA pulses for reliable room-wide transmission. We use a 2N2222 NPN transistor to switch the high current, protecting your Arduino from silicon meltdown.
Bill of Materials
- Microcontroller: Arduino Uno R3 (or Nano v3 with ATmega328P)
- IR Receiver: VS1838B (38kHz, 5V tolerant) mounted on a breakout board
- IR Emitter: TSAL6200 (940nm, high power)
- Driver Transistor: 2N2222 (NPN) or 2N3904
- Resistors: 1x 10kΩ (base), 1x 47Ω (collector current limiter)
- Capacitor: 10µF electrolytic (decoupling for VS1838B)
Pin Mapping Table
| Component | Component Pin | Arduino Uno R3 Pin | Notes |
|---|---|---|---|
| VS1838B Receiver | OUT (Signal) | D2 | Default RX pin for IRremote v4 |
| VS1838B Receiver | VCC | 5V | Add 10µF cap between VCC and GND |
| VS1838B Receiver | GND | GND | Common ground |
| 2N2222 Transistor | Base | D3 (via 10kΩ) | D3 is tied to Timer2 for PWM/TX |
| 2N2222 Transistor | Collector | TSAL6200 Cathode | Connect LED anode to 5V via 47Ω |
| 2N2222 Transistor | Emitter | GND | Common ground |
Complete IRremote Transmit and Receive Code
The following code is written for IRremote library v4.x. It initializes the receiver on Pin 2 and the transmitter on Pin 3. It includes basic error handling to prevent the serial monitor from flooding with null reads, and demonstrates how to echo a received NEC command back out as a transmit signal.
/*
* IRremote v4.x Transceiver Example
* Target: Arduino Uno R3 (ATmega328P)
* RX Pin: 2 | TX Pin: 3
*/
#include <IRremote.hpp>
// Pin Definitions
const int IR_RECEIVE_PIN = 2;
const int IR_TRANSMIT_PIN = 3;
// State tracking to prevent TX/RX collision
bool isTransmitting = false;
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port (useful for Leonardo/Micro)
Serial.println(F("IRremote v4.x Transceiver Initialized"));
// Initialize Receiver
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
// Initialize Transmitter
IrSender.begin(IR_TRANSMIT_PIN, ENABLE_LED_FEEDBACK);
}
void loop() {
// 1. Check for incoming IR signals
if (IrReceiver.decode()) {
// Ignore noise or unknown protocols if strict filtering is needed
if (IrReceiver.decodedIRData.protocol != UNKNOWN) {
// Print human-readable output to Serial
IrReceiver.printIRResultShort(&Serial);
// Example: Echo the received command back after a short delay
// Pause receiver to prevent decoding our own reflection
IrReceiver.stop();
isTransmitting = true;
uint16_t command = IrReceiver.decodedIRData.command;
uint8_t protocol = IrReceiver.decodedIRData.protocol;
Serial.print(F("Echoing Command: "));
Serial.println(command, HEX);
// Transmit based on decoded protocol (Example for NEC)
if (protocol == NEC) {
// NEC address 0x00, command, 32 bits, no repeat
IrSender.sendNEC(0x00, command, 32, 0);
}
delay(100); // Allow time for transmission and physical settling
// Resume receiver
IrReceiver.start();
isTransmitting = false;
}
// Clear the buffer for the next read
IrReceiver.resume();
}
}
Debugging: Exact Errors and the First Three Checks
When working with the IRremote library for Arduino, hardware timer conflicts and API version mismatches cause 90% of all build failures. Before rewriting your code, run through these first three diagnostic checks.
The First Three Things to Check
- Library API Version: Open the Arduino IDE Library Manager. If your IRremote version is 4.0.0 or higher, the old
IRrecvobject no longer exists. You must use the globalIrReceiverandIrSenderinstances shown in the code above. - Timer1 / Timer2 Conflicts: On the ATmega328P, IRremote uses Timer2 for transmitting (Pin 3) and Timer1 for receiving. If you include
<Servo.h>or<tone()>in the same sketch, they will fight for Timer1, causing the IR receiver to silently fail or the servo to jitter wildly. - Receiver Power Rail Noise: The VS1838B is highly sensitive to power supply ripple. If your receiver outputs random hex codes when no remote is pressed, solder a 10µF electrolytic capacitor directly across the VCC and GND pins of the sensor breakout board.
Exact Error Strings and Ranked Causes
| Exact Compiler / Runtime Error | Root Cause | Fix |
|---|---|---|
error: 'IRrecv' does not name a type; did you mean 'IrReceiver'? |
Using legacy v2.x code with the modern v3.x/v4.x library installed. | Replace IRrecv irrecv(PIN) with IrReceiver.begin(PIN). Update all irrecv.decode(&results) calls to IrReceiver.decode(). |
error: 'TIMER_DISABLE_INTR' was not declared in this scope |
Attempting to use deprecated v2 macros to pause interrupts during transmission. | Delete the macro. IRremote v4 handles interrupt pausing internally via IrReceiver.stop() and IrReceiver.start(). |
error: #error "Board not supported!" (during compilation) |
Using an unsupported architecture or failing to define the board variant in boarddefs.h (common with ATtiny85 or clone boards). |
Ensure you selected the correct board in the IDE Tools menu. For ATtiny, use the ATTinyCore by SpenceKonde, which includes proper timer mappings. |
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout) (ESP32 Runtime) |
IRremote's hardware timer ISR is starving the ESP32's WiFi/Bluetooth stack or conflicting with hw_timer_t. |
On ESP32, IRremote defaults to Timer 0. Move it to Timer 2 by adding #define IR_SEND_PIN 4 and ensuring no other libraries (like FastLED) are hogging the RMT peripherals. |
Extending and Simplifying the Build
Depending on your project scope, you may need to strip this build down to its bare essentials or scale it up for IoT integration.
How to Simplify: The Raw Dump Approach
If you are reverse-engineering a proprietary remote (like a cheap ceiling fan or an AC unit that uses complex, non-standard bit streams), standard protocol decoding will fail. Simplify your code by removing the protocol checks and using the raw dump function. Replace the contents of the if (IrReceiver.decode()) block with:
IrReceiver.printIRResultRaw(&Serial);
This outputs the exact microsecond mark/space timing array. You can copy this array directly into IrSender.sendRaw(rawData, rawDataLength, 38) to clone the signal without needing to understand the underlying encoding scheme.
How to Extend: ESP32 and MQTT Integration
To turn this into a smart-home IR blaster, migrate the hardware to an ESP32 DevKit V1. The ESP32 uses the RMT (Remote Control Transceiver) peripheral instead of standard timers, which frees up the CPU for WiFi tasks.
- Pin Change: Use GPIO 15 for RX and GPIO 4 for TX on the ESP32. Avoid GPIO 2 (boot strapping) and GPIO 34-39 (input only).
- Library Addition: Install the
PubSubClientlibrary alongside IRremote. - Architecture: Subscribe to an MQTT topic like
home/livingroom/ir/send. When a JSON payload arrives containing the protocol, address, and command, parse it and pass it toIrSender. This allows Home Assistant to trigger your physical AC unit via a simple MQTT publish command.
For comprehensive details on ESP32-specific pin restrictions and RMT peripheral quirks, consult the IRremote ESP32 Wiki. Always verify your specific ESP32 variant's pinout, as WROOM and WROVER modules route internal flash SPI pins differently.






