Arduino IR Remote Tutorial: Control an LED, Then Add a Servo Door

Want to control real hardware with a TV-style remote? In this Arduino IR remote tutorial, you will build a wireless LED controller using an infrared remote and a KY-022 receiver module, then upgrade the same circuit to operate a servo-driven door. The project is structured in two progressive stages so you can learn at your own pace: first a simple LED toggle, then a mechanical door that opens and closes on command.

Specifically, this tutorial builds on the door mechanism from our Arduino Password Door Lock project. If you already built that servo latch, you can reuse it here and swap keypad control for IR remote control. If you are new to servo motors, our servo motor with joystick and OLED tutorial is a great companion guide.

What You’ll Build

  • LED-only controller (Part 1): Press one remote button to toggle an LED on pin D8. This teaches IR decoding, repeat filtering, and basic digital output.
  • LED + Servo door upgrade (Part 2): Add two more remote buttons to OPEN and CLOSE a micro servo door on pin D9. The LED automatically mirrors the door state (ON when open, OFF when closed).

How the System Works

In particular, understanding the signal chain helps you debug problems and adapt the project to your own needs. Here is what happens each time you press a button on the remote:

  1. Remote transmits an IR signal. When you press a button, the remote flashes its infrared LED with a modulated 38 kHz carrier. Each button sends a unique code defined by the NEC (or similar) protocol.
  2. Next, the KY-022 receiver demodulates the signal. The IR receiver module strips away the 38 kHz carrier and outputs a clean digital pulse train on its OUT pin, which is connected to Arduino pin D2.
  3. IRremote library decodes the frame. The library interrupt-captures the pulse timings, identifies the protocol, and exposes the decoded command byte and the full decodedRawData word. It also flags repeat frames that the remote sends while you hold a button down.
  4. Arduino acts on the decoded command. Your sketch compares the received command against stored values. In the LED-only build, a match toggles the LED. In the servo upgrade, OPEN and CLOSE commands move the servo to the target angle while the LED follows the door state.

Because the architecture is identical for both stages, upgrading from LED-only to LED + servo is simply a matter of adding wiring and mapping two extra remote buttons.

Components Required

In summary, the table below lists every part you need. Components marked with a check in the LED-only column are enough for Part 1. Add the remaining parts when you are ready for the servo door upgrade in Part 2.

ComponentLED-Only (Part 1)Servo Upgrade (Part 2)Notes
Arduino Uno (or Nano)YesYesAny ATmega328-based board works
IR receiver module (KY-022 / VS1838B / TSOP1838)YesYes3-pin module; OUT goes to D2
IR remote (kit remote)YesYesMost NEC-protocol remotes work
LED (any color)YesYesStandard 5 mm through-hole
220 ohm resistorYesYesCurrent-limiting resistor for the LED
Breadboard and jumper wiresYesYesHalf-size breadboard is enough
Micro servo (SG90 or MG90S)NoYesSG90 for lightweight doors
External 5 V power supply (1 A or more)NoYesPrevents brownouts; do not power the servo from Arduino 5 V
Door latch or mechanismNoYes3D-printed, wire, or cardboard

Power tip: Servo motors draw large current spikes when they start moving. Importantly, always power the servo from a separate 5 V supply and connect the supply GND to the Arduino GND (common ground). Without this connection, you will experience resets, jitter, and unreliable IR reception.

Part 1 — Arduino IR Remote LED (LED-Only Build)

Circuit Diagram — LED-Only Wiring

To build the circuit, wire the IR receiver and LED to the Arduino as shown in the table and diagram below. The IR receiver OUT pin connects to D2 (the IRremote library default). The LED anode connects through a 220 ohm resistor to D8.

ComponentPin / TerminalConnects To
IR receiver (KY-022)OUT (S)Arduino D2
IR receiver (KY-022)VCC (+)Arduino 5 V
IR receiver (KY-022)GND (-)Arduino GND
LED anode (long leg)Anode (+)220 ohm resistor, then to Arduino D8
LED cathode (short leg)Cathode (-)Arduino GND

In particular, note this beginner tip — LED polarity: The longer leg of the LED is the anode (positive). The shorter leg is the cathode (negative, often marked with a flat edge on the LED body). The 220 ohm resistor can go on either side of the LED; it limits current to protect the LED and the Arduino pin. If the LED does not light up, try flipping it around.

Arduino IR remote LED wiring: KY-022 IR receiver to D2, LED with 220Ω to D8 on Arduino Uno, 5V and GND shared.

LED-only wiring diagram: KY-022 IR receiver connected to D2, LED with 220 ohm resistor connected to D8 on Arduino Uno.

Step 1 — Install the IRremote Library

First, open the Arduino IDE and go to Tools > Manage Libraries. Search for IRremote by Arduino-IRremote and install the latest version. This tutorial uses the modern IRremote 3.x+ API with IRremote.hpp and IrReceiver.begin().

The library handles all the low-level timing and protocol detection. After installing, you can include it and initialize the receiver with two lines:

#include <IRremote.hpp>
// ...
IrReceiver.begin(pin, ENABLE_LED_FEEDBACK);
    

For further reference, the official IRremote GitHub repository has full documentation if you want to explore advanced features.

Step 2 — Scan Your Remote (IR Code Scanner)

Every remote sends different codes, even if they look identical. Therefore, before writing the final sketch you must scan your specific remote to find out what code each button sends. This step is essential because the example values in the final code (0x45, 0x46, etc.) almost certainly do not match your remote.

Upload the scanner sketch below, open Serial Monitor at 115200 baud, and press each remote button a few times. We use 115200 baud (instead of the common 9600) because the IRremote library prints detailed output that benefits from the faster speed.

// IR Code Scanner (IRremote >= 3.x)
#include <IRremote.hpp>
const uint8_t IR_RECEIVE_PIN = 2;
void setup(){
  Serial.begin(115200);
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  Serial.println(F("IR Scanner ready. Press remote buttons..."));
}
void loop(){
  if(IrReceiver.decode()){
    IrReceiver.printIRResultShort(&Serial); // concise one-liner
    Serial.print(F("Protocol="));   Serial.print(IrReceiver.decodedIRData.protocol);
    Serial.print(F("  Command=0x"));Serial.print(IrReceiver.decodedIRData.command, HEX);
    Serial.print(F("  RAW=0x"));    Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
    IrReceiver.resume();
  }
}
    

What to look for: Each button press prints a Command value and a RAW value. With most NEC-protocol kit remotes, the command byte is short and stable across presses. Write it down. If you notice that command changes randomly or shows 0x00, use the full 32-bit decodedRawData (RAW) value instead. The RAW value is always available as a fallback regardless of protocol.

Pick one button for LED toggle. Additionally, if you plan to do the servo upgrade, also pick separate buttons for OPEN and CLOSE. Finally, write down all three values before you continue.

Step 3 — Arduino IR Remote LED Code (Final LED-Only Sketch)

Next, replace CMD_LED_TOGGLE with the command value you found in the scanner. If your remote’s command byte was unreliable, uncomment the RAW line and use RAW_LED_TOGGLE instead.

// IR Code Scanner (IRremote >= 3.x)
#include <IRremote.hpp>
const uint8_t IR_RECEIVE_PIN = 2; // IR OUT → D2
const uint8_t LED_PIN        = 8; // LED → 220Ω → D8
// Replace with your scanned value:
const uint32_t CMD_LED_TOGGLE = 0x45;  // example NEC command
// Alternative if command is unstable:
// const uint32_t RAW_LED_TOGGLE = 0x00FFA25D; // example 32-bit RAW
bool ledOn = false;
void setLED(bool on){
  ledOn = on;
  digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
}
void setup(){
  pinMode(LED_PIN, OUTPUT);
  setLED(false);
  Serial.begin(115200);
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  Serial.println(F("LED-only mode. Press your toggle key."));
}
void loop(){
  if(!IrReceiver.decode()) return;
  if(!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)){
    uint32_t cmd = IrReceiver.decodedIRData.command;
    uint32_t raw = IrReceiver.decodedIRData.decodedRawData;
    if (cmd == CMD_LED_TOGGLE)        setLED(!ledOn);
    // else if (raw == RAW_LED_TOGGLE) setLED(!ledOn); // use if needed
    Serial.print(F("[IR] cmd=0x")); Serial.print(cmd, HEX);
    Serial.print(F(" raw=0x"));     Serial.println(raw, HEX);
  }
  IrReceiver.resume();
}
    

How the LED-Only Code Works

Here is a breakdown of the key parts of the sketch so you understand what each section does and can modify it confidently:

  • IRDATA_FLAGS_IS_REPEAT: When you hold a button on an IR remote, the remote continuously sends repeat frames at short intervals. Without filtering, a single long press would toggle the LED many times. The line if(!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)) ignores these repeat frames so only the first press counts.
  • command vs. decodedRawData: The command field contains the short protocol-specific button code (usually 1 byte for NEC). The decodedRawData field holds the full 32-bit raw frame including address and command. Use command when it is stable for your remote. Fall back to decodedRawData if command returns inconsistent values or zero.
  • setLED() helper function: Centralizing the LED logic in setLED() means every part of the program changes the LED the same way. This matters more in the servo upgrade where the LED must mirror the door state. By calling setLED() everywhere instead of digitalWrite() directly, you avoid bugs where the LED and the internal state variable get out of sync.
  • Replacing the example value: The value 0x45 is just an example. Open the scanner sketch from Step 2, press the button you want to use, and replace 0x45 with the Command hex value shown in Serial Monitor. If you need the RAW fallback, uncomment the RAW line and replace 0x00FFA25D with your scanned RAW value.

Part 2 — Upgrade to Arduino IR Remote LED + Servo Door

Now extend the build: specifically, keep all the LED logic from Part 1, add a servo motor, and map two more remote keys (OPEN and CLOSE). The LED will automatically show the door status: ON means the door is open, OFF means it is closed.

Servo Wiring (Add to LED-Only Circuit)

First, keep all the IR receiver and LED wiring from Part 1. Then add the servo connections listed in the table below.

ComponentPin / WireConnects To
Servo (SG90)Signal (orange/yellow)Arduino D9 (PWM)
Servo (SG90)VCC (red)External 5 V supply (+)
Servo (SG90)GND (brown/black)External 5 V supply (-)
External supply GNDGNDArduino GND (common ground)

Why use an external 5 V supply? Importantly, the Arduino’s onboard 5 V regulator can only provide around 500 mA. A servo motor draws 200-700 mA in normal operation and even higher during stall. Drawing this much current through the Arduino causes voltage drops that reset the board, corrupt IR reception, and make the servo jitter. A separate 5 V 1 A (or higher) supply avoids all of these problems.

Why common ground is required: Furthermore, the Arduino and the external supply must share a ground connection. Without it, the servo signal wire has no reference voltage and the servo will not respond or will behave erratically.

Arduino IR remote LED and servo door wiring: KY-022 on D2, LED on D8, SG90 servo on D9 with external 5V and common ground.

Full wiring diagram for the LED + servo door upgrade: KY-022 on D2, LED on D8, SG90 servo on D9 powered by an external 5 V supply with common ground to the Arduino.

Combined Code — LED + Servo Door

Before uploading, replace CMD_LED_TOGGLE, CMD_OPEN, and CMD_CLOSE with the three values you recorded in the scanner step. If your remote needs the RAW fallback, uncomment those lines and fill in your values.

Full Combined Sketch

// Arduino IR remote LED + Servo Door (IRremote >= 3.x)
#include <IRremote.hpp>
#include <Servo.h>
const uint8_t IR_RECEIVE_PIN = 2;   // IR OUT → D2
const uint8_t LED_PIN        = 8;   // LED → 220Ω → D8
const uint8_t SERVO_PIN      = 9;   // Servo signal → D9
// === Replace with your scanned values ===
const uint32_t CMD_LED_TOGGLE = 0x45; // LED toggle
const uint32_t CMD_OPEN       = 0x46; // door open
const uint32_t CMD_CLOSE      = 0x47; // door close
// RAW fallback (uncomment if needed):
// const uint32_t RAW_LED_TOGGLE = 0x00FFA25D;
// const uint32_t RAW_OPEN       = 0x00FF629D;
// const uint32_t RAW_CLOSE      = 0x00FF22DD;
const int OPEN_ANGLE  = 20;  // tune to your linkage
const int CLOSE_ANGLE = 90;
const int STEP_DEG    = 2;
const int STEP_MS     = 10;
Servo door;
bool ledOn = false;
int  curAngle = CLOSE_ANGLE;
void setLED(bool on){
  ledOn = on;
  digitalWrite(LED_PIN, on ? HIGH : LOW);
}
void moveServoTo(int target){
  int dir = (target > curAngle) ? 1 : -1;
  while(curAngle != target){
    curAngle += dir * STEP_DEG;
    if((dir > 0 && curAngle > target) || (dir < 0 && curAngle < target)) curAngle = target;
    door.write(curAngle);
    delay(STEP_MS);
  }
}
void setDoorOpen(bool open){
  moveServoTo(open ? OPEN_ANGLE : CLOSE_ANGLE);
  setLED(open); // LED shows door status: ON=open, OFF=closed
}
void setup(){
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  setLED(false);
  door.attach(SERVO_PIN);
  door.write(CLOSE_ANGLE);
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  Serial.println(F("LED + Servo mode. Use TOGGLE / OPEN / CLOSE keys."));
}
void loop(){
  if(!IrReceiver.decode()) return;
  if(!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)){
    uint32_t cmd = IrReceiver.decodedIRData.command;
    uint32_t raw = IrReceiver.decodedIRData.decodedRawData;
    if (cmd == CMD_LED_TOGGLE) setLED(!ledOn);
    else if (cmd == CMD_OPEN)  setDoorOpen(true);
    else if (cmd == CMD_CLOSE) setDoorOpen(false);
    // RAW fallback:
    // if (raw == RAW_LED_TOGGLE) setLED(!ledOn);
    // if (raw == RAW_OPEN)       setDoorOpen(true);
    // if (raw == RAW_CLOSE)      setDoorOpen(false);
  }
  IrReceiver.resume();
}
    

How the Servo Door Code Works

Overall, the combined sketch adds three important features on top of the LED-only version. Each one addresses a specific challenge of moving a physical mechanism reliably:

  • OPEN_ANGLE and CLOSE_ANGLE: These constants define how far the servo rotates for the open and closed positions. The default values (20 and 90 degrees) are starting points. You should tune them to match your specific door linkage. Test with the servo detached first, then attach the horn and adjust until the latch fully opens and fully closes.
  • Small-step movement (moveServoTo): Instead of jumping directly to the target angle, the servo moves in 2-degree increments with a 10 ms delay between steps. This gradual movement reduces mechanical stress on the servo gears, lowers the current spike, and makes the door motion quieter and smoother. You can increase STEP_DEG for faster movement or decrease it for even smoother motion.
  • LED mirrors door state: The setDoorOpen() function calls setLED() after moving the servo, so the LED is always in sync with the door position. ON means the door is open, OFF means it is closed. You can still toggle the LED independently with the LED toggle button if you want.

How to Test and Tune the Build

To verify everything works correctly, follow this step-by-step sequence to bring up the project safely and avoid frustration:

Step-by-Step Testing Sequence

  1. Test the scanner first. Upload the scanner sketch from Step 2. Open Serial Monitor at 115200. Press each remote button and confirm you see consistent output. If nothing appears, check your wiring and make sure the IR receiver OUT goes to D2.
  2. Verify LED-only functionality. Upload the LED-only sketch from Step 3. Press your chosen toggle button. The LED on D8 should turn on with the first press and off with the second. Watch Serial Monitor for the decoded command values to confirm they match your expectations.
  3. Test the servo without the door mechanism. Before attaching the servo to any door or latch, upload the combined sketch and test with the servo loose on the desk. Press OPEN and CLOSE and verify the servo rotates smoothly between the two angles. This prevents stripped gears if the angles are wrong.
  4. Tune open and close angles. Adjust OPEN_ANGLE and CLOSE_ANGLE in the code until the servo arm reaches the correct positions for your latch. Re-upload and test after each change. Small changes of 5-10 degrees at a time are safest.
  5. Attach the mechanism and final test. Mount the servo horn to your door latch. Run the combined sketch and verify that the door opens, closes, and that the LED correctly mirrors the state.

Troubleshooting

If something is not working, check these common issues:

ProblemLikely CauseSolution
No serial output appearsWrong baud rate or wiring errorSet Serial Monitor to 115200. Check that IR receiver OUT is connected to D2 and the module has 5 V and GND.
Remote button does not trigger reliablyWeak signal, distance, or anglePoint the remote directly at the receiver from within 3 meters. Remove bright IR sources (sunlight, some lamps) that can overload the receiver.
Command value changes unexpectedlyProtocol mismatch or noisy signalUse the RAW (decodedRawData) fallback instead of command. Some remotes use protocols where the command byte varies.
LED does not toggleWrong command value in codeOpen the scanner sketch, press the button, and compare the hex value to what you have in the LED-only sketch. Also check LED polarity (flip it if needed).
Long presses cause repeated togglingRepeat filtering missing or not workingMake sure the IRDATA_FLAGS_IS_REPEAT check is present in your loop. Do not remove the flag check.
Servo jitters or buzzesInsufficient power or noisy signalPower the servo from an external 5 V supply with common ground. Ensure the signal wire is short and not near power cables.
Servo does not move at allWrong pin, no power, or wrong commandVerify the signal wire is on D9. Check that the external supply is on and GND is connected to Arduino GND. Verify OPEN and CLOSE command values match your scanner output.
Servo moves the wrong directionOPEN_ANGLE and CLOSE_ANGLE swappedSwap the values of OPEN_ANGLE and CLOSE_ANGLE in the code and re-upload.
Arduino resets when servo movesServo drawing too much current from ArduinoThis is the most common problem. Use an external 5 V supply for the servo. The Arduino 5 V pin cannot handle servo current spikes.
IR stops working after adding the servoPower supply noise or timer conflictAdd a 100 uF capacitor across the servo power lines. Make sure IRremote uses Timer 2 (the default on Uno) and Servo uses Timer 1.

Frequently Asked Questions

What is the KY-022 IR receiver module?

The KY-022 is a small breakout board that carries a VS1838B or TSOP1838 infrared receiver. It demodulates 38 kHz IR signals and outputs a clean digital signal that the Arduino can read. The module exposes three pins: signal (OUT), VCC, and GND. As a result, it is one of the most common IR receiver modules in Arduino starter kits.

Which Arduino pins does the IR receiver use?

In this tutorial, the IR receiver OUT pin connects to digital pin D2. The IRremote library uses a hardware timer interrupt, not a specific pin, so you can change the receive pin if needed. However, D2 is the conventional default for most IRremote examples.

Can I use a different IR remote instead of the kit remote?

Yes. Any IR remote that uses a 38 kHz carrier will work with the KY-022 receiver. TV remotes, air conditioner remotes, and universal remotes all work. Just run the scanner sketch to find the button codes for your specific remote.

What is the difference between command and decodedRawData in the IRremote library?

The command field contains the protocol-decoded button code, typically one byte for NEC remotes. The decodedRawData field contains the full 32-bit raw frame including address bits. Use command when it gives stable results. Use decodedRawData as a fallback if command returns zero or varies between presses.

Why does my LED toggle twice on a single button press?

This usually means the repeat frame filter is missing or not working correctly. Make sure your loop includes the IRDATA_FLAGS_IS_REPEAT check. Also make sure you are using IRremote version 3.x or newer, as the API changed significantly from version 2.x.

Can I control more than one LED with the IR remote?

Yes. Scan additional buttons with the scanner sketch, assign each to a different pin, and add more if conditions in the loop. The architecture in this tutorial scales easily to multiple outputs.

Do I need an external power supply for the LED-only build?

No. An LED-only build draws very little current and runs fine from USB power alone. For the servo motor upgrade in Part 2, however, an external 5 V supply is required.

Why does the servo jitter when it is not moving?

Generally, servo jitter is almost always a power issue. If the servo shares the Arduino 5 V rail, voltage fluctuations cause the servo to twitch. Use a dedicated external 5 V supply with common ground. Adding a 100 uF electrolytic capacitor across the servo power rails also helps.

Can I use this IR remote setup for home automation?

Indeed, this project is a great starting point for simple IR-based home automation. You can add relays to control lights, fans, or other appliances. For a more advanced door control project, see our Arduino Password Door Lock tutorial, which adds keypad authentication to the same servo door mechanism.

What should I do if the IRremote library conflicts with the Servo library?

Notably, on the Arduino Uno, the IRremote library uses Timer 2 and the Servo library uses Timer 1, so they do not conflict by default. If you are using a different board or a different IR library version, check the timer assignments in the library documentation. On boards with fewer timers, you may need to use a software servo library instead.

Downloads and Resources

Code From This Tutorial

  • Scanner sketch — Use the IR Code Scanner in Step 2 above to find your remote’s button codes.
  • LED-only sketch — The final LED-only code in Step 3 toggles an LED on D8 with one remote button.
  • Combined LED + Servo sketch — The full combined code in Part 2 controls both the LED and the servo door.

Related OmArTronics Tutorials

External Resources

Suggested Next Projects

Now that you can decode IR signals and control outputs, here are some ideas to expand your skills. For example, you could:

  • For instance, add a relay module to control a mains-powered lamp or fan with the same IR remote.
  • Additionally, add an LCD or OLED screen to display the current door status and the last received command.
  • Combine IR remote input with the keypad door lock for dual-authentication security.
  • Alternatively, log IR commands to an SD card to create a simple access log for your door.

Conclusion

In this Arduino IR remote tutorial, you built a complete wireless control system in two stages. Part 1 gave you a simple LED toggle controlled by an infrared remote and a KY-022 receiver, teaching you how to scan button codes, filter repeat frames, and decode IR signals with the IRremote library. Part 2 upgraded the same circuit with a servo-driven door, where the LED automatically reflects whether the door is open or closed.

As a result, this modular approach means you can reuse the same IR remote architecture in any project that needs wireless button input. Whether you add more LEDs, relays, or motors, the scanning and decoding logic stays the same.

Finally, if you want to take the door mechanism further, check out the Arduino Password Door Lock for keypad-based security, or explore our servo motor with joystick tutorial for more ways to control servo motion with Arduino.

Leave a Comment