Code Lock: a 4×4 Keypad, an LCD and a PIN
A 4×4 membrane keypad needs only 8 pins thanks to matrix scanning. We build a code lock: PIN entry shown as asterisks on an LCD, "#" to confirm, "*" to clear, and an LED standing in for the electric lock.
About the project
16 buttons, 16 pins? No, eight are enough: the keys of a membrane keypad are wired as a matrix of 4 rows × 4 columns. We build a code lock with an LCD 1602 and a "lock" that opens for 2 seconds after the right PIN.
What you need
- ESP32 DevKit
- 4×4 membrane keypad
- LCD 1602 with an I2C backpack
- LED + 220 Ω resistor (in a real build, a relay or MOSFET and an electric lock)
- The Keypad and LiquidCrystal_I2C libraries
Wiring
| Keypad | R1 | R2 | R3 | R4 | C1 | C2 | C3 | C4 |
|---|---|---|---|---|---|---|---|---|
| ESP32 | GPIO19 | GPIO18 | GPIO5 | GPIO17 | GPIO16 | GPIO4 | GPIO13 | GPIO14 |
- LCD: VCC → 3V3, GND → GND, SDA → GPIO21, SCL → GPIO22.
- "Lock": GPIO26 → 220 Ω → LED → GND.
The keypad ribbon has 8 contacts: left to right (keys facing you), the four rows come first, then the four columns.
How the matrix is scanned
The library pulls one column LOW at a time and reads the rows with INPUT_PULLUP. If row R2 reads LOW while column C3 is active, the key at that crossing, "6", is pressed. A full scan takes microseconds, so the user never notices.
keypad.getKey() returns a character once per press (debounced inside), or 0 when nothing is pressed.
Code
// Code lock: 4×4 keypad + LCD 1602, PIN 1234
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'},
};
byte rowPins[ROWS] = {19, 18, 5, 17};
byte colPins[COLS] = {16, 4, 13, 14};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int LOCK = 26;
const String PIN_CODE = "1234";
String entered;
void show() {
lcd.clear();
lcd.print("PIN: ");
for (unsigned int i = 0; i < entered.length(); i++) lcd.print('*'); // never show the digits
}
void setup() {
lcd.init();
lcd.backlight();
pinMode(LOCK, OUTPUT);
show();
}
void loop() {
char k = keypad.getKey();
if (!k) return;
if (k == '#') { // confirm
lcd.clear();
if (entered == PIN_CODE) {
lcd.print("Access granted");
digitalWrite(LOCK, HIGH); // open the lock for 2 s
delay(2000);
digitalWrite(LOCK, LOW);
} else {
lcd.print("Wrong PIN");
delay(1500);
}
entered = "";
show();
} else if (k == '*') { // clear
entered = "";
show();
} else if (entered.length() < 8) {
entered += k;
show();
}
}
Doing it for real
- The lock. A 12 V electric strike draws 0.3–1 A: switch it through a MOSFET or relay, with a diode across the coil.
- Brute-force protection. After three wrong PINs, block input for a minute, then double the pause each time.
- Keep the PIN out of the code. Store it in
Preferences(the ESP32 flash) and allow changing it, e.g. "A" + old PIN + new PIN. - Power. A lock that opens on any glitch is a bad idea. Think about what happens if the ESP32 resets while the output is HIGH.