Built-in project · Raspberry Pi Pico W

Pico W infrared remote decoder on GP17

MicroPython on a Pico W: an IR receiver on GP17 and a small NEC decoder timed from pin interrupts print the name of each remote button pressed. The whole build, a Raspberry Pi Pico W and 2 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 $7.50. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • How an NEC infrared remote encodes buttons
  • Timing pin edges with an interrupt in MicroPython
  • Error checking with inverse bytes
  • Looking up a code in a dictionary
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Raspberry Pi Pico W
  • Parts: 4

How it works

A VS1838B infrared receiver on GP17 pulls its pin low while it sees infrared light. The remote sends each button as a train of bursts and gaps in the NEC format. A small decoder in nec.py times every edge with a pin interrupt.

A frame starts with a 9 ms burst and a 4.5 ms gap, then 32 bits. A short gap is a 0 and a long gap is a 1. The decoder checks that the second byte is the inverse of the first, and likewise for the command, and then hands the command to main.py, which prints the button name.

Parts list

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

Kits that can build it

These 2 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 pinConnects to
Infrared receiver, VS1838B pin OUTRaspberry Pi Pico W pin GP17
Infrared receiver, VS1838B pin GNDGround
Infrared receiver, VS1838B pin VCC3.3 V
Infrared remote, 21 buttons pin IRInfrared receiver, VS1838B pin IR

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 W
# IR remote control: the receiver's output is on GP17. nec.py, in its
# own tab, times its edges and decodes the remote's NEC frames.
from machine import Pin
from nec import NEC
import time

NAMES = {0x45: "CH-", 0x46: "CH", 0x47: "CH+", 0x44: "PREV", 0x40: "NEXT",
         0x43: "PLAY/PAUSE", 0x07: "VOL-", 0x15: "VOL+", 0x09: "EQ",
         0x16: "0", 0x19: "100+", 0x0D: "200+", 0x0C: "1", 0x18: "2",
         0x5E: "3", 0x08: "4", 0x1C: "5", 0x5A: "6", 0x42: "7",
         0x52: "8", 0x4A: "9"}
pressed = []

def got(address, command):
    pressed.append(command)

NEC(Pin(17, Pin.IN), got)
print("point the remote and press a button")

while True:
    while pressed:
        command = pressed.pop(0)
        print("key", NAMES.get(command, hex(command)))
    time.sleep_ms(20)
nec.py · Raspberry Pi Pico W
# nec.py: decode an NEC infrared remote from the receiver's output.
# The receiver pulls its pin low while light arrives. A frame starts with
# a 9 ms burst and a 4.5 ms gap, then 32 bits: each bit is a 562 us burst
# followed by a short gap (0) or a long one (1), least significant first.
# The bytes are the address, its inverse, the command and its inverse.
from machine import Pin
import time

class NEC:
    def __init__(self, pin, callback):
        self.pin = pin
        self.callback = callback
        self.edges = []
        self.last = time.ticks_us()
        pin.irq(handler=self._edge, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING)

    def _edge(self, pin):
        now = time.ticks_us()
        gap = time.ticks_diff(now, self.last)
        self.last = now
        if gap > 12000:             # a long quiet spell: a new frame begins
            self.edges = []
        self.edges.append(gap)
        if len(self.edges) == 67:   # the start, the leader's burst and gap, 32 bits
            self._decode(self.edges)
            self.edges = []

    def _decode(self, edges):
        bits = 0
        for i in range(32):
            space = edges[4 + 2 * i]          # the gap after each burst
            if space > 1100:
                bits |= 1 << i
        address = bits & 0xFF
        command = (bits >> 16) & 0xFF
        if (bits >> 8) & 0xFF == address ^ 0xFF and bits >> 24 == command ^ 0xFF:
            self.callback(address, command)

Code walkthrough

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

got main.py

A callback that the decoder runs when it has read a valid button press. It does very little: it adds the command byte to the pressed list. The main loop then prints the name, which keeps slow work out of the interrupt.

def got(address, command):
    pressed.append(command)

Troubleshooting

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

  • Nothing prints when you press buttons This project
    Check the receiver's OUT goes to GP17 and that it has power and ground.
  • A key prints as a hex number This project
    That code is not in NAMES. Add it to the dictionary with a label.
  • Buttons are missed now and then This project
    The decoder drops frames whose check bytes do not match. Press again and point the remote at the receiver.
  • 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.
  • 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.
  • Pins are powered from 5 V, or a 5 V signal goes into a GPIO. The RP2040 pins are 3.3 V only and are not 5 V tolerant. Raspberry Pi Pico W
    Power the board from VBUS or VSYS at 5 V, but keep GPIO signals at 3.3 V. Use a level shifter or a divider for 5 V parts.

Change it and keep it

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