Saltar al contenido
Nivel intermedio

Abrir la cerradura con una clave

Conectar el teclado matricial 4x4 y el buzzer

Sumamos un teclado matricial 4x4 de membrana (16 teclas) y un buzzer. El teclado reemplaza a los pulsadores del nivel inicial: en vez de dos botones sueltos, ahora tenemos las 16 teclas para ingresar una clave. El servo y los dos LED siguen conectados igual que antes (por eso unificamos los pines: el circuito no se rehace, solo se le suman el teclado y el buzzer).

Lo interesante del teclado es su diseño matricial. En lugar de un cable por tecla (que serían 16), las teclas se organizan como una matriz de 4 filas por 4 columnas: cada tecla es el cruce de una fila con una columna. Así, con solo 8 pines (4 filas + 4 columnas) la placa puede distinguir las 16 teclas: recorre filas y columnas y detecta en qué cruce se cerró el contacto.

Esquema de conexión del nivel intermedio: un teclado matricial 4x4 (teclas 1-9, 0, A, B, C, D, asterisco y numeral) con un conector de 8 pines hacia el Arduino UNO, un buzzer a la izquierda, y en la protoboard un LED verde, un LED rojo y el servomotor.
El sistema del nivel intermedio (figura original del proyecto): el teclado 4x4 y el buzzer conectados al Arduino, junto al servomotor y los dos LED que vienen del nivel inicial.

En el Arduino UNO, el teclado usa las filas en los pines 9, 8, 7 y 6 y las columnas en los pines 5, 4, 3 y 2. El buzzer va al pin 12. El servo (pin 10) y los LED verde (11) y rojo (13) quedan donde estaban.

En el ESP32, el teclado usa las filas en los GPIO 16, 17, 18 y 19 y las columnas en los GPIO 21, 22, 23 y 27. El buzzer va al GPIO 14. El servo (GPIO 13) y los LED verde (GPIO 25) y rojo (GPIO 26) quedan donde estaban.

Incluir la librería Keypad

Leer un teclado matricial “a mano” —recorrer filas y columnas para ver qué cruce se cerró— es tedioso. Por suerte existe una librería que lo resuelve: Keypad. En el Arduino IDE 2.x abrimos el Gestor de bibliotecas (el ícono de los libritos en la barra lateral), buscamos “Keypad” (la de Mark Stanley y Alexander Brevig) y la instalamos.

Con la librería instalada, en el código describimos el teclado una sola vez: el mapa de teclas (qué carácter tiene cada posición de la matriz) y los pines de filas y columnas. A partir de ahí, con teclado.getKey() obtenemos directamente la tecla presionada, sin ocuparnos del escaneo.

Programar el teclado y la clave

Ahora la lógica de la clave. Primero definimos cuántos dígitos tiene y cuáles son: en este caso, 4 dígitos y la clave del original, 1 5 9 D. La guardamos en una lista (clave) y usamos otra (tecleado) para ir juntando lo que la persona ingresa.

El programa acumula las teclas que se presionan. Cuando junta 4, las compara con la clave:

  • Si coinciden: suena un pitido agudo (clave correcta) y la cerradura conmuta su estado —si estaba cerrada, abre; si estaba abierta, cierra—, moviendo el servo y actualizando los LED.
  • Si no coinciden: suena un doble pitido grave (clave incorrecta) y la cerradura no se mueve. El sistema queda listo para un nuevo intento de 4 teclas.

Para el aviso sonoro usamos la función tone(pin, frecuencia, duración): un tono agudo (1000 Hz) para el acierto y uno grave (400 Hz) para el error. Así los dos avisos se distinguen de oído, algo que el simple encendido/apagado del buzzer del original no permitía.

Ver el código completo · cerradura-teclado.ino
cerradura-teclado.ino Arduino UNO
// Cerradura automatizada — Nivel Intermedio (Arduino UNO).
//
// Se reemplazan los dos pulsadores por un TECLADO MATRICIAL 4x4 y se suma un
// ZUMBADOR (buzzer). El usuario tipea una clave de 4 digitos; si coincide con
// la clave guardada, la cerradura conmuta su estado (abre si estaba cerrada,
// cierra si estaba abierta) y suena un pitido de OK. Si la clave es incorrecta,
// suena un doble pitido de error y no se mueve la cerradura. Los LED siguen
// indicando el estado (verde = abierta, rojo = cerrada), como pidio el abuelo.
//
// Clave del original: '1' '5' '9' 'D'.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION (servo 5 V, teclado, LED,
// buzzer): sin 220 V ni agua.
//
// Nivel intermedio: se permite delay() (fiel al original) para los pitidos.
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 3) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios respecto del original (ver README):
//  (1) PINES UNIFICADOS: servo=10, LED verde=11, buzzer=12, LED rojo=13, y el
//      teclado en filas {9,8,7,6} / columnas {5,4,3,2} — el MISMO esquema que
//      inicial y avanzado (el original cambiaba pines entre niveles).
//  (2) BUZZER con tone(): el original prendia/apagaba el buzzer con
//      digitalWrite (un buzzer "activo"); tone() genera una frecuencia audible
//      y sirve para cualquier buzzer, ademas de permitir distinguir el tono de
//      OK del de error.
//  (3) Angulos del servo unificados (ABIERTA=90, CERRADA=0). El sistema arranca
//      CERRADO (LED rojo), estado seguro por defecto.

#include <Keypad.h>
#if defined(ESP32)
  #include <ESP32Servo.h>
#else
  #include <Servo.h>
#endif

// --- Pines (esquema UNIFICADO del contrato) ---
const int PIN_SERVO     = 10;
const int PIN_LED_VERDE = 11;  // encendido = ABIERTA
const int PIN_BUZZER    = 12;
const int PIN_LED_ROJO  = 13;  // encendido = CERRADA

// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;

// --- Teclado matricial 4x4 ---
const byte FILAS    = 4;
const byte COLUMNAS = 4;
char teclas[FILAS][COLUMNAS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte pinesFilas[FILAS]       = {9, 8, 7, 6};
byte pinesColumnas[COLUMNAS] = {5, 4, 3, 2};
Keypad teclado = Keypad(makeKeymap(teclas), pinesFilas, pinesColumnas,
                        FILAS, COLUMNAS);

// --- Clave (la del original) y buffer de lo tecleado ---
const byte LARGO_CLAVE = 4;
char clave[LARGO_CLAVE]    = {'1', '5', '9', 'D'};
char tecleado[LARGO_CLAVE] = {0, 0, 0, 0};
byte indice = 0;

// --- Buzzer: tonos distintos para OK y error ---
const unsigned int FREQ_OK        = 1000;  // Hz, pitido agudo = correcta
const unsigned int FREQ_ERROR     = 400;   // Hz, pitido grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PAUSA_BEEP_MS    = 300;

// Estado de la puerta: true = abierta, false = cerrada.
bool puertaAbierta = false;

Servo cerradura;

void mostrarEstadoEnLeds() {
  digitalWrite(PIN_LED_VERDE, puertaAbierta ? HIGH : LOW);
  digitalWrite(PIN_LED_ROJO, puertaAbierta ? LOW : HIGH);
}

void beepCorrecta() {
  tone(PIN_BUZZER, FREQ_OK, DURACION_BEEP_MS);
  delay(DURACION_BEEP_MS);
  noTone(PIN_BUZZER);
}

void beepIncorrecta() {
  // Doble pitido grave.
  for (byte i = 0; i < 2; i++) {
    tone(PIN_BUZZER, FREQ_ERROR, DURACION_BEEP_MS);
    delay(DURACION_BEEP_MS);
    noTone(PIN_BUZZER);
    delay(PAUSA_BEEP_MS);
  }
}

// Compara lo tecleado con la clave guardada.
bool claveCorrecta() {
  for (byte i = 0; i < LARGO_CLAVE; i++) {
    if (tecleado[i] != clave[i]) {
      return false;
    }
  }
  return true;
}

void setup() {
  pinMode(PIN_LED_VERDE, OUTPUT);
  pinMode(PIN_LED_ROJO, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);

  cerradura.attach(PIN_SERVO);
  // Arranca CERRADA (LED rojo).
  puertaAbierta = false;
  cerradura.write(ANGULO_CERRADA);
  mostrarEstadoEnLeds();
}

void loop() {
  char tecla = teclado.getKey();
  if (!tecla) {
    return;  // no se presiono ninguna tecla en esta vuelta
  }

  // Guarda la tecla y avanza el indice.
  tecleado[indice] = tecla;
  indice++;

  // Cuando se completan 4 digitos, se evalua la clave.
  if (indice == LARGO_CLAVE) {
    indice = 0;

    if (claveCorrecta()) {
      beepCorrecta();
      // Conmuta el estado de la cerradura.
      puertaAbierta = !puertaAbierta;
      cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
      mostrarEstadoEnLeds();
    } else {
      beepIncorrecta();  // no toca la cerradura
    }
  }
}
cerradura-teclado.ino ESP32
// Cerradura automatizada — Nivel Intermedio (ESP32 DevKit v1).
//
// Se reemplazan los dos pulsadores por un TECLADO MATRICIAL 4x4 y se suma un
// ZUMBADOR (buzzer). El usuario tipea una clave de 4 digitos; si coincide con
// la clave guardada, la cerradura conmuta su estado (abre si estaba cerrada,
// cierra si estaba abierta) y suena un pitido de OK. Si es incorrecta, suena
// un doble pitido de error y la cerradura no se mueve. Los LED indican el
// estado (verde = abierta, rojo = cerrada).
//
// Clave del original: '1' '5' '9' 'D'.
//
// Plataforma: ESP32 (DevKit v1). Servo con ESP32Servo; el buzzer usa tone(),
// disponible en el core de ESP32. Nota electrica: el ESP32 trabaja a 3,3 V
// (alimentar el servo desde 5 V con GND comun). Proyecto de BAJA TENSION.
//
// Nivel intermedio: se permite delay() (fiel al original) para los pitidos.
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 3) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): pines GPIO seguros del contrato; buzzer con tone();
// angulos unificados (ABIERTA=90, CERRADA=0); arranque CERRADO (LED rojo).

#include <Keypad.h>
#if defined(ESP32)
  #include <ESP32Servo.h>
#else
  #include <Servo.h>
#endif

// --- Pines (GPIO seguros del contrato) ---
const int PIN_SERVO     = 13;
const int PIN_LED_VERDE = 25;  // encendido = ABIERTA
const int PIN_LED_ROJO  = 26;  // encendido = CERRADA
const int PIN_BUZZER    = 14;

// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;

// --- Teclado matricial 4x4 ---
const byte FILAS    = 4;
const byte COLUMNAS = 4;
char teclas[FILAS][COLUMNAS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte pinesFilas[FILAS]       = {16, 17, 18, 19};
byte pinesColumnas[COLUMNAS] = {21, 22, 23, 27};
Keypad teclado = Keypad(makeKeymap(teclas), pinesFilas, pinesColumnas,
                        FILAS, COLUMNAS);

// --- Clave (la del original) y buffer de lo tecleado ---
const byte LARGO_CLAVE = 4;
char clave[LARGO_CLAVE]    = {'1', '5', '9', 'D'};
char tecleado[LARGO_CLAVE] = {0, 0, 0, 0};
byte indice = 0;

// --- Buzzer: tonos distintos para OK y error ---
const unsigned int FREQ_OK        = 1000;  // Hz, pitido agudo = correcta
const unsigned int FREQ_ERROR     = 400;   // Hz, pitido grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PAUSA_BEEP_MS    = 300;

// Estado de la puerta: true = abierta, false = cerrada.
bool puertaAbierta = false;

Servo cerradura;

void mostrarEstadoEnLeds() {
  digitalWrite(PIN_LED_VERDE, puertaAbierta ? HIGH : LOW);
  digitalWrite(PIN_LED_ROJO, puertaAbierta ? LOW : HIGH);
}

void beepCorrecta() {
  tone(PIN_BUZZER, FREQ_OK, DURACION_BEEP_MS);
  delay(DURACION_BEEP_MS);
  noTone(PIN_BUZZER);
}

void beepIncorrecta() {
  // Doble pitido grave.
  for (byte i = 0; i < 2; i++) {
    tone(PIN_BUZZER, FREQ_ERROR, DURACION_BEEP_MS);
    delay(DURACION_BEEP_MS);
    noTone(PIN_BUZZER);
    delay(PAUSA_BEEP_MS);
  }
}

// Compara lo tecleado con la clave guardada.
bool claveCorrecta() {
  for (byte i = 0; i < LARGO_CLAVE; i++) {
    if (tecleado[i] != clave[i]) {
      return false;
    }
  }
  return true;
}

void setup() {
  pinMode(PIN_LED_VERDE, OUTPUT);
  pinMode(PIN_LED_ROJO, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);

  cerradura.attach(PIN_SERVO);
  // Arranca CERRADA (LED rojo).
  puertaAbierta = false;
  cerradura.write(ANGULO_CERRADA);
  mostrarEstadoEnLeds();
}

void loop() {
  char tecla = teclado.getKey();
  if (!tecla) {
    return;  // no se presiono ninguna tecla en esta vuelta
  }

  tecleado[indice] = tecla;
  indice++;

  if (indice == LARGO_CLAVE) {
    indice = 0;

    if (claveCorrecta()) {
      beepCorrecta();
      puertaAbierta = !puertaAbierta;
      cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
      mostrarEstadoEnLeds();
    } else {
      beepIncorrecta();  // no toca la cerradura
    }
  }
}
Cerradura por clave: el teclado 4x4 junta 4 dígitos y los compara con la clave 1 5 9 D. Si coincide, un pitido agudo y la cerradura conmuta; si no, un doble pitido grave y no se mueve. La única diferencia entre plataformas son los pines y la librería del servo.

Podés probar el ingreso de la clave —el pitido de acierto o de error y la cerradura conmutando— en el simulador:

Simulación del comportamiento

La misma máquina de estados del firmware, portada a TypeScript, maneja el comportamiento del dispositivo. No se ejecuta el binario real: la ejecución del .hex con avr8js está en el roadmap.

Simulación lista. Presioná «Iniciar» para arrancar la secuencia.

Cierre del nivel

Al terminar este nivel, la cerradura dejó de abrirse con un botón cualquiera: ahora exige una clave de 4 dígitos que se ingresa por el teclado matricial 4x4, y un buzzer confirma con un tono si el código fue correcto o no. Viste cómo un teclado organiza 16 teclas en una matriz de filas y columnas para usar solo 8 pines, y cómo el programa recuerda el estado de la puerta para alternarlo. En el nivel avanzado vamos a que el abuelo pueda enterarse de la apertura o el cierre aunque no esté en la casa: sumaremos Internet de las Cosas para monitorear la cerradura a distancia.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El teclado matricial 4x4 y el buzzer están conectados sobre el mismo circuito del nivel inicial (servo y LED): los pulsadores fueron reemplazados por el teclado.
  • Instalaste la librería Keypad desde el Gestor de bibliotecas del Arduino IDE.
  • Al tipear la clave correcta (1 5 9 D) suena un pitido agudo y la cerradura conmuta su estado (abre si estaba cerrada, cierra si estaba abierta), acompañada por los LED; con una clave incorrecta suena un doble pitido grave y la cerradura no se mueve.