Build A Demo Matrix Keypad Lock With An Arduino

About the project

A demo project to read a 4x4 Keypad and drive a relay that controls a lock when the correct code is entered. It also has an OLED display to give the users prompt, a buzzer to provide audible feedback, and an override button to force the lock to open.

Project info

Difficulty: Easy

Platforms: Arduino

Estimated time: 1 hour

License: GNU General Public License, version 3 or later (GPL3+)

Items used in this project

Hardware components

Arduino Uno - R3 Arduino Uno - R3 Any 5v Uno should work x 1
4x4 Matrix Membrane Keypad 4x4 Matrix Membrane Keypad Adjustments need to be made if you use a 4x3 keypad x 1
DC 5V Relay Module DC 5V Relay Module I used a 4 relay module as that is what I had. Only one is required x 1
Piezo Buzzer PS1240 Piezo Buzzer PS1240 x 1
100 ohm resister 100 ohm resister Optional x 1
Half-size Breadboard Half-size Breadboard Just big enough to hold the display, buzzer and override push-button x 1
USB Cable A-B for Arduino Uno/Mega USB Cable A-B for Arduino Uno/Mega x 1
Jumper wire Jumper wire The relay board had header pins so it needed 3 M/F, the rest were M/M x 25
momentary push button momentary push button Used for the inside override x 1
Monochrome 0.91 128x32 I2C OLED Display - STEMMA QT / Qwiic Monochrome 0.91 128x32 I2C OLED Display - STEMMA QT / Qwiic x 1

View all

Software apps and online services

Arduino IDE Arduino IDE

Hand tools and fabrication machines

Computer Computer Capable of running the Arduino IDE x 1

Story

Introduction

This project is designed to help you get familiar with several aspects of Arduino development.  You will be accessing several inputs and creating data to be sent to various outputs.  You will use a few of the many libraries available in the Arduino ecosystem.

We will be modeling a real world use for the Arduino, managing a secure door lock with an entry pass code and an override switch.  The system will include a keypad for data entry, a screen for prompts and feedback, a relay to control the door lock, a buzzer to provide audio feedback and a momentary push button to act as an override access switch.

All of this will be connected to our Arduino Uno.  You can use almost any 5 volt Arduino for this project as long as it has 11 available digital pins.  You can use a 3.3 volt Arduino, but you will need an external power supply for the relay module.  Everything else will function with 3.3 volts.

Let's get started.

Add The Keypad

The keypad used in this demo is a 4 row by 4 column 16 key membrane keypad. It is one of the simplest implementations of a keypad.

Wire The Keypad

The keypad is wired up as shown.  Each pin in order from Row 1 through Col 4 into Digital inputs 9 through 2 

Install the Library

The library we will be using is the Keypad Library by Mark Stanley and Alexander Brevig. It can be loaded in the Arduino IDE through the library manager.  Go to Sketch > Include Library > Manage Libraries and search for "keypad". Click on the library, then click install.

Testing The Keypad

You can now run this small sample program that will validate that you have the keypad properly connected.  It will print the decoded value for each keypad key press in the serial monitor.

  1. #include <Keypad.h>
  2. // Define Keypad setup
  3. const byte ROWS = 4;
  4. const byte COLS = 4;
  5. char hexaKeys[ROWS][COLS] = {
  6. {'1', '2', '3', 'A'},
  7. {'4', '5', '6', 'B'},
  8. {'7', '8', '9', 'C'},
  9. {'*', '0', '#', 'D'}};
  10. byte rowPins[ROWS] = {9, 8, 7, 6};
  11. byte colPins[COLS] = {5, 4, 3, 2};
  12. char customKey;
  13. Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
  14. void setup()
  15. {
  16. Serial.begin(115200);
  17. Serial.println("Keypad Test");
  18. }
  19. void loop()
  20. {
  21. customKey = customKeypad.getKey();
  22. if (customKey)
  23. {
  24. Serial.print("Key = ");
  25. Serial.println(customKey);
  26. }
  27. }

Using a 4x3 Keypad

If you use a 4x3 keypad, then you can omit the last column, Col 4, which connects to input D2.  In the code you will need to replace the keypad definition with this one:

  1. // This is for 4x3 keypad
  2. const byte ROWS = 4;
  3. const byte COLS = 3;
  4.  
  5. char hexaKeys[ROWS][COLS] = {
  6. {'1', '2', '3'},
  7. {'4', '5', '6'},
  8. {'7', '8', '9'},
  9. {'*', '0', '#'}};
  10.  
  11. byte rowPins[ROWS] = {9, 8, 7, 6};
  12. byte colPins[COLS] = {5, 4, 3};

Add the Breadboard

To support the rest of the prototyping we will use a solder-less breadboard.  A good discussion of what a solder-less breadboard is and how it is used can be found here.

Wire the Breadboard

We will go ahead and connect the power rails.  One for 5 Volts the other for 3.3 Volts, as show below. Note that I am using black wires for the ground connections, red for the +5 volts and orange for the +3.3 volts.  I set the top rail to be 3.3 volts and the bottom one to be the 5 volt rail.

The top rail will be the 3.3 volt and ground connections and the bottom will be the 5 volt and ground connections.  After you have done this, the test program should continue to run as before.

Add The OLED Display

The OLED display is used to prompt the user to enter the pass code and tell them if they got it right or wrong..  It is not a very large display so there is not a lot of room for many details. A good discussion of the 1306 based OLED displays can be found on the Adafruit site

First connect the GND pin on the display to the ground rail.  Then the Vcc pin to the 3.3 volt rail,  This will power the display.  Now we will connect the I2C connection SCL and SDA.  As you can see in the diagram below, the SCL and SDA pins are in the upper left corner of the Arduino next to the USB Connector.  On some Arduino devices they are A5 (SCL) and A4 (SDA).  A nice discussion of I2C can be found here

Adding The Libraries

The OLED display requires the built in Wire library to communicate over I2C and the Adafruit SSD1306 library to control the OLED display..   To add the SSD1306 library, go to Sketch > Include Library > Manage Libraries and search for "SSD1306". Click on the Adafruit SSD1306 library, then click install. Since this is an I2c connected device we need to be aware of its address.  The display I am using is 0x3C.  This is used in the display.begin() call that sets up the actual display.

Testing the display

For this, we will just display every key press on the screen instead of sending it through the serial interface.  Replace the existing code with this code to test the display.

  1. #include <Wire.h>
  2. #include <Adafruit_SSD1306.h>
  3.  
  4. #include <Keypad.h>
  5.  
  6. #define SCREEN_WIDTH 128 // OLED display width, in pixels
  7. #define SCREEN_HEIGHT 32 // OLED display height, in pixels
  8.  
  9. // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
  10. #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
  11. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  12.  
  13. const byte ROWS = 4;
  14. const byte COLS = 4;
  15.  
  16. char hexaKeys[ROWS][COLS] = {
  17. {'1', '2', '3', 'A'},
  18. {'4', '5', '6', 'B'},
  19. {'7', '8', '9', 'C'},
  20. {'*', '0', '#', 'D'}};
  21.  
  22. byte rowPins[ROWS] = {9, 8, 7, 6};
  23. byte colPins[COLS] = {5, 4, 3, 2};
  24.  
  25. char customKey;
  26.  
  27. Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
  28.  
  29. void setup()
  30. {
  31. Serial.begin(115200);
  32.  
  33. // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  34. // Address 0x3C for 128x32 or 0x3D for 128x64
  35. if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
  36. {
  37. Serial.println(F("SSD1306 allocation failed"));
  38. for (;;)
  39. ; // Don't proceed, loop forever
  40. }
  41.  
  42. // Show initial display buffer contents on the screen --
  43. // the library initializes this with an Adafruit splash screen.
  44. display.display();
  45. delay(2000); // Pause for 2 seconds
  46.  
  47. // Set up the display for big characters
  48. display.setTextSize(2); // Draw 2x-scale text
  49. display.setTextColor(SSD1306_WHITE);
  50.  
  51. // Clear the display and show it.
  52. display.clearDisplay();
  53. display.display();
  54. }
  55.  
  56. void loop()
  57. {
  58. customKey = customKeypad.getKey();
  59.  
  60. if (customKey)
  61. {
  62. display.clearDisplay();
  63. display.setCursor(0, 0);
  64. display.print("Key = ");
  65. display.print(customKey);
  66. display.display();
  67. }
  68. }

Running this should cause the display to show the Adafruit logo for a couple of seconds then go blank.  Pressing any key will display "Key = " and the decoded key pressed.

Adding The Security Logic

Now that we can display information to and get information from the user, it is time to add the basic security logic to the application.

Given that we have a master pass code that will open the door, we want to collect key presses until we have that many characters.  Then if the collected key presses match the master pass code, we can report success and clear the collected data, otherwise we report the failure and clear the collected data.

We will start out defining how long the password should be in bytes

  1. #define Password_Length 8

Then we will set up a place to hold the key presses, along with the actual master password.

  1. char Data[Password_Length];
  2. char Master[Password_Length] = "123A456";

Now let us have a counter of the number of key presses.

  1. byte data_count = 0;

There is no additional code in the setup function, but everything else goes in the loop function.  Starting at the top of the loop, we will go to the top of the screen and prompt the user for the password.

  1. display.setCursor(0, 0);
  2. display.setTextSize(1); // Draw normal-scale text
  3. display.setTextColor(SSD1306_WHITE);
  4. display.print(F("Enter Password:"));
  5. display.display();

Now in the block that is executed when there is a key pressed.  We will add that key press to the array of characters that have been entered and increment the counter.  Now we will display an asterisk instead of the character.

  1. Data[data_count] = customKey;
  2. display.setCursor(data_count * 8, 20);
  3. display.print('*');
  4. display.display();
  5. data_count++;

Now if we have enough characters, let's see if it is a match.  If so Welcome the user otherwise tell them Access Denied.  Either way, report the success or failure to the serial monitor, clear out all the key presses, and start over.

  1. if (data_count == Password_Length - 1)
  2. {
  3. display.clearDisplay();
  4. display.setCursor(0, 0);
  5.  
  6. if (!strcmp(Data, Master))
  7. {
  8. // The passcode matches, so tell the user they can enter
  9. display.setTextSize(3); // Draw 3X-scale text
  10. display.print("Welcome");
  11. display.display();
  12. Serial.println("Open");
  13.  
  14. delay(2000);
  15. }
  16. else
  17. {
  18. display.setTextSize(2); // Draw 2X-scale text
  19. display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  20. display.print(" Access n Denied ");
  21. display.display();
  22. Serial.println("Failed");
  23. delay(2000);
  24. }
  25. clearData();
  26. display.clearDisplay();
  27. }

Here is the updated code with this logic added along with the new clearData() function.

  1. #include <Wire.h>
  2. #include <Adafruit_SSD1306.h>
  3.  
  4. #include <Keypad.h>
  5.  
  6. #define SCREEN_WIDTH 128 // OLED display width, in pixels
  7. #define SCREEN_HEIGHT 32 // OLED display height, in pixels
  8.  
  9. // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
  10. #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
  11. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  12.  
  13. #define Password_Length 8
  14.  
  15. const byte ROWS = 4;
  16. const byte COLS = 4;
  17.  
  18. char hexaKeys[ROWS][COLS] = {
  19. {'1', '2', '3', 'A'},
  20. {'4', '5', '6', 'B'},
  21. {'7', '8', '9', 'C'},
  22. {'*', '0', '#', 'D'}};
  23.  
  24. byte rowPins[ROWS] = {9, 8, 7, 6};
  25. byte colPins[COLS] = {5, 4, 3, 2};
  26.  
  27. char Data[Password_Length];
  28. char Master[Password_Length] = "123A456";
  29. byte data_count = 0;
  30. char customKey;
  31.  
  32. Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
  33.  
  34. void setup()
  35. {
  36. Serial.begin(115200);
  37.  
  38. // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  39. // Address 0x3C for 128x32 or 0x3D for 128x64
  40. if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
  41. {
  42. Serial.println(F("SSD1306 allocation failed"));
  43. for (;;)
  44. ; // Don't proceed, loop forever
  45. }
  46.  
  47. // Show initial display buffer contents on the screen --
  48. // the library initializes this with an Adafruit splash screen.
  49. display.display();
  50. delay(2000); // Pause for 2 seconds
  51.  
  52. // Clear the display and show it.
  53. display.clearDisplay();
  54. display.display();
  55. }
  56.  
  57. void loop()
  58. {
  59. display.setCursor(0, 0);
  60. display.setTextSize(1); // Draw normal-scale text
  61. display.setTextColor(SSD1306_WHITE);
  62. display.print(F("Enter Password:"));
  63. display.display();
  64.  
  65. customKey = customKeypad.getKey();
  66.  
  67. if (customKey)
  68. {
  69. Data[data_count] = customKey;
  70. display.setCursor(data_count * 8, 20);
  71. display.print('*');
  72. display.display();
  73. data_count++;
  74. }
  75.  
  76. if (data_count == Password_Length - 1)
  77. {
  78. display.clearDisplay();
  79. display.setCursor(0, 0);
  80.  
  81. if (!strcmp(Data, Master))
  82. {
  83. // The passcode matches, so tell the user they can enter
  84. display.setTextSize(3); // Draw 3X-scale text
  85. display.print("Welcome");
  86. display.display();
  87. Serial.println("Open");
  88. delay(2000);
  89. }
  90. else
  91. {
  92. display.setTextSize(2); // Draw 2X-scale text
  93. display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  94. display.print(" Access n Denied ");
  95. display.display();
  96. Serial.println("Failed");
  97. delay(2000);
  98. }
  99. clearData();
  100. display.clearDisplay();
  101. }
  102. }
  103.  
  104. void clearData()
  105. {
  106. while (data_count != 0)
  107. {
  108. Data[data_count--] = 0;
  109. }
  110. return;
  111. }

Adding the Lock Relay

The relay module will allow us to manipulate the high voltage necessary to drive the lock release.  We will use pin 10 to control the relay.  The module requires 5 volts.  In a real situation, you should use a separate power supply for the relay, but in this small implementation we can drive it from the 5 volt pin on the Arduino.  Wire the Vcc pin to the 5 volt rail on the breadboard and the Gnd pin to the ground rail.  Finally connect the Int 1 pin on the relay module to D10 on the Arduino.  

Code changes to support the Relay

The relay module that I am using is active low.  This means that writing LOW to the pin will activate the relay.  You will see this in the code. So let us modify the test code to support the relay module.

After the declaration of the display object add this #define statement to identify the pin we will be using to control the relay.

  1. #define LOCK_RELAY_PIN 10

Next in the setup function, after we initialize the display we will set up the pin to control the relay module.  Note that we immediately set the pin HIGH to keep the relay from activating.

  1. pinMode(LOCK_RELAY_PIN, OUTPUT);
  2. digitalWrite(LOCK_RELAY_PIN, HIGH);

Finally when the correct pass code is entered, we need to drive the pin LOW to open the door.  In the loop function, after we print out that the door has been opened, we need to toggle the lock relay pin.  We will leave it LOW for 5000 milliseconds, or 5 seconds, to give the user time to open the door before rearming the lock by setting the pin HIGH.

Replace the delay(2000); with this code.

  1. digitalWrite(LOCK_RELAY_PIN, LOW);
  2. delay(5000);
  3. digitalWrite(LOCK_RELAY_PIN, HIGH);

Recompile the application and upload it to the Arduino.  You should now see the relay energize when a valid code is entered on the keypad at the prompt.

Add A Buzzer

The buzzer gives the user audible feedback as to the success or failure of entering the lock code. We will make it click on each key press, generate a pleasant sound on successful unlocking of the door and an irritating sound on failure.

The buzzer is simple to wire.  Add it to the breadboard.  Tie one leg of the buzzer to one side of  the optional 100 ohm resister,  Connect the other side of the resistor to the ground rail.  The other side of the buzzer will be connected to pin D11 on the Arduino. 

Code to drive the Buzzer

The sound generation will be supplied by the built in Tone library functions: tone() and noTone(). The tone*( function  takes a pin number and a frequency in Hertz.  The noTone() function just needs a pin number. A good example of playing melodies with the tone functions can be found here.  You will see that I use some of the constants from this example.

Let's add the code to define a few constants.  These will go after the define of the lock relay pin.

  1. #define BUZZER_PIN 11
  2.  
  3. #define NOTE_G4 392
  4. #define NOTE_D5 587
  5. #define NOTE_C6 1047
  6.  

Now to add the key click sound for button presses.  In the loop function replace the first if block with this code. 

  1. if (customKey)
  2. {
  3. tone(BUZZER_PIN, 33);
  4. Data[data_count] = customKey;
  5. display.setCursor(data_count * 8, 20);
  6. display.print('*');
  7. display.display();
  8. data_count++;
  9. noTone(BUZZER_PIN);
  10. }

We are just playing a very low frequency sound for just as long as it takes to display the asterisk for each key press.

Next we will add the success tones. For this we are just going to replace the delay(5000); in the passcode matches block of code with this code

  1. // Play Ding for half a second
  2. tone(BUZZER_PIN, NOTE_D5);
  3. delay(500);
  4. // Play Dong for half a second
  5. tone(BUZZER_PIN, NOTE_G4);
  6. delay(500);
  7. // Turn off the buzxzer
  8. noTone(BUZZER_PIN);
  9. // Wait for the rest of the 5 seconds, only 4 now.
  10. delay(4000);

Finally the failure tones.  In the access denied block, replace the delay(2000) with this code to also play the warning tone.

  1. // Start the annoying sound
  2. tone(BUZZER_PIN, NOTE_C6);
  3. delay(2000);
  4. // And stop it now.
  5. noTone(BUZZER_PIN);

Add an Override to Unlock

The last addition is an override button that would go inside the secured area to allow someone to open the door to leave. We will be using a momentary push button for this.  Place the button in the center of the bread board so that the pins straddle the gap between the rows of pins.  This will help us to properly isolate the connections of the switch.

Most momentary contact push buttons switches have 4 pins.  Two of them tied together in pairs.  Pressing the button will then connect the two sides of the switch.  the switch in the diagram below is placed so the one pair is on top and the other below.  If, after connecting everything and updating the code, the system acts like you are always pressing the override, take the switch out and turn in 90 degrees and re insert it and it should behave properly.

Code changes to Support the Override

For this, we need to reed the override pin every time through the loop if there is not a key press available from the keypad.  If the button is pressed, then we notify the user and open the door, playing the chime as if the user entered a valid code.

First we add the constant that defines the pin that will read the override button.  This goes right after the constant for the buzzer pin

  1. #define OVERRIDE 12

Next we add this code to the setup function to initialize the pin for reading.  Note that we tell the Arduino to use its built in pull up resistor.  This goes right after the defintions of the lock relay pin.

  1. // Setup the Override button input pin
  2. pinMode(OVERRIDE, INPUT_PULLUP);

Finally, we replace the loop function with these two functions.

  1. void loop()
  2. {
  3. display.setCursor(0, 0);
  4. display.setTextSize(1); // Draw normal-scale text
  5. display.setTextColor(SSD1306_WHITE);
  6. display.print(F("Enter Password:"));
  7. display.display();
  8.  
  9. customKey = customKeypad.getKey();
  10.  
  11. if (customKey)
  12. {
  13. tone(BUZZER_PIN, 33);
  14. Data[data_count] = customKey;
  15. display.setCursor(data_count * 8, 20);
  16. display.print('*');
  17. display.display();
  18. data_count++;
  19. noTone(BUZZER_PIN);
  20. }
  21.  
  22. if (data_count == Password_Length - 1)
  23. {
  24. display.clearDisplay();
  25. display.setCursor(0, 0);
  26.  
  27. if (!strcmp(Data, Master))
  28. {
  29. // The passcode matches, so tell the user they can enter
  30. display.setTextSize(3); // Draw 3X-scale text
  31. display.print("Welcome");
  32. display.display();
  33. Serial.println("Open");
  34. openLock();
  35. }
  36. else
  37. {
  38. display.setTextSize(2); // Draw 2X-scale text
  39. display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  40. display.print(" Access n Denied ");
  41. display.display();
  42. Serial.println("Failed");
  43.  
  44. // Start the annoying sound
  45. tone(BUZZER_PIN, NOTE_C6);
  46. delay(2000);
  47. // And stop it now.
  48. noTone(BUZZER_PIN);
  49. }
  50. clearData();
  51. display.clearDisplay();
  52. }
  53. else
  54. {
  55. if (digitalRead(OVERRIDE) == LOW) {
  56. display.clearDisplay();
  57. display.setCursor(0, 0);
  58. display.setTextSize(3); // Draw 3X-scale text
  59. display.print(" Enter");
  60. display.display();
  61. Serial.println("Override");
  62. openLock();
  63. clearData();
  64. display.clearDisplay();
  65. }
  66. }
  67. }
  68.  
  1. void openLock()
  2. {
  3. // Energize the relay by driving the control pin LOW
  4. digitalWrite(LOCK_RELAY_PIN, LOW);
  5.  
  6. // Play Ding for half a second
  7. tone(BUZZER_PIN, NOTE_D5);
  8. delay(500);
  9.  
  10. // Play Dong for half a second
  11. tone(BUZZER_PIN, NOTE_G4);
  12. delay(500);
  13.  
  14. // Turn off the buzxzer
  15. noTone(BUZZER_PIN);
  16.  
  17. // Wait for the rest of the 5 seconds, only 4 now.
  18. delay(4000);
  19.  
  20. // De-energize the relay
  21. digitalWrite(LOCK_RELAY_PIN, HIGH);
  22. }
  23.  

This refactors the open the door code into another function and then calls it from both the override and the successful password entry section.

Wrapping it all up

Now that everything is together, you should be able to exercise all the functionality just like this short demonstration of my prototype going through a successful entry, a failed code entry, and an override entry.

Future Enhancements

There are many enhancements you might want to add. 

  • A time out on entry that will clear the existing key presses and allow you to start over.
  • Make one of the keys be a backspace key to delete the previous key.
  • Make one of the keys be a reset key that will clear the existing key presses and allow you to start over.
  • Adding more than one pass code and tracking who's pass code was used.

Above all you should have fun experimenting with this project.

Schematics, diagrams and documents

Arduino-Matrix-Keypad

Circuit Layout on breadboard

Code

Arduino-Matrix-Keypad

Here is the final version of the code. I had also included a link to the github.com repository.

Arduino Matrix Keypad

A demo program to read a 4x4 Keypad and drive a relay when correct code is entered

Credits

Leave your feedback...