MicroPython infrared remote codes on GPIO 15, on an ESP32-DevKitC V4
MicroPython on an ESP32: an IR receiver on GPIO 15 and a small NEC decoder print the code of each button pressed on the remote. The whole build, an ESP32-DevKitC V4 and 3 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.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $11.55. On Mokxi they're free.
What you'll learn
- How an infrared receiver turns light bursts into a digital signal
- How the NEC protocol sends bits as timed gaps
- Using a pin interrupt to catch edges as they happen
- Checking a command against its inverse to catch errors
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: ESP32-DevKitC V4
- Parts: 6
How it works
The VS1838B receiver picks up the remote's infrared light and turns it into a clean signal on its OUT pin. That pin idles high and goes low during each burst. A 10k resistor pulls it up, and it goes to GPIO 15.
A small decoder in nec.py counts the time between falling edges. An NEC frame is a long header burst, then 32 bits. A gap around 2.25 ms means a 1, and a shorter gap means a 0.
The decoder checks that the command and its inverse agree, then main.py prints the code in hex every time you press a button, starting with 0xff.
Parts list
6 parts, plus the jumper wires. They retail for about $11.55; on Mokxi they're free.
- 1 × ESP32-DevKitC V4
- 1 × Infrared remote, 21 buttons
- 1 × Infrared receiver, VS1838B
- 1 × Resistor, 10k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 2 × Power, 3.3 V
Kits that can build it
These 3 starter kits have every part in the circuit. Each kit's page lists what else it can build.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Infrared receiver, VS1838B pin OUT | ESP32-DevKitC V4 pin 15 |
| Resistor, 10k Ω pin 2 | ESP32-DevKitC V4 pin 15 |
| Infrared receiver, VS1838B pin IR | Infrared remote, 21 buttons pin IR |
| Infrared receiver, VS1838B pin GND | Ground |
| Infrared receiver, VS1838B pin VCC | 3.3 V |
| Resistor, 10k Ω pin 1 | 3.3 V |
The code
The MicroPython code exactly as the editor opens it. Change a line there and press Run: the board runs it on the official MicroPython firmware, with nothing to compile.
# Infrared remote: press a button on the remote and its code is
# printed. The receiver's output is on GPIO 15 with a 10k pull-up.
from nec import NEC
import time
remote = NEC(15)
print("Point the remote and press a button")
while True:
code = remote.read()
if code is not None:
print("0x%06x" % code)
time.sleep_ms(20)
# nec.py: read an NEC infrared remote through a VS1838B-style receiver.
# The receiver's output idles high and goes low during each burst. A
# frame is a 9 ms burst, a 4.5 ms gap, then 32 bits, least significant
# first: a bit is a short burst and a short gap (0) or a long gap (1).
from machine import Pin
import time
class NEC:
def __init__(self, gpio):
self.pin = Pin(gpio, Pin.IN)
self.edges = []
self.last = 0
self.code = None
self.pin.irq(trigger=Pin.IRQ_FALLING, handler=self._edge)
def _edge(self, pin):
now = time.ticks_us()
gap = time.ticks_diff(now, self.last)
self.last = now
if gap > 20000: # a long quiet: a new frame starts
self.edges = []
return
self.edges.append(gap)
if len(self.edges) == 33: # the header and 32 bits
bits = 0
for i, t in enumerate(self.edges[1:]):
if t > 1700: # 2.25 ms between edges is a 1
bits |= 1 << i
self.code = bits
self.edges = []
def read(self):
"""The last frame as 0xff then the address and command, MSB first, or None."""
bits, self.code = self.code, None
if bits is None:
return None
command = (bits >> 16) & 0xff
if command ^ ((bits >> 24) & 0xff) != 0xff:
return None # the command and its inverse disagree
# the way most tutorials print it: each byte bit-reversed, 0xff first
return 0xff0000 | _reverse(command) << 8 | _reverse(command ^ 0xff)
def _reverse(byte):
out = 0
for i in range(8):
out = out << 1 | (byte >> i) & 1
return out
Code walkthrough
The key functions of the sketch, and what each one does.
_reverse nec.py
Flips the eight bits of a byte end to end. NEC sends the least significant bit first, but most tutorials print each byte the other way around. read uses this to print the codes in the form you will see online, with 0xff first.
def _reverse(byte):
out = 0
for i in range(8):
out = out << 1 | (byte >> i) & 1
return outTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- Nothing prints when you press a button. This project
Check that the receiver's OUT goes to GPIO 15, and that it has 3.3 V on VCC and ground on GND. - The code is the same for every button. This project
Check the 10k pull-up resistor from OUT to 3.3 V. - Button codes come out wrong or do not match another sketch. Infrared remote
Remotes differ. Print the raw codes for your buttons first, then compare against those values. A repeat while you hold a button is a special repeat code, not a new press. - Nothing is received at all. Infrared remote
Check the receiver pin order of OUT, GND and VCC, which varies by module. Use the pin your IR library expects. Keep the receiver away from bright light and aim the remote at it. - No signal is seen, or the module gets hot, because the three legs are in the wrong order. Infrared receiver
On the VS1838B, with the dome facing you, the legs are usually OUT, GND, VCC from left to right. Check your module's marking before powering it. Supply is 2.7 to 5.5 V. - A plain LED blinking at the receiver is never detected. Infrared receiver
The receiver only responds to light modulated at about 38 kHz. Send with a library such as IRremote, which makes the carrier. OUT idles high and goes low during a burst, so decode that inverted signal. - The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure. - A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.