arduino-caja-fuerte-servo

Cómo hacer una caja con clave y servo

  • 7 min

Una caja con clave es un cierre didáctico que libera un pestillo al reconocer una secuencia de botones.

Este proyecto es nuestro primer acercamiento a los Sistemas de Control de Acceso. La lógica subyacente es la misma que usas para desbloquear tu móvil con un PIN.

El reto técnico aquí no es encender el motor, sino la gestión de la información:

  1. Necesitamos almacenar una “contraseña maestra”.
  2. Necesitamos escuchar y guardar temporalmente lo que pulsa el usuario (buffer).
  3. Necesitamos comparar ambas secuencias y decidir si abrimos o denegamos el acceso.

Usaremos un servomotor SG90 estándar. El servo puede mantener una posición mientras está alimentado, pero este montaje no sustituye a una cerradura de seguridad: un corte de corriente, una pieza de plástico o un esfuerzo excesivo pueden vencerlo.

El servo como pestillo

Para convertir una caja de cartón o madera en una caja fuerte, necesitamos un mecanismo de bloqueo.

Un Servomotor es ideal para esto. A diferencia de un motor DC que gira libremente, el servo se mueve a un ángulo específico (por ejemplo, 0º o 90º) y se queda ahí.

  • Posición 0º (cerrado): El brazo del servo bloquea la tapa.
  • Posición 90º (abierto): El brazo se retira y libera la tapa.

Componentes

  • [1x|blue] Arduino Uno | El cerebro del proyecto
  • [1x|green] Servomotor (SG90 o MG90S) | Mecanismo de cierre
  • [3x|orange] Pulsadores (Pushbuttons) | Introducir la clave
  • [1x|green] LED Verde | Indica “Abierto”
  • [1x|red] LED Rojo | Indica “Error”
  • [2x|gray] Resistencia 220Ω | Limitan corriente de LEDs
  • [Varios|gray] Protoboard y cables | Conexiones

Para introducir la clave, usaremos 3 pulsadores. Esto nos permite combinaciones como “1-1-2-3” o “3-2-1”.

Esquema de conexión

El montaje tiene dos partes: la entrada de datos (botones) y la salida (Servo + LEDs).

Servo de cierre

El servo mueve el pestillo. La señal va al pin 9; la alimentación puede venir de una fuente externa regulada si el servo provoca reinicios.

[ { “from”: “Servo Señal (Naranja)”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Servo VCC (Rojo)”, “to”: “Fuente externa de 5V”, “color”: “red” }, { “from”: “Servo GND (Marrón)”, “to”: “GND fuente + GND Arduino”, “color”: “black” } ]

Pulsadores de clave

Los botones usan INPUT_PULLUP, así que cada pulsador cierra contra GND.

[ { “from”: “Botón 1”, “to”: “Pin 2”, “color”: “orange”, “note”: “INPUT_PULLUP, otro lado a GND” }, { “from”: “Botón 2”, “to”: “Pin 3”, “color”: “orange”, “note”: “INPUT_PULLUP, otro lado a GND” }, { “from”: “Botón 3”, “to”: “Pin 4”, “color”: “orange”, “note”: “INPUT_PULLUP, otro lado a GND” }, { “from”: “Otro lado de botones”, “to”: “GND Arduino”, “color”: “black” } ]

LEDs de estado

Los LEDs muestran si la clave es correcta o si se ha producido un error.

[ { “from”: “LED Verde + 220Ω”, “to”: “Pin 5”, “color”: “green” }, { “from”: “LED Rojo + 220Ω”, “to”: “Pin 6”, “color”: “red” }, { “from”: “Cátodos LED”, “to”: “GND Arduino”, “color”: “black” } ]

El código

La parte crítica es la lógica de comparación. No podemos usar un if simple para ver si ha pulsado el botón correcto, porque una contraseña es una secuencia.

Usaremos dos Arrays:

  1. claveMaestra[]: La contraseña correcta.
  2. intentoUsuario[]: Donde iremos guardando lo que el usuario pulsa.

Cada vez que el usuario pulsa un botón, lo guardamos en el array de intento y avanzamos un índice. Cuando el usuario ha pulsado tantos botones como tiene la clave, comparamos los dos arrays.

Configuración y constantes

Necesitamos la librería Servo.h para manejar el motor fácilmente.

#include <Servo.h>

Servo miCierre;

// --- Configuración de Hardware ---
const int pinServo = 9;
const int pinLedVerde = 5;
const int pinLedRojo = 6;

// Botones para introducir la clave (Botón 1, Botón 2, Botón 3)
const int pinBotones[] = {2, 3, 4}; 
const int NUM_BOTONES = 3;

// --- Configuración de Seguridad ---
// Definimos la clave correcta: Botón 1, Botón 1, Botón 3, Botón 2
const int LONGITUD_CLAVE = 4;
const int CLAVE_MAESTRA[LONGITUD_CLAVE] = {0, 0, 2, 1}; 

// Variables de estado
int intentoUsuario[LONGITUD_CLAVE]; // Buffer para guardar lo que se pulsa
int indiceActual = 0;               // Cuántos números lleva pulsados

bool estaAbierta = false;
Copied!

setup

Inicializamos pines y colocamos el servo en posición de “Cerrado” al arrancar.

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

  // Configurar LEDs
  pinMode(pinLedRojo, OUTPUT);
  pinMode(pinLedVerde, OUTPUT);
  
  // Configurar Botones con PULLUP interno
  for(int i=0; i < NUM_BOTONES; i++){
    pinMode(pinBotones[i], INPUT_PULLUP);
  }

  // Configurar servo y adoptar el estado inicial cerrado
  miCierre.attach(pinServo);
  cerrarCaja();
  
  Serial.println("SISTEMA BLOQUEADO. Introduzca clave.");
}
Copied!

El bucle principal

En el loop, nuestra misión es detectar pulsaciones. Para evitar que una pulsación se registre múltiples veces (rebote), usaremos una pequeña función de espera.

void loop() {
  if (!estaAbierta) {
    // Solo leemos botones si la caja está cerrada
    leerBotones();
  } else {
    // Si está abierta, esperamos pulsar cualquier botón para cerrar
    if (detectarCualquierBoton()) {
      cerrarCaja();
    }
  }
}

void leerBotones() {
  for (int i = 0; i < NUM_BOTONES; i++) {
    // Leemos el botón (LOW significa pulsado en Input Pullup)
    if (digitalRead(pinBotones[i]) == LOW) {
      
      // Feedback: parpadeo rápido del LED amarillo/rojo al pulsar
      digitalWrite(pinLedRojo, HIGH); 
      delay(100); 
      digitalWrite(pinLedRojo, LOW);
      
      // Guardamos el botón pulsado en el array (0, 1 o 2)
      intentoUsuario[indiceActual] = i;
      indiceActual++;
      
      Serial.print("* "); // Imprimimos asterisco como seguridad
      
      // Esperamos a que suelte el botón (Antirebote simple)
      while(digitalRead(pinBotones[i]) == LOW) { delay(10); }

      // ¿Hemos llegado al final de la longitud de la clave?
      if (indiceActual == LONGITUD_CLAVE) {
        verificarClave();
      }
    }
  }
}
Copied!

Lógica de verificación

Aquí es donde comparamos los dos arrays elemento a elemento.

void verificarClave() {
  bool claveCorrecta = true;
  
  // Recorremos ambos arrays comparando posición a posición
  for (int i = 0; i < LONGITUD_CLAVE; i++) {
    if (intentoUsuario[i] != CLAVE_MAESTRA[i]) {
      claveCorrecta = false;
      break; // Si uno falla, ya no hace falta seguir mirando
    }
  }
  
  if (claveCorrecta) {
    abrirCaja();
  } else {
    errorClave();
  }
  
  // Reiniciamos el índice para el siguiente intento
  indiceActual = 0;
}
Copied!

Acciones de apertura, cierre y error

Finalmente, las funciones que mueven el mundo físico.

void abrirCaja() {
  Serial.println(" -> CLAVE CORRECTA. Abriendo...");
  digitalWrite(pinLedVerde, HIGH);
  miCierre.write(90); // Ángulo para desbloquear
  estaAbierta = true;
  delay(500); // Tiempo para que el servo llegue
}

void cerrarCaja() {
  Serial.println(" -> CERRANDO...");
  digitalWrite(pinLedVerde, LOW);
  miCierre.write(0); // Ángulo para bloquear
  estaAbierta = false;
  indiceActual = 0;  // Reseteamos cualquier intento a medias
  delay(500);
}

void errorClave() {
  Serial.println(" -> ERROR. Clave incorrecta.");
  // Parpadeo de error
  for(int i=0; i<3; i++){
    digitalWrite(pinLedRojo, HIGH);
    delay(100);
    digitalWrite(pinLedRojo, LOW);
    delay(100);
  }
}

// Función auxiliar para re-cerrar
bool detectarCualquierBoton() {
   for (int i = 0; i < NUM_BOTONES; i++) {
     if (digitalRead(pinBotones[i]) == LOW) {
       while(digitalRead(pinBotones[i]) == LOW) delay(10); // Esperar soltar
       return true;
     }
   }
   return false;
}
Copied!

Análisis del código

Observa un detalle importante: el estado lógico inicial es cerrado. Tras configurar las salidas, setup() llama a cerrarCaja(). Aun así, el programa no puede confirmar que el pestillo haya llegado físicamente a su posición; para eso necesitaríamos un final de carrera.

Además, el uso de arrays (intentoUsuario) actúa como un buffer. No tomamos la decisión botón a botón, sino que acumulamos la información hasta tener el paquete completo (la clave entera) y entonces procesamos.

Posibles mejoras

Este proyecto es la base perfecta para complicarlo todo lo que queráis:

  1. Bloqueo temporal: Si fallas la clave 3 veces, ignorar los botones durante 1 minuto (para evitar ataques de fuerza bruta… ¡o hermanos molestos!).
  2. Teclado Matricial: Sustituir los 3 botones por un teclado numérico 4x4 para tener claves de verdad.
  3. Sensor de puerta: Añadir un final de carrera o sensor magnético para saber si la puerta está físicamente cerrada antes de girar el servo (y evitar que el pestillo golpee el marco).