anti-windup-y-saturacion-actuadores

Anti-windup y saturación de actuadores

  • 4 min

La saturación de un actuador es el límite físico que impide aplicar más mando aunque el PID lo pida, como el voltaje máximo de una batería o el ángulo tope de un servo.

En el artículo anterior, nosotros implementamos un PID digital “perfecto”. Matemáticamente era impecable, pero tenía un pecado original: asumía que nuestro hardware tiene recursos infinitos. En el papel, si nuestro PID calcula que necesitamos 5000V para corregir un error, la fórmula nos da 5000V. Pero en el mundo real, si tu fuente de alimentación es de 12V, tienes 12V y punto.

Hoy vamos a ver por qué esta limitación física puede estropear la respuesta y cómo podemos corregirla con técnicas de anti-windup.

El límite físico: la saturación

Todos los actuadores del mundo real tienen límites.

  • Una salida PWM tiene un valor máximo, como 255 cuando usamos una resolución de 8 bits.
  • Un driver de motor satura en su VCC.
  • Una válvula hidráulica satura cuando está 100% abierta.

Cuando el error es muy grande (por ejemplo, al arrancar el sistema), el PID calculará una salida enorme. Nosotros, en nuestro código, aplicaremos una saturación (un constrain o clamp) para no desbordar los registros:

float salida_calculada = P + I + D;
float salida_final = constrain(salida_calculada, 0, 255); 
Copied!

Hasta aquí parece que no hay problema. El motor irá a tope y listo. Sin embargo, el término Integral sigue vivo dentro de la memoria del microcontrolador.

El fenómeno del integral windup

Imaginemos que tenemos un motor bloqueado mecánicamente. El PID detecta que el error de posición es grande y empieza a actuar:

  1. El término P se pone al máximo. La salida satura a 255.
  2. Como el motor no se mueve, el error persiste.
  3. El término I empieza a sumar ese error ciclo tras ciclo.
  4. Pasan los segundos. La salida sigue saturada en 255, pero internamente, la variable integral_acumulada ha crecido hasta valer millones.

Ahora, de repente, desbloqueamos el motor. El motor acelera y llega al Setpoint. El error se vuelve cero. En un mundo ideal, el motor debería frenar. Pero, ¡sorpresa!, el motor sigue a máxima potencia.

¿Por qué? Porque para que la salida baje de 255, primero el error tiene que volverse negativo durante mucho tiempo para “drenar” esos millones acumulados en la integral. El resultado es un overshoot gigantesco y un sistema que parece haber perdido el sentido común.

El Windup (enrollamiento) ocurre porque el bucle de control se “rompe” cuando el actuador satura; el controlador cree que está actuando, pero la planta ya no responde a más potencia.

Algoritmos anti-windup

Para evitar este problema, debemos impedir que la integral siga creciendo en la dirección de la saturación. Existen varias técnicas; una de las más sencillas es el clamping condicional.

La regla es sencilla: Deja de integrar si se cumplen estas dos condiciones a la vez:

  1. La salida ya está saturada (al máximo o al mínimo).
  2. El error tiene el mismo signo que la salida (es decir, la integral quiere seguir creciendo en la dirección de la saturación).

Implementación del clamping en C++

Vamos a actualizar nuestro código del artículo anterior para que sea resistente a la saturación. Introduciremos los límites del actuador y la lógica de parada de la integral.

// Definimos los límites del hardware
const float LIMIT_MAX = 255.0;
const float LIMIT_MIN = 0.0;

float Kp = 2.0, Ki = 5.0, Kd = 1.0;
float integral_acumulada = 0.0;
float error_anterior = 0.0;

float calcularPID(float setpoint, float pv, float dt) {
    float error = setpoint - pv;
    
    // 1. Acción Proporcional
    float P = Kp * error;
    
    // 2. Acción Derivativa
    float D = Kd * (error - error_anterior) / dt;
    
    // 3. Acción Integral con anti-windup (clamping condicional)
    float salida_actual = P + D + (Ki * integral_acumulada);
    bool satura_arriba = salida_actual >= LIMIT_MAX;
    bool satura_abajo = salida_actual <= LIMIT_MIN;

    // Integramos en zona lineal o si el error ayuda a salir del límite
    bool permite_integrar = (!satura_arriba && !satura_abajo)
                          || (satura_arriba && error < 0.0)
                          || (satura_abajo && error > 0.0);

    if (permite_integrar) {
        integral_acumulada += error * dt;
    }

    // 4. Recalcular y aplicar la saturación física
    float salida_total = P + D + (Ki * integral_acumulada);
    float salida_saturada = constrain(salida_total, LIMIT_MIN, LIMIT_MAX);
    
    error_anterior = error;
    return salida_saturada;
}
Copied!

El clamping condicional es sencillo y consume pocos recursos. Otras técnicas, como el back-calculation o el seguimiento de la salida, pueden ofrecer una recuperación más suave en plantas y controladores concretos.