Built-in project · Raspberry Pi Pico

MicroPython IR remote on a Pico: print each button’s code

MicroPython on a Raspberry Pi Pico: an IR receiver on GP15 decodes the kit remote’s NEC frames and prints each button’s hex code. The whole build, a Raspberry Pi Pico 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 $5.55. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • How the NEC infrared protocol encodes bits as pulse widths
  • Timing pulses in microseconds with MicroPython
  • Splitting code into a helper module
  • Reversing bit order to get a familiar code
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Raspberry Pi Pico
  • Parts: 6

How it works

The VS1838B receiver pulls its output low while it sees the remote's 38 kHz infrared light. That output goes to GP15, which has a 10k resistor to 3.3 V.

The remote sends an NEC frame: a 9 ms leader, a 4.5 ms gap, then 32 bits. Each bit is a short low pulse followed by a short or long high. The code in ir_nec.py times those pulses with time_pulse_us to rebuild the bits.

main.py prints each button as a code such as 0xFF.... A held button sends a repeat, which prints repeat.

Parts list

6 parts, plus the jumper wires. They retail for about $5.55; on Mokxi they're free.

Kits that can build it

This starter kit has 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 pinConnects to
Infrared receiver, VS1838B pin OUTRaspberry Pi Pico pin GP15
Resistor, 10k Ω pin 2Raspberry Pi Pico pin GP15
Infrared receiver, VS1838B pin GNDGround
Infrared receiver, VS1838B pin VCC3.3 V
Infrared remote, 21 buttons pin IRInfrared receiver, VS1838B pin IR
Resistor, 10k Ω pin 13.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.

main.py · Raspberry Pi Pico
# The kit's remote and an IR receiver on GP15: each button's NEC frame is
# decoded and printed as a hex code. ir_nec.py, in the next tab, times the
# receiver's pulses.
from ir_nec import NEC, code

remote = NEC(15)
print("Point the remote and press a button")

while True:
    frame = remote.read()
    if frame == "repeat":
        print("repeat")
    elif frame is not None:
        print("code 0x%06X" % code(frame))
ir_nec.py · Raspberry Pi Pico
# ir_nec.py: read an NEC infrared remote from a receiver's output.
# The receiver pulls its output low while it sees the remote's 38 kHz light.
# A frame is a 9 ms low leader, a 4.5 ms gap, then 32 bits, each a 0.56 ms
# low followed by a short (0) or long (1) high. A held button repeats with a
# 2.25 ms gap and no bits.
from machine import Pin, time_pulse_us

class NEC:
    def __init__(self, pin):
        self.pin = Pin(pin, Pin.IN, Pin.PULL_UP)

    def read(self, wait_us=200000):
        """A frame's 32 bits (first bit lowest), 'repeat', or None."""
        if time_pulse_us(self.pin, 0, wait_us) < 8000:
            return None
        gap = time_pulse_us(self.pin, 1, 6000)
        if gap < 0:
            return None
        if gap < 3000:
            return "repeat"
        bits = 0
        for i in range(32):
            if time_pulse_us(self.pin, 0, 2000) < 0:
                return None
            high = time_pulse_us(self.pin, 1, 3000)
            if high < 0:
                return None
            if high > 1000:
                bits |= 1 << i
        return bits

def reverse8(b):
    out = 0
    for _ in range(8):
        out = (out << 1) | (b & 1)
        b >>= 1
    return out

def code(bits):
    """A frame as the familiar 0xFF.... code: the bits read in arrival order."""
    command = (bits >> 16) & 0xFF
    return 0xFF0000 | (reverse8(command) << 8) | reverse8(command ^ 0xFF)

Code walkthrough

The key functions of the sketch, and what each one does.

reverse8 ir_nec.py

Flips the order of the 8 bits in a byte, so the lowest bit becomes the highest. The frame arrives lowest bit first, so a byte has to be flipped to read the way remote codes are usually written.

def reverse8(b):
    out = 0
    for _ in range(8):
        out = (out << 1) | (b & 1)
        b >>= 1
    return out

code ir_nec.py

Takes the 32-bit frame and picks out the command byte. It flips that byte with reverse8, flips its inverse too, and packs both into a code that starts with 0xFF. That is the familiar number for each button.

def code(bits):
    """A frame as the familiar 0xFF.... code: the bits read in arrival order."""
    command = (bits >> 16) & 0xFF
    return 0xFF0000 | (reverse8(command) << 8) | reverse8(command ^ 0xFF)

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • Nothing prints when you press a button This project
    Check OUT goes to GP15, VCC to 3.3 V and GND to ground.
  • Codes look like reversed bits This project
    The frame arrives lowest bit first. code flips each byte with reverse8 to fix that.
  • 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.
  • 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.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.