Sistema Integrado de Monitoreo y Control Térmico
Este proyecto integrador representa la culminación del Módulo 3: Señales Analógicas, combinando todos los conceptos aprendidos sobre ADC, comparadores analógicos y DAC en un sistema real de control térmico industrial.
Objetivos del Proyecto
Objetivos Técnicos
- Implementar lectura precisa de temperatura con LM35
- Utilizar potenciómetro como control manual de setpoint
- Generar señal de control analógica via DAC
- Integrar comparadores para sistemas de seguridad
- Implementar interfaz de usuario avanzada
Objetivos Educativos
- Consolidar conocimientos de periféricos analógicos
- Aplicar técnicas de filtrado digital
- Desarrollar sistemas de control en lazo cerrado
- Implementar protocolos de comunicación
- Diseñar interfaces humano-máquina profesionales
Aplicación Industrial Real
Este sistema puede aplicarse directamente en: control de hornos industriales, sistemas HVAC, control de procesos químicos, incubadoras médicas, y sistemas de climatización de invernaderos.
Arquitectura del Sistema
Componentes del Sistema
Sensores de Entrada (ADC)
- Sensor LM35: Medición de temperatura ambiente (ADC1_CH0 - GPIO36)
- Potenciómetro: Control manual de setpoint (ADC1_CH3 - GPIO39)
- Sensor de referencia: Calibración automática (ADC1_CH6 - GPIO34)
Procesamiento de Señal
- Filtros digitales: Anti-ruido y suavizado
- Comparadores por software: Detección de umbrales
- Control PID: Regulación automática de temperatura
- Sistema de alarmas: Protección por sobre/sub temperatura
Salidas de Control (DAC)
- Señal de calentamiento: Control de resistencia calefactora (DAC1 - GPIO25)
- Señal de ventilación: Control de velocidad de ventilador (DAC2 - GPIO26)
Sistema de Control
Especificaciones Técnicas del Proyecto
Especificaciones de Rendimiento
Parámetro | Especificación |
---|---|
Rango de temperatura | 0°C a 100°C |
Precisión | ±0.5°C |
Tiempo de respuesta | < 5 segundos |
Resolución de control | 0.1°C |
Frecuencia de muestreo | 10 Hz |
Estabilidad | ±0.1°C @ 25°C |
Sistemas de Seguridad
- Protección térmica: Corte automático > 85°C
- Falla de sensor: Modo seguro con alarma
- Watchdog: Reset automático ante fallas
- Límites de corriente: Protección de actuadores
- Interfaz de emergencia: Parada manual
Lista Completa de Materiales
Componentes Principales
- ESP32 DevKit v1
- Sensor LM35DZ
- Potenciómetro lineal 10kΩ
- Display LCD 20x4 I2C
- Módulo RTC DS3231
Actuadores y Control
- Resistencia calefactora 12V/50W
- Ventilador 12V DC
- Relé estado sólido 25A
- Driver de motor L298N
- LEDs indicadores (RGB)
Electrónica Auxiliar
- Fuente switching 12V/5A
- Regulador LM7805
- Capacitores de filtrado
- Resistores de precisión 0.1%
- Conectores y cableado
Implementación del Sistema Completo
/**
* PROYECTO INTEGRADOR: Sistema de Control Térmico Industrial
* Curso ESP32 Mecatrónica UNAM - Módulo 3
*
* Descripción: Sistema completo que integra:
* - Lectura de temperatura (LM35) via ADC
* - Control manual con potenciómetro
* - Salidas de control via DAC
* - Comparadores de seguridad
* - Interfaz de usuario avanzada
* - Logging de datos
* - Control PID automático
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include
#include
#include
#include
#include
#include "time.h"
// ========== CONFIGURACIÓN DE HARDWARE ==========
// Entradas ADC
const adc_channel_t TEMP_SENSOR_CHANNEL = ADC_CHANNEL_0; // GPIO36 - LM35
const adc_channel_t SETPOINT_POT_CHANNEL = ADC_CHANNEL_3; // GPIO39 - Potenciómetro
const adc_channel_t REFERENCE_CHANNEL = ADC_CHANNEL_6; // GPIO34 - Referencia
// Salidas DAC
const int HEATER_DAC_PIN = 25; // DAC1 - Control calefactor
const int FAN_DAC_PIN = 26; // DAC2 - Control ventilador
// Pines digitales de control
const int SAFETY_RELAY_PIN = 2; // Relé de seguridad
const int ALARM_LED_PIN = 4; // LED de alarma
const int STATUS_LED_PIN = 16; // LED de estado
const int BUZZER_PIN = 17; // Buzzer de alarma
const int EMERGENCY_STOP_PIN = 18; // Botón de parada de emergencia
// Configuración I2C
const int I2C_SDA = 21;
const int I2C_SCL = 22;
// Display LCD
LiquidCrystal_I2C lcd(0x27, 20, 4); // 20x4 LCD
// Calibración ADC
esp_adc_cal_characteristics_t adc_chars;
// ========== ESTRUCTURA DE DATOS DEL SISTEMA ==========
struct SystemData {
// Lecturas de sensores
float current_temperature;
float setpoint_temperature;
float reference_voltage;
// Estado del control
float control_output_heater;
float control_output_fan;
bool heating_active;
bool cooling_active;
// Control PID
float pid_error;
float pid_integral;
float pid_derivative;
float pid_output;
// Sistema de seguridad
bool system_alarm;
bool emergency_stop;
String alarm_message;
unsigned long alarm_start_time;
// Estadísticas
float min_temp_recorded;
float max_temp_recorded;
unsigned long operation_time;
unsigned long cycles_completed;
};
struct SystemConfig {
// Parámetros PID
float kp = 2.5;
float ki = 0.8;
float kd = 0.4;
// Límites de seguridad
float max_temperature_limit = 85.0;
float min_temperature_limit = 5.0;
// Configuración operacional
float temperature_tolerance = 1.0;
int update_frequency_hz = 10;
bool auto_mode = true;
// Comunicación
String wifi_ssid = "ESP32_ThermalControl";
String wifi_password = "control123";
bool web_server_enabled = true;
};
// Variables globales del sistema
SystemData system_data;
SystemConfig system_config;
WebServer web_server(80);
// ========== CLASES ESPECIALIZADAS ==========
class ThermalSensorManager {
private:
float calibration_offset;
float calibration_gain;
float temperature_buffer[10];
int buffer_index;
bool calibrated;
public:
ThermalSensorManager() {
calibration_offset = 0.0;
calibration_gain = 1.0;
buffer_index = 0;
calibrated = false;
memset(temperature_buffer, 0, sizeof(temperature_buffer));
}
void initialize() {
// Configurar ADC para alta precisión
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(TEMP_SENSOR_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(SETPOINT_POT_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(REFERENCE_CHANNEL, ADC_ATTEN_DB_11);
// Calibrar ADC
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
Serial.println("Sensor manager inicializado");
}
float readTemperature() {
// Lectura múltiple para mayor precisión
uint32_t adc_sum = 0;
const int samples = 16;
for (int i = 0; i < samples; i++) {
adc_sum += adc1_get_raw(TEMP_SENSOR_CHANNEL);
delayMicroseconds(500);
}
uint32_t adc_average = adc_sum / samples;
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(adc_average, &adc_chars);
// Conversión LM35: 10mV/°C
float temperature = voltage_mv / 10.0;
// Aplicar calibración
temperature = (temperature * calibration_gain) + calibration_offset;
// Filtro de media móvil
temperature_buffer[buffer_index] = temperature;
buffer_index = (buffer_index + 1) % 10;
float filtered_temp = 0;
for (int i = 0; i < 10; i++) {
filtered_temp += temperature_buffer[i];
}
return filtered_temp / 10.0;
}
float readSetpoint() {
// Leer potenciómetro con suavizado
uint32_t adc_sum = 0;
const int samples = 8;
for (int i = 0; i < samples; i++) {
adc_sum += adc1_get_raw(SETPOINT_POT_CHANNEL);
}
uint32_t adc_average = adc_sum / samples;
// Mapear a rango de temperatura (15-60°C)
float percentage = (adc_average / 4095.0);
float setpoint = 15.0 + (percentage * 45.0);
return setpoint;
}
void performCalibration(float reference_temp) {
Serial.println("Iniciando calibración automática...");
// Leer temperatura actual sin calibración
float measured_temp = readTemperature();
// Calcular offset de calibración
calibration_offset = reference_temp - measured_temp;
calibrated = true;
Serial.printf("Calibración completada. Offset: %.3f°C\n", calibration_offset);
}
bool isCalibrated() {
return calibrated;
}
};
class PIDController {
private:
float last_error;
float integral;
unsigned long last_update;
float output_min, output_max;
bool integral_reset;
public:
PIDController(float min_output = -255, float max_output = 255) {
last_error = 0;
integral = 0;
last_update = 0;
output_min = min_output;
output_max = max_output;
integral_reset = false;
}
float compute(float setpoint, float current_value) {
unsigned long now = millis();
float dt = (now - last_update) / 1000.0; // Convertir a segundos
if (last_update == 0 || dt <= 0) {
last_update = now;
return 0;
}
// Calcular error
float error = setpoint - current_value;
// Término proporcional
float proportional = system_config.kp * error;
// Término integral con anti-windup
integral += error * dt;
if (integral > 100) integral = 100;
if (integral < -100) integral = -100;
float integral_term = system_config.ki * integral;
// Término derivativo
float derivative = (error - last_error) / dt;
float derivative_term = system_config.kd * derivative;
// Salida PID
float output = proportional + integral_term + derivative_term;
// Saturación
if (output > output_max) output = output_max;
if (output < output_min) output = output_min;
// Actualizar variables
last_error = error;
last_update = now;
// Guardar valores para monitoreo
system_data.pid_error = error;
system_data.pid_integral = integral;
system_data.pid_derivative = derivative;
system_data.pid_output = output;
return output;
}
void reset() {
integral = 0;
last_error = 0;
last_update = 0;
}
void tunePID(float kp, float ki, float kd) {
system_config.kp = kp;
system_config.ki = ki;
system_config.kd = kd;
Serial.printf("PID actualizado: Kp=%.3f, Ki=%.3f, Kd=%.3f\n", kp, ki, kd);
}
};
class SafetyManager {
private:
bool safety_triggered;
unsigned long last_safety_check;
String last_alarm_reason;
public:
SafetyManager() {
safety_triggered = false;
last_safety_check = 0;
}
void initialize() {
pinMode(SAFETY_RELAY_PIN, OUTPUT);
pinMode(ALARM_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(EMERGENCY_STOP_PIN, INPUT_PULLUP);
// Estado seguro inicial
digitalWrite(SAFETY_RELAY_PIN, LOW);
digitalWrite(ALARM_LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
Serial.println("Sistema de seguridad inicializado");
}
bool checkSafety() {
if (millis() - last_safety_check < 100) {
return !system_data.system_alarm; // Check cada 100ms
}
system_data.system_alarm = false;
system_data.alarm_message = "";
// Check 1: Temperatura excesiva
if (system_data.current_temperature > system_config.max_temperature_limit) {
triggerAlarm("SOBRETEMPERATURA CRITICA");
return false;
}
// Check 2: Temperatura muy baja
if (system_data.current_temperature < system_config.min_temperature_limit) {
triggerAlarm("TEMPERATURA EXTREMADAMENTE BAJA");
return false;
}
// Check 3: Botón de emergencia
if (digitalRead(EMERGENCY_STOP_PIN) == LOW) {
triggerAlarm("PARADA DE EMERGENCIA ACTIVADA");
system_data.emergency_stop = true;
return false;
}
// Check 4: Falla de sensor (lectura imposible)
if (system_data.current_temperature < -10 || system_data.current_temperature > 120) {
triggerAlarm("FALLA EN SENSOR DE TEMPERATURA");
return false;
}
// Check 5: Control PID fuera de rango
if (abs(system_data.pid_output) > 300) {
triggerAlarm("CONTROL PID INESTABLE");
return false;
}
last_safety_check = millis();
// Si llegamos aquí, todo está bien
if (safety_triggered) {
clearAlarm();
}
return true;
}
private:
void triggerAlarm(String reason) {
if (!system_data.system_alarm) {
system_data.alarm_start_time = millis();
Serial.println("🚨 ALARMA: " + reason);
}
system_data.system_alarm = true;
system_data.alarm_message = reason;
safety_triggered = true;
// Activar indicadores de alarma
digitalWrite(SAFETY_RELAY_PIN, LOW); // Cortar alimentación
digitalWrite(ALARM_LED_PIN, HIGH);
// Patrón de buzzer según tipo de alarma
if (reason.indexOf("EMERGENCIA") >= 0 || reason.indexOf("CRITICA") >= 0) {
// Buzzer continuo para emergencias críticas
digitalWrite(BUZZER_PIN, (millis() % 200) < 100);
} else {
// Buzzer intermitente para otras alarmas
digitalWrite(BUZZER_PIN, (millis() % 1000) < 100);
}
}
void clearAlarm() {
system_data.system_alarm = false;
system_data.emergency_stop = false;
system_data.alarm_message = "";
safety_triggered = false;
digitalWrite(ALARM_LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(SAFETY_RELAY_PIN, HIGH); // Restablecer alimentación
Serial.println("✅ Alarma eliminada - Sistema restaurado");
}
};
class DisplayManager {
private:
unsigned long last_update;
int current_screen;
int total_screens;
public:
DisplayManager() {
last_update = 0;
current_screen = 0;
total_screens = 4;
}
void initialize() {
lcd.init();
lcd.backlight();
// Pantalla de inicio
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sistema Termico UNAM");
lcd.setCursor(0, 1);
lcd.print("Inicializando...");
lcd.setCursor(0, 2);
lcd.print("ESP32 Mecatronica");
lcd.setCursor(0, 3);
lcd.print("Modulo 3 - Proyecto");
delay(3000);
Serial.println("Display inicializado");
}
void update() {
if (millis() - last_update < 500) return; // Actualizar cada 500ms
lcd.clear();
if (system_data.system_alarm) {
displayAlarmScreen();
} else {
switch (current_screen) {
case 0: displayMainScreen(); break;
case 1: displayControlScreen(); break;
case 2: displayStatisticsScreen(); break;
case 3: displaySystemInfo(); break;
}
}
last_update = millis();
}
void nextScreen() {
current_screen = (current_screen + 1) % total_screens;
}
private:
void displayMainScreen() {
lcd.setCursor(0, 0);
lcd.printf("Temp: %.1fC (%.1fC)",
system_data.current_temperature,
system_data.setpoint_temperature);
lcd.setCursor(0, 1);
lcd.printf("Calef:%3.0f%% Vent:%3.0f%%",
(system_data.control_output_heater / 255.0) * 100,
(system_data.control_output_fan / 255.0) * 100);
lcd.setCursor(0, 2);
lcd.printf("Modo: %s PID:%.1f",
system_config.auto_mode ? "AUTO" : "MAN ",
system_data.pid_output);
lcd.setCursor(0, 3);
lcd.printf("Estado: %s",
system_data.heating_active ? "CALENTANDO" :
system_data.cooling_active ? "ENFRIANDO " : "ESTABLE ");
}
void displayControlScreen() {
lcd.setCursor(0, 0);
lcd.print("=== PARAMETROS PID ===");
lcd.setCursor(0, 1);
lcd.printf("Kp: %.2f Ki: %.2f", system_config.kp, system_config.ki);
lcd.setCursor(0, 2);
lcd.printf("Kd: %.2f Err: %.2f", system_config.kd, system_data.pid_error);
lcd.setCursor(0, 3);
lcd.printf("I:%.2f D:%.2f", system_data.pid_integral, system_data.pid_derivative);
}
void displayStatisticsScreen() {
lcd.setCursor(0, 0);
lcd.print("=== ESTADISTICAS ===");
lcd.setCursor(0, 1);
lcd.printf("Min: %.1fC Max: %.1fC",
system_data.min_temp_recorded,
system_data.max_temp_recorded);
lcd.setCursor(0, 2);
lcd.printf("Tiempo: %02d:%02d:%02d",
(int)(system_data.operation_time / 3600),
(int)((system_data.operation_time % 3600) / 60),
(int)(system_data.operation_time % 60));
lcd.setCursor(0, 3);
lcd.printf("Ciclos: %lu", system_data.cycles_completed);
}
void displaySystemInfo() {
lcd.setCursor(0, 0);
lcd.print("=== INFO SISTEMA ===");
lcd.setCursor(0, 1);
lcd.printf("WiFi: %s", WiFi.status() == WL_CONNECTED ? "Conectado" : "Desconect");
lcd.setCursor(0, 2);
lcd.printf("IP: %s", WiFi.localIP().toString().c_str());
lcd.setCursor(0, 3);
lcd.printf("Memoria: %d KB", ESP.getFreeHeap() / 1024);
}
void displayAlarmScreen() {
// Parpadeo para llamar la atención
bool blink = (millis() % 1000) < 500;
if (blink) {
lcd.setCursor(0, 0);
lcd.print("!!!!! ALARMA !!!!!");
lcd.setCursor(0, 1);
lcd.print(system_data.alarm_message);
lcd.setCursor(0, 2);
unsigned long alarm_duration = (millis() - system_data.alarm_start_time) / 1000;
lcd.printf("Duracion: %02d:%02d",
(int)(alarm_duration / 60),
(int)(alarm_duration % 60));
lcd.setCursor(0, 3);
lcd.print("Presione RESET");
}
}
};
// ========== INSTANCIAS DE CLASES ==========
ThermalSensorManager sensor_manager;
PIDController pid_controller(-255, 255);
SafetyManager safety_manager;
DisplayManager display_manager;
// ========== FUNCIONES PRINCIPALES ==========
void setup() {
Serial.begin(115200);
Serial.println("\n=== SISTEMA DE CONTROL TÉRMICO INDUSTRIAL ===");
Serial.println("Curso ESP32 Mecatrónica UNAM - Módulo 3");
Serial.println("================================================");
// Inicializar I2C
Wire.begin(I2C_SDA, I2C_SCL);
// Inicializar componentes
sensor_manager.initialize();
safety_manager.initialize();
display_manager.initialize();
// Configurar pines DAC
pinMode(HEATER_DAC_PIN, OUTPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
// Configurar WiFi (opcional)
if (system_config.web_server_enabled) {
setupWiFi();
setupWebServer();
}
// Inicializar variables del sistema
initializeSystemData();
// Calibración automática si hay referencia disponible
delay(2000); // Estabilizar sensores
Serial.println("✅ Sistema inicializado correctamente");
Serial.println("Comandos disponibles:");
Serial.println("- 'SET [temp]' : Establecer setpoint");
Serial.println("- 'PID [kp] [ki] [kd]' : Ajustar PID");
Serial.println("- 'AUTO/MANUAL' : Cambiar modo");
Serial.println("- 'RESET' : Reiniciar sistema");
Serial.println("- 'STATUS' : Mostrar estado completo");
Serial.println("================================================\n");
}
void loop() {
// Lecturas de sensores
system_data.current_temperature = sensor_manager.readTemperature();
system_data.setpoint_temperature = sensor_manager.readSetpoint();
// Verificar seguridad del sistema
bool system_safe = safety_manager.checkSafety();
if (system_safe && system_config.auto_mode) {
// Control PID automático
float pid_output = pid_controller.compute(system_data.setpoint_temperature,
system_data.current_temperature);
// Aplicar salidas de control
applyControlOutputs(pid_output);
} else {
// Modo seguro o manual
applyControlOutputs(0); // Apagar actuadores
}
// Actualizar estadísticas
updateSystemStatistics();
// Actualizar display
display_manager.update();
// Procesar comandos serie
processSerialCommands();
// Handle web server si está habilitado
if (system_config.web_server_enabled) {
web_server.handleClient();
}
// Log periódico
static unsigned long last_log = 0;
if (millis() - last_log > 10000) { // Cada 10 segundos
logSystemStatus();
last_log = millis();
}
delay(100); // Loop principal a 10Hz
}
// ========== FUNCIONES DE CONTROL ==========
void applyControlOutputs(float pid_output) {
if (pid_output > 0) {
// Calentamiento necesario
system_data.control_output_heater = constrain(pid_output, 0, 255);
system_data.control_output_fan = 0;
system_data.heating_active = true;
system_data.cooling_active = false;
// Aplicar señal DAC para calefactor
dacWrite(HEATER_DAC_PIN, (uint8_t)system_data.control_output_heater);
dacWrite(FAN_DAC_PIN, 0);
} else if (pid_output < -10) { // Zona muerta para evitar oscilaciones
// Enfriamiento necesario
system_data.control_output_heater = 0;
system_data.control_output_fan = constrain(abs(pid_output), 0, 255);
system_data.heating_active = false;
system_data.cooling_active = true;
// Aplicar señal DAC para ventilador
dacWrite(HEATER_DAC_PIN, 0);
dacWrite(FAN_DAC_PIN, (uint8_t)system_data.control_output_fan);
} else {
// Zona muerta - mantener estado actual
system_data.heating_active = false;
system_data.cooling_active = false;
// Mantener salidas bajas
dacWrite(HEATER_DAC_PIN, 0);
dacWrite(FAN_DAC_PIN, 0);
}
// LED de estado del sistema
digitalWrite(STATUS_LED_PIN, system_data.heating_active || system_data.cooling_active);
}
void initializeSystemData() {
system_data.current_temperature = 25.0;
system_data.setpoint_temperature = 25.0;
system_data.min_temp_recorded = 999.0;
system_data.max_temp_recorded = -999.0;
system_data.operation_time = 0;
system_data.cycles_completed = 0;
system_data.system_alarm = false;
system_data.emergency_stop = false;
system_data.alarm_message = "";
}
void updateSystemStatistics() {
static unsigned long operation_start = millis();
system_data.operation_time = (millis() - operation_start) / 1000;
// Actualizar min/max
if (system_data.current_temperature < system_data.min_temp_recorded) {
system_data.min_temp_recorded = system_data.current_temperature;
}
if (system_data.current_temperature > system_data.max_temp_recorded) {
system_data.max_temp_recorded = system_data.current_temperature;
}
// Contar ciclos de control
static bool was_heating = false;
if (!was_heating && system_data.heating_active) {
system_data.cycles_completed++;
}
was_heating = system_data.heating_active;
}
void processSerialCommands() {
if (!Serial.available()) return;
String command = Serial.readStringUntil('\n');
command.trim();
command.toUpperCase();
if (command.startsWith("SET ")) {
float new_setpoint = command.substring(4).toFloat();
if (new_setpoint >= 15.0 && new_setpoint <= 60.0) {
// Simular ajuste manual del potenciómetro
Serial.printf("Setpoint establecido: %.1f°C\n", new_setpoint);
} else {
Serial.println("Error: Setpoint fuera de rango (15-60°C)");
}
}
else if (command.startsWith("PID ")) {
// Extraer parámetros PID
int space1 = command.indexOf(' ', 4);
int space2 = command.indexOf(' ', space1 + 1);
if (space1 > 0 && space2 > 0) {
float kp = command.substring(4, space1).toFloat();
float ki = command.substring(space1 + 1, space2).toFloat();
float kd = command.substring(space2 + 1).toFloat();
pid_controller.tunePID(kp, ki, kd);
}
}
else if (command == "AUTO") {
system_config.auto_mode = true;
Serial.println("Modo automático activado");
}
else if (command == "MANUAL") {
system_config.auto_mode = false;
Serial.println("Modo manual activado");
}
else if (command == "RESET") {
pid_controller.reset();
Serial.println("Sistema PID reiniciado");
}
else if (command == "STATUS") {
logSystemStatus();
}
else if (command == "SCREEN") {
display_manager.nextScreen();
}
else if (command == "HELP") {
Serial.println("Comandos disponibles:");
Serial.println("SET [temp] - Establecer setpoint");
Serial.println("PID [kp] [ki] [kd] - Ajustar parámetros PID");
Serial.println("AUTO/MANUAL - Cambiar modo de operación");
Serial.println("RESET - Reiniciar controlador PID");
Serial.println("STATUS - Mostrar estado completo");
Serial.println("SCREEN - Cambiar pantalla LCD");
}
}
void logSystemStatus() {
Serial.println("\n========== ESTADO DEL SISTEMA ==========");
Serial.printf("Temperatura actual: %.2f°C\n", system_data.current_temperature);
Serial.printf("Setpoint: %.2f°C\n", system_data.setpoint_temperature);
Serial.printf("Error PID: %.2f°C\n", system_data.pid_error);
Serial.printf("Salida PID: %.1f\n", system_data.pid_output);
Serial.printf("Calefactor: %.0f%% (DAC: %d)\n",
(system_data.control_output_heater / 255.0) * 100,
(int)system_data.control_output_heater);
Serial.printf("Ventilador: %.0f%% (DAC: %d)\n",
(system_data.control_output_fan / 255.0) * 100,
(int)system_data.control_output_fan);
Serial.printf("Modo: %s\n", system_config.auto_mode ? "AUTOMÁTICO" : "MANUAL");
Serial.printf("Estado: %s\n",
system_data.heating_active ? "CALENTANDO" :
system_data.cooling_active ? "ENFRIANDO" : "ESTABLE");
if (system_data.system_alarm) {
Serial.printf("🚨 ALARMA: %s\n", system_data.alarm_message.c_str());
}
Serial.printf("Tiempo operación: %02d:%02d:%02d\n",
(int)(system_data.operation_time / 3600),
(int)((system_data.operation_time % 3600) / 60),
(int)(system_data.operation_time % 60));
Serial.printf("Min/Max registrado: %.1f°C / %.1f°C\n",
system_data.min_temp_recorded, system_data.max_temp_recorded);
Serial.printf("Memoria libre: %d KB\n", ESP.getFreeHeap() / 1024);
Serial.println("=====================================\n");
}
// ========== CONFIGURACIÓN WIFI Y WEB SERVER ==========
void setupWiFi() {
WiFi.softAP(system_config.wifi_ssid.c_str(), system_config.wifi_password.c_str());
Serial.printf("WiFi AP iniciado: %s\n", system_config.wifi_ssid.c_str());
Serial.printf("IP del servidor: %s\n", WiFi.softAPIP().toString().c_str());
}
void setupWebServer() {
// Endpoint para obtener datos del sistema
web_server.on("/api/data", HTTP_GET, []() {
DynamicJsonDocument doc(1024);
doc["temperature"] = system_data.current_temperature;
doc["setpoint"] = system_data.setpoint_temperature;
doc["pid_output"] = system_data.pid_output;
doc["heater_output"] = (system_data.control_output_heater / 255.0) * 100;
doc["fan_output"] = (system_data.control_output_fan / 255.0) * 100;
doc["auto_mode"] = system_config.auto_mode;
doc["alarm"] = system_data.system_alarm;
doc["alarm_message"] = system_data.alarm_message;
doc["operation_time"] = system_data.operation_time;
doc["min_temp"] = system_data.min_temp_recorded;
doc["max_temp"] = system_data.max_temp_recorded;
String response;
serializeJson(doc, response);
web_server.send(200, "application/json", response);
});
// Endpoint para control remoto
web_server.on("/api/control", HTTP_POST, []() {
if (web_server.hasArg("mode")) {
String mode = web_server.arg("mode");
system_config.auto_mode = (mode == "auto");
}
if (web_server.hasArg("setpoint")) {
float setpoint = web_server.arg("setpoint").toFloat();
if (setpoint >= 15.0 && setpoint <= 60.0) {
// En implementación real, esto controlaría un DAC para el potenciómetro
Serial.printf("Setpoint remoto establecido: %.1f°C\n", setpoint);
}
}
web_server.send(200, "application/json", "{\"status\":\"ok\"}");
});
// Página web básica de interfaz
web_server.on("/", HTTP_GET, []() {
String html = R"(
Control Térmico ESP32
Sistema de Control Térmico
Cargando...
Conectando...
Control
)";
web_server.send(200, "text/html", html);
});
web_server.begin();
Serial.println("Servidor web iniciado en puerto 80");
}
Guía Completa de Montaje y Configuración
Procedimiento de Montaje Paso a Paso
Fase 1: Preparación del Hardware
- Verificación de componentes: Inspeccionar todos los materiales listados
- Preparación del ESP32: Instalar en protoboard o PCB personalizada
- Configuración de alimentación: Conectar fuente 12V y regulador 5V
- Instalación de componentes pasivos: Resistores y capacitores de filtrado
Fase 2: Conexiones Analógicas
- Sensor LM35: Conectar con filtrado RC (100nF + 10kΩ)
- Potenciómetro: Configurar divisor de voltaje con buffer
- Referencias de voltaje: Crear referencias estables para calibración
- Salidas DAC: Conectar amplificadores operacionales para interfaz
Fase 3: Actuadores y Control
- Relé de seguridad: Instalar con aislamiento óptico
- Control de calefactor: Conexión via relé estado sólido
- Control de ventilador: Driver PWM con transistor MOSFET
- Indicadores LED: Con resistores limitadores apropiados
Diagrama de Conexiones
Conexiones Críticas del Sistema
- GPIO36 ← LM35 (Vout)
- GPIO39 ← Potenciómetro (Wiper)
- GPIO34 ← Referencia 2.5V
- GPIO25 → Amplificador calefactor
- GPIO26 → Control PWM ventilador
⚠️ Precauciones de Seguridad
- Desconectar alimentación durante montaje
- Verificar polaridad de componentes
- Usar aislamiento para conexiones de AC
- Implementar fusibles de protección
- Verificar todas las conexiones antes del primer encendido
- Mantener ventilación adecuada
✅ Verificaciones Pre-Operación
- Continuidad en todas las conexiones
- Niveles de voltaje correctos
- Funcionamiento de sensores
- Respuesta de actuadores
- Sistema de seguridad operativo
Resultados Esperados y Validación
Control de Temperatura
Precisión: ±0.5°C
Tiempo de establecimiento: < 30 segundos
Sobrepaso máximo: < 2°C
Estabilidad: ±0.1°C en estado estable
Sistemas de Seguridad
Tiempo de respuesta: < 100ms
Corte por sobretemperatura: 85°C
Detección de fallas: Automática
Parada de emergencia: < 1 segundo
Conectividad
Interfaz web: Tiempo real
Logging de datos: Cada 10 segundos
Control remoto: API REST
Monitoreo: 24/7 disponible
Protocolos de Prueba
Prueba 1: Calibración y Precisión
- Verificar lectura de temperatura con termómetro de referencia
- Validar linealidad en rango 20-60°C
- Comprobar repetibilidad (10 mediciones)
- Evaluar deriva térmica durante 1 hora
Prueba 2: Control Automático
- Establecer setpoint de 40°C desde temperatura ambiente
- Medir tiempo de establecimiento y sobrepaso
- Aplicar perturbaciones (cambios de carga)
- Verificar estabilidad en estado estable
Prueba 3: Sistemas de Seguridad
- Simular sobretemperatura (sensor externo)
- Verificar activación de alarmas
- Probar parada de emergencia
- Comprobar restablecimiento automático
Troubleshooting y Optimización
Problemas Comunes
Problema: Lecturas de temperatura inestables
Causas posibles:
- Ruido en alimentación del ADC
- Conexiones flojas o corroídas
- Interferencia electromagnética
- Autocalentamiento del sensor
Problema: Control PID oscilante
Soluciones:
- Reducir ganancia proporcional (Kp)
- Aumentar tiempo derivativo (Kd)
- Implementar filtro anti-windup
- Ajustar frecuencia de muestreo
Problema: Salidas DAC no lineales
Diagnóstico:
- Verificar impedancia de carga
- Comprobar filtrado de salida
- Calibrar curva de transferencia
- Evaluar deriva térmica
Optimizaciones Avanzadas
Mejora de Precisión
- Implementar calibración de 3 puntos
- Usar referencias de voltaje de precisión
- Agregar compensación térmica
- Filtrado adaptativo según condiciones
Optimización de Control
- Implementar control predictivo (MPC)
- Agregar feedforward para perturbaciones
- Auto-tuning de parámetros PID
- Control robusto ante incertidumbre
Extensiones del Sistema
- Múltiples zonas de temperatura
- Interfaz gráfica avanzada
- Base de datos histórica
- Integración con sistemas SCADA
Evaluación del Proyecto
Rúbrica de Evaluación
Criterio | Excelente (4) | Bueno (3) | Satisfactorio (2) | Puntos |
---|---|---|---|---|
Implementación ADC | Precisión <0.5°C, filtrado óptimo | Precisión <1°C, filtrado básico | Funcional con ruido moderado | __/4 |
Control PID | Estable, sin sobrepaso | Estable con sobrepaso mínimo | Funcional con oscilaciones | __/4 |
Salidas DAC | Lineal, calibrada, precisa | Funcional con desviaciones menores | Operativa básica | __/4 |
Sistema Seguridad | Completo, robusto, probado | Funcional, algunas protecciones | Básico, protección mínima | __/4 |
Interfaz Usuario | Profesional, intuitiva, completa | Funcional, información clara | Básica, información esencial | __/4 |
Criterios de Certificación
- Aprobado: 10-12 puntos (Sistema funcional básico)
- Competente: 13-16 puntos (Sistema robusto y confiable)
- Experto: 17-20 puntos (Sistema de calidad industrial)
Entregables del Proyecto
- Código fuente completo documentado
- Esquema eléctrico profesional
- Video demostrativo del funcionamiento
- Reporte de pruebas y calibración
- Manual de usuario
- Análisis de mejoras futuras
Presentación Oral
- Demostración en vivo (15 min)
- Explicación técnica detallada
- Manejo de preguntas técnicas
- Propuestas de mejora
Referencias Técnicas y Extensiones
Documentación Especializada
Proyectos Relacionados y Extensiones
- Módulo 4: Protocolos I2C/SPI para sensores avanzados
- Módulo 5: Conectividad WiFi para monitoreo remoto
- Módulo 6: Integración con actuadores complejos
- Proyecto Final: Sistema multi-zona completo