A 4×4 membrane keypad is eight wires — four rows, four columns — and a scan loop. On the Pico 2 W the direction of that scan matters: drive the rows LOW and read the columns with pull-ups, so the RP2350’s pull-down erratum never comes into play.
Rows go to GP6–GP9, columns to GP10–GP13. The script prints each key and checks a four-digit PIN (# to enter, * to clear), lighting the on-board LED when it is right.
Wiring
| Raspberry Pi Pico 2 W | 4×4 matrix membrane keypad | Wire | Notes |
|---|---|---|---|
| GP6 | R1 | red | Physical pin 9. |
| GP7 | R2 | orange | Physical pin 10. |
| GP8 | R3 | yellow | Physical pin 11. |
| GP9 | R4 | green | Physical pin 12. |
| GP10 | C1 | blue | Physical pin 14. |
| GP11 | C2 | purple | Physical pin 15. |
| GP12 | C3 | white | Physical pin 16. |
| GP13 | C4 | brown | Physical pin 17. |
No supply — passive switch matrix part, 3.3 V board. The keypad has no electronics, so it works at 3.3 V or 5 V. It does need eight GPIOs; on ESP32 avoid the strapping pins (0, 2, 5, 12, 15) and the input-only pins (34–39) for the row outputs.
Board quirks
- The scan drives one row LOW at a time and reads the columns with
Pin.PULL_UP. Many online examples do the opposite (rows HIGH, columns PULL_DOWN) — on RP2350 A2 chips that hits erratum E9 and keys appear stuck. - The keypad is just switches, so it needs no power wire. Eight GPIOs in a row (GP6–GP13) keep the ribbon cable straight; GND pin 13 sits between them and is not used.
- Pressing two keys in the same column at once can ghost a third. For PIN entry that never matters; for a game controller, add a diode per key.
- This is a demo lock. A real door lock also needs a delay after wrong PINs and a relay or solenoid driver (with its own supply).
Code
# 4x4 membrane keypad on a Raspberry Pi Pico 2 W (MicroPython): scan keys and check a PIN code.
# Wiring: R1-R4 -> GP6-GP9 (pins 9, 10, 11, 12), C1-C4 -> GP10-GP13 (pins 14, 15, 16, 17).
# Rows are driven LOW one at a time; columns use the internal PULL-UPs and read LOW when pressed.
# (Many tutorials drive rows HIGH and use PULL_DOWN on columns - that fails on RP2350 A2 chips,
# erratum E9, where an internal pull-down can hold the pin at about 2 V.)
from machine import Pin
import time
SECRET = "1234"
KEYS = ("123A", "456B", "789C", "*0#D")
rows = [Pin(n, Pin.OUT, value=1) for n in (6, 7, 8, 9)]
cols = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (10, 11, 12, 13)]
led = Pin("LED", Pin.OUT)
def scan():
"""Return the key held down, or None."""
for r, row in enumerate(rows):
row.value(0)
time.sleep_us(10) # let the line settle
for c, col in enumerate(cols):
if col.value() == 0:
row.value(1)
return KEYS[r][c]
row.value(1)
return None
print("Keypad ready. Type the PIN then #, * to clear")
entered = ""
last = None
while True:
key = scan()
if key and key != last: # act once per press
if key == "*":
entered = ""
print("Cleared")
elif key == "#":
if entered == SECRET:
print("PIN OK - unlocked")
led.on()
time.sleep(3)
led.off()
else:
print("Wrong PIN")
entered = ""
else:
entered += key
print("Key {} entered: {}".format(key, "*" * len(entered)))
last = key
time.sleep_ms(30) # also debounces# 4x4 membrane keypad on a Raspberry Pi Pico 2 W (MicroPython): scan keys and check a PIN code.
# Wiring: R1-R4 -> GP6-GP9 (pins 9, 10, 11, 12), C1-C4 -> GP10-GP13 (pins 14, 15, 16, 17).
# Rows are driven LOW one at a time; columns use the internal PULL-UPs and read LOW when pressed.
# (Many tutorials drive rows HIGH and use PULL_DOWN on columns - that fails on RP2350 A2 chips,
# erratum E9, where an internal pull-down can hold the pin at about 2 V.)
from machine import Pin
import time
SECRET = "1234"
KEYS = ("123A", "456B", "789C", "*0#D")
rows = [Pin(n, Pin.OUT, value=1) for n in (6, 7, 8, 9)]
cols = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (10, 11, 12, 13)]
led = Pin("LED", Pin.OUT)
def scan():
"""Return the key held down, or None."""
for r, row in enumerate(rows):
row.value(0)
time.sleep_us(10) # let the line settle
for c, col in enumerate(cols):
if col.value() == 0:
row.value(1)
return KEYS[r][c]
row.value(1)
return None
print("Keypad ready. Type the PIN then #, * to clear")
entered = ""
last = None
while True:
key = scan()
if key and key != last: # act once per press
if key == "*":
entered = ""
print("Cleared")
elif key == "#":
if entered == SECRET:
print("PIN OK - unlocked")
led.on()
time.sleep(3)
led.off()
else:
print("Wrong PIN")
entered = ""
else:
entered += key
print("Key {} entered: {}".format(key, "*" * len(entered)))
last = key
time.sleep_ms(30) # also debouncesExpected serial output
Keypad ready. Type the PIN then #, * to clear
Key 1 entered: *
Key 2 entered: **
Key 3 entered: ***
Key 4 entered: ****
PIN OK - unlockedKeypad ready. Type the PIN then #, * to clear Key 1 entered: * Key 2 entered: ** Key 3 entered: *** Key 4 entered: **** PIN OK - unlocked
Common errors
Keys come out as the wrong characters
Your keypad’s connector order differs. Check which pin is R1 with a multimeter (continuity while pressing “1”) and reorder the GP lists in code.
Whole row or column never responds
A loose jumper in the 8-pin connector. Press it home, or use a male header strip.