Una laminadora industrial sin control de temperatura es un cacharro inútil: demasiado frío y el plástico no pega, demasiado caliente y lo quema. El PID original de una VANSDA Thermal Laminator palmo y en lugar de pagar por uno propietario, lo reemplacé con una ESP32 SuperMini, una sonda DS18B20, un SSR y un potenciómetro. La v5 del firmware lleva PID adaptativo, filtrado doble y cálculo de ETA en pantalla.
01 — La laminadora y el PID roto
La laminadora es una VANSDA 380/450/520 — tres modelos que son básicamente el mismo chasis con o sin mesa semiautomática, así que esta reparación sirve para cualquiera de ellos. Y en general, para cualquier laminadora térmica con resistencia y sonda de temperatura.
El síntoma era simple: no calentaba nada. El PID original dejó de funcionar y sin control de temperatura la máquina es inútil. Repuesto propietario caro y difícil de conseguir; solución: reemplazarlo con componentes de uso general.
02 — Hardware del reemplazo
El montaje físico no tiene ningún misterio. El SSR va en serie con la resistencia calefactora del rodillo: se corta uno de los cables de alimentación de la resistencia y se intercala el relé. Eso es todo.
La sonda es un DS18B20 en encapsulado metálico. La clave del montaje es cómo se sujeta al rodillo: con un muellecito pequeño que la mantiene siempre en contacto con la superficie metálica pero con muy poca fuerza, lo justo para que no pierda el contacto sin rayar ni deformar nada. Sin ese resorte la sonda vibra con el giro del rodillo y las lecturas se vuelven erráticas.
El esquema de componentes es deliberadamente simple:
- ESP32 SuperMini — el cerebro. Pequeño, barato, con ADC de 12 bits para el potenciómetro y soporte I2C para el LCD.
- DS18B20 — sonda de temperatura digital OneWire en el rodillo calefactor. Resolución de 0,0625 °C, encapsulado metálico resistente a la temperatura.
- SSR — relé de estado sólido que controla la resistencia calefactora. Sin partes mecánicas, conmutación silenciosa, apto para carga resistiva en AC.
- Potenciómetro — ajuste de temperatura objetivo. Leído por el ADC1_CHANNEL_0 (GPIO0) con resolución de 12 bits (0–4095).
- LCD I2C 16×2 (hd44780) — muestra temperatura actual, temperatura objetivo y ETA. SDA en GPIO8, SCL en GPIO9.
// Pines const int ONE_WIRE_BUS = 4; // DS18B20 const int SSR_PIN = 2; // SSR const int POT_PIN = 0; // Potenciómetro (ADC1_CHANNEL_0) // I2C: SDA=8, SCL=9
03 — Lectura de temperatura y filtrado
La sonda DS18B20 se lee con la librería DallasTemperature sobre OneWire. El valor bruto pasa por un filtro exponencial (EMA, Exponential Moving Average) antes de llegar al PID:
filteredTemp = alfaTemp * input + (1 - alfaTemp) * filteredTemp; // alfaTemp = 0.2
Con α=0,2 la temperatura filtrada pondera un 20 % la lectura nueva y un 80 % el histórico. El resultado es una señal suave que no enloquece al PID con picos de ruido, a costa de un pequeño retardo en seguir cambios bruscos.
El rango de operación está acotado entre 5 °C y 140 °C. Si la temperatura filtrada supera los 140 °C, el SSR se apaga por hardware independientemente de lo que diga el PID:
if (filteredTemp >= MAX_TEMPERATURE) {
digitalWrite(SSR_PIN, LOW);
return;
}
04 — Control PID y PWM sobre el SSR
El setpoint lo fija el potenciómetro. El mapeo está invertido: girar a la derecha baja la temperatura objetivo (de 130 °C en un extremo a 5 °C en el otro). El ADC se configura a 12 bits con atenuación de 11 dB para cubrir el rango completo de 0 a 3,3 V:
adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // ... setpoint = map(potValue, 0, 4095, MAX_TEMPERATURE - 10, MIN_TEMPERATURE);
La librería PID_v1 calcula la salida en una escala de 0 a 255. Esa salida no va directamente a un pin PWM del ESP32 — los SSR de CA no se llevan bien con frecuencias altas — sino que se convierte en un PWM software a 1 Hz: el SSR está encendido durante una fracción del segundo proporcional a la salida del PID y apagado el resto.
const int PWM_FREQUENCY = 1; // 1 Hz
const int PWM_PERIOD = 1000 / PWM_FREQUENCY; // 1000 ms
// En el loop:
if (output > 10) {
if (currentTime - pwmStartTime >= PWM_PERIOD) {
pwmStartTime = currentTime;
}
if (currentTime - pwmStartTime <= (output * PWM_PERIOD / 255)) {
digitalWrite(SSR_PIN, HIGH);
} else {
digitalWrite(SSR_PIN, LOW);
}
} else {
digitalWrite(SSR_PIN, LOW);
}
Si la salida del PID es 128 (mitad de escala), el SSR estará encendido 500 ms y apagado 500 ms cada segundo. A salida 255, encendido todo el segundo. El umbral mínimo de 10 evita parpadeos inútiles cuando el PID pide calor casi nulo.
05 — PID adaptativo y ETA
Hay dos capas por encima del PID básico.
La primera es el Kp adaptativo. Cada segundo se calcula la velocidad de cambio de la temperatura (°C/s) y se filtra con otro EMA más conservador (α=0,1). Si la velocidad supera 2 °C/s — la temperatura está subiendo rápido — Kp sube de 2 a 5 para que el PID reaccione con más fuerza y evite el sobreimpulso. En régimen estacionario vuelve a 2:
if (abs(filteredRateOfChange) > 2) {
Kp = 5.0;
} else {
Kp = 2.0;
}
myPID.SetTunings(Kp, Ki, Kd);
La segunda es el ETA (Estimated Time of Arrival): cuántos segundos faltan para llegar al setpoint a la velocidad actual de calentamiento. El cálculo es directo — error dividido entre velocidad de cambio — y se muestra en el LCD para saber si vale la pena esperar o si el rodillo ya está a temperatura:
if (filteredRateOfChange != 0) {
timeRemaining = (setpoint - filteredTemp) / filteredRateOfChange;
} else {
timeRemaining = -1; // Estático o sin cambio
}
Cuando el ETA es negativo (temperatura estancada o bajando), el LCD muestra ETA:-.
06 — Pantalla LCD y resultado
El LCD 16×2 se actualiza cada segundo con temperatura objetivo en la fila superior y temperatura actual más ETA en la inferior. En la foto, la laminadora está calentando hacia 54 °C partiendo de 38 °C, con un ETA estimado de 628 segundos:
Funciona perfectamente, aunque los primeros minutos después del arranque son los más movidos. El rodillo está lleno de aceite y tiene mucha inercia térmica: programado a 110 °C, la temperatura sube hasta los 119–120 °C antes de que el PID consiga frenarla, luego baja hasta los 107–108 °C, y a partir de ahí se mantiene estable sin importar si la máquina está en uso o en reposo.
No es fácil de ajustar, que conste. La inercia del rodillo hace que los parámetros que funcionan bien en régimen estacionario sean demasiado lentos durante el arranque, y viceversa. El Kp adaptativo ayuda, pero el overshoot del primer calentamiento es inevitable con esta masa térmica.
07 — Código completo
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>
#include "driver/adc.h"
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
hd44780_I2Cexp lcd;
const int ONE_WIRE_BUS = 4;
const int SSR_PIN = 2;
const int POT_PIN = 0;
const float MAX_TEMPERATURE = 140.0;
const float MIN_TEMPERATURE = 5.0;
double input, output, setpoint;
double Kp = 2.0, Ki = 5.0, Kd = 1.0;
PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
float previousTemperature = 0.0;
float filteredRateOfChange = 0.0;
float filteredTemp = 0.0;
float timeRemaining = 0.0;
unsigned long lastTime = 0;
unsigned long pwmStartTime = 0;
unsigned long lcdUpdateTime = 0;
const int PWM_FREQUENCY = 1;
const int PWM_PERIOD = 1000 / PWM_FREQUENCY;
const float alpha = 0.1;
const float alfaTemp = 0.2;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Arrancado...");
pinMode(SSR_PIN, OUTPUT);
digitalWrite(SSR_PIN, LOW);
sensors.begin();
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(0, 255);
Wire.begin(8, 9);
int status = lcd.begin(16, 2);
if(status) {
Serial.print("Error LCD: ");
Serial.println(status);
}
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Arrancado");
}
void loop() {
int potValue = adc1_get_raw(ADC1_CHANNEL_0);
setpoint = map(potValue, 0, 4095, MAX_TEMPERATURE - 10, MIN_TEMPERATURE);
sensors.requestTemperatures();
input = sensors.getTempCByIndex(0);
filteredTemp = alfaTemp * input + (1 - alfaTemp) * filteredTemp;
unsigned long currentTime = millis();
if (currentTime - lastTime >= 1000) {
lastTime = currentTime;
float rateOfChange = filteredTemp - previousTemperature;
previousTemperature = filteredTemp;
filteredRateOfChange = alpha * rateOfChange + (1 - alpha) * filteredRateOfChange;
if (filteredRateOfChange != 0) {
timeRemaining = (setpoint - filteredTemp) / filteredRateOfChange;
} else {
timeRemaining = -1;
}
}
if (currentTime - lcdUpdateTime >= 1000) {
lcdUpdateTime = currentTime;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("T Dest:");
lcd.print(setpoint);
lcd.setCursor(0, 1);
lcd.print("T:");
lcd.print(filteredTemp);
lcd.setCursor(9, 1);
if (timeRemaining >= 0) {
lcd.print("ETA:");
lcd.print(timeRemaining);
} else {
lcd.print("ETA:-");
}
}
if (abs(filteredRateOfChange) > 2) {
Kp = 5.0;
} else {
Kp = 2.0;
}
myPID.SetTunings(Kp, Ki, Kd);
myPID.Compute();
if (filteredTemp >= MAX_TEMPERATURE) {
digitalWrite(SSR_PIN, LOW);
return;
}
if (output > 10) {
if (currentTime - pwmStartTime >= PWM_PERIOD) {
pwmStartTime = currentTime;
}
if (currentTime - pwmStartTime <= (output * PWM_PERIOD / 255)) {
digitalWrite(SSR_PIN, HIGH);
} else {
digitalWrite(SSR_PIN, LOW);
}
} else {
digitalWrite(SSR_PIN, LOW);
}
}