Práctica 3: Teclado Matricial, Pantalla GLCD, Sensor DHT11 y Control de LEDs

Descripción General: Este proyecto integra múltiples periféricos sobre una placa Arduino (Mega 2560): una barra de 9 diodos LED con diversos patrones de animación, 4 pulsadores físicos y un teclado matricial 4x4 como dispositivos de entrada, un sensor ambiental DHT11 (temperatura y humedad), y una pantalla gráfica GLCD ST7920 (128x64 píxeles) orientada a 180° (U8G2_R2) controlada por Software SPI mediante la librería U8g2. La conversión de valores decimales para la pantalla se realiza mediante dtostrf para garantizar la compatibilidad con microcontroladores AVR.

1. Asignación de Pines y Conexiones (Pinout)

Distribución de entradas, salidas y líneas de comunicación con el microcontrolador:

Módulo / Dispositivo Pines en Arduino Tipo / Señal Modo / Configuración
Barra de 9 LEDs 13, 12, 11, 10, 9, 8, 7, 6, 5 Salidas Digitales OUTPUT (Nivel HIGH enciende el LED)
4 Pulsadores Físicos 28, 26, 24, 22 Entradas Digitales INPUT_PULLUP (Activos a nivel LOW al pulsar)
Teclado Matricial 4x4 (Filas) 23, 25, 27, 29 Filas R1, R2, R3, R4 Gestionado por librería Keypad
Teclado Matricial 4x4 (Columnas) 31, 33, 35, 37 Columnas C1, C2, C3, C4 Gestionado por librería Keypad
Sensor DHT11 Pin 41 Señal de Datos (Data) Unidireccional digital + VCC (5V) + GND
Pantalla GLCD ST7920 (128x64) 50 (RS / CS), 51 (R/W / DATA), 53 (E / CLK) SPI Serie por Software Modo serie: Rotación 180° (U8G2_R2) + VCC/GND

2. Teclado Matricial 4x4 y Pulsadores

El sistema permite el control de los modos de iluminación tanto a través de los 4 pulsadores cableados directamente a las entradas digitales con pull-up, como a través del teclado matricial 4x4.

Matriz de Teclas del Teclado 4x4

Fila \ Col Col 1 (Pin 31) Col 2 (Pin 33) Col 3 (Pin 35) Col 4 (Pin 37)
Fila 1 (Pin 23)'1' (Modo 0)'2' (Modo 1)'3' (Modo 2)'A'
Fila 2 (Pin 25)'4' (Modo 3)'5''6''B'
Fila 3 (Pin 27)'7''8''9''C'
Fila 4 (Pin 29)'*''0''#''D'

Correspondencia de Entradas y Modos

Modo Pulsador Físico (Pin) Tecla Matricial Última Tecla Registrada Acción sobre los LEDs
0 Pulsador 0 (Pin 28) '1' '1' Enciende todos los 9 LEDs simultáneamente
1 Pulsador 1 (Pin 26) '2' '2' Apaga todos los LEDs (Estado de reposo por defecto)
2 Pulsador 2 (Pin 24) '3' '3' Animación Secuencial (un solo LED desplazándose)
3 Pulsador 3 (Pin 22) '4' '4' Animación Acumulativa (relleno progresivo de la barra)

3. Modos de Animación de los LEDs

Lógica de Animación (Función actualizarLeds()):

Los modos 0 y 1 son estados fijos (estáticos). Los modos 2 y 3 ejecutan secuencias temporizadas cada 300 ms (INTERVALO_LED) sin detener el procesador:

4. Pantalla Gráfica GLCD ST7920 (128x64)

Se utiliza la librería U8g2 en modo búfer completo con Software SPI y rotación de 180° (U8G2_ST7920_128X64_F_SW_SPI pantalla(U8G2_R2, ...)).

Organización de la Información en Pantalla

Línea / Coordenada Y Texto / Formato Fuente Descripción
Y = 10 px Practica 3 EMP u8g2_font_6x10_tf Encabezado principal fijo del proyecto
Y = 27 px Temp: XX.X C u8g2_font_6x10_tf Temperatura leída formateada con dtostrf
Y = 40 px Hum: XX.X % u8g2_font_6x10_tf Humedad relativa leída formateada con dtostrf
Y = 53 px Ultima Tecla: X u8g2_font_6x10_tf Último botón físico o tecla pulsada ('0', '1', '2', '3', '4')

Formateo de Floats en AVR: Debido a que snprintf no soporta especificadores %f en Arduino AVR por defecto, se utiliza la función nativa dtostrf() para transformar las variables de temperatura y humedad en arreglos de caracteres (c-strings) antes de concatenarlas:

dtostrf(temperatura, 4, 1, strTemp);

5. Temporizaciones y Multitarea No Bloqueante (millis)

Para evitar retrasos en la respuesta a las pulsaciones y mantener fluidas las animaciones de los LEDs y el refresco de pantalla, el programa prescinde completamente de la función delay() y utiliza el temporizador del sistema millis():

Tarea Concurrente Intervalo Variable de Control Función Asociada
Animación de LEDs 300 ms tiempoLED actualizarLeds()
Actualización de Pantalla 500 ms tiempoPantalla actualizarPantalla()
Muestreo Sensor DHT11 2000 ms tiempoDHT actualizarDHT()
Lectura de Pulsadores y Teclado Tiempo real (cada ciclo) Inmediata leerBotones(), leerTeclado()

Código Arduino (practica3.ino)

#include <Keypad.h>
#include <DHT.h>
#include <U8g2lib.h>

// ==========================================
// 1. PINES DE LOS LED
// ==========================================

const int leds[] = {13, 12, 11, 10, 9, 8, 7, 6, 5};
const int NUM_LEDS = sizeof(leds) / sizeof(leds[0]);

// ==========================================
// 2. PULSADORES FISICOS
// ==========================================

const int botones[] = {28, 26, 24, 22};
const int NUM_BOTONES = sizeof(botones) / sizeof(botones[0]);

// ==========================================
// 3. 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] = {23, 25, 27, 29};
byte pinesColumnas[COLUMNAS] = {31, 33, 35, 37};

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

// ==========================================
// 4. SENSOR DHT11
// ==========================================

const int PIN_DHT = 41;
DHT dht(PIN_DHT, DHT11);

float temperatura = 0;
float humedad = 0;

unsigned long tiempoDHT = 0;
const unsigned long INTERVALO_DHT = 2000;

// ==========================================
// 5. PANTALLA GLCD
// ==========================================

const int GLCD_RS = 50;
const int GLCD_RW = 51;
const int GLCD_E  = 53;

U8G2_ST7920_128X64_F_SW_SPI pantalla(
  U8G2_R2,
  GLCD_E,
  GLCD_RW,
  GLCD_RS,
  U8X8_PIN_NONE
);

// ==========================================
// 6. VARIABLES DEL PROGRAMA
// ==========================================

int modo = 1;
int ledActual = 0;
bool acumulativo = false;

char ultimaTecla = '0';

unsigned long tiempoLED = 0;
const unsigned long INTERVALO_LED = 300;

unsigned long tiempoPantalla = 0;
const unsigned long INTERVALO_PANTALLA = 500;

// ==========================================
// 7. FUNCIONES DE LOS LED
// ==========================================

void apagarLeds() {
  for (int i = 0; i < NUM_LEDS; i++) {
    digitalWrite(leds[i], LOW);
  }
}

void encenderLeds() {
  for (int i = 0; i < NUM_LEDS; i++) {
    digitalWrite(leds[i], HIGH);
  }
}

void seleccionarModo(int nuevoModo) {
  modo = nuevoModo;
  ledActual = 0;
  acumulativo = false;
  apagarLeds();

  if (modo == 0) {
    encenderLeds();
  }
}

// ==========================================
// 8. LECTURA DE LOS PULSADORES FISICOS
// ==========================================

void leerBotones() {
  for (int i = 0; i < NUM_BOTONES; i++) {
    if (digitalRead(botones[i]) == LOW) {
      ultimaTecla = '1' + i;
      seleccionarModo(i);
    }
  }
}

// ==========================================
// 9. LECTURA DEL TECLADO MATRICIAL
// ==========================================

void leerTeclado() {
  char tecla = teclado.getKey();

  if (tecla >= '1' && tecla <= '4') {
    ultimaTecla = tecla;
    seleccionarModo(tecla - '1');
  }
}

// ==========================================
// 10. ANIMACION DE LOS LED
// ==========================================

void actualizarLeds() {
  if (modo < 2) {
    return;
  }

  if (millis() - tiempoLED < INTERVALO_LED) {
    return;
  }

  tiempoLED = millis();

  if (modo == 2) {
    apagarLeds();
    digitalWrite(leds[ledActual], HIGH);

    ledActual++;

    if (ledActual >= NUM_LEDS) {
      ledActual = 0;
    }
  }

  else if (modo == 3) {
    digitalWrite(leds[ledActual], HIGH);
    ledActual++;

    if (ledActual >= NUM_LEDS) {
      apagarLeds();
      ledActual = 0;
    }
  }
}

// ==========================================
// 11. LECTURA DEL DHT11
// ==========================================

void actualizarDHT() {
  if (millis() - tiempoDHT >= INTERVALO_DHT) {
    tiempoDHT = millis();

    float nuevaHumedad = dht.readHumidity();
    float nuevaTemperatura = dht.readTemperature();

    if (!isnan(nuevaHumedad) && !isnan(nuevaTemperatura)) {
      humedad = nuevaHumedad;
      temperatura = nuevaTemperatura;
    }
  }
}

// ==========================================
// 12. ACTUALIZACION DE LA PANTALLA
// ==========================================

void actualizarPantalla() {
  if (millis() - tiempoPantalla < INTERVALO_PANTALLA) {
    return;
  }

  tiempoPantalla = millis();

  pantalla.clearBuffer();

  pantalla.setFont(u8g2_font_6x10_tf);

  pantalla.drawStr(0, 10, "Practica 3 EMP");

  char strTemp[10];
  char strHum[10];
  char textoTemperatura[25];
  char textoHumedad[25];
  char textoTecla[25];

  // Conversión segura de float a texto en AVR
  dtostrf(temperatura, 4, 1, strTemp);
  dtostrf(humedad, 4, 1, strHum);

  snprintf(textoTemperatura, sizeof(textoTemperatura), "Temp: %s C", strTemp);
  snprintf(textoHumedad, sizeof(textoHumedad), "Hum: %s %%", strHum);
  snprintf(textoTecla, sizeof(textoTecla), "Ultima Tecla: %c", ultimaTecla);

  pantalla.drawStr(0, 27, textoTemperatura);
  pantalla.drawStr(0, 40, textoHumedad);
  pantalla.drawStr(0, 53, textoTecla);

  pantalla.sendBuffer();
}

// ==========================================
// 13. CONFIGURACION INICIAL
// ==========================================

void setup() {
  for (int i = 0; i < NUM_LEDS; i++) {
    pinMode(leds[i], OUTPUT);
  }

  for (int i = 0; i < NUM_BOTONES; i++) {
    pinMode(botones[i], INPUT_PULLUP);
  }
  Serial.begin(115200);
  apagarLeds();

  dht.begin();
  pantalla.begin();

  pantalla.clearBuffer();
  pantalla.setFont(u8g2_font_6x10_tf);
  pantalla.drawStr(0, 15, "Practica 3 EMP");
  pantalla.drawStr(0, 30, "Iniciando...");
  pantalla.sendBuffer();

  tiempoDHT = millis() - INTERVALO_DHT;
  tiempoPantalla = millis() - INTERVALO_PANTALLA;
}

// ==========================================
// 14. BUCLE PRINCIPAL
// ==========================================

void loop() {
  leerBotones();
  leerTeclado();
  actualizarLeds();
  actualizarDHT();
  actualizarPantalla();
}