Sensor DHT22 (AM2302)
Pin Digital 41 (Data) • Muestreo cada 2sBarra de 9 LEDs (Salidas Digitales Arduino Mega)
Conexión: Pin 13 al Pin 5 con resistencias limitadoras a GND
⌨️ 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
PULSADOR 1
PULSADOR 2
PULSADOR 3
PULSADOR 4
🔌 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.
| Línea | Pin Mega (Conector 90°) | Pin Mega (Cables sueltos) | Tipo | Teclas |
|---|---|---|---|---|
| Fila 1 (R1) | Pin 23 | Pin 22 | Salida / Barrido | 1, 2, 3, A |
| Fila 2 (R2) | Pin 25 | Pin 23 | Salida / Barrido | 4, 5, 6, B |
| Fila 3 (R3) | Pin 27 | Pin 24 | Salida / Barrido | 7, 8, 9, C |
| Fila 4 (R4) | Pin 29 | Pin 25 | Salida / Barrido | *, 0, #, D |
| Columna 1 (C1) | Pin 31 | Pin 26 | Entrada (Pull-Up) | 1, 4, 7, * |
| Columna 2 (C2) | Pin 33 | Pin 27 | Entrada (Pull-Up) | 2, 5, 8, 0 |
| Columna 3 (C3) | Pin 35 | Pin 28 | Entrada (Pull-Up) | 3, 6, 9, # |
| Columna 4 (C4) | Pin 37 | Pin 29 | Entrada (Pull-Up) | A, B, C, D |
| Componente | Terminal | Pin Arduino Mega | Función |
|---|---|---|---|
| GLCD ST7920 | RS (CS) | Pin 53 | Chip Select (SS) |
| R/W (SID) | Pin 51 | Datos Serie (MOSI) | |
| E (SCLK) | Pin 52 | Reloj Serie (SCK) | |
| PSB | GND | Selecciona Modo Serie | |
| Sensor DHT22 | DATA (Pin 2) | Pin 41 | Bus 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().
/*
=============================================================================
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.