reparaciones 2025 · Laminadora VANSDA no calienta

Pakulaminator: reparando el PID de una laminadora industrial con ESP32

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.

VANSDA Thermal Laminator con el nuevo controlador PID basado en ESP32 montado en el panel frontal, mostrando el LCD azul encendido
La laminadora VANSDA con el panel de control nuevo ya instalado. El LCD azul en el frontal muestra temperatura objetivo, temperatura actual y ETA.

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:

Relé de estado sólido Fotek SSR-25 DA: carga 24-380VAC en terminales 1-2, control 3-32VDC en terminales 3-4
Fotek SSR-25 DA. Terminales 1-2: carga AC (en serie con la resistencia calefactora). Terminales 3-4: señal de control DC desde el ESP32.
// 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:

Pantalla LCD I2C 16x2 de la laminadora mostrando T.Dest:54.00 en la fila superior y T:38.15 ETA:628 en la inferior
El LCD durante el calentamiento: setpoint 54 °C, temperatura actual 38,15 °C, ETA 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);
  }
}