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.

Common IR Protocol Specifications & IRremote Constants
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.

Target Board: This wiring and code specifically target the Arduino Uno R3 (ATmega328P). If you are using an ESP32 DevKit V1, see the debugging section for timer conflict resolutions.

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

  1. Library API Version: Open the Arduino IDE Library Manager. If your IRremote version is 4.0.0 or higher, the old IRrecv object no longer exists. You must use the global IrReceiver and IrSender instances shown in the code above.
  2. 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.
  3. 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 PubSubClient library 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 to IrSender. 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.