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.
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.
- 1 × ESP32-DevKitC V4
- 1 × Keypad, 4x4In Vending Machine Kit, Escape Box Kit.
- 1 × BuzzerIn Starter Kit, Rocket Flight Computer Kit.
- 1 × Resistor, 1k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Transistor (NPN or PNP)In LED Cube Kit.
- 1 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
- 1 × Power, 3.3 V
- 1 × Power, 5 V
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 pin | Connects to |
|---|---|
| Keypad, 4x4 pin R4 | ESP32-DevKitC V4 pin 25 |
| Keypad, 4x4 pin R3 | ESP32-DevKitC V4 pin 26 |
| Keypad, 4x4 pin R2 | ESP32-DevKitC V4 pin 27 |
| Keypad, 4x4 pin R1 | ESP32-DevKitC V4 pin 14 |
| Keypad, 4x4 pin C1 | ESP32-DevKitC V4 pin 13 |
| Keypad, 4x4 pin C4 | ESP32-DevKitC V4 pin 23 |
| Keypad, 4x4 pin C3 | ESP32-DevKitC V4 pin 22 |
| Keypad, 4x4 pin C2 | ESP32-DevKitC V4 pin 21 |
| Hobby servo pin PWM | ESP32-DevKitC V4 pin 19 |
| Resistor, 1k Ω pin 1 | ESP32-DevKitC V4 pin 18 |
| Buzzer pin 1 | 3.3 V |
| Transistor (NPN or PNP) pin C | Buzzer pin 2 |
| Transistor (NPN or PNP) pin B | Resistor, 1k Ω pin 2 |
| Transistor (NPN or PNP) pin E | Ground |
| Hobby servo pin GND | Ground |
| Hobby servo pin VCC | 5 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.
# 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: 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 inKeypadmust 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.