Built-in project · ESP32-DevKitC V4

MicroPython keypad door lock with a servo on an ESP32-DevKitC V4

MicroPython on an ESP32: type 1234 on a 4x4 keypad to swing the servo on GPIO 19 open; every key beeps a buzzer behind an S8050 on GPIO 18. The whole build, an ESP32-DevKitC V4 and 5 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.

Advanced About 40 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $14.55. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Scanning a matrix keypad row by row
  • Pull-up inputs and active-low scanning
  • Driving a servo with PWM at 50 Hz
  • Switching a buzzer with a transistor
  • Collecting key presses into a string
  • Difficulty: Advanced
  • Time: About 40 minutes
  • Board: ESP32-DevKitC V4
  • Parts: 8

How it works

The keypad is scanned by a small helper module. It pulls each row low in turn and reads the columns, which are held high by pull-ups, to see which key joins them. It reports each press once.

Every key gives a 50 ms beep from the buzzer, which is switched by a transistor on GPIO 18. After four keys, the program compares them to CODE, which is 1234. A match turns the servo on GPIO 19 to 90 degrees for one second, then back to 0. A wrong code gives one long beep, and the typed keys start over.

The servo wants a 50 Hz pulse. The sketch sets that with PWM and turns an angle into a duty value.

Parts list

8 parts, plus the jumper wires. They retail for about $14.55; 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
Keypad, 4x4 pin R4ESP32-DevKitC V4 pin 25
Keypad, 4x4 pin R3ESP32-DevKitC V4 pin 26
Keypad, 4x4 pin R2ESP32-DevKitC V4 pin 27
Keypad, 4x4 pin R1ESP32-DevKitC V4 pin 14
Keypad, 4x4 pin C1ESP32-DevKitC V4 pin 13
Keypad, 4x4 pin C4ESP32-DevKitC V4 pin 23
Keypad, 4x4 pin C3ESP32-DevKitC V4 pin 22
Keypad, 4x4 pin C2ESP32-DevKitC V4 pin 21
Hobby servo pin PWMESP32-DevKitC V4 pin 19
Resistor, 1k Ω pin 1ESP32-DevKitC V4 pin 18
Buzzer pin 13.3 V
Transistor (NPN or PNP) pin CBuzzer pin 2
Transistor (NPN or PNP) pin BResistor, 1k Ω pin 2
Transistor (NPN or PNP) pin EGround
Hobby servo pin GNDGround
Hobby servo pin VCC5 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 · ESP32-DevKitC V4
# Keypad door: type the four-digit code. Each key beeps; the right code
# (1234) turns the servo on GPIO 19 to open the door for a second, and a
# wrong one gives a long beep. The buzzer is behind an S8050 on GPIO 18.
from machine import Pin, PWM
from keypad import Keypad
import time

pad = Keypad([14, 27, 26, 25], [13, 21, 22, 23])
buzzer = Pin(18, Pin.OUT, value=0)
servo = PWM(Pin(19), freq=50)
CODE = "1234"

def angle(deg):
    # 50 Hz is 20 ms a period: 0.5 ms (duty 26) is 0 degrees and
    # 2.5 ms (duty 128) is 180 degrees
    servo.duty(26 + deg * 102 // 180)

def beep(ms):
    buzzer.value(1)
    time.sleep_ms(ms)
    buzzer.value(0)

angle(0)
typed = ""
print("Enter the code")
while True:
    key = pad.key()
    if key:
        beep(50)
        typed += key
        print("typed", typed)
        if len(typed) == 4:
            if typed == CODE:
                print("open")
                angle(90)
                time.sleep(1)
                angle(0)
            else:
                print("wrong code")
                beep(1000)
            typed = ""
    time.sleep_ms(10)
keypad.py · ESP32-DevKitC V4
# keypad.py: a 4x4 matrix keypad. Each row is pulled low in turn and the
# columns, held high by pull-ups, show which key joins them to that row.
from machine import Pin
import time

KEYS = ["123A", "456B", "789C", "*0#D"]

class Keypad:
    def __init__(self, rows, cols):
        self.rows = [Pin(n, Pin.OUT, value=1) for n in rows]
        self.cols = [Pin(n, Pin.IN, Pin.PULL_UP) for n in cols]
        self.down = None

    def _held(self):
        for r, row in enumerate(self.rows):
            row.value(0)
            for c, col in enumerate(self.cols):
                if col.value() == 0:
                    row.value(1)
                    return KEYS[r][c]
            row.value(1)
        return None

    def key(self):
        """The key just pressed, once per press, or None."""
        held = self._held()
        if held == self.down:
            return None
        self.down = held
        if held is not None:
            time.sleep_ms(10)           # let the contact settle
        return held

Code walkthrough

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

angle main.py

angle sets the servo position. At 50 Hz one period is 20 ms. A 0.5 ms pulse (duty 26) means 0 degrees and a 2.5 ms pulse (duty 128) means 180. The function scales the angle between those two numbers.

def angle(deg):
    # 50 Hz is 20 ms a period: 0.5 ms (duty 26) is 0 degrees and
    # 2.5 ms (duty 128) is 180 degrees
    servo.duty(26 + deg * 102 // 180)

beep main.py

beep turns the buzzer pin on, waits the given milliseconds and turns it off. A short beep confirms a key and a long one, 1000 ms, signals a wrong code.

def beep(ms):
    buzzer.value(1)
    time.sleep_ms(ms)
    buzzer.value(0)

Troubleshooting

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

  • Right keys give a wrong code. This project
    The row and column pin lists in Keypad must match the wiring. Rows are 14, 27, 26, 25 and columns 13, 21, 22, 23.
  • The servo does not move. This project
    The signal goes to GPIO 19. The servo needs 5 V and a common ground.
  • The buzzer stays silent or sounds all the time. This project
    Check the transistor: its base goes through the 1k resistor to GPIO 18, its emitter to ground, its collector to the buzzer.
  • Pressing one key reports several keys, or the wrong key. Keypad
    Match the keymap and the row and column pin order to your keypad's pinout. Without diodes, pressing three keys at once can ghost a fourth.
  • No keys are detected. Keypad
    Rows need pull-ups, or use INPUT_PULLUP, while one column is driven low at a time. The Keypad library does this for you if you pass the pins in the right order.
  • The buzzer makes a constant tone, or you cannot change its pitch. Buzzer
    An active buzzer has its own oscillator, so it only beeps on and off. A passive buzzer needs tone() to set pitch. Check which type you have.
  • The buzzer is silent or very quiet. Buzzer
    Check polarity: the plus mark goes to the pin or supply. For loud buzzers, drive them through a transistor instead of straight from a pin.
  • 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.