Built-in project · ESP32-DevKitC V4

MicroPython remote sets an LED’s brightness on an ESP32-DevKitC V4

MicroPython on an ESP32: keys 0 to 3 on an IR remote set the PWM brightness of the LED on GPIO 14, and a buzzer beeps on each key. The whole build, an ESP32-DevKitC V4 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 $12.25. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Infrared remote protocol (NEC) basics
  • PWM duty for LED brightness
  • Interrupts that catch pulse edges
  • Switching a buzzer with a transistor
  • Looking up values in a dictionary
  • Difficulty: Intermediate
  • Time: About 30 minutes
  • Board: ESP32-DevKitC V4
  • Parts: 12

How it works

An IR receiver on GPIO 15 listens for a button press on the 21-key remote. The nec.py module decodes the pulses into a code and checks that the command matches its inverse.

In main.py, keys 0 to 3 are looked up in a small table. A match sets the PWM duty on the LED at GPIO 14 to 1, 100, 300 or 1000. The first is nearly off and the last is brightest.

Each recognized key also beeps the buzzer for 100 ms. The buzzer is switched by a transistor from GPIO 13 through a 1k resistor. Other keys do nothing.

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 pinConnects to
Resistor, 1k Ω (1) pin 1ESP32-DevKitC V4 pin 14
Resistor, 1k Ω (2) pin 1ESP32-DevKitC V4 pin 13
Infrared receiver, VS1838B pin OUTESP32-DevKitC V4 pin 15
Resistor, 10k Ω pin 2ESP32-DevKitC V4 pin 15
Infrared receiver, VS1838B pin IRInfrared remote, 21 buttons pin IR
Infrared receiver, VS1838B pin GNDGround
Infrared receiver, VS1838B pin VCC3.3 V
Resistor, 10k Ω pin 13.3 V
LED, red pin AResistor, 1k Ω (1) pin 2
LED, red pin CGround
Buzzer pin 13.3 V
Transistor (NPN or PNP) pin CBuzzer pin 2
Transistor (NPN or PNP) pin BResistor, 1k Ω (2) pin 2
Transistor (NPN or PNP) pin EGround

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 · ESP32-DevKitC V4
# Remote-controlled LED: keys 0 to 3 on the remote set the brightness
# of the LED on GPIO 14, and every recognized key beeps the buzzer
# behind an S8050 on GPIO 13.
from machine import Pin, PWM
from nec import NEC
import time

remote = NEC(15)
led = PWM(Pin(14), freq=10000, duty=0)
buzzer = Pin(13, Pin.OUT, value=0)
levels = {0xff6897: 1, 0xff30cf: 100, 0xff18e7: 300, 0xff7a85: 1000}   # keys 0, 1, 2, 3

while True:
    code = remote.read()
    if code in levels:
        buzzer.value(1)
        time.sleep_ms(100)
        buzzer.value(0)
        led.duty(levels[code])
        print("brightness", levels[code])
    time.sleep_ms(20)
nec.py · ESP32-DevKitC V4
# 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 order of the 8 bits in a byte. The NEC protocol sends bits least significant first, but remote codes are usually written the other way around. read uses this to produce codes that match the values in levels.

def _reverse(byte):
    out = 0
    for i in range(8):
        out = out << 1 | (byte >> i) & 1
    return out

Troubleshooting

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

  • Pressing keys does nothing This project
    Check that the receiver's OUT pin goes to GPIO 15 and that it has 3.3V and ground. The 10k resistor pulls the line up to 3.3V.
  • The buzzer beeps but the LED stays dark This project
    Check the LED's resistor on GPIO 14 and which way the LED faces. Key 0 sets the duty to 1, which is very dim.
  • A key you add to the table never works This project
    The keys in levels are the printed code values. Print code for the new key and use that number.
  • 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.

Change it and keep it

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