Remote-controlled lamps on an Arduino Uno
A 21-button handset, a VS1838B and three lamps. NEC decoded from pulse widths, and a receiver that inverts everything it hears. The whole build, an Arduino Uno and 8 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
IntermediateRuns in your browser. Free, and no account needed.
Step by step, with the wiring and the common mistakes: Arduino IR Remote: Decode NEC, Switch Lamps
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Remote-controlled lamps: an infrared receiver on pin 2, three lamps.
//
// pin 2 the VS1838B's OUT
// pin 5 red lamp press 1 on the handset
// pin 6 green lamp press 2
// pin 7 blue lamp press 3
// 0 turns all three off; CH- and CH+ step the brightness of whichever is on
//
// The handset's IR pin is wired to the receiver's: in Mokxi two parts only meet
// on a net, so the beam is a wire. Everything else here is what happens on a
// real desk.
//
// # The receiver inverts
//
// OUT idles HIGH and goes LOW while a burst of light is arriving. So the 9 ms
// leader burst is 9 ms of LOW, and every timing in the decoder is written from
// that side. It is the first surprise and it catches everybody once.
//
// # NEC is widths, not levels
//
// One unit is 562.5 us, 21 cycles of the 38 kHz carrier, which is why it is
// not a round number. A leader is 16 units of burst and 8 of space; then
// thirty-two bits, each one unit of burst and either one unit of space (a
// nought) or three (a one); then a stop burst. The decoder measures the space
// after each burst and splits them at 1.1 ms.
//
// The thirty-two bits are address, address inverted, command, command inverted,
// **least significant bit first**. The two inversions are the whole of the
// error checking, and firmware/lib/mokxi_ir.h checks both, so a frame that
// arrived with somebody's headlights through it is thrown away rather than
// acted on.
//
// # The number every table prints
//
// Tutorials call the CH- button 0xFFA25D; this sketch calls it command 0x45.
// They are the same thirty-two bits. The bits arrive least significant first
// and the old Arduino decoder assembled them the other way round, so each byte
// came out bit-reversed: 0x45 reversed is 0xA2. The sketch prints both, so
// the two readings can be seen to be one thing.
//
// # Repeats
//
// Hold a button and the handset sends a whole frame once and then a short
// repeat every 110 ms. A sketch that treats a repeat as a fresh press is why
// volume buttons run away, so this one only acts on repeats for the two
// brightness keys, where running is the point.
#include "mokxi_ir.h"
const int IR_PIN = 2;
const int LAMPS[3] = {5, 6, 7};
IrReceiver ir(IR_PIN);
int lit = -1; // which lamp is on, or -1
int brightness = 200; // 0..255, on whichever lamp is lit
unsigned char lastCommand = 0;
void pick(int which);
void applyBrightness(int by);
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(LAMPS[i], OUTPUT);
analogWrite(LAMPS[i], 0);
}
Serial.begin(115200);
Serial.println("Mokxi Uno: NEC remote, 1/2/3 pick a lamp, 0 is off");
ir.begin();
}
void loop() {
IrFrame frame = ir.receive(300);
if (!frame.valid) {
return;
}
if (frame.repeated) {
// Only the two that should run while held.
if (lastCommand == IR_CH_DOWN || lastCommand == IR_CH_UP) {
applyBrightness(lastCommand == IR_CH_UP ? 25 : -25);
}
return;
}
lastCommand = frame.command;
Serial.print("address 0x");
Serial.print((long)frame.address, HEX);
Serial.print(" command 0x");
Serial.print((long)frame.command, HEX);
Serial.print(" (tables call it 0x");
Serial.print((long)frame.legacy(), HEX);
Serial.println(")");
switch (frame.command) {
case IR_1:
pick(0);
break;
case IR_2:
pick(1);
break;
case IR_3:
pick(2);
break;
case IR_0:
pick(-1);
break;
case IR_CH_UP:
applyBrightness(25);
break;
case IR_CH_DOWN:
applyBrightness(-25);
break;
default:
Serial.println(" no lamp on that button");
break;
}
}
// Light one lamp and put the other two out. -1 is all off.
void pick(int which) {
lit = which;
for (int i = 0; i < 3; i++) {
analogWrite(LAMPS[i], i == which ? brightness : 0);
}
Serial.print(" lamp ");
Serial.println(which < 0 ? -1 : which + 1);
}
void applyBrightness(int by) {
brightness += by;
if (brightness < 0) {
brightness = 0;
}
if (brightness > 255) {
brightness = 255;
}
if (lit >= 0) {
analogWrite(LAMPS[lit], brightness);
}
Serial.print(" brightness ");
Serial.println(brightness);
}
Parts list
10 parts, plus the jumper wires. Every one is in the editor's parts bin.
- 1 × Arduino Uno R3
- 1 × Half breadboard
- 1 × Infrared remote, 21 buttons
- 1 × Infrared receiver, VS1838B
- 3 × Resistor, 220 Ω
- 1 × LED, red
- 1 × LED, green
- 1 × LED, blue
How it is wired
10 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Uno R3 pin 7; Resistor, 220 Ω (3) pin 1
- Arduino Uno R3 pin 6; Resistor, 220 Ω (2) pin 1
- Arduino Uno R3 pin 5; Resistor, 220 Ω (1) pin 1
- Arduino Uno R3 pin 2; Infrared receiver, VS1838B pin OUT
- Arduino Uno R3 pin 5V; Infrared receiver, VS1838B pin VCC
- Ground: Arduino Uno R3 pin GND; Infrared receiver, VS1838B pin GND; LED, red pin C; LED, green pin C; LED, blue pin C
- Infrared remote, 21 buttons pin IR; Infrared receiver, VS1838B pin IR
- Resistor, 220 Ω (1) pin 2; LED, red pin A
- Resistor, 220 Ω (2) pin 2; LED, green pin A
- Resistor, 220 Ω (3) pin 2; LED, blue pin A
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.