Control a mood lamp with the IR remote

A 3D-printed crystal lamp that glows in any colour. Pick a colour with the number keys, dim it with the arrows, start a rainbow or a flickering candle, and set a sleep timer, all from the IR remote.
  • Level:Intermediate
  • Duration: 1 h
  • Ages 10+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Control a mood lamp with the IR remote – EEMENTOR STEM™ lesson

In the RGB LED lesson you mixed colours with three numbers, and in the IR turret lesson you sent commands with the remote. Now you put both together in a lamp you will actually want on your desk. The number keys choose a colour, the arrows make it brighter, dimmer or shift the colour, OK starts a slow rainbow and the star key turns it into a flickering candle. The hash key starts a sleep timer: after 15 minutes the lamp fades out by itself.

What you'll learn

  • How to describe a colour by hue, saturation and brightness instead of red, green and blue
  • How to turn hue and brightness into the three PWM values for the LED
  • How to run animations without delay(), so the remote always answers
  • Why IRremote and some PWM pins can't be used together

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • IR receiver module and IR remote (UNO R3 Starter Kit)
  • RGB LED and 3 resistors, 220 Ω (UNO R3 Starter Kit)
  • The printed parts from the 3D printing box below (crystal lamp), or the cardboard version described there
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Breadboard wiring diagram: Control a mood lamp with the IR remote
Breadboard view. Check each connection against the wiring table below.
From To
IR receiver S (signal) UNO pin 2
IR receiver + 5V
IR receiver − GND
RGB LED red leg 220 Ω resistor, then UNO pin 9
RGB LED green leg 220 Ω resistor, then UNO pin 10
RGB LED blue leg 220 Ω resistor, then UNO pin 6
RGB LED longest leg (common −) GND

The RGB LED has four legs. The longest one is the common minus (GND). Next to it on one side is red, on the other side green and then blue.

Blue is on pin 6, not 11. The IRremote library borrows the timer behind pins 3 and 11, so those two pins can't fade an LED while the receiver is listening.

The sketch

In the Arduino IDE, open Library Manager and install IRremote (by shirriff, z3t0 and ArminJo, version 4 or newer).

#include <IRremote.hpp>

const int IR_PIN = 2;
const int RED_PIN = 9, GREEN_PIN = 10, BLUE_PIN = 6;   // not 11: IRremote uses its timer
const unsigned long SLEEP_MINUTES = 15;

// Codes of the 17-key remote; replace them with the codes your remote shows
const uint8_t KEY_1 = 0x45, KEY_2 = 0x46, KEY_3 = 0x47, KEY_4 = 0x44, KEY_5 = 0x40;
const uint8_t KEY_6 = 0x43, KEY_7 = 0x07, KEY_8 = 0x15, KEY_9 = 0x09, KEY_0 = 0x19;
const uint8_t KEY_UP = 0x18, KEY_DOWN = 0x52, KEY_LEFT = 0x08, KEY_RIGHT = 0x5A;
const uint8_t KEY_OK = 0x1C, KEY_STAR = 0x16, KEY_HASH = 0x0D;
const uint8_t NUMBER_KEYS[9] = {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9};

// Colours for keys 1 to 9: hue (0-359) and saturation (0 = white, 255 = full colour)
const int PRESET_HUE[9] = {0, 25, 55, 120, 175, 210, 265, 310, 0};
const byte PRESET_SAT[9] = {255, 255, 255, 255, 255, 255, 255, 255, 0};

enum Mode { STEADY, RAINBOW, CANDLE };
Mode mode = STEADY;
int hue = 25;                    // warm orange to start
byte sat = 255;
int brightness = 160;            // 0-255
bool lampOn = true;
unsigned long sleepAt = 0;       // 0 = no sleep timer
unsigned long lastStep = 0;

// Hue, saturation and brightness -> red, green and blue
void writeColour(int h, int s, int v) {
  h = ((h % 360) + 360) % 360;
  int region = h / 60;
  long rem = (h % 60) * 255L / 60;
  int p = v * (255L - s) / 255;
  int q = v * (255L - s * rem / 255) / 255;
  int t = v * (255L - s * (255 - rem) / 255) / 255;
  int r, g, b;
  switch (region) {
    case 0:  r = v; g = t; b = p; break;
    case 1:  r = q; g = v; b = p; break;
    case 2:  r = p; g = v; b = t; break;
    case 3:  r = p; g = q; b = v; break;
    case 4:  r = t; g = p; b = v; break;
    default: r = v; g = p; b = q; break;
  }
  analogWrite(RED_PIN, r);
  analogWrite(GREEN_PIN, g);
  analogWrite(BLUE_PIN, b);
}

void show() {
  if (!lampOn) writeColour(0, 0, 0);
  else if (mode == STEADY) writeColour(hue, sat, brightness);
}

void blink() {                   // quick flash to say "got it"
  writeColour(0, 0, 0);
  delay(120);
  writeColour(hue, sat, brightness);
  delay(120);
  show();
}

void handleKey(uint8_t key, bool held) {
  for (int i = 0; i < 9; i++) {
    if (key == NUMBER_KEYS[i] && !held) {
      mode = STEADY;
      lampOn = true;
      hue = PRESET_HUE[i];
      sat = PRESET_SAT[i];
      show();
      return;
    }
  }
  if (key == KEY_0 && !held) {
    lampOn = !lampOn;
    sleepAt = 0;
    show();
  } else if (key == KEY_UP) {                  // holding the key keeps going
    brightness = min(brightness + 15, 255);
    lampOn = true;
    show();
  } else if (key == KEY_DOWN) {
    brightness = max(brightness - 15, 10);
    show();
  } else if (key == KEY_LEFT || key == KEY_RIGHT) {
    hue += key == KEY_RIGHT ? 8 : -8;
    sat = 255;
    mode = STEADY;
    show();
  } else if (key == KEY_OK && !held) {
    mode = mode == RAINBOW ? STEADY : RAINBOW;
    lampOn = true;
    show();
  } else if (key == KEY_STAR && !held) {
    mode = mode == CANDLE ? STEADY : CANDLE;
    lampOn = true;
    show();
  } else if (key == KEY_HASH && !held) {
    sleepAt = millis() + SLEEP_MINUTES * 60000UL;
    Serial.println("Sleep timer started");
    blink();
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);
  IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK);
  show();
  Serial.println("Point the remote at the lamp and press a key");
}

void loop() {
  if (IrReceiver.decode()) {
    uint8_t key = IrReceiver.decodedIRData.command;
    bool held = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
    IrReceiver.resume();                       // ready for the next code
    Serial.print("Code 0x");
    Serial.println(key, HEX);
    handleKey(key, held);
  }

  unsigned long now = millis();
  if (lampOn && mode == RAINBOW && now - lastStep >= 40) {
    lastStep = now;
    hue = (hue + 1) % 360;                     // once round the colour wheel in about 15 s
    writeColour(hue, 255, brightness);
  }
  if (lampOn && mode == CANDLE && now - lastStep >= 70) {
    lastStep = now;
    int flame = random(brightness / 2, brightness + 1);
    writeColour(random(18, 32), 255, flame);   // flickering orange
  }
  if (sleepAt != 0 && (long)(now - sleepAt) >= 0) {
    sleepAt = 0;
    for (int v = brightness; v >= 0; v -= 2) { // fade out over about 4 seconds
      writeColour(hue, sat, v);
      delay(50);
    }
    lampOn = false;
    show();
  }
}

The codes in the sketch belong to the 17-key remote in the kit. If your remote is different, open the Serial Monitor at 9600 baud: every key you press prints its code, for example Code 0x45. Copy your codes into the KEY_… lines.

Try it

  1. Upload the sketch. The lamp glows warm orange.
  2. Press 1 to 9 for red, orange, yellow, green, turquoise, blue, purple, pink and white.
  3. Hold the up and down arrows to make it brighter or dimmer, and the left and right arrows to slide the colour along the rainbow.
  4. Press OK for a slow rainbow, and the star key for a flickering candle. Press the same key again to stop.
  5. Press the hash key: the lamp blinks once, and 15 minutes later it fades out by itself. Key 0 switches it on and off.

How it works

Turning three numbers for red, green and blue is a clumsy way to choose a colour. Lamps and drawing programs use hue, saturation and brightness instead. Hue is the position on the colour wheel, from 0 (red) through 120 (green) and 240 (blue) back to 360 (red again). Saturation says how strong the colour is: 0 is white, 255 is the full colour. Brightness is simply how much light.

writeColour() does the maths to turn those three values back into the red, green and blue the LED needs. The wheel is cut into six slices of 60 degrees. In each slice one colour is at full brightness, one is rising or falling and one is off. That is why a slow rainbow only needs hue + 1 every 40 ms.

The animations never use a long delay(). Each pass through loop() checks the remote first and only then moves the rainbow or candle a tiny step when its time has come. That way the lamp answers a key press straight away. The sleep timer works the same way: it stores the time when the lamp should go dark, and loop() checks whether that moment has passed.

Try this next

  • Add a "sunset" key that fades slowly from white through orange to deep red.
  • Make the candle flicker faster when you press the star key twice.
  • Save the last colour in EEPROM so the lamp remembers it after a power cut.

Troubleshooting

  • The remote does nothing: check that the IR receiver's S pin goes to pin 2, and look at the Serial Monitor: if codes appear, copy them into the KEY_… lines.
  • One colour never lights: check its resistor and wire. Blue must be on pin 6; pin 11 doesn't fade while IRremote is running.
  • White looks pink or blue: the three LED colours are not equally bright. Lower the strongest one, for example analogWrite(RED_PIN, r * 3 / 4).
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit