Control de Temperatura ON/OFF con ESP32, LabVIEW y MAX6675

Objetivos de la Práctica

  • Implementar control ON/OFF de temperatura

  • Monitorear temperatura en tiempo real con MAX6675

  • Crear interfaz HMI en LabVIEW para control térmico

  • Implementar histéresis para evitar ciclado rápido

  • Registrar datos y generar alertas


Materiales Requeridos

Hardware:

  • ESP32 Dev Board

  • Módulo MAX6675 + Termopar Tipo K

  • Relé de estado sólido o mecánico

  • Calentador (resistencia 100W o bombilla)

  • Fuente de alimentación 12V/5A

  • Protoboard y cables

  • Caja de control

  • Disipador de calor (opcional)

Software:

  • LabVIEW

  • Arduino IDE

  • VI Package Manager

  • LIFA Toolkit


Parte 1: Esquema de Conexiones

Diagrama de Conexiones ESP32:

cpp
// MAX6675
MAX6675 → ESP32
VCC     → 3.3V
GND     → GND
SO      → GPIO 19
CS      → GPIO 5
SCK     → GPIO 18

// Relé
RELAY   → GPIO 12

// LED Indicador
LED     → GPIO 2 (LED interno)

Esquema de Potencia:

text
Fuente 12V ────┐
              │
        ┌─────┴─────┐
        │   RELÉ    │
        └─────┬─────┘
              │
         CALENTADOR
         (100W 12V)

Parte 2: Programación del ESP32

Código Principal (Control_Temperatura_ESP32.ino):

cpp
#include <max6675.h>

// Pines MAX6675
int thermoSO = 19;
int thermoCS = 5;
int thermoSCK = 18;

// Pin Relé y LED
int relayPin = 12;
int ledPin = 2;

MAX6675 thermocouple(thermoSCK, thermoCS, thermoSO);

// Variables de control
float setpoint = 25.0;        // Temperatura objetivo
float hysteresis = 2.0;       // Histéresis
bool heating = false;         // Estado del calentador
bool autoMode = true;         // Modo automático

// Variables de monitoreo
float currentTemp = 0.0;
unsigned long lastRead = 0;
const unsigned long readInterval = 1000; // Leer cada 1 segundo
unsigned long heatingStartTime = 0;
float maxTemp = 0.0;
float minTemp = 100.0;

// Buffers para comunicación
String inputString = "";
bool stringComplete = false;

void setup() {
  // Configurar pines
  pinMode(relayPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  
  // Inicialmente apagar relé
  digitalWrite(relayPin, LOW);  // LOW = Apagado (depende del relé)
  digitalWrite(ledPin, LOW);
  
  // Comunicación serial
  Serial.begin(115200);
  
  // Esperar inicialización MAX6675
  delay(500);
  
  Serial.println("CONTROL_TEMPERATURA_READY");
  Serial.println("Comandos: SET=xx.x, HYST=xx.x, AUTO_ON, AUTO_OFF, HEATER_ON, HEATER_OFF, STATUS");
}

void loop() {
  // Leer temperatura cada intervalo
  if (millis() - lastRead >= readInterval) {
    readTemperature();
    lastRead = millis();
  }
  
  // Control ON/OFF automático
  if (autoMode) {
    controlHeater();
  }
  
  // Enviar datos a LabVIEW cada 2 segundos
  static unsigned long lastDataSend = 0;
  if (millis() - lastDataSend >= 2000) {
    sendDataToLabVIEW();
    lastDataSend = millis();
  }
  
  // Procesar comandos seriales
  processSerialCommands();
  
  delay(100);
}

void readTemperature() {
  float temp = thermocouple.readCelsius();
  
  // Verificar error de lectura
  if (isnan(temp)) {
    Serial.println("ERROR: Lectura de temperatura fallida");
    return;
  }
  
  currentTemp = temp;
  
  // Actualizar max y min
  if (temp > maxTemp) maxTemp = temp;
  if (temp < minTemp) minTemp = temp;
  
  // LED indicador (parpadeo según temperatura)
  updateStatusLED();
}

void controlHeater() {
  if (currentTemp < (setpoint - hysteresis) && !heating) {
    // Encender calentador
    digitalWrite(relayPin, HIGH);  // HIGH = Encendido
    heating = true;
    heatingStartTime = millis();
    Serial.println("CALENTADOR: ENCENDIDO");
  }
  else if (currentTemp > (setpoint + hysteresis) && heating) {
    // Apagar calentador
    digitalWrite(relayPin, LOW);   // LOW = Apagado
    heating = false;
    unsigned long heatingTime = (millis() - heatingStartTime) / 1000;
    Serial.print("CALENTADOR: APAGADO - Tiempo encendido: ");
    Serial.print(heatingTime);
    Serial.println(" segundos");
  }
}

void updateStatusLED() {
  static unsigned long lastBlink = 0;
  static bool ledState = false;
  
  if (heating) {
    // Parpadeo rápido cuando está calentando
    if (millis() - lastBlink >= 200) {
      ledState = !ledState;
      digitalWrite(ledPin, ledState);
      lastBlink = millis();
    }
  } else {
    // LED apagado cuando no está calentando
    digitalWrite(ledPin, LOW);
  }
}

void sendDataToLabVIEW() {
  Serial.print("DATA:");
  Serial.print(currentTemp, 2);
  Serial.print(",");
  Serial.print(setpoint, 2);
  Serial.print(",");
  Serial.print(heating ? "1" : "0");
  Serial.print(",");
  Serial.print(autoMode ? "1" : "0");
  Serial.print(",");
  Serial.print(maxTemp, 2);
  Serial.print(",");
  Serial.print(minTemp, 2);
  Serial.print(",");
  Serial.print(hysteresis, 2);
  Serial.println();
}

void processSerialCommands() {
  if (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
  
  if (stringComplete) {
    inputString.trim();
    
    if (inputString.startsWith("SET=")) {
      setpoint = inputString.substring(4).toFloat();
      Serial.print("SETPOINT: ");
      Serial.println(setpoint);
      resetMinMax(); // Resetear min/max al cambiar setpoint
    }
    else if (inputString.startsWith("HYST=")) {
      hysteresis = inputString.substring(5).toFloat();
      Serial.print("HYSTERESIS: ");
      Serial.println(hysteresis);
    }
    else if (inputString == "AUTO_ON") {
      autoMode = true;
      Serial.println("MODO: AUTOMATICO");
    }
    else if (inputString == "AUTO_OFF") {
      autoMode = false;
      Serial.println("MODO: MANUAL");
    }
    else if (inputString == "HEATER_ON") {
      digitalWrite(relayPin, HIGH);
      heating = true;
      Serial.println("CALENTADOR: ENCENDIDO (MANUAL)");
    }
    else if (inputString == "HEATER_OFF") {
      digitalWrite(relayPin, LOW);
      heating = false;
      Serial.println("CALENTADOR: APAGADO (MANUAL)");
    }
    else if (inputString == "STATUS") {
      sendDetailedStatus();
    }
    else if (inputString == "RESET_MINMAX") {
      resetMinMax();
      Serial.println("MIN/MAX RESETEADO");
    }
    else {
      Serial.println("ERROR: Comando no reconocido");
    }
    
    inputString = "";
    stringComplete = false;
  }
}

void sendDetailedStatus() {
  Serial.println("=== CONTROL TEMPERATURA STATUS ===");
  Serial.print("Temperatura Actual: "); Serial.print(currentTemp, 2); Serial.println(" °C");
  Serial.print("Setpoint: "); Serial.print(setpoint, 2); Serial.println(" °C");
  Serial.print("Histéresis: "); Serial.print(hysteresis, 2); Serial.println(" °C");
  Serial.print("Calentador: "); Serial.println(heating ? "ENCENDIDO" : "APAGADO");
  Serial.print("Modo: "); Serial.println(autoMode ? "AUTOMATICO" : "MANUAL");
  Serial.print("Temperatura Máxima: "); Serial.print(maxTemp, 2); Serial.println(" °C");
  Serial.print("Temperatura Mínima: "); Serial.print(minTemp, 2); Serial.println(" °C");
  Serial.print("Banda de Control: ");
  Serial.print(setpoint - hysteresis, 2);
  Serial.print(" - ");
  Serial.print(setpoint + hysteresis, 2);
  Serial.println(" °C");
  Serial.println("=================================");
}

void resetMinMax() {
  maxTemp = currentTemp;
  minTemp = currentTemp;
}

Parte 3: Librería MAX6675 (si es necesaria)

max6675.h (si no está disponible):

cpp
#ifndef MAX6675_h
#define MAX6675_h

#include <Arduino.h>

class MAX6675 {
  public:
    MAX6675(int8_t SCLK, int8_t CS, int8_t MISO);
    float readCelsius(void);
    float readFahrenheit(void);
    
  private:
    int8_t sclk, miso, cs;
    uint8_t spiread(void);
};

#endif

max6675.cpp:

cpp
#include "max6675.h"

MAX6675::MAX6675(int8_t SCLK, int8_t CS, int8_t MISO) {
  sclk = SCLK;
  cs = CS;
  miso = MISO;

  pinMode(cs, OUTPUT);
  pinMode(sclk, OUTPUT); 
  pinMode(miso, INPUT);

  digitalWrite(cs, HIGH);
}

float MAX6675::readCelsius(void) {
  uint16_t v;
  digitalWrite(cs, LOW);
  delayMicroseconds(10);
  
  v = spiread();
  v <<= 8;
  v |= spiread();
  
  digitalWrite(cs, HIGH);
  
  if (v & 0x4) {
    return NAN; // Termopar abierto
  }
  
  v >>= 3;
  return v * 0.25;
}

float MAX6675::readFahrenheit(void) {
  return readCelsius() * 9.0 / 5.0 + 32;
}

uint8_t MAX6675::spiread(void) {
  int i;
  uint8_t d = 0;
  
  for (i = 7; i >= 0; i--) {
    digitalWrite(sclk, LOW);
    delayMicroseconds(10);
    if (digitalRead(miso)) {
      d |= (1 << i);
    }
    digitalWrite(sclk, HIGH);
    delayMicroseconds(10);
  }
  return d;
}

Parte 4: Programación en LabVIEW

VI Principal: Control_Temperatura_ONOFF.vi

Front Panel Design:

labview
// GRUPO: INDICADORES PRINCIPALES
- Termómetro: Temperatura Actual (0-300°C)
- Numérico: Setpoint Actual
- Numérico: Histéresis
- LED: Estado Calentador
- LED: Modo Automático
- LED: Alarma Temperatura

// GRUPO: GRÁFICOS
- Waveform Chart: Temperatura vs Tiempo
- Waveform Chart: Setpoint y Bandas
- Waveform Chart: Estado Calentador

// GRUPO: CONTROL
- Botón: Modo Automático/Manual
- Botón: Encender Calentador (manual)
- Botón: Apagar Calentador (manual)
- Selector Numérico: Nuevo Setpoint
- Selector Numérico: Nueva Histéresis
- Botón: Aplicar Configuración

// GRUPO: ESTADÍSTICAS
- Numérico: Temperatura Máxima
- Numérico: Temperatura Mínima
- Numérico: Tiempo Encendido Acumulado
- Numérico: Ciclos de Trabajo

// GRUPO: CONFIGURACIÓN COMUNICACIÓN
- String: Puerto COM
- Numérico: Baud Rate (115200)
- Botón: Conectar/Desconectar
- LED: Estado Conexión

Diagrama de Bloques Principal:

labview
// ESTRUCTURA PRINCIPAL
While Loop con [Stop] button

// INICIALIZACIÓN
Case Structure: First Call?
  - VISA Configure Serial Port
  - Inicializar variables
  - Configurar gráficos

// ESTADO DE LA MÁQUINA
Case Structure dentro del While Loop:
  Estado 0: Desconectado
  Estado 1: Conectando
  Estado 2: Conectado - Monitoreo
  Estado 3: Error - Reconexión

// PROCESAMIENTO DE DATOS
Event Structure:
  - Timeout: 100ms → Leer datos seriales
  - Value Change: Botones → Enviar comandos

// SUBVIs
- "Parsear Datos Temperatura.vi"
- "Controlar Calentador.vi"
- "Calcular Estadísticas.vi"
- "Gestión Alertas.vi"

SubVI: Parsear Datos Temperatura.vi

labview
// Input: String serial (ej: "DATA:25.50,25.00,1,1,30.00,20.00,2.00")
// Output: Cluster con datos parseados

// Algoritmo:
1. Buscar "DATA:" en string
2. Extraer substring después de "DATA:"
3. Separar por comas → Array de strings
4. Convertir a números:
   [0] = Temperatura actual
   [1] = Setpoint
   [2] = Estado calentador (0/1)
   [3] = Modo automático (0/1)
   [4] = Temp máxima
   [5] = Temp mínima
   [6] = Histéresis

SubVI: Controlar Calentador.vi

labview
// Inputs: Modo (Auto/Manual), Comando Manual, Temperatura, Setpoint, Histéresis
// Output: Comando a enviar

Case Structure:
  - Modo Automático:
    * Si Temp < (Setpoint - Histéresis) → "HEATER_ON"
    * Si Temp > (Setpoint + Histéresis) → "HEATER_OFF"
  
  - Modo Manual:
    * Seguir comando manual del usuario

SubVI: Gestión Alertas.vi

labview
// Inputs: Temperatura, Setpoint, Histéresis
// Outputs: Alerta, Nivel de Alerta

Case Structure:
  - Si Temperatura > (Setpoint + 3*Histéresis): "ALERTA: SOBRETEMPERATURA"
  - Si Temperatura < (Setpoint - 3*Histéresis): "ALERTA: SUBTEMPERATURA"
  - Si error de sensor: "ALERTA: SENSOR DESCONECTADO"

// Acciones:
- Cambiar color de indicadores
- Guardar en log de eventos
- Reproducir sonido de alerta

Parte 5: Configuración y Calibración

Procedimiento de Puesta en Marcha:

  1. Conexiones Físicas:

    • Verificar polaridad del termopar

    • Conectar relé correctamente (NO/NC)

    • Asegurar aislamiento eléctrico

  2. Configuración ESP32:

    cpp
    // Cargar código
    // Abrir Monitor Serial
    // Verificar mensaje "CONTROL_TEMPERATURA_READY"
  3. Configuración LabVIEW:

    • Establecer puerto COM correcto

    • Baud rate 115200

    • Configurar límites de temperatura

Calibración del Sistema:

  1. Prueba de Sensor:

    • Comparar con termómetro de referencia

    • Verificar respuesta a cambios de temperatura

    • Ajustar compensación si es necesario

  2. Ajuste de Histéresis:

    • Para sistemas lentos: histéresis 2-5°C

    • Para sistemas rápidos: histéresis 0.5-2°C

    • Evitar ciclado excesivo del relé


Parte 6: Pruebas y Validación

Pruebas de Funcionamiento:

  1. Prueba de Control ON/OFF:

    • Establecer setpoint a temperatura ambiente + 10°C

    • Verificar encendido del calentador

    • Monitorear alcanze del setpoint

    • Verificar apagado del calentador

  2. Prueba de Histéresis:

    • Configurar histéresis de 2°C

    • Verificar banda de control

    • Contar ciclos por hora

  3. Prueba de Modos:

    • Cambio entre automático y manual

    • Control manual del calentador

    • Respuesta a cambios de setpoint

Ejemplo de Datos de Prueba:

labview
// Datos esperados durante operación:
Temperatura: 25.0°C → Calentador: ON
Temperatura: 36.5°C → Calentador: ON  (Setpoint: 37°C, Histéresis: 2°C)
Temperatura: 38.5°C → Calentador: OFF
Temperatura: 36.0°C → Calentador: ON

Parte 7: Mejoras y Extensiones

Características Avanzadas:

  1. Control PID (opcional):

    cpp
    // En lugar de ON/OFF, implementar PID
    double error = setpoint - currentTemp;
    double output = Kp * error + Ki * integral + Kd * derivative;
  2. Suavizado de Temperatura:

    cpp
    // Filtro media móvil
    float readSmoothedTemperature() {
      static float readings[10];
      static int index = 0;
      
      readings[index] = thermocouple.readCelsius();
      index = (index + 1) % 10;
      
      float sum = 0;
      for (int i = 0; i < 10; i++) {
        sum += readings[i];
      }
      return sum / 10;
    }
  3. Web Server ESP32:

    cpp
    #include <WebServer.h>
    WebServer server(80);
    
    void setupServer() {
      server.on("/", HTTP_GET, []() {
        String html = "<html><body>";
        html += "<h1>Control Temperatura</h1>";
        html += "<p>Temperatura: " + String(currentTemp) + " °C</p>";
        html += "</body></html>";
        server.send(200, "text/html", html);
      });
      server.begin();
    }

Parte 8: Evaluación de la Práctica

Criterios de Evaluación:

  • ✅ Comunicación serial estable

  • ✅ Lectura correcta de temperatura con MAX6675

  • ✅ Control ON/OFF funcional con histéresis

  • ✅ Interfaz LabVIEW intuitiva y completa

  • ✅ Cambio correcto entre modos auto/manual

  • ✅ Sistema de alertas operativo

  • ✅ Registro de datos y estadísticas

Entregables:

  1. Código completo ESP32 + MAX6675

  2. VI de LabVIEW funcionando

  3. Diagramas de conexión

  4. Reporte de pruebas con gráficos

  5. Análisis de estabilidad del sistema

Métricas de Desempeño:

  • Precisión: ±1°C del setpoint

  • Estabilidad: Menos de 5 ciclos/hora

  • Respuesta: Tiempo para alcanzar setpoint

  • Confiabilidad: 0 falsas alarmas


Esta práctica proporciona una base sólida para el control térmico ON/OFF con capacidades industriales. ¿Te gustaría que agregue alguna funcionalidad específica o que profundice en alguna parte del sistema?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *