Práctica 4: Integración de Control Infrarrojo (IR) y Mando a Distancia

Descripción General: Esta práctica amplía el sistema de la Práctica 3 incorporando control inalámbrico mediante Infrarrojos (IR) a través de un receptor conectado en el Pin 43 de la placa Arduino Mega 2560. El sistema mantiene la barra de 9 LEDs, los 4 pulsadores físicos, el teclado matricial 4x4, el sensor DHT11 y la pantalla gráfica GLCD ST7920 sin bloqueos temporales mediante el uso de millis().

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

Distribución de entradas, salidas y líneas de comunicación del sistema:

Módulo / Dispositivo Pines en Arduino Mega 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)
Teclado Matricial 4x4 Filas: 23, 25, 27, 29 | Cols: 31, 33, 35, 37 E/S Matricial Gestionado por librería Keypad
Sensor DHT11 Pin 41 Señal Digital (Data) Lectura ambiental de Tª y Humedad
Pantalla GLCD ST7920 50 (RS/CS), 51 (R/W/DATA), 53 (E/CLK) Software SPI Librería U8g2 (128x64 px)
Receptor Infrarrojo (IR) Pin 43 Entrada Digital (Out) Demodulador a 38 kHz (Librería IRremote)

2. Receptor IR y Librería IRremote

El sensor infrarrojo capta luz IR modulada a una frecuencia estándar de 38 kHz para rechazar interferencias de la luz ambiental. Al recibir señales, demodula la trama y entrega pulsos digitales al Pin 43 de la Arduino Mega.

Reasignación de Temporizadores en Arduino Mega 2560:

Por defecto, la librería IRremote usa el Timer 2 en microcontroladores AVR para medir los tiempos entre pulsos. Para evitar conflictos con el refresco de pantalla o salidas PWM, se reconfigura expresamente para emplear el Timer 3 declarando la siguiente directiva antes de incluir la librería:

#define IR_USE_AVR_TIMER3

Mecanismo de Decodificación y Prevención de Repeticiones

La lectura del sensor se realiza en la función leerIR() de forma no bloqueante:

3. Mando a Distancia e Integración de Códigos Hexadecimales

El mando a distancia utiliza el protocolo **NEC** para transmitir los códigos. Al presionar una tecla, el receptor obtiene el comando en formato hexadecimal (HEX) mapeado hacia los modos del sistema:

Botón del Mando Código Hexadecimal (Command) Tecla Registrada Acción sobre los LEDs
Botón 1 0x45 '1' Modo 0: Enciende los 9 LEDs fijamente
Botón 2 0x46 '2' Modo 1: Apaga todos los LEDs
Botón 3 0x47 '3' Modo 2: Secuencia LED individual (barrido)
Botón 4 0x44 '4' Modo 3: Secuencia LED acumulativa

4. Resumen de Entradas del Sistema

Origen del Control Dispositivo Función de Lectura Dato / Trama Procesada
Pulsadores Físicos Botones (Pines 28, 26, 24, 22) leerBotones() Lectura digital directa (LOW)
Teclado Matricial Matriz 4x4 (Pines 23 a 37) leerTeclado() Caracteres ASCII ('1' a '4')
Control Infrarrojo Mando IR + Receptor (Pin 43) leerIR() Comandos Hex (0x45, 0x46, 0x47, 0x44)

Código Arduino (practica4_ir.ino)

/*
  =============================================================================
  PRÁCTICA 4: Integración de Receptor IR (Pin 43)
  Placa: Arduino Mega 2560
  =============================================================================
*/

// ==========================================
// CONFIGURACIÓN DE TIMERS (ANTES DE INCLUIR LIBRERÍAS)
// ==========================================
#define IR_USE_AVR_TIMER3

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

// ==========================================
// 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. RECEPTOR IR
// ==========================================

const int PIN_IR = 43; // Receptor IR en Pin 43

#define IR_BOTON_1 0x45
#define IR_BOTON_2 0x46
#define IR_BOTON_3 0x47
#define IR_BOTON_4 0x44

// ==========================================
// 7. 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;

// ==========================================
// 8. 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();
  }
}

// ==========================================
// 9. 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);
    }
  }
}

// ==========================================
// 10. LECTURA DEL TECLADO MATRICIAL
// ==========================================

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

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

// ==========================================
// 11. LECTURA DEL MANDO IR
// ==========================================

void leerIR() {
  if (IrReceiver.decode()) {
    if (!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)) {
      uint8_t comando = IrReceiver.decodedIRData.command & 0xFF;

      Serial.print("IR recibido: 0x");
      Serial.println(comando, HEX);

      switch (comando) {
        case IR_BOTON_1:
          ultimaTecla = '1';
          seleccionarModo(0);
          break;
        case IR_BOTON_2:
          ultimaTecla = '2';
          seleccionarModo(1);
          break;
        case IR_BOTON_3:
          ultimaTecla = '3';
          seleccionarModo(2);
          break;
        case IR_BOTON_4:
          ultimaTecla = '4';
          seleccionarModo(3);
          break;
      }
    }
    IrReceiver.resume();
  }
}

// ==========================================
// 12. 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;
    }
  }
}

// ==========================================
// 13. 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;
    }
  }
}

// ==========================================
// 14. 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];

  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();
}

// ==========================================
// 15. 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();

  IrReceiver.begin(PIN_IR, DISABLE_LED_FEEDBACK);
  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;
}

// ==========================================
// 16. BUCLE PRINCIPAL
// ==========================================

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