4x4 Matrix Keypads for Maker Projects: Row/Column Scanning, Debounce, and ESP32/Arduino Code
A 4x4 matrix keypad shows up in maker projects as a cheap, compact way to get 16 buttons' worth of input — door entry codes, menu navigation, calculator-style interfaces — using far fewer microcontroller pins than you'd expect from 16 individual buttons. The trick is in how it's wired, and that same trick is exactly what trips people up the first time: a matrix keypad isn't 16 independent switches, it's a grid of connections that needs to be scanned in software, and getting the scanning and debouncing wrong produces ghost keypresses that look like a hardware fault but are almost always a code problem.
Why a Matrix Instead of 16 Individual Pins
Wiring 16 buttons directly would need 16 input pins (plus a common ground) on your microcontroller. A matrix keypad instead arranges the 16 keys at the intersections of 4 rows and 4 columns, so each key is identified by which row and column it connects when pressed — needing only 8 pins total (4 rows + 4 columns) instead of 16. Scale this further and the savings get bigger: a matrix needs only rows + columns pins for rows × columns keys, so a larger keypad gains efficiency even faster.
How Scanning Works
- The microcontroller sets one row pin LOW (or HIGH, depending on your pull configuration) while leaving the other rows in their inactive state.
- It then reads each column pin in turn. If a key at the intersection of the active row and a given column is pressed, that column pin reads as connected to the active row's state.
- The microcontroller cycles through each row in sequence, repeating the column read each time, building up a full picture of which single key (or combination) is currently pressed.
- This whole cycle repeats continuously, fast enough (typically every few milliseconds) that key presses feel instantaneous to the user even though the controller is technically scanning one row at a time.
Wiring and Pin Configuration
SignalPin ModeTypical Config Row pins (4)OutputDriven LOW one at a time during scan, HIGH (or high-impedance) otherwise Column pins (4)Input with pull-upInternal pull-up resistor (INPUT_PULLUP on Arduino, or GPIO_PULLUP on ESP32) keeps the line HIGH until a pressed key pulls it LOW through the active rowUsing internal pull-ups on the column pins avoids needing 4 external resistors and is supported natively on both Arduino (pinMode(colPin, INPUT_PULLUP)) and ESP32 (same call in Arduino framework, or gpio_pullup_en() in ESP-IDF).
Debouncing: The Part That Actually Causes Problems
A mechanical keypad's contacts don't close cleanly — they bounce, making and breaking contact several times over a few milliseconds before settling. Scanned fast enough without debouncing, this reads as multiple rapid keypresses from a single press. There are two practical approaches:
- Software time-based debounce: after detecting a key press, ignore further changes on that key for a short window (typically 20–50ms) before accepting a new state change. Simple to implement and sufficient for nearly all matrix keypad use cases.
- Library-based debounce: the Arduino Keypad library (by Mark Stanley and Alexander Brevig) and similar libraries for ESP32 handle scanning and debouncing together, and are the practical default for anything beyond a quick prototype — they also handle multi-key states and provide clean callback-style key event detection instead of requiring you to hand-roll the scan loop.
Ghosting and Masking: The Multi-Key Problem
Pressing certain combinations of three keys simultaneously on a basic matrix (without isolation diodes) can create a phantom fourth keypress — current finds a path through the grid that makes an unpressed key at the fourth corner of the rectangle appear pressed. This is called ghosting, and it's a real limitation of simple matrix wiring, not a software bug. For a numeric keypad entry application where users press one key at a time, this almost never matters in practice. For an application expecting legitimate multi-key combinations (a custom macro keypad, for example), adding a diode in series with each key (anode toward the row) blocks the reverse current path and eliminates ghosting entirely, at the cost of 16 extra diodes and more wiring.
A Practical Starting Project: ESP32 Keypad Entry Code
A natural first project pairing a matrix keypad with an ESP32 is a simple PIN-code entry system: scan the keypad, buffer digits as they're entered, compare against a stored code (hashed or at minimum not stored in plaintext in your source if this will ever leave your bench), and trigger a relay or servo on match. Add a short lockout timer after a handful of failed attempts to prevent simple brute-forcing of a 4-digit code by someone with physical access and patience, the same principle used in commercial keypad locks.
Matrix keypads are one of the cheapest, most pin-efficient ways to add deliberate physical input to a project, and the entire design hinges on understanding that you're reading a grid, not 16 switches. Get the scan loop and debounce timing right once, ideally by starting with a proven library rather than a hand-rolled scan routine, and the keypad becomes a solved problem you can drop into any future project without re-debugging ghost keypresses from scratch.