Práctica 4: Integración de Control por Infrarrojos (IR) y Módulo Bluetooth

Descripción General: Esta práctica amplía la funcionalidad del sistema desarrollado en la Práctica 3 agregando dos nuevos métodos de control inalámbrico: un receptor de Infrarrojos (IR) conectado en el Pin 43 y un módulo de comunicación Bluetooth (HC-05 / HC-06) en el puerto serie por hardware Serial3 (Pines 14 y 15) de la placa Arduino Mega 2560. El sistema mantiene el control de la barra de 9 LEDs, el sensor DHT11 y la pantalla gráfica GLCD ST7920 sin bloqueos temporales.

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

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

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)
Módulo Bluetooth (HC-05/06) Pin 15 (RX3) y Pin 14 (TX3) UART Serie Hardware Puerto Serial3 a 9600 baudios

2. Receptor IR y Librería IRremote

El sensor infrarrojo utilizado (VS1838B o receptor estándar de 3 pines) capta luz infrarroja modulada a una frecuencia de 38 kHz, lo que permite ignorar la interferencia de la luz ambiental. Al detectar los pulsos, el receptor entrega una señal digital demodulada al Pin 43 de la placa Arduino Mega.

Configuración de Timers en Arduino Mega 2560:

Por defecto, la librería IRremote utiliza el Timer 2 de los microcontroladores AVR para medir la anchura de los pulsos en microsegundos. Sin embargo, para evitar incompatibilidades y asegurar que la refrescada gráfica de la pantalla GLCD y los pines PWM operen de forma fluida, se reconfigura la librería para utilizar el Timer 3 especificando la siguiente directiva antes del include:

#define IR_USE_AVR_TIMER3

Mecanismo de Decodificación y Prevención de Repeticiones

La lectura del sensor se realiza a través de la función no bloqueante leerIR():

3. Mando a Distancia y Mapeo Hexadecimal

El mando emite pulsos de luz infrarroja siguiendo el protocolo estándar **NEC**. Cada botón pulsado transmite una trama única que se decodifica en un comando en formato hexadecimal (HEX).

Botón del Mando Código Hexadecimal (Command) Tecla Equivalente Acción sobre los LEDs
Botón 1 0x45 '1' Selecciona Modo 0: Enciende los 9 LEDs simultáneamente
Botón 2 0x46 '2' Selecciona Modo 1: Apaga todos los LEDs
Botón 3 0x47 '3' Selecciona Modo 2: Animación secuencial (barrido)
Botón 4 0x44 '4' Selecciona Modo 3: Animación acumulativa

4. Módulo Bluetooth (HC-05 / HC-06) y Puerto Hardware Serial3

Para la conectividad Bluetooth se utiliza un módulo HC-05 o HC-06 conectado al puerto serie por hardware Serial3 del ATmega2560 (Pin 15/RX3 y Pin 14/TX3). El puerto se inicializa en setup() a una velocidad estándar de 9600 baudios (Serial3.begin(9600)).

Ventaja del uso de UART Hardware: A diferencia del uso de librerías por software (como SoftwareSerial en Arduino Uno), el canal Serial3 del Mega gestiona las interrupciones y el almacenamiento en buffer de forma independiente a nivel de hardware, evitando la pérdida de caracteres y liberando al procesador principal.

5. Aplicación Móvil: Arduino Bluetooth Controller

Para el control remoto desde un dispositivo móvil Android se emplea la aplicación Arduino Bluetooth Controller (o equivalente en emulación terminal/botones).

Configuración y Filtrado de Caracteres

La aplicación transmite comandos en formato ASCII a través del perfil SPP (Serial Port Profile). Los botones de la app se configuran para enviar los caracteres '1', '2', '3' o '4'.

En la función leerBluetooth(), se aplica un filtro especial para descartar los caracteres de terminación de línea que envían automáticamente algunas aplicaciones móviles al pulsar un botón:

if (dato == '\n' || dato == '\r') return;

6. Resumen General 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)
Bluetooth Movil App Móvil + HC-05 (Serial3) leerBluetooth() Caracteres ASCII ('1' a '4') sin \r/\n

Código Arduino (practica4.ino)

/*
  =============================================================================
  PRÁCTICA 4: Integración de IR (Pin 43) y Bluetooth (Serial3)
  Basado en la versión funcional de la Práctica 3
  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 Y BLUETOOTH
// ==========================================

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

// Códigos Hexadecimales para los botones 1, 2, 3 y 4 del mando
#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()) {
    // Comprobar que no sea un código de repetición
    if (!(IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT)) {
      uint8_t comando = IrReceiver.decodedIRData.command & 0xFF; // Extraer byte de comando
      
      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(); // Habilitar la recepción del siguiente pulso
  }
}

// ==========================================
// 12. LECTURA DEL MODULO BLUETOOTH (Serial3)
// ==========================================

void leerBluetooth() {
  if (Serial3.available() > 0) {
    char dato = Serial3.read();

    // Filtrar caracteres de nueva línea o retorno de carro
    if (dato == '\n' || dato == '\r') {
      return;
    }

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

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

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

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

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

  // Inicialización de comunicaciones Serie
  Serial.begin(115200); // Monitor Serie
  Serial3.begin(9600);   // Bluetooth HC-05/06 en RX3 (Pin 15) y TX3 (Pin 14)

  apagarLeds();

  // Inicialización de componentes
  IrReceiver.begin(PIN_IR, DISABLE_LED_FEEDBACK); // Receptor IR en Pin 43
  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;
}

// ==========================================
// 17. BUCLE PRINCIPAL
// ==========================================

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