MicroPython IR remote dimmer on a Pico: keys 0 to 3
MicroPython on a Raspberry Pi Pico: remote keys 0 to 3, read by an IR receiver on GP16, set four levels for the LED on GP14 and beep a buzzer. The whole build, a Raspberry Pi Pico 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.
Teaching? Assign it to your class in one click.
Intermediate About 30 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $6.25. On Mokxi they're free.
What you'll learn
- How an NEC infrared remote sends bits
- Measuring pulse lengths with
time_pulse_us - Dimming an LED with PWM
- Using a dictionary as a lookup table
- Difficulty: Intermediate
- Time: About 30 minutes
- Board: Raspberry Pi Pico
- Parts: 12
How it works
A VS1838B receiver on GP16 pulls its output low when it sees a remote's 38 kHz light. The helper file ir_nec.py times those pulses: a 9 ms leader, a 4.5 ms gap, then 32 bits. A held button sends a short repeat frame, which the main program ignores.
The main program turns each frame into the familiar 0xFF.... code and looks it up in LEVELS. Keys 0, 1, 2 and 3 set the LED on GP14 to four PWM levels: off, 6000, 20000 and 65535 of 65535. Each recognized key also beeps the buzzer on GP15 for 100 ms and prints the brightness in percent.
Other keys are ignored.
Parts list
12 parts, plus the jumper wires. They retail for about $6.25; on Mokxi they're free.
- 1 × Raspberry Pi PicoIn Fight Stick Kit.
- 1 × Infrared receiver, VS1838B
- 1 × Resistor, 10k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Infrared remote, 21 buttons
- 2 × Resistor, 1k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × Transistor (NPN or PNP)In LED Cube Kit.
- 1 × BuzzerIn Starter Kit, Rocket Flight Computer Kit.
- 3 × Power, 3.3 V
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 pin | Connects to |
|---|---|
| Resistor, 1k Ω (1) pin 1 | Raspberry Pi Pico pin GP14 |
| Resistor, 1k Ω (2) pin 1 | Raspberry Pi Pico pin GP15 |
| Infrared receiver, VS1838B pin OUT | Raspberry Pi Pico pin GP16 |
| Resistor, 10k Ω pin 2 | Raspberry Pi Pico pin GP16 |
| Infrared receiver, VS1838B pin GND | Ground |
| Infrared receiver, VS1838B pin VCC | 3.3 V |
| Infrared remote, 21 buttons pin IR | Infrared receiver, VS1838B pin IR |
| Resistor, 10k Ω pin 1 | 3.3 V |
| LED, red pin A | Resistor, 1k Ω (1) pin 2 |
| LED, red pin C | Ground |
| Transistor (NPN or PNP) pin B | Resistor, 1k Ω (2) pin 2 |
| Buzzer pin 2 | Transistor (NPN or PNP) pin C |
| Transistor (NPN or PNP) pin E | Ground |
| Buzzer 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.
# Dim an LED from the remote: keys 0 to 3 set four brightness levels for
# the LED on GP14 (PWM), and each key beeps the buzzer behind GP15.
from machine import Pin, PWM
import time
from ir_nec import NEC, code
remote = NEC(16)
led = PWM(Pin(14))
led.freq(10000)
led.duty_u16(0)
buzzer = Pin(15, Pin.OUT, value=0)
LEVELS = {0xFF6897: ("0", 0), 0xFF30CF: ("1", 6000), 0xFF18E7: ("2", 20000), 0xFF7A85: ("3", 65535)}
while True:
frame = remote.read()
if frame is None or frame == "repeat":
continue
key = LEVELS.get(code(frame))
if key:
name, duty = key
buzzer.value(1)
led.duty_u16(duty)
print("key", name, "brightness", duty * 100 // 65535, "%")
time.sleep_ms(100)
buzzer.value(0)
# 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
reverse8 flips the order of the eight bits in a byte. The remote sends bits lowest first, so a byte read in arrival order is backwards compared with the usual printed codes.
def reverse8(b):
out = 0
for _ in range(8):
out = (out << 1) | (b & 1)
b >>= 1
return outcode ir_nec.py
code takes the 32-bit frame, pulls out the command byte, and builds a code in the usual 0xFF.... form. It reverses the command and its inverse so the number matches the codes in LEVELS.
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.
- Pressing keys does nothing. This project
Only keys 0 to 3 on this remote are inLEVELS. Point the remote at the receiver, whose OUT goes to GP16. - The LED never changes. This project
The LED goes through a 1k resistor to GP14, with its anode on the resistor side. - The buzzer stays on. This project
Check the transistor: GP15 goes through the 1k resistor to its base, and the buzzer is between 3.3 V and the collector. - 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.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.