01 · Punto de partida
Qué le falta al proporcional
El tema 42 terminó con dos faltas. El proporcional puro deja un error permanente cuando hay carga, y cuando se sube la ganancia para reducirlo aparecen la oscilación, el ruido y la saturación. Son dos problemas distintos y necesitan dos remedios distintos:
- Para eliminar el error permanente, el controlador tiene que recordar cuánto error hubo durante un tiempo y seguir empujando mientras exista: la acción integral.
- Para frenar la oscilación, tiene que anticiparse a lo que viene mirando con qué velocidad cambia el error: la acción derivativa.
En este tema cambiamos de planta. Usamos el motor de posición de los temas 40 y 41 (τ = 0,5 s y K = 2), porque ahí los tres términos se ven bien: el motor integra la velocidad, una carga constante le resta par, y la orden al motor está limitada a ±1 (la tensión máxima del driver, tema 12). Las cifras no son idénticas a las del tema 41, donde el motor no saturaba: con Kp = 2 el sobreimpulso es de 25,2 % y no de 30,5 %, porque la saturación recorta la orden.
El tema 44 se ocupa de la sintonía sistemática y del filtrado de la medida. Este deja el controlador armado y probado.
02 · La estructura
Tres acciones sobre el error
Con el error e = r − y (referencia menos medida), la orden de un PID es la suma de tres términos:
u = Kp · e + Ki · ∫ e dt + Kd · de/dt
Una forma de pensarlo:
- P (presente): cuánto error hay ahora. Más error, más orden.
- I (pasado): cuánto error se acumuló. Mientras haya error, la integral crece y la orden sigue subiendo, hasta que el error se anula.
- D (futuro): con qué velocidad cambia el error. Si el error se achica rápido, la derivada resta orden por adelantado para no pasarse.
Las ganancias tienen unidades que conviene tener presentes: con una orden sin dimensión (de −1 a 1) y la posición en radianes, Kp va en 1/rad, Ki en 1/(rad · s) y Kd en s/rad. Otra convención, muy usada en la industria, expresa Ki = Kp / Ti y Kd = Kp · Td con tiempos integral y derivativo; son la misma estructura escrita de otra forma. En todo el tema usamos ganancias independientes.
03 · Recordar para corregir
La acción integral
Con el motor y una carga constante de 0,2 (equivale a una orden de 0,2 que el motor «pierde»), el proporcional puro necesita un error para sostener esa orden, igual que el horno del tema 42: e_ss = carga / Kp.
| Kp | Error final | Fórmula 0,2 / Kp | Sobreimpulso |
|---|---|---|---|
| 1 | 0,200 rad | 0,200 | 16,6 % |
| 2 | 0,100 rad | 0,100 | 25,2 % |
| 5 | 0,040 rad | 0,040 | 24,7 % |
| 10 | 0,020 rad | 0,020 | 24,1 % |
La integral elimina ese residuo con una idea simple: I = Ki · ∫ e dt crece mientras el error no sea cero. Sólo se estabiliza cuando e = 0, y en ese momento I vale exactamente la orden que la carga exige: 0,2. El integrador «descubre» la carga por sí solo, sin que nadie se la diga. Es lo que en el tema 42 hacía el ajuste manual u0, ahora automático y para cualquier carga.
| Ki | Sobreimpulso | Asentamiento (2 %) | Recuperación tras la carga | Error a los 20 s |
|---|---|---|---|---|
| 0 (solo P) | 25,2 % | 4,03 s | no recupera | 0,100 |
| 0,5 | 27,1 % | 4,12 s | 6,57 s | 0,008 |
| 1 | 28,7 % | 5,41 s | 3,89 s | 0,000 |
| 2 | 31,9 % | 7,74 s | 3,71 s | 0,000 |
| 4 | 40,7 % | no se asienta | no recupera | 0,271 (oscila) |
La integral tiene un precio claro. Cada vez que se la sube, el sobreimpulso crece y el asentamiento se alarga: es una memoria que sigue empujando cuando el error ya cambió de signo. Con Ki = 4 y Kp = 2, el lazo ya oscila. Dos lecciones: la integral se agrega para eliminar el error permanente, no para acelerar; y se usa lo mínimo que resuelve el problema. Una integral lenta tarda en corregir (Ki = 0,5 tarda más de 6 s), una demasiado grande desestabiliza.
04 · Anticipar para frenar
La acción derivativa
La derivada Kd · de/dt se opone a los cambios rápidos. Cuando el motor se acerca a la posición pedida, el error se achica con rapidez, de/dt es negativa y el término resta orden: frena antes de llegar. Es un amortiguador agregado por software: aumenta el amortiguamiento ζ del tema 41 sin tocar la rigidez.
| Kd | Sobreimpulso | Tiempo de subida | Asentamiento (2 %) |
|---|---|---|---|
| 0 | 25,2 % | 0,64 s | 4,03 s |
| 0,3 | 10,5 % | 0,72 s | 2,21 s |
| 0,5 | 4,0 % | 0,82 s | 2,20 s |
| 0,8 | 0,0 % | 1,08 s | 1,83 s |
| 1,2 | 0,0 % | 1,56 s | 2,88 s |
La derivada reduce el sobreimpulso de 25 % a casi cero y acorta el asentamiento, a cambio de una subida más lenta. Si se pasa (Kd = 1,2), el sistema queda «sobreamortiguado» y se asienta más tarde que con Kd = 0,8. Además no corrige el error permanente: cuando el error es constante, su derivada es cero y el término no aporta nada.
El ruido: el peligro de la derivada
Derivar significa restar dos medidas consecutivas y dividir por un tiempo pequeño. Si la medida tiene un ruido de 5 mrad (la cuantización de un encoder, tema 9, o una lectura inestable) y se muestrea cada 10 ms, el cociente cambia en 0,005 · √2 / 0,01 ≈ 0,7 rad/s de una muestra a la siguiente, multiplicado por Kd. La orden se llena de ruido de alta frecuencia que el motor convierte en vibración y calor:
| Derivada | Desvío de u |
|---|---|
| sin derivada (Kd = 0) | 0,031 |
| Kd = 0,3 | 0,234 |
| Kd = 1 | 0,606 |
Con Kd = 1 el desvío de la orden es 0,6 sobre un máximo de 1: el motor está temblando permanentemente. Por eso la derivada casi siempre se usa junto con un filtro en la medida o en el propio término derivativo, tema del 44. Mientras tanto: Kd pequeña, muestreo no demasiado rápido y medida lo más limpia posible.
La derivada sobre la medida, no sobre el error
Hay otra trampa. Si la referencia cambia de golpe, el error pega un salto y su derivada es enorme durante una muestra: el llamado golpe derivativo (derivative kick). Con Kd = 1, Ts = 10 ms y un escalón de 1 rad, la orden calculada llega a 106 si se deriva el error, contra 6 si se deriva la medida. La medida no salta cuando cambia la referencia, así que derivarla da el mismo efecto amortiguador sin el golpe. Como de/dt = dr/dt − dy/dt y r es constante entre cambios, resulta −dy/dt. Es la forma que usa el código de este tema.
05 · Combinar
P, PI, PD y PID
| Controlador | Sobreimpulso | Asentamiento (2 %) | Caída por la carga | Recuperación | Error final |
|---|---|---|---|---|---|
| P (Kp = 2) | 25,2 % | 4,03 s | 0,131 | no recupera | 0,100 |
| PI (2 / 1) | 28,7 % | 5,41 s | 0,116 | 3,89 s | 0,000 |
| PD (2 / 0,5) | 4,0 % | 2,20 s | 0,104 | no recupera | 0,100 |
| PID (2 / 1 / 0,5) | 15,8 % | 3,87 s | 0,089 | 3,29 s | 0,000 |
| PID (6 / 3 / 1) | 7,9 % | 1,77 s | 0,033 | 1,38 s | 0,000 |
La tabla resume el tema. El PI elimina el error pero oscila más; el PD amortigua pero deja el error; el PID (2 / 1 / 0,5) conserva Kp y Ki del PI y le agrega la derivada: el sobreimpulso baja de 28,7 % a 15,8 % y el error desaparece, es decir, se queda con lo bueno de los dos. Y la fila final muestra lo que permite el PID: subir las ganancias proporcional e integral —que por sí solas llevarían a la oscilación— porque la derivada las amortigua. El resultado es mucho mejor en todo: 7,9 % de sobreimpulso, asentamiento en 1,77 s, una caída por la carga cuatro veces menor y recuperación en 1,4 s.
Muchos sistemas no necesitan los tres términos. Una regla útil: PI para procesos lentos y con ruido (temperatura, nivel, caudal), donde la derivada sobra; PID para sistemas con inercia donde importa frenar a tiempo (posición, velocidad con carga variable); P solo cuando el error permanente es tolerable (el seguidor de línea del tema 31).
06 · Cuando el actuador no da más
Saturación y anti-windup
La orden calculada puede ser mayor que la que el actuador admite. Con un escalón de 3 rad, el error inicial es 3 y un Kp de 4 pide una orden de 12; el driver entrega 1. Durante el arranque, el motor va a la máxima tensión sin poder ir más rápido. Hasta aquí es normal. El problema está en el integrador: mientras tanto sigue sumando el error, que es grande, y acumula una cantidad enorme. Es el windup (integrador «recargado»).
Cuando por fin la posición alcanza la referencia, el error cambia de signo, pero el integrador está cargado con una cantidad que la orden tiene que descargar antes de reaccionar. El motor se pasa mucho y vuelve despacio:
| Controlador | Anti-windup | Sobreimpulso | Asentamiento (2 %) |
|---|---|---|---|
| PI (4 / 4) | no | 85,3 % | 9,96 s |
| PI (4 / 4) | sí | 10,8 % | 5,79 s |
| PID (6 / 3 / 1) | no | 52,4 % | 8,51 s |
| PID (6 / 3 / 1) | sí | 3,2 % | 2,59 s |
El mismo controlador pasa de 85 % a 11 % de sobreimpulso sólo por no cargar el integrador de más. Las defensas más usadas:
- Integración condicional (la que usa el código de este tema): si la salida está saturada y el error empuja más hacia el límite, no se actualiza el integrador. Es simple y funciona bien.
- Retrocálculo (back-calculation): se realimenta al integrador la diferencia entre la orden sin limitar y la limitada, de modo que se descarga solo. Es el lazo punteado del diagrama de bloques.
- Limitar la referencia o suavizarla: en lugar de un escalón de 3 rad, pedir una rampa o un perfil de velocidad (tema 17), con lo que el error inicial nunca es enorme. Se combina con las anteriores.
- Limitar el propio integrador a un rango razonable, de modo que no pueda acumular más de lo que la orden puede usar.
También conviene recordar el cambio de modo: si el controlador estuvo apagado o en manual y se pone en automático, el integrador debe arrancar en un valor coherente con la orden actual para que no haya un salto (transferencia sin golpe). Y cada vez que una máquina de estados del tema 38 cambie de estado y reinicie un lazo, hay que reiniciar también el integrador.
07 · El PID en un microcontrolador
El tiempo de muestreo
Un microcontrolador no deriva ni integra en continuo: calcula cada Ts segundos. La integral se aproxima por una suma, I += Ki · e · Ts, y la derivada por una diferencia, (y − y_anterior) / Ts. Entre dos cálculos la orden se mantiene constante (un retén de orden cero). Ese retén introduce, de hecho, un retardo medio de Ts/2 que ya conocemos del tema 41 como enemigo del amortiguamiento.
| Ts | Sobreimpulso | Asentamiento (2 %) | Caída por la carga | Recuperación |
|---|---|---|---|---|
| 10 ms | 7,9 % | 1,77 s | 0,033 | 1,38 s |
| 50 ms | 7,4 % | 1,67 s | 0,034 | 1,32 s |
| 100 ms | 8,1 % | 1,53 s | 0,035 | 1,24 s |
| 150 ms | 9,7 % | 2,29 s | 0,040 | 1,51 s |
| 200 ms | 13,5 % | 5,23 s | 0,047 | 1,74 s |
| 300 ms | 21,9 % | no se asienta | 0,251 | no recupera |
De 10 a 100 ms la respuesta es prácticamente la misma (el motor tiene un tiempo de subida de unos 0,65 s: 6 a 65 muestras por subida). A partir de 150 ms empieza a degradarse, y con 300 ms (dos muestras por subida) el lazo está cerca de oscilar sin amortiguarse. La regla práctica: muestrear al menos 10 veces dentro del tiempo de subida (o del menor tiempo característico de la planta), y no más rápido de lo que el sensor puede renovar la medida, porque entonces la derivada sólo amplifica ruido.
Tan importante como el valor es su regularidad. El PID supone un Ts constante; si el bucle se ejecuta cuando le toca, con tiempos que varían, la integral y la derivada se calculan mal. Se programa con el esquema periódico y sin bloqueos del tema 37: if (millis() − ultimo ≥ TS) { ultimo += TS; … }, y se pasa el Ts real al cálculo. Evitá delay() en el lazo de control.
08 · Un orden para ajustar
Un orden para ajustar
El tema 44 trata métodos sistemáticos. Mientras tanto, un procedimiento manual que casi siempre funciona, con la ayuda de lo visto:
- Todo en cero, salvo Kp muy pequeña. Verificá que el sentido de la acción es correcto: un error positivo debe empujar la variable hacia la referencia. Un signo invertido hace huir al sistema.
- Subí Kp hasta obtener una respuesta rápida, aunque oscile un poco. Parate ante el primer signo de saturación sostenida o de ruido en la orden.
- Subí Kd hasta amortiguar esa oscilación sin que el ruido de la orden moleste.
- Si sobra amortiguamiento, volvé a subir Kp un poco y repetí 2 y 3.
- Agregá Ki en pequeño, sólo lo necesario para eliminar el error permanente en un tiempo aceptable. Si empieza a oscilar, es demasiado.
- Probá con escalones grandes y con la peor carga; activá el anti-windup antes de esas pruebas.
Con el motor: Kp = 2 da 25 % de sobreimpulso; con Kd = 0,5 baja a 4 %; ahora se puede subir Kp a 6 y agregar Ki = 3 para eliminar el error con carga. El PID (6 / 3 / 1) de la tabla sale de ese camino.
09 · Código para la placa
Un PID en Arduino
El código define el PID como una estructura reutilizable, con derivada sobre la medida, integración condicional y salida limitada, y lo prueba sobre el motor simulado. La clase PID es la que se usaría con un encoder y un driver reales: sólo cambiaría la llamada, que recibiría la posición medida y devolvería la orden para el PWM (tema 13). El resto es la planta.
// Controlador PID discreto con anti-windup, probado sobre un motor de posición simulado.
// Motor: TAU * w' = -w + K * (u - carga), th' = w. La orden u está limitada a [-1, 1].
const float DT = 0.001, TAU = 0.5, K = 2.0, UMAX = 1.0, CARGA = 0.2;
const long K_CARGA = 10000; // la carga aparece a los 10 s
struct PID {
float kp, ki, kd;
float integral = 0, medidaPrev = 0;
bool primera = true, antiWindup = true;
PID(float p, float i, float d) : kp(p), ki(i), kd(d) {}
float calcular(float ref, float medida, float ts) {
float e = ref - medida;
float d = primera ? 0.0 : -(medida - medidaPrev) / ts; // derivada sobre la medida: sin golpe al cambiar la referencia
primera = false;
medidaPrev = medida;
float nueva = integral + ki * e * ts;
float salida = kp * e + nueva + kd * d;
bool satura = salida > UMAX || salida < -UMAX;
// Anti-windup: si satura y el error empuja más hacia el límite, no se acumula integral
if (!antiWindup || !satura || (salida > UMAX && e < 0) || (salida < -UMAX && e > 0)) integral = nueva;
return constrain(salida, -UMAX, UMAX);
}
};
void ensayar(const char* nombre, float kp, float ki, float kd, float ref, bool aw) {
PID pid(kp, ki, kd);
pid.antiWindup = aw;
float th = 0, w = 0, u = 0, maximo = 0;
long ultimo = -1;
for (long k = 0; k < 20000; k++) {
if (k % 10 == 0) u = pid.calcular(ref, th, 0.01); // el PID corre cada 10 ms
w += DT * (-w + K * (u - (k >= K_CARGA ? CARGA : 0.0))) / TAU;
th += DT * w;
if (k < K_CARGA) { // medidas del escalón: primeros 10 s
maximo = max(maximo, th);
if (fabs(th - ref) > 0.02 * ref) ultimo = k;
}
}
Serial.print(nombre);
Serial.print(" Mp="); Serial.print(max(0.0f, (maximo - ref) / ref * 100.0f), 2); Serial.print(" %");
if (ultimo >= K_CARGA - 1) Serial.print(" ts=no se asienta");
else { Serial.print(" ts="); Serial.print((ultimo + 1) * DT, 2); Serial.print(" s"); }
Serial.print(" error final="); Serial.println(ref - th, 3);
}
void setup() {
Serial.begin(115200);
ensayar("P 2 ", 2, 0, 0, 1, true);
ensayar("PI 2/1 ", 2, 1, 0, 1, true);
ensayar("PID 6/3/1 ", 6, 3, 1, 1, true);
ensayar("PID ref 3 sin", 6, 3, 1, 3, false); // sin anti-windup
ensayar("PID ref 3 con", 6, 3, 1, 3, true); // con anti-windup
}
void loop() {}
La salida repite las tablas del texto:
P 2 Mp=25.24 % ts=4.03 s error final=0.100
PI 2/1 Mp=28.68 % ts=5.41 s error final=0.000
PID 6/3/1 Mp=7.92 % ts=1.77 s error final=0.000
PID ref 3 sin Mp=52.40 % ts=8.51 s error final=0.000
PID ref 3 con Mp=3.23 % ts=2.59 s error final=0.000
Las diferencias de centésimas con Python vienen de la aritmética de 32 bits. Con un sistema real, el lazo se programa así (sin delay()):
const unsigned long TS_MS = 10; // período de muestreo
unsigned long ultimo = 0;
PID pid(6, 3, 1);
void loop() {
if (millis() - ultimo >= TS_MS) {
ultimo += TS_MS; // sin acumular retrasos
float posicion = leerPosicion(); // encoder (tema 18)
float orden = pid.calcular(referencia, posicion, TS_MS / 1000.0);
aplicarMotor(orden); // PWM y sentido, ya limitada a [-1, 1]
}
}
Si la referencia puede estar mucho más lejos que lo que el motor alcanza en un ciclo, agregá un límite de velocidad de referencia o un perfil (tema 17) y reiniciá pid.integral = 0 al volver de un estado de falla o de un modo manual.
10 · Exploración interactiva
Laboratorios del PID
1. El PID sobre el motor
La línea violeta es la posición y la naranja inferior, la orden (de −1 a 1). A los 10 s aparece una carga de 0,2. Con la referencia de 3 rad el motor satura al arrancar y se ve el efecto del windup.
Mové los controles.
Probá en orden: Kp = 2 y el resto en 0 (25 % de sobreimpulso y un error de 0,1 con la carga); agregá Kd = 0,5; agregá Ki = 1; subí a 6 / 3 / 1. Después poné la referencia en 3 rad, apagá el anti-windup y observá el sobreimpulso con un PI de 4 / 4 (85 %).
2. Muestreo y ruido
El PID 6 / 3 con la Kd elegida. Cambiá el período de muestreo y el ruido de la medida. Las cifras de ruido se refieren al desvío de la orden entre los 14 y los 20 s.
Elegí los valores.
Probá con ruido de 5 mrad: con Kd = 0 la orden casi no se mueve (0,03), con Kd = 1 tiembla (0,6). Después subí el muestreo a 300 ms y mirá cómo el lazo pierde el control aunque no haya ruido.
11 · Código ejecutable
Probar el PID en Python
El mismo motor y el mismo PID, con las variantes, el windup, el muestreo, el ruido y el golpe derivativo. El código reproduce las tablas del tema.
Probalo en el navegador. Python se descarga con Pyodide la primera vez y requiere conexión. El código es editable y no controla ningún equipo real. La primera ejecución puede tardar unos segundos porque hace una treintena de simulaciones.
from math import sqrt
DT, TAU, K, UMAX, CARGA, T_CARGA = 0.001, 0.5, 2.0, 1.0, 0.2, 10.0
def azar(semilla):
"""Generador pseudoaleatorio de 32 bits (mulberry32), para que el ruido sea repetible."""
a = semilla & 0xFFFFFFFF
def siguiente():
nonlocal a
a = (a + 0x6D2B79F5) & 0xFFFFFFFF
t = ((a ^ (a >> 15)) * (1 | a)) & 0xFFFFFFFF
t = (((t + (((t ^ (t >> 7)) * (61 | t)) & 0xFFFFFFFF)) & 0xFFFFFFFF) ^ t)
return ((t ^ (t >> 14)) & 0xFFFFFFFF) / 4294967296.0
return siguiente
def simular(kp=2.0, ki=0.0, kd=0.0, aw=True, ts=0.01, ref=1.0, t_ref=0.0, ruido=0.0,
d_error=False, carga=CARGA, t_fin=20.0, semilla=7):
"""Motor de posición con un PID discreto. Devuelve posición, orden saturada y orden sin saturar."""
rnd = azar(semilla)
th = w = u = integral = med_prev = err_prev = raw = 0.0
primera = True
nc = max(1, round(ts / DT))
pos, orden, crudo = [], [], []
for k in range(round(t_fin / DT)):
t = k * DT
if k % nc == 0:
medida = th + ruido * (sum(rnd() for _ in range(12)) - 6.0)
e = (ref if t >= t_ref else 0.0) - medida
d = 0.0
if not primera:
d = (e - err_prev) / ts if d_error else -(medida - med_prev) / ts
primera, med_prev, err_prev = False, medida, e
nueva = integral + ki * e * ts
raw = kp * e + nueva + kd * d
sat = raw > UMAX or raw < -UMAX
u = min(UMAX, max(-UMAX, raw))
if not aw or not sat or (raw > UMAX and e < 0) or (raw < -UMAX and e > 0):
integral = nueva # anti-windup: no integra si satura y el error empuja más
w += DT * (-w + K * (u - (carga if t >= T_CARGA else 0.0))) / TAU
th += DT * w
pos.append(th); orden.append(u); crudo.append(raw)
return pos, orden, crudo
def escalon(pos, ref=1.0):
"""Sobreimpulso (%) y asentamiento al 2 % durante los primeros T_CARGA segundos."""
n = round(T_CARGA / DT)
ult = max((i for i in range(n) if abs(pos[i] - ref) > 0.02 * ref), default=-1)
return max(0.0, (max(pos[:n]) - ref) / ref * 100.0), (None if ult >= n - 1 else (ult + 1) * DT)
def resp_carga(pos, ref=1.0):
"""Caída máxima, tiempo de recuperación y error final tras aparecer la carga."""
i0, n = round(T_CARGA / DT), len(pos)
ult = max((i for i in range(i0, n) if abs(pos[i] - ref) > 0.02 * ref), default=-1)
rec = 0.0 if ult < i0 else (None if ult >= n - 1 else (ult + 1 - i0) * DT)
return max(0.0, ref - min(pos[i0:])), rec, ref - pos[-1]
def desvio(serie, a, b):
seg = serie[round(a / DT):round(b / DT)]
m = sum(seg) / len(seg)
return sqrt(sum((x - m) ** 2 for x in seg) / len(seg))
def f(v, d=2, u=""):
return " — " if v is None else f"{v:.{d}f}{u}"
if __name__ == "__main__":
print("Variantes sobre el motor (escalón de 1 rad; carga de 0,2 a los 10 s)")
print("controlador Mp ts(2%) caída recup. error final")
for nombre, kw in (("P Kp=2", dict(kp=2)), ("PI 2 / 1", dict(kp=2, ki=1)), ("PD 2 / 0,5", dict(kp=2, kd=0.5)),
("PID 2 / 1 / 0,5", dict(kp=2, ki=1, kd=0.5)), ("PID 6 / 3 / 1", dict(kp=6, ki=3, kd=1))):
pos, _, _ = simular(**kw)
mp, ts = escalon(pos); caida, rec, ef = resp_carga(pos)
print(f"{nombre:<20} {mp:6.2f} % {f(ts, 2, ' s'):>8} {caida:6.3f} {f(rec, 2, ' s'):>8} {ef:7.3f}")
print("\nAnti-windup: escalón de 3 rad (el motor satura durante el arranque)")
for nombre, kw in (("PI 4 / 4", dict(kp=4, ki=4)), ("PID 6 / 3 / 1", dict(kp=6, ki=3, kd=1))):
for aw in (False, True):
pos, _, _ = simular(ref=3.0, aw=aw, **kw)
mp, ts = escalon(pos, 3.0)
print(f"{nombre:<14} anti-windup {'sí' if aw else 'no'}: Mp {mp:6.2f} % ts {f(ts, 2, ' s')}")
print("\nTiempo de muestreo con PID 6 / 3 / 1")
for ts_m in (0.01, 0.05, 0.1, 0.15, 0.2, 0.3):
pos, _, _ = simular(kp=6, ki=3, kd=1, ts=ts_m)
mp, ts = escalon(pos); caida, rec, _ = resp_carga(pos)
print(f"Ts = {ts_m * 1000:4.0f} ms: Mp {mp:6.2f} % ts {f(ts, 2, ' s'):>8} caída {caida:.3f} recup. {f(rec, 2, ' s')}")
print("\nRuido de 5 mrad en la medida: desvío de la orden u (de 14 a 20 s)")
for nombre, kd in (("sin derivada", 0.0), ("Kd = 0,3", 0.3), ("Kd = 1", 1.0)):
_, orden, _ = simular(kp=6, ki=3, kd=kd, ruido=0.005)
print(f"{nombre:<13} desvío de u = {desvio(orden, 14, 20):.3f}")
print("\nEscalón de 1 rad a los 1 s, Kd = 1: pico de la orden calculada (sin saturar)")
for d_error in (False, True):
_, _, crudo = simular(kp=6, ki=0, kd=1, t_ref=1.0, d_error=d_error)
print(f"derivada sobre {'el error ' if d_error else 'la medida '}: {max(crudo):7.1f}")La salida repite las tablas del texto: cinco controladores con carga, el windup con y sin anti-windup, seis períodos de muestreo, el ruido con tres valores de Kd y el golpe derivativo.
Probá cambiar ki a 4 en el PI de Kp = 2 y comprobar que el lazo oscila, o poner carga=0.5 y ver que la orden de 0,5 es la que el integrador acaba sosteniendo.
12 · Método y diagnóstico
Pruebas y diagnóstico
- Verificá el signo y la escala: con las ganancias casi en cero, un error positivo debe mover la salida hacia la referencia, y la orden debe estar en las mismas unidades que el actuador.
- Registrá referencia, medida, error, orden calculada y orden aplicada. Si la calculada pasa de los límites, hay saturación; si la integral crece sin que el error baje, hay windup.
- Fijá un
Tsconstante y medí el real (conmicros()): si varía, el cálculo es incorrecto. - Ajustá en el orden de la sección 08 y guardá cada juego de ganancias con su resultado (sobreimpulso, asentamiento, error con carga).
- Probá con la carga más desfavorable, con escalones grandes y pequeños, y con el sensor ruidoso.
- Si la orden tiembla, mirá primero la derivada y el ruido; si oscila lentamente, mirá la integral; si oscila rápido, mirá Kp y el retardo.
- Prepará el estado seguro: ante pérdida del sensor o parada de emergencia, apagar el actuador y reiniciar el integrador (tema 39).
«Un PID siempre es mejor que un P»
No: con ruido, retardo o un actuador que no admite movimientos finos, los términos I y D empeoran. Se agregan sólo si resuelven un problema concreto.
«La integral hace al sistema más rápido»
Elimina el error permanente, pero aumenta el sobreimpulso y alarga el asentamiento. La rapidez la da Kp, amortiguada por Kd.
«La derivada corrige el error permanente»
Con error constante su derivada es cero; no aporta nada. Sólo actúa mientras algo cambia.
«Si la salida satura, no pasa nada»
La salida puede saturar sin problema, pero el integrador no. Sin anti-windup, la recuperación es lenta y con un sobreimpulso grande.
«Cuanto más rápido muestreo, mejor»
Hasta cierto punto. Más allá de lo que el sensor renueva, la derivada sólo amplifica ruido y el microcontrolador se carga sin beneficio.
«Derivar el error o la medida es lo mismo»
Con la referencia constante sí; cuando la referencia salta, derivar el error produce un golpe enorme en una muestra.
13 · Práctica guiada
Predecí antes de comprobar
1. Error del proporcional
El motor con carga de 0,2 usa sólo Kp. ¿Qué Kp hace falta para que el error permanente no pase de 0,02 rad?
Ver solución
e = 0,2 / Kp ≤ 0,02 da Kp ≥ 10, que la tabla confirma (0,020). Con esa ganancia el ruido se multiplica por 10; la integral logra el mismo resultado con una Kp moderada.
2. Qué guarda la integral
El PID está asentado con la carga de 0,2 aplicada. ¿Cuánto vale el error, cuánto el término integral y cuánto el proporcional?
Ver solución
El error es cero, y por lo tanto el término proporcional y el derivativo valen cero. Toda la orden es el término integral: 0,2, exactamente lo que la carga exige.
3. Ruido en la derivada
La medida tiene 5 mrad de ruido, Ts = 10 ms y Kd = 1. ¿Cuál es el orden de magnitud de la variación de la orden por ruido? ¿Qué pasa si se baja Ts a 5 ms?
Ver solución
Kd · 0,005 · √2 / Ts = 1 · 0,005 · 1,41 / 0,01 ≈ 0,7. La simulación da 0,6 porque la orden satura. Con Ts = 5 ms se duplica a 1,4: derivar con un muestreo más rápido es peor, no mejor.
4. Golpe derivativo
Con Kp = 6, Kd = 1 y Ts = 10 ms, la referencia pasa de 0 a 1 rad. ¿Cuánto vale la orden calculada en esa muestra si se deriva el error, y si se deriva la medida?
Ver solución
Sobre el error: 6 · 1 + 1 · 1 / 0,01 = 106. Sobre la medida: 6 · 1 + 0 = 6. En ambos casos el driver entrega 1 y el motor no nota la diferencia, pero el cálculo sin saturar muestra el golpe, y un driver o un filtro lo notarían.
5. Windup
Un PI (4 / 4) recibe un escalón de 3 rad y satura al arrancar. ¿Qué pasa con y sin anti-windup?
Ver solución
Sin anti-windup el integrador se carga durante la saturación y el sobreimpulso es del 85 % (la posición llega a unos 5,6 rad); con integración condicional baja a 10,8 %. La corrección no cambia las ganancias, sólo qué se acumula.
6. Elegir el muestreo
El motor con el PID (6 / 3 / 1) tiene un tiempo de subida de 0,65 s. ¿Qué Ts elegirías, y por qué 300 ms no sirve?
Ver solución
Por la regla de 10 muestras por subida, Ts ≤ 65 ms; 10 a 50 ms es razonable. Con 300 ms hay sólo dos muestras por subida: el sobreimpulso sube a 22 %, no se asienta y la caída por carga se multiplica por casi ocho (0,251).
7. Diagnóstico
Un motor con PID oscila alrededor de la referencia con un período de unos 2 s, y la orden no tiembla. ¿Qué término sospechás y qué harías?
Ver solución
Una oscilación lenta y limpia es típica de demasiada integral (Ki = 4 con Kp = 2 oscila así, con un período de 2,2 s que no se extingue). Bajar Ki, o subir Kp y Kd si se necesita la misma velocidad de corrección. Si la orden temblara, sospecharía de la derivada y el ruido.
14 · Síntesis
Ideas para recordar
- El PID suma tres acciones sobre el error: proporcional (presente), integral (pasado) y derivativa (futuro):
u = Kp·e + Ki·∫e dt + Kd·de/dt. - La integral elimina el error permanente y acaba almacenando la orden que la carga exige; a cambio aumenta el sobreimpulso y puede desestabilizar.
- La derivada amortigua el sobreimpulso y permite subir las otras ganancias, pero no corrige el error permanente y amplifica el ruido.
- Se deriva la medida y no el error, para evitar el golpe derivativo cuando cambia la referencia.
- Con el actuador saturado, la integral se recarga (windup) y provoca un sobreimpulso enorme; el anti-windup evita acumularla.
- El muestreo debe ser regular y dar al menos unas 10 muestras por tiempo de subida; muestrear mucho más rápido sólo aumenta el ruido.
- Se ajusta en orden: Kp para la velocidad, Kd para amortiguar, Ki al final y poco.
- Muchos problemas se resuelven con un PI o hasta con un P; los términos se agregan si resuelven algo concreto.
¿Listo para el tema 44?
Antes de avanzar, con el laboratorio 2 anotá el desvío de la orden con 5 mrad de ruido para Kd = 0, 0,5 y 1, y el período de muestreo a partir del cual el sobreimpulso supera el 15 %. Escribí cuánto ruido de la orden tolerarías en un motor real.
En el próximo tema aprendemos a ajustar el PID de forma sistemática y a filtrar las señales: la sintonía por prueba y error, el método de Ziegler–Nichols y los filtros de la medida que permiten usar la derivada sin pagar el ruido.