Control de Temperatura con ESP32, MAX6675, LCD I2C y Dimmer Digital
???? Materiales Necesarios
-
1 × ESP32 (NodeMCU ESP32 o similar)
-
1 × Display LCD 16×2 con módulo I2C
-
4 × Botones pulsadores
-
1 × Módulo MAX6675 con termopar tipo K
-
1 × MOC3031 (Optoacoplador triac)
-
1 × Triac (BT136 o similar, según potencia requerida)
-
1 × Resistencia de 180Ω 1/4W
-
1 × Resistencia de 330Ω 1/4W
-
1 × Carga resistiva (calefactor, foco incandescente, etc.)
-
1 × Protoboard y cables
-
1 × Disipador de calor para el triac (opcional, según potencia)
???? Esquema de Conexiones
Conexiones ESP32:
-
LCD I2C:
-
SDA → GPIO 21
-
SCL → GPIO 22
-
VCC → 3.3V
-
GND → GND
-
-
MAX6675:
-
SO → GPIO 19 (MISO)
-
CS → GPIO 5
-
SCK → GPIO 18 (SCK)
-
VCC → 3.3V
-
GND → GND
-
-
Botones (con pull-up interno):
-
UP → GPIO 12
-
DOWN → GPIO 13
-
PLAY → GPIO 14
-
SETTING → GPIO 27
-
Todos los botones conectados también a GND
-
-
Salida Dimmer:
-
CONTROL → GPIO 26 (Salida PWM)
-
Circuito de Potencia con MOC3031 y Triac:
ESP32 GPIO26 → Resistencia 330Ω → Pin 1 MOC3031 Pin 2 MOC3031 → GND Pin 6 MOC3031 → Resistencia 180Ω → Compuerta Triac MT1 Triac → Línea AC MT2 Triac → Carga La otra terminal de la carga → Línea AC neutral
???? Código para ESP32 con MAX6675
#include <Wire.h> #include <LiquidCrystal_I2C.h> #include <PID_v1.h> #include <max6675.h> // Configuración del LCD I2C LiquidCrystal_I2C lcd(0x27, 16, 2); // Dirección I2C 0x27, display 16x2 // Configuración del MAX6675 const int thermoSO = 19; const int thermoCS = 5; const int thermoSCK = 18; MAX6675 thermocouple(thermoSCK, thermoCS, thermoSO); // Definición de pines const int up = 12; const int down = 13; const int play = 14; const int setting = 27; const int salidaPWM = 26; // Variables de control int var_modo = 1; // modo de control (0: on/off, 1: PID) double temp; // temperatura medida double Output; // Salida PWM double error; // error setpoint-temp double p = 1; double i = 1; double d = 1; double tdes = 25; int tmin = 25; int tmax = 27; // Crear objeto PID PID myPID(&temp, &Output, &tdes, p, i, d, DIRECT); // Estados del sistema #define modo 0 #define ganancia_P 1 #define ganancia_I 2 #define ganancia_D 3 #define temp_des 4 #define pid 5 #define temp_min 6 #define temp_max 7 #define on_off 8 int estado = modo; // Estado actual del sistema // Configuración PWM para ESP32 const int freq = 1000; const int pwmChannel = 0; const int resolution = 8; void setup() { Serial.begin(115200); // Inicializar LCD lcd.init(); lcd.backlight(); lcd.clear(); // Configurar pines pinMode(up, INPUT_PULLUP); pinMode(down, INPUT_PULLUP); pinMode(play, INPUT_PULLUP); pinMode(setting, INPUT_PULLUP); // Configurar PWM para el dimmer ledcSetup(pwmChannel, freq, resolution); ledcAttachPin(salidaPWM, pwmChannel); ledcWrite(pwmChannel, 0); // Configurar PID myPID.SetSampleTime(1000); // 1 segundo para coincidir con la lectura del MAX6675 myPID.SetMode(AUTOMATIC); myPID.SetOutputLimits(0, 255); mostrarMensajeInicial(); } void loop() { // Leer temperatura del MAX6675 temp = thermocouple.readCelsius(); // Verificar si la lectura es válida if (isnan(temp)) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Error sensor!"); delay(1000); return; } switch (estado) { case modo: manejarModo(); break; case ganancia_P: manejarGananciaP(); break; case ganancia_I: manejarGananciaI(); break; case ganancia_D: manejarGananciaD(); break; case temp_des: manejarTempDeseada(); break; case pid: manejarPID(); break; case temp_min: manejarTempMin(); break; case temp_max: manejarTempMax(); break; case on_off: manejarOnOff(); break; } delay(10); } void manejarModo() { lcd.setCursor(0, 0); lcd.print("Modo:"); lcd.setCursor(7, 0); if (var_modo == 0) { lcd.print("on_off"); } else { lcd.print("pid"); } lcd.setCursor(0, 1); lcd.print("Ti:"); lcd.print(temp, 1); lcd.print("C"); if (digitalRead(down) == LOW) { var_modo = !var_modo; lcd.clear(); delay(200); } if (digitalRead(play) == LOW) { lcd.clear(); if (var_modo == 0) { estado = temp_min; } else { estado = ganancia_P; } delay(200); } } void manejarGananciaP() { lcd.setCursor(0, 0); lcd.print("Ganancia P"); lcd.setCursor(0, 1); lcd.print(p, 2); if (digitalRead(up) == LOW) { p += 0.1; actualizarDisplayVariable(p); } if (digitalRead(down) == LOW) { if (p > 0.1) { p -= 0.1; actualizarDisplayVariable(p); } else { lcd.setCursor(0, 1); lcd.print("No permitido"); delay(200); lcd.setCursor(0, 1); lcd.print(p, 2); } } if (digitalRead(play) == LOW) { estado = ganancia_I; lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarGananciaI() { lcd.setCursor(0, 0); lcd.print("Ganancia I"); lcd.setCursor(0, 1); lcd.print(i, 2); if (digitalRead(up) == LOW) { i += 0.1; actualizarDisplayVariable(i); } if (digitalRead(down) == LOW) { if (i > 0.1) { i -= 0.1; actualizarDisplayVariable(i); } } if (digitalRead(play) == LOW) { estado = ganancia_D; lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarGananciaD() { lcd.setCursor(0, 0); lcd.print("Ganancia D"); lcd.setCursor(0, 1); lcd.print(d, 2); if (digitalRead(up) == LOW) { d += 0.1; actualizarDisplayVariable(d); } if (digitalRead(down) == LOW) { if (d > 0.1) { d -= 0.1; actualizarDisplayVariable(d); } } if (digitalRead(play) == LOW) { estado = temp_des; lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarTempDeseada() { lcd.setCursor(0, 0); lcd.print("Temp deseada"); lcd.setCursor(0, 1); lcd.print(tdes, 1); lcd.print("C"); if (digitalRead(up) == LOW) { tdes += 0.5; actualizarDisplayVariable(tdes); } if (digitalRead(down) == LOW) { tdes -= 0.5; actualizarDisplayVariable(tdes); } if (digitalRead(play) == LOW) { if (p == 0 && i == 0 && d == 0) { estado = ganancia_P; } else if (tdes != 0) { estado = pid; } lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarPID() { myPID.SetTunings(p, i, d); error = tdes - temp; myPID.Compute(); // Controlar dimmer con PWM ledcWrite(pwmChannel, (int)Output); lcd.setCursor(0, 0); lcd.print("Tm:"); lcd.print(temp, 1); lcd.print("C"); lcd.setCursor(0, 1); lcd.print("Td:"); lcd.print(tdes, 1); lcd.print("C Out:"); lcd.print((int)Output); if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } delay(200); } void manejarTempMin() { lcd.setCursor(0, 0); lcd.print("Temp minima"); lcd.setCursor(0, 1); lcd.print(tmin); lcd.print("C"); if (digitalRead(up) == LOW) { tmin++; actualizarDisplayVariable(tmin); } if (digitalRead(down) == LOW) { tmin--; actualizarDisplayVariable(tmin); } if (digitalRead(play) == LOW) { estado = temp_max; lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarTempMax() { lcd.setCursor(0, 0); lcd.print("Temp max"); lcd.setCursor(0, 1); lcd.print(tmax); lcd.print("C"); if (digitalRead(up) == LOW) { tmax++; actualizarDisplayVariable(tmax); } if (digitalRead(down) == LOW) { tmax--; actualizarDisplayVariable(tmax); } if (digitalRead(play) == LOW) { estado = on_off; lcd.clear(); delay(200); } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } } void manejarOnOff() { lcd.setCursor(0, 0); lcd.print("Temp:"); lcd.print(temp, 1); lcd.print("C"); lcd.setCursor(0, 1); lcd.print("min"); lcd.print(tmin); lcd.print(" max"); lcd.print(tmax); if (temp <= tmin) { ledcWrite(pwmChannel, 255); // Encender completamente } if (temp >= tmax) { ledcWrite(pwmChannel, 0); // Apagar completamente } if (digitalRead(setting) == LOW) { estado = modo; lcd.clear(); delay(200); } delay(200); } void actualizarDisplayVariable(double valor) { lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print(valor, 1); delay(200); } void mostrarMensajeInicial() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Control Temp"); lcd.setCursor(0, 1); lcd.print("ESP32 + MAX6675"); delay(2000); // Esperar a que el MAX6675 se estabilice lcd.clear(); lcd.setCursor(0, 0); lcd.print("Inicializando"); lcd.setCursor(0, 1); lcd.print("sensor..."); for (int i = 0; i < 3; i++) { thermocouple.readCelsius(); // Lecturas iniciales de descarte delay(500); } lcd.clear(); }
???? Librerías Necesarias
Para compilar este código, necesitas instalar las siguientes librerías en el IDE de Arduino:
-
LiquidCrystal_I2C by Frank de Brabander
-
PID by Brett Beauregard
-
max6675 por Adafruit o compatible
Puedes instalarlas a través del Gestor de Librerías del IDE de Arduino.
⚙️ Configuración y Uso
Características del MAX6675:
-
Rango de medición: 0°C to 1024°C
-
Precisión: ±2°C entre 0°C y 700°C
-
Resolución: 0.25°C
-
Interface SPI
-
Incluye compensación de unión fría
Ventajas sobre LM35:
-
Mayor rango de temperatura (hasta 1024°C vs 150°C)
-
Aislamiento eléctrico (útil en aplicaciones industriales)
-
Mayor precisión a altas temperaturas
-
Compensación de unión fría integrada
Consideraciones importantes:
-
El MAX6675 requiere una inicialización y varias lecturas iniciales para estabilizarse
-
La conexión del termopar debe ser correcta (polaridad)
-
El tiempo entre lecturas debe ser de al menos 200ms
-
El cableado del termopar debe alejarse de fuentes de interferencia
???? Calibración y Ajustes
-
Verificar la lectura del MAX6675 con una fuente de temperatura conocida
-
Ajustar las constantes PID según las características de tu sistema
-
Calibrar el dimmer para que coincida con la respuesta de tu carga
-
Configurar los límites de temperatura según tus necesidades específicas
Este sistema proporciona un control preciso de temperatura con un rango extendido gracias al uso del MAX6675 con termopar tipo K, manteniendo todas las funcionalidades del control PID y la interfaz de usuario intuitiva.
