∞
Arduino Mega 2560 Práctica 3 Maqueta Interactiva • Teclado 4x4, Pantalla GLCD (ST7920) y Sensor DHT22
Ampliación Práctica 2 • Control con Teclado 4x4, GLCD y Sensor DHT22

Teclado Matricial, GLCD y DHT22 con Arduino Mega

Control completo de secuencias de 9 LEDs mediante teclado matricial 4x4, visualización gráfica en pantalla GLCD 128x64 y medición continua de temperatura y humedad con sensor DHT22 sin retardos bloqueantes delay().

Panel Virtual de la Maqueta - Práctica 3
Modo / Tecla Activa: Tecla 2 (Apagados) millis() Virtual: 0 ms
Pantalla GLCD ST7920 128x64 (Modo Serie SPI) Pines: E=52, RW=51, RS=53
TEMPERATURA 22 °C
HUMEDAD 45 %
PULSADOR 2
Resolución: 128 x 64 px Refresco continuo con millis()
🌡️

Sensor DHT22 (AM2302)

Pin Digital 41 (Data) • Muestreo cada 2s
CONECTADO
Temperatura Ambiente: 22 °C
10 °C 25 °C 45 °C
Humedad Relativa: 45 %
20 % 60 % 95 %
Valores rápidos:

Barra de 9 LEDs (Salidas Digitales Arduino Mega)

Conexión: Pin 13 al Pin 5 con resistencias limitadoras a GND

D13
LED 1 Rojo
D12
LED 2 Ámbar
D11
LED 3 Verde
D10
LED 4 Rojo
D9
LED 5 Ámbar
D8
LED 6 Verde
D7
LED 7 Rojo
D6
LED 8 Ámbar
D5
LED 9 Verde
LEDs activos actualmente: 0 de 9
Paso de secuencia: - Frecuencia: 150 ms

⌨️ Teclado Matricial 4x4 (Entradas Digitales Pines 22 a 29)

Haz clic en cualquier tecla o pulsa las teclas 1, 2, 3, 4 en tu teclado físico

Keypad 4x4 Matrix
Funciones Requeridas en la Práctica 3
1
Tecla 1 → Encender Todos Enciende los 9 LEDs. Pantalla: PULSADOR 1
2
Tecla 2 → Apagar Todos Apaga los 9 LEDs. Pantalla: PULSADOR 2
3
Tecla 3 → Secuencia 3 (Marquesina) Enciende un LED y apaga el anterior. Pantalla: PULSADOR 3
4
Tecla 4 → Secuencia 4 (Relleno) Acumula encendidos y luego resetea. Pantalla: PULSADOR 4
* El sistema lee el teclado matricial por barrido de filas y columnas sin bloquear el procesador.
Velocidad / Intervalo: 150 ms

🔌 Asignación de Pines y Circuito Eléctrico (Práctica 3)

Conexiones completas del Arduino Mega 2560: Barra de LEDs, Teclado Matricial 4x4, Pantalla GLCD y Sensor DHT22.

Teclado Matricial 4x4 (Conector Maqueta Mega) Keypad.h
Línea Pin Mega (Conector 90°) Pin Mega (Cables sueltos) Tipo Teclas
Fila 1 (R1)Pin 23Pin 22Salida / Barrido1, 2, 3, A
Fila 2 (R2)Pin 25Pin 23Salida / Barrido4, 5, 6, B
Fila 3 (R3)Pin 27Pin 24Salida / Barrido7, 8, 9, C
Fila 4 (R4)Pin 29Pin 25Salida / Barrido*, 0, #, D
Columna 1 (C1)Pin 31Pin 26Entrada (Pull-Up)1, 4, 7, *
Columna 2 (C2)Pin 33Pin 27Entrada (Pull-Up)2, 5, 8, 0
Columna 3 (C3)Pin 35Pin 28Entrada (Pull-Up)3, 6, 9, #
Columna 4 (C4)Pin 37Pin 29Entrada (Pull-Up)A, B, C, D
Pantalla GLCD 128x64 & Sensor DHT22 U8g2 / DHT.h
Componente Terminal Pin Arduino Mega Función
GLCD ST7920RS (CS)Pin 53Chip Select (SS)
R/W (SID)Pin 51Datos Serie (MOSI)
E (SCLK)Pin 52Reloj Serie (SCK)
PSBGNDSelecciona Modo Serie
Sensor DHT22DATA (Pin 2)Pin 41Bus digital unifilar

⚡ ¿Por qué la GLCD usa solo 3 cables de control?

El controlador ST7920 de la pantalla de la maqueta tiene el pin PSB conectado a GND. Esto activa el protocolo SPI en modo serie, ahorrando decenas de pines del Arduino y requiriendo únicamente Clock (Pin 52), Datos SID (Pin 51) y Chip Select (Pin 53).

💻 Código Fuente Arduino (.ino) - Práctica 3

Implementación completa para Arduino Mega 2560 con Keypad, U8g2 / LiquidCrystal y DHT22 con millis().

Descargar .ino
maqueta_mega_practica3.ino C++ / Wiring • Arduino Mega 2560
/*
  =============================================================================
   PROYECTO: Maqueta Arduino Mega 2560 - Práctica 3
   TECLADO MATRICIAL 4x4, PANTALLA GLCD (ST7920) / LCD Y SENSOR DHT22
  =============================================================================
   Enlace oficial de referencia:
   https://unicarlos.es/MODULOS/EMP/maqueta-mega/practica3.html

   OBJETIVOS DE LA PRÁCTICA 3:
     - Tecla 1: Enciende todos los 9 LEDs. Pantalla: PULSADOR 1
     - Tecla 2: Apaga todos los 9 LEDs. Pantalla: PULSADOR 2
     - Tecla 3: Secuencia 3 (Marquesina cíclica). Pantalla: PULSADOR 3
     - Tecla 4: Secuencia 4 (Relleno acumulativo cíclico). Pantalla: PULSADOR 4
     - Sensor DHT22: Muestreo periódico sin bloqueo de Temperatura y Humedad.
     - Pantalla (GLCD ST7920 o LCD): Muestra en vivo la plantilla oficial:
         Línea 1: TEMPERATURA [T] °C
         Línea 2: HUMEDAD [H] %
         Línea 3: PULSADOR [X]

   CONEXIÓN DEL TECLADO MATRICIAL 4x4 EN LA MAQUETA ARDUINO MEGA:
     En la maqueta física oficial, el teclado matricial se conecta a la regleta
     de pines del Arduino Mega mediante un conector macho acodado a 90°.
     Ese conector se enchufa en la fila exterior de pines impares (23 a 37):
       - Filas:    Pines 23, 25, 27, 29
       - Columnas: Pines 31, 33, 35, 37
     (Si utilizas cables sueltos en pines consecutivos 22..29, consulta el
      selector TIPO_CONEXION_TECLADO a continuación).

   SALIDAS DIGITALES (9 LEDs):
     LED 1..9: Pines 13, 12, 11, 10, 9, 8, 7, 6, 5 con resistencia a GND.

   SENSOR DHT22 (TEMPERATURA Y HUMEDAD):
     Pin DATA -> Pin 41 (con resistencia pull-up o módulo integrado).

   PANTALLA GLCD ST7920 128x64 (Modo Serie SPI con pin PSB a GND):
     RS / CS   -> Pin 53 (Chip Select)
     R/W / SID -> Pin 51 (MOSI / Serial Data)
     E / SCLK  -> Pin 52 (SCK / Serial Clock)
     (Compatible también con LiquidCrystal alfanumérico estándar).
  =============================================================================
*/

// =============================================================================
// 1. CONFIGURACIÓN DEL HARDWARE
// =============================================================================

// --- TIPO DE PANTALLA ---
// 1 = Pantalla gráfica GLCD ST7920 128x64 en modo serie (Biblioteca U8g2)
// 0 = Pantalla alfanumérica LCD estándar (Biblioteca LiquidCrystal)
#define USAR_GLCD_U8G2 1

// --- TIPO DE CONEXIÓN DEL TECLADO MATRICIAL 4x4 ---
// 1 = CONECTOR A 90° EN PINES IMPARES (Pines 23, 25, 27, 29, 31, 33, 35, 37) -> Maqueta oficial
// 2 = CABLES SUELTOS A PINES CONSECUTIVOS (Pines 22, 23, 24, 25, 26, 27, 28, 29)
// 3 = CONECTOR A 90° EN PINES PARES (Pines 22, 24, 26, 28, 30, 32, 34, 36)
// 4 = CONECTOR INVERTIDO 180° EN PINES IMPARES (Pines 29, 27, 25, 23 y 37, 35, 33, 31)
#define TIPO_CONEXION_TECLADO 1

// =============================================================================
// 2. INCLUSIÓN DE BIBLIOTECAS
// =============================================================================
#include <Keypad.h>
#include <DHT.h>

#if USAR_GLCD_U8G2
  #include <U8g2lib.h>
  #include <SPI.h>
#else
  #include <LiquidCrystal.h>
#endif

// =============================================================================
// 3. ASIGNACIÓN DE PINES
// =============================================================================

// --- SALIDAS: 9 LEDS ---
const uint8_t PINES_LEDS[] = {13, 12, 11, 10, 9, 8, 7, 6, 5};
const uint8_t NUM_LEDS     = sizeof(PINES_LEDS) / sizeof(PINES_LEDS[0]);

// --- ENTRADAS: TECLADO MATRICIAL 4x4 ---
const byte FILAS = 4;
const byte COLUMNAS = 4;

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

#if TIPO_CONEXION_TECLADO == 1
  // Maqueta oficial: Conector acodado a 90° en regleta exterior impar
  byte pinesFilas[FILAS]       = {23, 25, 27, 29};
  byte pinesColumnas[COLUMNAS] = {31, 33, 35, 37};

#elif TIPO_CONEXION_TECLADO == 2
  // Cables individuales en pines consecutivos 22 a 29
  byte pinesFilas[FILAS]       = {22, 23, 24, 25};
  byte pinesColumnas[COLUMNAS] = {26, 27, 28, 29};

#elif TIPO_CONEXION_TECLADO == 3
  // Conector acodado en regleta interior par
  byte pinesFilas[FILAS]       = {22, 24, 26, 28};
  byte pinesColumnas[COLUMNAS] = {30, 32, 34, 36};

#elif TIPO_CONEXION_TECLADO == 4
  // Conector a 90° invertido 180°
  byte pinesFilas[FILAS]       = {29, 27, 25, 23};
  byte pinesColumnas[COLUMNAS] = {37, 35, 33, 31};
#endif

Keypad teclado = Keypad(makeKeymap(mapaTeclas), pinesFilas, pinesColumnas, FILAS, COLUMNAS);

// --- SENSOR DHT22 ---
#define PIN_DHT 41
#define TIPO_DHT DHT22
DHT dht(PIN_DHT, TIPO_DHT);

// --- PANTALLA ---
#if USAR_GLCD_U8G2
  // Constructor para GLCD ST7920 128x64 en modo Serie SPI:
  // E=Reloj (52), RW=Datos SID (51), RS=Chip Select CS (53)
  U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 52, /* data=*/ 51, /* cs=*/ 53);
#else
  // Constructor para pantalla LCD alfanumérica estándar
  LiquidCrystal lcd(38, 39, 40, 41, 42, 43);
#endif

// =============================================================================
// 4. MÁQUINA DE ESTADOS Y VARIABLES CON MILLIS()
// =============================================================================
enum ModoOperacion {
  MODO_ENCENDIDO,     // Tecla 1: Todos los LEDs encendidos
  MODO_APAGADO,       // Tecla 2: Todos los LEDs apagados
  MODO_SECUENCIA_3,   // Tecla 3: Marquesina cíclica
  MODO_SECUENCIA_4    // Tecla 4: Relleno acumulativo cíclico
};

ModoOperacion modoActual = MODO_APAGADO;
uint8_t pulsadorActivo   = 2; // Inicia en Modo 2 (Todos apagados)

// Control de animación de LEDs con millis()
unsigned long tiempoUltimoPasoLED = 0;
const unsigned long INTERVALO_ANIMACION = 150; // 150 ms por paso
int pasoActual = 0;

// Sensor DHT22 con millis()
float temperaturaActual = 22.0;
float humedadActual     = 45.0;
unsigned long tiempoUltimaLecturaDHT = 0;
const unsigned long INTERVALO_DHT   = 2000; // Lectura cada 2000 ms

// Bandera de refresco de pantalla
bool pantallaNecesitaActualizar = true;

// =============================================================================
// 5. SETUP
// =============================================================================
void setup() {
  Serial.begin(9600);

  // Configuración de los 9 pines de LEDs como salidas
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    pinMode(PINES_LEDS[i], OUTPUT);
    digitalWrite(PINES_LEDS[i], LOW);
  }

  // Inicialización del sensor DHT22
  dht.begin();

  // Inicialización de la pantalla
#if USAR_GLCD_U8G2
  u8g2.begin();
  u8g2.setContrast(140);
#else
  lcd.begin(20, 4);
  lcd.clear();
#endif

  // Estado inicial
  apagarTodos();
  modoActual = MODO_APAGADO;
  pulsadorActivo = 2;

  // Primera lectura del sensor
  actualizarSensorDHT();
  actualizarPantalla();

  // Mensajes de diagnóstico y verificación por puerto serie
  Serial.println(F("=========================================================="));
  Serial.println(F("  MAQUETA ARDUINO MEGA - PRÁCTICA 3 INICIALIZADA"));
  Serial.println(F("  Control por Teclado Matricial 4x4, GLCD y Sensor DHT22"));
  Serial.println(F("=========================================================="));
  Serial.print(F("Pines Filas Teclado:    "));
  for (int i = 0; i < FILAS; i++) { Serial.print(pinesFilas[i]); Serial.print(F(" ")); }
  Serial.println();
  Serial.print(F("Pines Columnas Teclado: "));
  for (int i = 0; i < COLUMNAS; i++) { Serial.print(pinesColumnas[i]); Serial.print(F(" ")); }
  Serial.println();
  Serial.println(F("=========================================================="));
  Serial.println(F("Comandos del Teclado / Monitor Serie:"));
  Serial.println(F("  [1] -> Encender todos los LEDs   (PULSADOR 1)"));
  Serial.println(F("  [2] -> Apagar todos los LEDs      (PULSADOR 2)"));
  Serial.println(F("  [3] -> Secuencia 3 (Marquesina)  (PULSADOR 3)"));
  Serial.println(F("  [4] -> Secuencia 4 (Relleno)     (PULSADOR 4)"));
  Serial.println(F("=========================================================="));
  Serial.println(F("Esperando pulsación de teclas..."));
}

// =============================================================================
// 6. LOOP: Bucle principal multitarea
// =============================================================================
void loop() {
  // 1. Escaneo inmediato del teclado matricial y del monitor serie
  leerTeclado();

  // 2. Muestreo periódico del sensor DHT22 cada 2 segundos sin bloqueo
  actualizarSensorDHT();

  // 3. Ejecución no bloqueante de la animación de LEDs activa
  switch (modoActual) {
    case MODO_ENCENDIDO:
      // LEDs permanecen encendidos
      break;

    case MODO_APAGADO:
      // LEDs permanecen apagados
      break;

    case MODO_SECUENCIA_3:
      secuencia3(INTERVALO_ANIMACION);
      break;

    case MODO_SECUENCIA_4:
      secuencia4(INTERVALO_ANIMACION);
      break;
  }

  // 4. Refresco de pantalla por evento (solo cuando cambia el modo o el sensor)
  if (pantallaNecesitaActualizar) {
    actualizarPantalla();
    pantallaNecesitaActualizar = false;
  }
}

// =============================================================================
// 7. LECTURA Y PROCESAMIENTO DEL TECLADO
// =============================================================================
void leerTeclado() {
  // Lectura directa del teclado matricial
  char tecla = teclado.getKey();

  // Soporte de prueba por Monitor Serie USB
  if (tecla == NO_KEY && Serial.available() > 0) {
    char c = Serial.read();
    if (c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c == '*' || c == '#') {
      tecla = c;
    }
  }

  // Si se ha pulsado alguna tecla
  if (tecla != NO_KEY) {
    Serial.print(F(">>> TECLA DETECTADA: [ "));
    Serial.print(tecla);
    Serial.println(F(" ]"));

    switch (tecla) {
      case '1':
        modoActual = MODO_ENCENDIDO;
        pulsadorActivo = 1;
        encenderTodos();
        pantallaNecesitaActualizar = true;
        Serial.println(F("    -> MODO 1: TODOS LOS LEDS ENCENDIDOS (PULSADOR 1)"));
        break;

      case '2':
        modoActual = MODO_APAGADO;
        pulsadorActivo = 2;
        apagarTodos();
        pantallaNecesitaActualizar = true;
        Serial.println(F("    -> MODO 2: TODOS LOS LEDS APAGADOS (PULSADOR 2)"));
        break;

      case '3':
        modoActual = MODO_SECUENCIA_3;
        pulsadorActivo = 3;
        apagarTodos();
        pasoActual = 0;
        tiempoUltimoPasoLED = millis() - INTERVALO_ANIMACION; // Respuesta instantánea
        pantallaNecesitaActualizar = true;
        Serial.println(F("    -> MODO 3: SECUENCIA 3 INICIADA (PULSADOR 3)"));
        break;

      case '4':
        modoActual = MODO_SECUENCIA_4;
        pulsadorActivo = 4;
        apagarTodos();
        pasoActual = 0;
        tiempoUltimoPasoLED = millis() - INTERVALO_ANIMACION; // Respuesta instantánea
        pantallaNecesitaActualizar = true;
        Serial.println(F("    -> MODO 4: SECUENCIA 4 INICIADA (PULSADOR 4)"));
        break;

      default:
        Serial.print(F("    -> Tecla auxiliar sin función asignada: "));
        Serial.println(tecla);
        break;
    }
  }
}

// =============================================================================
// 8. FUNCIONES DE CONTROL DE LEDS
// =============================================================================
void apagarTodos() {
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    digitalWrite(PINES_LEDS[i], LOW);
  }
}

void encenderTodos() {
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    digitalWrite(PINES_LEDS[i], HIGH);
  }
}

// SECUENCIA 3: Enciende un LED y apaga el anterior de forma cíclica (Marquesina)
void secuencia3(unsigned long retardo) {
  unsigned long tiempoActual = millis();

  if (tiempoActual - tiempoUltimoPasoLED >= retardo) {
    tiempoUltimoPasoLED = tiempoActual;

    apagarTodos();
    digitalWrite(PINES_LEDS[pasoActual], HIGH);

    pasoActual++;
    if (pasoActual >= NUM_LEDS) {
      pasoActual = 0;
    }
  }
}

// SECUENCIA 4: Enciende acumulando LEDs hasta el último y luego apaga todo (Cíclico)
void secuencia4(unsigned long retardo) {
  unsigned long tiempoActual = millis();

  if (tiempoActual - tiempoUltimoPasoLED >= retardo) {
    tiempoUltimoPasoLED = tiempoActual;

    if (pasoActual < NUM_LEDS) {
      digitalWrite(PINES_LEDS[pasoActual], HIGH);
      pasoActual++;
    } else {
      apagarTodos();
      pasoActual = 0;
    }
  }
}

// =============================================================================
// 9. LECTURA DEL SENSOR DHT22 CON MILLIS()
// =============================================================================
void actualizarSensorDHT() {
  unsigned long tiempoActual = millis();

  if (tiempoActual - tiempoUltimaLecturaDHT >= INTERVALO_DHT) {
    tiempoUltimaLecturaDHT = tiempoActual;

    float t = dht.readTemperature();
    float h = dht.readHumidity();

    if (!isnan(t) && !isnan(h)) {
      if (round(t) != round(temperaturaActual) || round(h) != round(humedadActual)) {
        pantallaNecesitaActualizar = true;
      }
      temperaturaActual = t;
      humedadActual     = h;

      Serial.print(F("[DHT22] Temperatura: "));
      Serial.print(temperaturaActual, 1);
      Serial.print(F(" °C  |  Humedad: "));
      Serial.print(humedadActual, 1);
      Serial.println(F(" %"));
    }
  }
}

// =============================================================================
// 10. ACTUALIZACIÓN DE PANTALLA
// =============================================================================
void actualizarPantalla() {
  int t = (int)round(temperaturaActual);
  int h = (int)round(humedadActual);

#if USAR_GLCD_U8G2
  u8g2.firstPage();
  do {
    // Marco exterior con esquinas redondeadas
    u8g2.drawRFrame(0, 0, 128, 64, 3);
    u8g2.setFont(u8g2_font_6x12_tf);

    // Línea 1: TEMPERATURA xx °C
    u8g2.setCursor(8, 18);
    u8g2.print(F("TEMPERATURA "));
    u8g2.print(t);
    u8g2.print(F(" \xb0\x43"));
    u8g2.drawLine(6, 23, 121, 23);

    // Línea 2: HUMEDAD xx %
    u8g2.setCursor(8, 37);
    u8g2.print(F("HUMEDAD "));
    u8g2.print(h);
    u8g2.print(F(" %"));
    u8g2.drawLine(6, 42, 121, 42);

    // Línea 3: PULSADOR x
    u8g2.setCursor(8, 56);
    u8g2.print(F("PULSADOR "));
    u8g2.print(pulsadorActivo);
  } while (u8g2.nextPage());

#else
  lcd.setCursor(0, 0);
  lcd.print(F("TEMPERATURA "));
  lcd.print(t);
  lcd.print(F(" \xDF\x43   "));

  lcd.setCursor(0, 1);
  lcd.print(F("HUMEDAD "));
  lcd.print(h);
  lcd.print(F(" %     "));

  lcd.setCursor(0, 2);
  lcd.print(F("PULSADOR "));
  lcd.print(pulsadorActivo);
  lcd.print(F("   "));
#endif
}

🧠 Fundamentos: ¿Por qué millis() es crítico en la Práctica 3?

Comparativa arquitectónica de temporización y multitarea concurrente.

❌ El problema de delay() con Teclado y DHT22

Bloqueante

Si se utilizara delay(150) para las secuencias de LEDs:

  • Pérdida de teclas: El escaneo matricial del teclado requiere consultar filas y columnas miles de veces por segundo. Un delay provocaría que el usuario tenga que mantener pulsada la tecla varios segundos para que sea detectada.
  • Colapso del sensor DHT22: El DHT22 requiere pausas exactas de 2000 ms. Si se usa delay, toda la maqueta se congelaría durante 2 segundos en cada lectura.
  • Parpadeo en la pantalla: El refresco gráfico de la GLCD se retrasaría drásticamente.

✅ La ventaja de millis() en Sistemas Complejos

Multitarea

Con millis(), el procesador ejecuta 4 tareas concurrentemente sin estorbarse:

  • Tarea 1 (Inmediata): Escaneo del teclado 4x4 y detección de tecla instantánea en microsegundos.
  • Tarea 2 (150 ms): Avance suave e ininterrumpido de las animaciones de los 9 LEDs.
  • Tarea 3 (2000 ms): Muestreo exacto del sensor DHT22 sin alterar el ritmo de los LEDs.
  • Tarea 4 (250 ms / por evento): Refresco limpio de la pantalla GLCD ST7920 sin parpadeo.