Tutorial · Principiante · 18 min de lectura

Cómo sintonizar un controlador PID: guía práctica paso a paso

Sintoniza un lazo PID paso a paso: primero la ganancia proporcional, luego la derivativa contra la sobreoscilación, al final la integral y evita el windup.

Un controlador PID convierte el error entre donde un sistema está y donde quieres que esté en una corrección. Tiene tres términos—proporcional, integral y derivativo— y el ajuste es el proceso de elegir con qué fuerza actúa cada uno. La forma fiable de hacerlo es un término cada vez, en un orden fijo, observando la respuesta después de cada cambio. Puedes seguirlo en el PID Controller Simulator.

Empieza por la ganancia proporcional

Pon las ganancias integral y derivativa a cero. Sube la ganancia proporcional (Kp) hasta que el sistema responda rápido a un cambio en el punto de consigna. Si es poca reacciona perezzosamente; si es demasiada se pasa de rosca y oscila. Para cuando la respuesta sea rápida pero solo ligeramente oscilatoria—ese es tu punto de partida para añadir los otros términos.

Una regla útil: P te da velocidad. Reacciona al error ahora mismo, y nada más.

Cuatro respuestas a escalón de la misma planta con distintas ganancias PID, representadas como posición frente a tiempo a lo largo de cuatro segundos con una línea discontinua horizontal objetivo en uno. Una ganancia proporcional baja sube despacio y se estabiliza muy por debajo del objetivo. Una ganancia proporcional alta se pasa muy por encima del objetivo y rebaba durante varias oscilaciones grandes antes de estabilizarse. Una ganancia proporcional moderada sola se pasa una vez y luego se estabiliza un poco por debajo del objetivo. La traza combinada de proporcional, integral y derivativo sube rápido, se pasa solo ligeramente y se estabiliza exactamente en el objetivo.
Una planta, cuatro ajustes. Cada fallo tiene su propia forma: un hueco permanente, un rebabado sostenido, o una curva que se para justo antes. Descargar SVG

Añade derivativo para domar el sobreimpulso

Ahora sube la ganancia derivativa (Kd). La acción derivativa responde a lo rápido que está cambiando el error, así que frena según el sistema se acerca al punto de consigna. Esto es lo que elimina el sobreimpulso y el rebabado que deja atrás el control proporcional puro.

Añade solo el derivativo justo para estabilizar la oscilación. Demasiado vuelve al controlador nervioso, porque el término derivativo amplifica cualquier ruido en la medida. Si tu señal es ruidosa, filtrala antes de derivar, o apóyate en la proporcional y la integral.

Añade la integral al final

Proporcional y derivativo juntos aún pueden dejar un pequeño error estacionario—el sistema aparca justo antes del punto de consigna. El término integral (Ki) arregla eso: acumula error a lo largo del tiempo y empuja hasta que el error desaparece.

Sube la ganancia integral despacio y solo si queda un error en estado estacionario repetible. La integral es el término más lento y el más fácil de pasarse, así que un poco hace mucho.

Vigila el windup integral

El windup integral es el fallo clásico del PID. Cuando el actuador se satura—un motor ya a plena velocidad, una válvula completamente abierta—el sistema no puede responder, pero el término integral sigue acumulando error. Cuando el error por fin se invierte, toda esa acción acumulada se descarga de golpe y el sistema se pasa de rosca con fuerza.

Prevento limitando el término integral (deja de crecer mientras la salida está saturada) o mediante cálculo hacia atrás (drena el integral usando la diferencia entre la salida pedida y la entregada). Un límite simple:

float candidateIntegral = integral + error * dt;
float raw = kp * error + ki * candidateIntegral + kd * derivative;
float output = constrain(raw, outputMin, outputMax);
if (raw == output) integral = candidateIntegral; // only accumulate when not saturated

Mantén honesta la temporización del bucle

Los tres términos dependen de un paso de tiempo consistente. En un microcontrolador, un delay() largo en el bucle añade tiempo muerto que deja inútil el término derivativo por alto que lo ajustes, y deforma el integral. Ejecuta el bucle rápido y a un intervalo constante, y pasa el tiempo real transcurrido al cálculo.

Acercarte rápido: el punto de partida de Ziegler–Nichols

Subir P hasta encontrar la oscilación no es solo una comprobación de cordura — te da un número del que puedes calcular todo el ajuste. Pon I y D a cero, sube P hasta que la salida oscile de forma constante sin crecer ni decrecer, y anota dos cosas:

  • Ku — la ganancia proporcional a la que eso ocurre (la ganancia última)
  • Tu — el periodo de la oscilación, en segundos

Entonces:

Controlador Kp Ki Kd
Solo P 0.5 Ku — —
PI 0.45 Ku 0.54 Ku / Tu —
PID (clásico) 0.6 Ku 1.2 Ku / Tu 0.075 Ku Tu
PID (sin sobreimpulso) 0.2 Ku 0.4 Ku / Tu 0.066 Ku Tu

Estos son un punto de partida, no una respuesta. La fila clásica es deliberadamente agresiva y normalmente se pasa de rosca un 20–40 %, lo cual está bien para un motor y es inaceptable para un brazo robótico cerca de una mesa. La última fila intercambia velocidad de respuesta por llegar sin sobreimpulso.

El valor del método es que sustituye tres incógnitas por dos medidas. Veinte minutos encontrando Ku y Tu te llevan más lejos que una tarde de adivinar, y luego puedes ajustar a ojo desde un sitio razonable.

Deriva la medida, no el error

Este es un cambio de una línea que arregla la queja más común sobre la D.

Cuando el punto de consigna da un escalón, el error da un escalón con él. La derivada de un escalón es enorme un instante, así que el controlador ordena una corrección violenta por un motivo que no tiene nada que ver con la física — no se movió nada, solo pediste algo distinto. En un robot eso es un tirón fuerte cada vez que cambias el objetivo.

// Derivative kick: the setpoint step goes straight into the output.
float derivative = (error - lastError) / dt;

// Derivative on measurement: the setpoint contributes nothing at all.
float derivative = -(measurement - lastMeasurement) / dt;

El signo se invierte porque el error es setpoint − measurement, así que derivar la medida en su lugar implica negarlo. Cuando el punto de consigna es constante las dos formas son matemáticamente idénticas; cuando cambia, solo la segunda se comporta bien.

Usa derivativo sobre la medida salvo que tengas una razón concreta para no hacerlo. Prácticamente no existe ningún caso en el que quieras el tirón.

Filtra el derivativo, o la D amplificará tu ruido

Derivar amplifica el contenido de alta frecuencia, y el ruido del sensor es de alta frecuencia por definición. Una señal con ±2 cuentas de jitter muestreada cada 10 ms produce una estimación de la derivativa que oscila ±200 cuentas por segundo — y la D convierte eso en órdenes de motor.

El resultado habitual es que la gente concluye que la D es inútil y la pone a cero. El problema real está antes:

// A first-order filter on the derivative term only.
// alpha = dt / (tau + dt); tau is the filter time constant in seconds.
const float tau = 0.05f;                       // 50 ms
const float alpha = dt / (tau + dt);
dFiltered += alpha * (dRaw - dFiltered);

Elegir tau es un intercambio: demasiado pequeño y el ruido sobrevive, demasiado grande y la D está actuando sobre información que ya está obsoleta, que es justo lo que la D existe para evitar. Empieza por tres a cinco veces tu periodo de bucle y súbelo solo hasta que pare el nerviosismo.

Un controlador completo

Todo lo de arriba, en un sitio:

struct Pid {
  float kp, ki, kd;
  float outMin, outMax;
  float tau;                 // derivative filter time constant

  float integral = 0;
  float lastMeasurement = 0;
  float dFiltered = 0;
  bool  primed = false;
};

float pidUpdate(Pid &c, float setpoint, float measurement, float dt) {
  if (dt <= 0) return 0;

  const float error = setpoint - measurement;

  // Derivative on measurement, low-pass filtered.
  if (!c.primed) { c.lastMeasurement = measurement; c.primed = true; }
  const float dRaw = -(measurement - c.lastMeasurement) / dt;
  const float alpha = dt / (c.tau + dt);
  c.dFiltered += alpha * (dRaw - c.dFiltered);
  c.lastMeasurement = measurement;

  // Provisional integral, committed only if the output does not saturate.
  const float candidate = c.integral + error * dt;
  const float raw = c.kp * error + c.ki * candidate + c.kd * c.dFiltered;
  const float out = constrain(raw, c.outMin, c.outMax);
  if (raw == out) c.integral = candidate;      // conditional integration

  return out;
}

Tres cosas de ahí son la diferencia entre un controlador que funciona en un robot y uno que funciona en un libro de texto: derivativo sobre la medida, un derivativo filtrado, y integración condicional para que el integral no pueda hacer windup mientras el actuador está saturado.

Diagnosticar a partir de la forma de la respuesta

Puedes nombrar casi cualquier fallo de ajuste solo con la curva de respuesta:

Lo que ves Qué significa Qué cambiar
Sube despacio, nunca llega P demasiado baja, sin I Sube P; añade I si queda un error
Se estabiliza corto del objetivo, permanentemente Sin término integral Añade I, despacio
Se pasa una vez y luego se estabiliza P algo alta, sin D Añade D
Se pasa repetidamente, decreciendo P demasiado alta Baja P, o añade D
Oscila para siempre a amplitud constante P en la ganancia última Has encontrado Ku — redúcela a la mitad
La oscilación crece P por encima de Ku Reduce inmediatamente
Salida rápida, irregular, nerviosa La D actuando sobre ruido Filtra el derivativo, o filtra el sensor
Da tirones fuertes cada vez que cambia el consigna Tirón derivativo Derivativo sobre la medida
Se va muy lejos pasado tras un movimiento grande Windup integral Integración condicional o un límite
Ayer bien, hoy perezoso La planta cambió — normalmente la batería Las ganancias son propiedades de la planta, no del código

Esa última fila merece énfasis porque no es un bug. Las ganancias describen una planta concreta, y la planta incluye la carga de la batería, la masa del chasis, la superficie y la temperatura de la reductora. Un ajuste que era perfecto con pilas nuevas será perezoso con pilasugas, porque el mismo ciclo de trabajo ahora produce menos par. Las respuestas profesionales son un lazo de velocidad por debajo (para que el controlador ordene velocidad y no ciclo de trabajo) o programación de ganancias (varios ajustes, seleccionados por condición de operación).

Temporización del bucle, cuantificada

Tanto I como D se calculan contra tiempo transcurrido, así que un bucle que corre cuando le da por terminar calcula ambos contra una mentira.

Comportamiento del bucle Efecto en el controlador
Intervalo fijo Todo funciona como se diseñó
Variable, pero dt medido y pasado También correcto — esta es la respuesta práctica
Variable, dt asumido constante I acumula mal; D es ruido
Contiene un delay() Tiempo muerto — ninguna cantidad de D lo compensa

Mide dt con micros() y pásalo. No cuesta nada, sobrevive a un bucle cuya duración cambia según añades funciones, y elimina una clase entera de misterio del ajuste.

Si además puedes hacer el bucle rápido, hazlo — un controlador que muestrea a 20 Hz está actuando sobre información de hasta 50 ms de antigüedad, y ninguna ganancia arregla información obsoleta. Quitar llamadas a delay() suele llevar a un primer robot de 20 Hz a varios cientos, y el seguimiento mejora sin tocar ninguna ganancia.

Pruébalo tú mismo

Abre el PID Controller Simulator, pon a cero las ganancias I y D, y recorre este orden. Observa cómo el sobreimpulso, el tiempo de estabilización y el error en estado estacionario responden a cada término por separado. Una vez que la forma tenga sentido aquí, la misma intuición se traslada a un motor real, a un calentador o al lazo de dirección del Line Follower Simulator.

Explora el grafo

Forma parte de estos montajes

Proyectos y rutas de aprendizaje que incluyen este tutorial.

Lecturas adicionales

Referencias