Skip to main content

4x4 Matrix Keypad

Introduction

A 4x4 matrix keypad gives you 16 buttons while only using 8 GPIO connections.

Instead of wiring every key separately, the keypad is arranged as:

  • 4 rows
  • 4 columns

When you press a key, one row connects to one column. The Pico scans the rows and checks the columns to work out which key was pressed.

This makes matrix keypads useful for:

  • password entry
  • menu systems
  • calculators
  • alarm panels
  • small control interfaces

Components Needed

ComponentQuantity
Raspberry Pi Pico or Pico W1
Micro USB cable1
Breadboard1
Jumper wiresSeveral
10k ohm resistor4
4x4 matrix keypad1

Fritzing Diagram

This tutorial uses the same wiring reference as the SunFounder keypad lesson:

4x4 matrix keypad wiring for Raspberry Pi Pico

Wiring Notes

Example Pico pin mapping used here:

  • R1 -> GP2
  • R2 -> GP3
  • R3 -> GP4
  • R4 -> GP5
  • C1 -> GP6
  • C2 -> GP7
  • C3 -> GP8
  • C4 -> GP9

Different keypad modules may label the pins differently, so check the order printed on your keypad before copying the wiring.

Basic Code

import machine
import utime

characters = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"],
]

row_pins = [2, 3, 4, 5]
col_pins = [6, 7, 8, 9]

rows = [machine.Pin(pin, machine.Pin.OUT) for pin in row_pins]
cols = [machine.Pin(pin, machine.Pin.IN) for pin in col_pins]

for row in rows:
row.low()

def read_key():
pressed_keys = []

for row_index, row in enumerate(rows):
row.high()

for col_index, col in enumerate(cols):
if col.value() == 1:
pressed_keys.append(characters[row_index][col_index])

row.low()

if pressed_keys:
return pressed_keys

return None

last_key = None

while True:
current_key = read_key()

if current_key != last_key and current_key is not None:
print(current_key)

last_key = current_key
utime.sleep_ms(100)

Code Explanation

Import the modules

import machine
import utime

machine gives access to the GPIO pins. utime is used to slow the scan loop down a little.

Define the keypad layout

characters = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"],
]

This 2D list matches the physical keypad layout. Each row and column combination points to one key.

Set up the row and column pins

row_pins = [2, 3, 4, 5]
col_pins = [6, 7, 8, 9]

These lists define which Pico GPIO pins are connected to the keypad.

rows = [machine.Pin(pin, machine.Pin.OUT) for pin in row_pins]
cols = [machine.Pin(pin, machine.Pin.IN) for pin in col_pins]

The rows are outputs because the Pico drives them one at a time. The columns are inputs because the Pico reads them back to see whether a key is pressed.

Keep the rows low by default

for row in rows:
row.low()

This makes sure the keypad starts in a known state before scanning begins.

Scan the keypad

def read_key():

This function scans the keypad and returns the pressed key or keys.

for row_index, row in enumerate(rows):
row.high()

Only one row is driven high at a time.

if col.value() == 1:
pressed_keys.append(characters[row_index][col_index])

If one of the column inputs goes high while that row is active, the code knows exactly which button has been pressed.

row.low()

After checking that row, it is driven low again and the code moves to the next row.

Avoid repeated prints

if current_key != last_key and current_key is not None:
print(current_key)

Without this check, the same key would print over and over while you hold it down.

Example 2 - Print a Single Character

The first example returns a list so it can handle multiple key presses. If you only want the first detected key, use this version:

import machine
import utime

characters = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"],
]

row_pins = [2, 3, 4, 5]
col_pins = [6, 7, 8, 9]

rows = [machine.Pin(pin, machine.Pin.OUT) for pin in row_pins]
cols = [machine.Pin(pin, machine.Pin.IN) for pin in col_pins]

for row in rows:
row.low()

def read_key():
for row_index, row in enumerate(rows):
row.high()

for col_index, col in enumerate(cols):
if col.value() == 1:
row.low()
return characters[row_index][col_index]

row.low()

return None

last_key = None

while True:
key = read_key()

if key != last_key and key is not None:
print("Pressed:", key)

last_key = key
utime.sleep_ms(100)

Example 3 - Simple Password Entry

This version stores key presses and checks the code when # is pressed.

import machine
import utime

characters = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"],
]

row_pins = [2, 3, 4, 5]
col_pins = [6, 7, 8, 9]

rows = [machine.Pin(pin, machine.Pin.OUT) for pin in row_pins]
cols = [machine.Pin(pin, machine.Pin.IN) for pin in col_pins]

for row in rows:
row.low()

password = "1234"
entered = ""

def read_key():
for row_index, row in enumerate(rows):
row.high()

for col_index, col in enumerate(cols):
if col.value() == 1:
row.low()
return characters[row_index][col_index]

row.low()

return None

last_key = None

while True:
key = read_key()

if key != last_key and key is not None:
print("Pressed:", key)

if key == "*":
entered = ""
print("Cleared")
elif key == "#":
if entered == password:
print("Access granted")
else:
print("Access denied")
entered = ""
else:
entered += key
print("Current:", entered)

last_key = key
utime.sleep_ms(100)

Common Problems

No key presses are detected

  • check the row and column order on your keypad
  • make sure the Pico and keypad share ground
  • confirm the GPIO numbers in your code match the wiring

The wrong characters are printed

That usually means the keypad pin order is different from the characters layout or the row and column wires are connected in a different order.

A key keeps repeating

That can happen if you remove the last_key check. Keep a short delay and only print when the value changes.

Some keys work and others do not

  • recheck the jumper wires
  • make sure each row and column connection is seated properly
  • test the keypad with a simpler sketch that prints every detected key

Summary

A 4x4 matrix keypad is a compact way to add lots of button input to a Raspberry Pi Pico project.

With simple row-and-column scanning, you can build:

  • keypad locks
  • menus
  • calculators
  • custom control panels

Once you can read the keys reliably, it is easy to combine the keypad with displays, buzzers, relays, or other project logic.