arduino-caja-fuerte-servo

How to Make a Keypad Box with a Servo

  • 7 min

A keypad box is a didactic lock that releases a latch when it recognizes a sequence of buttons.

This project is our first approach to Access Control Systems. The underlying logic is the same one you use to unlock your mobile phone with a PIN.

The technical challenge here is not turning on the motor, but information management:

  1. We need to store a “master password”.
  2. We need to listen to and temporarily save what the user presses (buffer).
  3. We need to compare both sequences and decide whether to open or deny access.

We will use a standard SG90 servomotor. The servo can hold a position while powered, but this assembly does not replace a security lock: a power cut, a plastic piece, or excessive force can defeat it.

The servo as a latch

To turn a cardboard or wooden box into a safe, we need a locking mechanism.

A Servomotor is ideal for this. Unlike a DC motor that spins freely, the servo moves to a specific angle (e.g., 0º or 90º) and stays there.

  • 0º position (closed): The servo arm blocks the lid.
  • 90º position (open): The arm retracts and releases the lid.

Components

  • [1x|blue] Arduino Uno | The brain of the project
  • [1x|green] Servomotor (SG90 or MG90S) | Locking mechanism
  • [3x|orange] Pushbuttons | Enter the key
  • [1x|green] Green LED | Indicates “Open”
  • [1x|red] Red LED | Indicates “Error”
  • [2x|gray] 220Ω Resistors | Limit LED current
  • [Various|gray] Breadboard and wires | Connections

To enter the key, we will use 3 pushbuttons. This allows combinations like “1-1-2-3” or “3-2-1”.

Connection diagram

The assembly has two parts: the data input (buttons) and the output (Servo + LEDs).

Closing servo

The servo moves the latch. The signal goes to pin 9; the power can come from an external regulated source if the servo causes resets.

[ { “from”: “Servo Signal (Orange)”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Servo VCC (Red)”, “to”: “External 5V source”, “color”: “red” }, { “from”: “Servo GND (Brown)”, “to”: “Source GND + Arduino GND”, “color”: “black” } ]

Keypad pushbuttons

The buttons use INPUT_PULLUP, so each pushbutton closes to GND.

[ { “from”: “Button 1”, “to”: “Pin 2”, “color”: “orange”, “note”: “INPUT_PULLUP, other side to GND” }, { “from”: “Button 2”, “to”: “Pin 3”, “color”: “orange”, “note”: “INPUT_PULLUP, other side to GND” }, { “from”: “Button 3”, “to”: “Pin 4”, “color”: “orange”, “note”: “INPUT_PULLUP, other side to GND” }, { “from”: “Other side of buttons”, “to”: “Arduino GND”, “color”: “black” } ]

Status LEDs

The LEDs show whether the key is correct or if an error has occurred.

[ { “from”: “Green LED + 220Ω”, “to”: “Pin 5”, “color”: “green” }, { “from”: “Red LED + 220Ω”, “to”: “Pin 6”, “color”: “red” }, { “from”: “LED cathodes”, “to”: “Arduino GND”, “color”: “black” } ]

The code

The critical part is the comparison logic. We cannot use a simple if to see if the correct button has been pressed, because a password is a sequence.

We will use two Arrays:

  1. masterKey[]: The correct password.
  2. userAttempt[]: Where we will store what the user presses.

Each time the user presses a button, we save it in the attempt array and advance an index. When the user has pressed as many buttons as the key length, we compare the two arrays.

Configuration and constants

We need the Servo.h library to easily control the motor.

#include <Servo.h>

Servo myLock;

// --- Hardware Configuration ---
const int servoPin = 9;
const int greenLedPin = 5;
const int redLedPin = 6;

// Buttons to enter the key (Button 1, Button 2, Button 3)
const int buttonPins[] = {2, 3, 4}; 
const int NUM_BUTTONS = 3;

// --- Security Configuration ---
// Define the correct key: Button 1, Button 1, Button 3, Button 2
const int KEY_LENGTH = 4;
const int MASTER_KEY[KEY_LENGTH] = {0, 0, 2, 1}; 

// State variables
int userAttempt[KEY_LENGTH]; // Buffer to store what is pressed
int currentIndex = 0;        // How many numbers have been pressed

bool isOpen = false;
Copied!

setup

We initialize pins and place the servo in the “Closed” position at startup.

void setup() {
  Serial.begin(9600);

  // Configure LEDs
  pinMode(redLedPin, OUTPUT);
  pinMode(greenLedPin, OUTPUT);
  
  // Configure Buttons with internal PULLUP
  for(int i=0; i < NUM_BUTTONS; i++){
    pinMode(buttonPins[i], INPUT_PULLUP);
  }

  // Configure servo and adopt initial closed state
  myLock.attach(servoPin);
  closeBox();
  
  Serial.println("SYSTEM LOCKED. Enter key code.");
}
Copied!

The main loop

In the loop, our mission is to detect presses. To prevent a single press from being registered multiple times (bouncing), we will use a small wait function.

void loop() {
  if (!isOpen) {
    // Only read buttons if the box is closed
    readButtons();
  } else {
    // If open, wait for any button press to close
    if (detectAnyButton()) {
      closeBox();
    }
  }
}

void readButtons() {
  for (int i = 0; i < NUM_BUTTONS; i++) {
    // Read the button (LOW means pressed in Input Pullup)
    if (digitalRead(buttonPins[i]) == LOW) {
      
      // Feedback: quick flash of the red/yellow LED on press
      digitalWrite(redLedPin, HIGH); 
      delay(100); 
      digitalWrite(redLedPin, LOW);
      
      // Save the pressed button in the array (0, 1, or 2)
      userAttempt[currentIndex] = i;
      currentIndex++;
      
      Serial.print("* "); // Print asterisk for security
      
      // Wait for button release (Simple debounce)
      while(digitalRead(buttonPins[i]) == LOW) { delay(10); }

      // Have we reached the end of the key length?
      if (currentIndex == KEY_LENGTH) {
        verifyKey();
      }
    }
  }
}
Copied!

Verification logic

Here is where we compare the two arrays element by element.

void verifyKey() {
  bool keyCorrect = true;
  
  // Loop through both arrays comparing position by position
  for (int i = 0; i < KEY_LENGTH; i++) {
    if (userAttempt[i] != MASTER_KEY[i]) {
      keyCorrect = false;
      break; // If one fails, no need to keep checking
    }
  }
  
  if (keyCorrect) {
    openBox();
  } else {
    keyError();
  }
  
  // Reset the index for the next attempt
  currentIndex = 0;
}
Copied!

Open, close, and error actions

Finally, the functions that move the physical world.

void openBox() {
  Serial.println(" -> KEY CORRECT. Opening...");
  digitalWrite(greenLedPin, HIGH);
  myLock.write(90); // Angle to unlock
  isOpen = true;
  delay(500); // Time for the servo to reach
}

void closeBox() {
  Serial.println(" -> CLOSING...");
  digitalWrite(greenLedPin, LOW);
  myLock.write(0); // Angle to lock
  isOpen = false;
  currentIndex = 0;  // Reset any half-finished attempt
  delay(500);
}

void keyError() {
  Serial.println(" -> ERROR. Incorrect key.");
  // Error flashing
  for(int i=0; i<3; i++){
    digitalWrite(redLedPin, HIGH);
    delay(100);
    digitalWrite(redLedPin, LOW);
    delay(100);
  }
}

// Helper function to re-close
bool detectAnyButton() {
   for (int i = 0; i < NUM_BUTTONS; i++) {
     if (digitalRead(buttonPins[i]) == LOW) {
       while(digitalRead(buttonPins[i]) == LOW) delay(10); // Wait for release
       return true;
     }
   }
   return false;
}
Copied!

Code analysis

Notice an important detail: the initial logical state is closed. After configuring the outputs, setup() calls closeBox(). Even so, the program cannot confirm that the latch has physically reached its position; for that we would need a limit switch.

Furthermore, the use of arrays (userAttempt) acts as a buffer. We do not make the decision button by button, but accumulate the information until we have the complete package (the entire key) and then process it.

Possible improvements

This project is the perfect foundation to complicate it as much as you want:

  1. Temporary lockout: If you fail the key 3 times, ignore the buttons for 1 minute (to prevent brute force attacks… or annoying siblings!).
  2. Matrix Keypad: Replace the 3 buttons with a 4x4 numeric keypad to have real keys.
  3. Door sensor: Add a limit switch or magnetic sensor to know if the door is physically closed before turning the servo (and prevent the latch from hitting the frame).