IoTSimulator

Build a Keypad Door Lock with an Arduino Mega

Construct an electronic PIN security access system using an Arduino Mega. Features a 4x4 matrix keypad, masked PIN entry on an I2C LCD1602 display, dual-color LED status indicators, and buzzer audio feedback.
Muhammad Ichsanul Fadhil
IoTSim Editor
October 6, 2026
Build a Keypad Door Lock with an Arduino Mega

Live project track

Interactive hardware & logic preview

From office doors and security safes to electronic gates, numeric keypads are one of the most reliable ways to protect physical spaces. Entering a secret 4-digit code requires active user input, and displaying masked characters such as asterisks (* * * *) prevents onlookers from stealing your passcode over your shoulder. Building your own security lock system teaches you how to coordinate multiple inputs and outputs simultaneously into a responsive, real-time device.

In this project, you will build a complete electronic keypad door lock using an Arduino Mega 2560. You will explore how matrix scanning allows 8 microcontroller pins to read 16 separate buttons, display masked real-time feedback on an I2C LCD1602 screen, and trigger green unlock chimes or red warning buzzers based on the entered passcode.

How Matrix Keypads Scan Buttons with Fewer Pins

How a 4x4 Matrix Keypad Scans 16 Keys with 8 Pins
Figure 1: Matrix multiplexing connects 16 tactile keys across 4 row outputs and 4 column inputs.

Wiring 16 separate pushbuttons individually to a microcontroller would normally require 16 dedicated digital pins, quickly cluttering your board. A 4x4 matrix keypad solves this challenge by organizing switches into a grid of 4 horizontal row wires and 4 vertical column wires. Pressing a key bridges the electrical contact between one specific row and one specific column.

Row / Column PinArduino Mega PinPin ModeScanning Behavior
Row 1 (R1)Digital Pin D2OUTPUTDriven LOW during Row 1 scan; HIGH otherwise
Row 2 (R2)Digital Pin D3OUTPUTDriven LOW during Row 2 scan; HIGH otherwise
Row 3 (R3)Digital Pin D4OUTPUTDriven LOW during Row 3 scan; HIGH otherwise
Row 4 (R4)Digital Pin D5OUTPUTDriven LOW during Row 4 scan; HIGH otherwise
Column 1 (C1)Digital Pin D6INPUT_PULLUPHeld at 5V by internal pull-up; drops to 0V when key pressed
Column 2 (C2)Digital Pin D7INPUT_PULLUPHeld at 5V by internal pull-up; drops to 0V when key pressed
Column 3 (C3)Digital Pin D8INPUT_PULLUPHeld at 5V by internal pull-up; drops to 0V when key pressed
Column 4 (C4)Digital Pin D9INPUT_PULLUPHeld at 5V by internal pull-up; drops to 0V when key pressed

The Arduino Mega continuously loops through each row one by one, temporarily pulling it LOW while keeping the other rows HIGH. When a key like '6' is pressed, it connects Row 2 to Column 3, pulling pin D8 to ground. The microcontroller instantly detects this LOW signal and identifies the exact character by matching row and column coordinates.

Handling PIN Verification and Access Feedback

Security PIN Verification and Access States
Figure 2: The access control system manages standby, masked input, authorized unlock, and denied alert states.

A dependable access terminal must clearly inform the user of what is happening while safeguarding confidential information. When numbers are entered, the system accumulates keystrokes in a temporary memory variable and updates the screen with brackets and asterisks ([* * _ _]). This provides visual confirmation of how many digits have been entered without revealing the secret numbers.

System StateLCD Display MessageLED IndicatorBuzzer Audio Response
Idle StandbyEnter PIN Code: [_ _ _ _]Both LEDs OFFSilent (Ready for interaction)
Key PressedUpdates masked asterisksBoth LEDs OFFShort click chirp (1500 Hz for 30 ms)
Access GrantedACCESS GRANTED! Door UnlockedGreen LED ON (D11)Two-tone rising chime (1800 Hz then 2400 Hz)
Access DeniedACCESS DENIED! Invalid PasscodeRed LED ON (D12)Low warning tone (400 Hz for 500 ms)
Buffer ClearedResets to [_ _ _ _]Both LEDs OFFShort click chirp on '*' key press

Pressing the '#' key signals that the user has finished entering their passcode and wants to submit it. The firmware compares the entered string against the stored master code '1234'. If they match, the green LED lights up and a two-tone chime sounds. If they do not match, the red LED illuminates, a low alarm tone sounds, and both states hold for a few seconds before returning automatically to standby.

Safe Box Access: Granted vs Denied

Digital safe box password check: Correct code unlocks, wrong code alarms
Figure 3: Safe box security logic: typing the correct 4-digit code unlocks the door, while a wrong code triggers the alarm.

Think of this project like a digital bank safe or hotel door lock. The microcontroller stores a secret 4-digit password in memory. Every time a user types four numbers on the keypad, it evaluates whether they match the secret code: a correct PIN instantly unlocks access with a green light and chime, while any wrong entry sounds an alert. The interactive connection tables below show how each module connects to the Arduino Mega.

Pin Connection Map
4x4 Matrix Keypad Pin
R1 (Row 1)
→
Arduino Mega Pin
Digital Pin D2
Explanation
Row 1 output strobe line
4x4 Matrix Keypad Pin
R2 (Row 2)
→
Arduino Mega Pin
Digital Pin D3
Explanation
Row 2 output strobe line
4x4 Matrix Keypad Pin
R3 (Row 3)
→
Arduino Mega Pin
Digital Pin D4
Explanation
Row 3 output strobe line
4x4 Matrix Keypad Pin
R4 (Row 4)
→
Arduino Mega Pin
Digital Pin D5
Explanation
Row 4 output strobe line
4x4 Matrix Keypad Pin
C1 (Column 1)
→
Arduino Mega Pin
Digital Pin D6
Explanation
Column 1 input line with internal pull-up
4x4 Matrix Keypad Pin
C2 (Column 2)
→
Arduino Mega Pin
Digital Pin D7
Explanation
Column 2 input line with internal pull-up
4x4 Matrix Keypad Pin
C3 (Column 3)
→
Arduino Mega Pin
Digital Pin D8
Explanation
Column 3 input line with internal pull-up
4x4 Matrix Keypad Pin
C4 (Column 4)
→
Arduino Mega Pin
Digital Pin D9
Explanation
Column 4 input line with internal pull-up

The membrane keypad ribbon cable connects directly to header pins D2 through D9 on the Arduino Mega. Because we enable the Mega's built-in pull-up resistors in software, you do not need any external pull-up resistors on the breadboard.

Pin Connection Map
LCD1602 (I2C Backpack)
SDA
→
Arduino Mega Pin
Digital Pin D20
Explanation
Serial Data line for I2C communication
LCD1602 (I2C Backpack)
SCL
→
Arduino Mega Pin
Digital Pin D21
Explanation
Serial Clock line for I2C communication
LCD1602 (I2C Backpack)
VCC
→
Arduino Mega Pin
5V.1
Explanation
Provides 5V operating power to display and backlight
LCD1602 (I2C Backpack)
GND
→
Arduino Mega Pin
GND.1
Explanation
Common ground reference return

On the Arduino Mega 2560, the hardware I2C bus pins are located at digital pins D20 (SDA) and D21 (SCL). Connecting the LCD backpack to these pins leaves all standard general-purpose digital pins free for other devices.

Pin Connection Map
Indicators & Audio
Green LED Anode (+)
→
Arduino Mega Pin
Digital Pin D11
Explanation
Access Granted visual indicator control
Indicators & Audio
Green LED Cathode (-)
→
Arduino Mega Pin
GND.2
Explanation
Ground return line
Indicators & Audio
Red LED Anode (+)
→
Arduino Mega Pin
Digital Pin D12
Explanation
Access Denied visual indicator control
Indicators & Audio
Red LED Cathode (-)
→
Arduino Mega Pin
GND.2
Explanation
Ground return line
Indicators & Audio
Piezo Buzzer Pin 1 (+)
→
Arduino Mega Pin
Digital Pin D13
Explanation
PWM frequency tone signal line
Indicators & Audio
Piezo Buzzer Pin 2 (-)
→
Arduino Mega Pin
GND.2
Explanation
Ground return line

Both LED cathodes and the piezo buzzer negative lead return to the common ground rail (GND.2) on the Mega header. Always observe proper LED polarity: the longer leg (anode) connects to the signal pin, while the shorter flat side (cathode) connects to ground.

Complete Code

The complete Arduino sketch below implements matrix keypad scanning, masked LCD character rendering, and dual-state audio-visual feedback. Upload this code directly to your Arduino Mega 2560:

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// ============================================================================
// Arduino Mega Keypad Security Door Lock
// 4x4 Matrix Keypad on Pins 2-9
// I2C LCD1602 on SDA (20) & SCL (21)
// Status LEDs (Pins 11 & 12) and Buzzer (Pin 13)
// ============================================================================

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

const int rowPins[4] = {2, 3, 4, 5};
const int colPins[4] = {6, 7, 8, 9};

const char keyMap[4][4] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

const int LED_GREEN_PIN = 11;
const int LED_RED_PIN   = 12;
const int BUZZER_PIN    = 13;

// Secret 4-digit Master Passcode
const String MASTER_PIN = "1234";
String enteredPIN = "";

char scanKeypad() {
  for (int r = 0; r < 4; r++) {
    digitalWrite(rowPins[r], LOW);
    for (int c = 0; c < 4; c++) {
      if (digitalRead(colPins[c]) == LOW) {
        delay(20); // Debounce
        while (digitalRead(colPins[c]) == LOW); // Wait for release
        digitalWrite(rowPins[r], HIGH);
        return keyMap[r][c];
      }
    }
    digitalWrite(rowPins[r], HIGH);
  }
  return '\0';
}

void showIdleScreen() {
  enteredPIN = "";
  digitalWrite(LED_GREEN_PIN, LOW);
  digitalWrite(LED_RED_PIN, LOW);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Enter PIN Code:");
  lcd.setCursor(0, 1);
  lcd.print("[_ _ _ _]");
}

void setup() {
  for (int r = 0; r < 4; r++) {
    pinMode(rowPins[r], OUTPUT);
    digitalWrite(rowPins[r], HIGH);
  }
  for (int c = 0; c < 4; c++) {
    pinMode(colPins[c], INPUT_PULLUP);
  }

  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  lcd.init();
  lcd.backlight();
  showIdleScreen();
}

void loop() {
  char key = scanKeypad();

  if (key != '\0') {
    tone(BUZZER_PIN, 1500, 30); // Key click chirp

    if (key == '#') { // Submit
      if (enteredPIN == MASTER_PIN) {
        // ACCESS GRANTED
        digitalWrite(LED_GREEN_PIN, HIGH);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ACCESS GRANTED!");
        lcd.setCursor(0, 1);
        lcd.print("Door Unlocked");
        tone(BUZZER_PIN, 1800, 200);
        delay(250);
        tone(BUZZER_PIN, 2400, 400);
        delay(2500);
      } else {
        // ACCESS DENIED
        digitalWrite(LED_RED_PIN, HIGH);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ACCESS DENIED!");
        lcd.setCursor(0, 1);
        lcd.print("Invalid Passcode");
        tone(BUZZER_PIN, 400, 500);
        delay(2000);
      }
      showIdleScreen();
    } else if (key == '*') { // Clear
      showIdleScreen();
    } else if (enteredPIN.length() < 4) {
      enteredPIN += key;
      lcd.setCursor(0, 1);
      String masked = "[";
      for (int i = 0; i < 4; i++) {
        if (i < enteredPIN.length()) masked += "*";
        else masked += "_";
        if (i < 3) masked += " ";
      }
      masked += "]";
      lcd.print(masked);
    }
  }
}

How the Code Works, Part by Part

The firmware is structured into three clean functional blocks: scanning the keypad grid without external libraries, rendering masked strings on the LCD display, and verifying submitted passcodes against the master code.

Scanning the Matrix Keypad

The scanning routine steps through each row pin, pulls it LOW, and checks all four column inputs for a zero-volt connection:

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char scanKeypad() {
  for (int r = 0; r < 4; r++) {
    digitalWrite(rowPins[r], LOW);
    for (int c = 0; c < 4; c++) {
      if (digitalRead(colPins[c]) == LOW) {
        delay(20); // Debounce
        while (digitalRead(colPins[c]) == LOW); // Wait for release
        digitalWrite(rowPins[r], HIGH);
        return keyMap[r][c];
      }
    }
    digitalWrite(rowPins[r], HIGH);
  }
  return '\0';
}

Adding a small 20 millisecond delay eliminates mechanical contact chatter (switch bounce). The while loop waits until the user releases the key before returning the character, guaranteeing that one single physical press registers as exactly one character.

Masking PIN Digits on the LCD Screen

To prevent visual eavesdropping, the program constructs a formatted bracket string with asterisks for entered digits and underscores for remaining slots:

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
if (enteredPIN.length() < 4) {
  enteredPIN += key;
  lcd.setCursor(0, 1);
  String masked = "[";
  for (int i = 0; i < 4; i++) {
    if (i < enteredPIN.length()) masked += "*";
    else masked += "_";
    if (i < 3) masked += " ";
  }
  masked += "]";
  lcd.print(masked);
}

By setting the cursor directly to line 1 and rewriting the formatted bracket string, the display updates cleanly on every key press without needing a full screen clear, avoiding display flicker.

Validating the Master Code and Triggering Alerts

When the user presses the '#' key, the code evaluates whether the entered string matches the stored master passcode:

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (key == '#') {
  if (enteredPIN == MASTER_PIN) {
    digitalWrite(LED_GREEN_PIN, HIGH);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS GRANTED!");
    lcd.setCursor(0, 1);
    lcd.print("Door Unlocked");
    tone(BUZZER_PIN, 1800, 200);
    delay(250);
    tone(BUZZER_PIN, 2400, 400);
    delay(2500);
  } else {
    digitalWrite(LED_RED_PIN, HIGH);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS DENIED!");
    lcd.setCursor(0, 1);
    lcd.print("Invalid Passcode");
    tone(BUZZER_PIN, 400, 500);
    delay(2000);
  }
  showIdleScreen();
}

If the entered code matches '1234', the green LED illuminates and a two-tone rising chime plays before granting access. If incorrect, the red LED lights up and a low 400 Hz warning tone plays. After a short delay, showIdleScreen() resets the system back to standby.

Fixing Common Problems

If your security lock is not behaving as expected, check the table below for common symptoms and their quick solutions:

SymptomProbable CauseSolution
LCD screen shows dark blue or black boxesContrast potentiometer incorrectly adjustedTurn the small blue potentiometer on the back of the I2C backpack using a screwdriver until characters appear.
LCD screen is completely blankIncorrect I2C address or wiringVerify that SDA connects to D20 and SCL connects to D21, and confirm your backpack uses address 0x27 or 0x3F.
Keypad registers incorrect letters or numbersRow and column wire order reversedConfirm that row pins R1 to R4 connect to D2 through D5, and column pins C1 to C4 connect to D6 through D9 in sequential order.
Single key press registers multiple charactersMissing switch debounce or floating pinsEnsure column pins are declared as INPUT_PULLUP in setup() and that the 20 ms debounce delay is present.
Buzzer makes no sound on key pressesReversed buzzer leads or wrong pin assignmentEnsure the buzzer positive pin is wired to D13 and the negative pin connects to GND.

Testing each peripheral individually—first verifying LCD text, then serial logging keypresses, and finally testing LED and buzzer tones—makes isolating hardware issues straightforward.

Try It in the Simulator

Click the Start Simulation button in the top toolbar to boot up the Arduino Mega. Use your mouse to press keys on the virtual 4x4 keypad. Try typing '1234' followed by '#' to see the screen show ACCESS GRANTED and illuminate the green LED. Then press '*' to clear the buffer, or type an invalid code like '9999' followed by '#' to test the ACCESS DENIED alert.

Keywords
#Arduino Mega #Keypad #LCD1602 #Buzzer #Security #Access Control #Intermediate
Total word count: 1414 words

Project discussion

Questions, feedback, and community insights
No discussions yet. Be the first to start!