Breadboard
docs
Example project · ESP32

Keypad door lock (keypad + OLED)

A real embedded project: an ESP32 scans a 4x4 matrix keypad and drives an OLED lock screen. Press keys to enter a code (default 1234), * to clear, # to submit; the display shows UNLOCKED or DENIED. Demonstrates matrix scanning, edge detection, and live display, all in MicroPython.

The Keypad door lock (keypad + OLED) circuit as rendered by the simulator

What's on the bench

  • Battery
  • ESP32-C3
  • Keypad 4×4
  • OLED 128×64

The code

This MicroPython script runs on the emulated board every boot; edit it in the Code tab.

from machine import I2C, Pin
import framebuf, time
i = I2C(0, scl=Pin(9), sda=Pin(8))
buf = bytearray(1024)
fb = framebuf.FrameBuffer(buf, 128, 64, framebuf.MONO_VLSB)
for c in b'\xae\xd5\x80\xa8\x3f\xd3\x00\x40\x8d\x14\x20\x00\xa1\xc8\xda\x12\x81\xcf\xd9\xf1\xdb\x40\xa4\xa6\xaf':
    i.writeto(0x3c, bytes([0, c]))
def show():
    i.writeto(0x3c, b'\x00\x21\x00\x7f\x22\x00\x07')
    i.writeto(0x3c, b'\x40' + buf)

# 4x4 matrix keypad: rows drive, columns read (active-high scan).
rows = [Pin(n, Pin.OUT) for n in (0, 1, 2, 3)]
cols = [Pin(n, Pin.IN) for n in (4, 5, 6, 7)]
KEYS = ['123A', '456B', '789C', '*0#D']
def scan():
    pressed = set()
    for r in range(4):
        for x in range(4):
            rows[x].value(1 if x == r else 0)
        time.sleep_ms(2)  # let the row drive settle before reading
        for c in range(4):
            if cols[c].value():
                pressed.add(KEYS[r][c])
    return pressed

PIN = '1234'
entry = ''
msg = 'ENTER CODE'
prev = set()
def render():
    fb.fill(0)
    fb.text('SECURE LOCK', 20, 2)
    fb.text('CODE: ' + '*' * len(entry), 8, 24)
    fb.text(msg, 8, 46)
    show()
render()
print('lock ready, enter code')
while True:
    cur = scan()
    for k in cur - prev:  # keys newly pressed since the last scan
        if k == '#':
            msg = 'UNLOCKED' if entry == PIN else 'DENIED'
            print('submit', entry, '->', msg)
            entry = ''
        elif k == '*':
            entry, msg = '', 'CLEARED'
        elif k in '0123456789':
            entry = (entry + k)[:8]
            msg = 'ENTER CODE'
    prev = cur
    render()
    time.sleep_ms(40)