← Índice del curso

Robótica y Automatización · Tema 40

Lazo abierto y lazo cerrado

Hasta ahora, casi todo lo que programamos ordenaba una acción y daba por hecho que ocurría. Un sistema de control hace algo distinto: mide el resultado, lo compara con lo que quería y corrige. Esa diferencia, un lazo que se cierra, es la base de todo lo que sigue en el curso.

01 · Punto de partida

Ordenar y medir

En el tema 17 movimos un servo con rampas, en el 19 un motor hasta una cantidad de pulsos, en el 20 cerramos un lazo con un encoder. Fueron tres ejemplos de la misma pregunta: ¿alcanza con dar la orden, o hay que mirar qué pasó? Este tema la responde con una idea general y con números.

Usaremos un caso sencillo: un motor que debe girar a 100 rpm. La potencia que se le aplica produce velocidad, pero una carga (un rozamiento, una pendiente, un cable que tira) la frena. El programa puede decidir la potencia; la velocidad real depende además de cosas que el programa no controla.

Es el primer tema de la parte de control. Los siguientes estudian cómo responde un sistema (tema 41) y cómo se diseñan los controladores, del todo o nada al PID (temas 42 a 44).

02 · Sin mirar el resultado

El lazo abierto

En un lazo abierto el controlador calcula la acción a partir de la referencia y de un modelo del sistema, y no consulta el resultado. Nuestro motor tiene una ganancia nominal de 2 rpm por cada 1 % de potencia, de modo que para 100 rpm el programa aplica u = 100 / 2 = 50 %. Una vez calculado, ese valor no cambia.

Si el modelo es exacto y no hay perturbaciones, el resultado es perfecto: en la simulación el motor llega a 100,00 rpm. Un lazo abierto bien calibrado es simple, rápido, barato y sin problemas de estabilidad, y por eso se usa mucho: un horno programado por tiempo, un semáforo, un motor paso a paso sin pérdida de pasos o el servo de aeromodelismo que llevamos a un ángulo.

Su debilidad es que confía. Dos cosas dejan de ser ciertas con el tiempo y el programa no se entera:

  • El modelo cambia. Con el uso, la batería baja o el motor se calienta, la ganancia real pasa de 2,0 a 1,6 rpm por %. Con la misma potencia de 50 %, la velocidad será 80 rpm: un error de 20 rpm.
  • Aparecen perturbaciones. Una carga que frena el motor 20 rpm lo deja en 60 rpm. El error total es de 40 rpm y el programa sigue aplicando 50 %.

Los números son los de la simulación del tema: 100,00 rpm con el modelo exacto, 80,00 con la ganancia envejecida, 60,00 cuando además se aplica la carga. La potencia permanece en 50,0 % en los tres casos.

03 · Mirar y corregir

El lazo cerrado

En un lazo cerrado (o con realimentación, feedback) un sensor mide la variable controlada, se la resta de la referencia y el resultado, el error, decide la acción. Los elementos tienen nombres que se usan en todo el control:

Diagrama de bloques de un lazo cerradoLa referencia entra a un sumador junto con la medición del sensor, con signo negativo. El error entra al controlador, que da la señal de control al actuador y la planta. La perturbación actúa sobre la planta. La salida se mide con el sensor, que cierra el lazo. referencia r Σ+− error e Controlador u Actuador Planta perturbación d y Sensor medición

La referencia (o consigna) es lo que se quiere. La planta es el sistema que se controla (el motor con su carga). El sensor la mide, el controlador calcula la señal de control a partir del error, y el actuador la aplica. Una perturbación es todo lo que afecta la salida sin pasar por el controlador: la carga, el viento, una puerta que se abre en el horno.

Nuestro controlador es el más simple con realimentación, uno proporcional: parte de la potencia calculada con el modelo y le suma una corrección proporcional al error, u = r / K_nom + Kp · (r − y). Si el motor va lento, el error es positivo y la potencia sube; si va rápido, baja. Con Kp = 0 se recupera el lazo abierto. En el tema 42 estudiamos este control con más detalle.

04 · Los números

Por qué corrige

El modelo del motor es de primer orden: τ · y' = −y + K · u − d, con τ = 0,5 s. Con ganancia real K, carga d y el controlador anterior, en régimen permanente el error resulta:

e = ( r · (1 − K / K_nom) + d ) / ( 1 + K · Kp )

El numerador es exactamente el error del lazo abierto (el error por modelo equivocado más el de la perturbación). El denominador, 1 + K · Kp, es la ganancia de lazo más uno: la realimentación divide los errores por ese factor. Con K = 1,6 y Kp = 2, el factor es 4,2 y los errores de 20 y 40 rpm pasan a ser 4,76 y 9,52 rpm: la simulación da 95,24 rpm antes de la carga y 90,48 después, y la potencia sube sola de 59,5 % a 69,0 %, que es lo que el programa «descubrió» que hacía falta.

Error en régimen según el lazo (K real = 1,6; r = 100 rpm; carga de 20 rpm a los 5 s). Los valores salen de la simulación.
ControladorError antes de la cargaError con la cargaPotencia final
Lazo abierto20,00 rpm40,00 rpm50,0 %
Cerrado, Kp = 0,117,24 rpm34,48 rpm53,4 %
Cerrado, Kp = 0,511,11 rpm22,22 rpm61,1 %
Cerrado, Kp = 2,04,76 rpm9,52 rpm69,0 %

Dos efectos más con el mismo denominador. Primero, la velocidad de respuesta: el lazo cerrado tiene una constante de tiempo τ / (1 + K · Kp), que con Kp = 2 es 0,5 / 4,2 = 0,119 s contra los 0,5 s del lazo abierto; realimentar acelera. Segundo, la insensibilidad al modelo: el controlador ya no necesita conocer la ganancia exacta, solo necesita que la ganancia de lazo sea grande.

Observá también lo que el lazo cerrado proporcional no hace: no elimina el error. Queda un residuo 1 / (1 + K · Kp) del original. Para llegar a cero hace falta una acción integral, tema del control PID (tema 43). Y fijate que con la planta nominal y sin carga, el lazo cerrado da exactamente lo mismo que el abierto (100,00 rpm), porque el error es cero y no hay nada que corregir.

05 · No es gratis

Lo que cuesta realimentar

Si subir Kp reduce el error y acelera la respuesta, ¿por qué no poner Kp = 1000? Porque la realimentación tiene costos:

  • El ruido del sensor llega a la salida. La medición tiene ruido, y el controlador lo multiplica por Kp. Con 2 rpm de ruido, la potencia fluctúa unos 4 % con Kp = 2 (4,02 en la simulación), 10,4 % con Kp = 5 y 33,4 % con Kp = 20, donde ya satura. Un actuador agitado se desgasta, hace ruido y consume.
  • La saturación. La potencia tiene límites (0 a 100 %). Cuando el controlador pide más de lo que el actuador puede dar, el lazo en la práctica se abre. Con un error grande y Kp alto, la salida está pegada al máximo.
  • El retardo. Nuestro modelo no tiene demoras entre el actuador y el sensor. Con un retardo, una ganancia alta convierte la corrección en un empuje que llega tarde y se vuelve oscilación. Es el tema de la estabilidad, tema 41.
  • Depende del sensor. Si el sensor falla, el lazo corrige contra una mentira. Un tacómetro desconectado que lee 0 rpm hace que el controlador pida potencia máxima (50 + Kp · 100 saturado a 100 %) con un motor que ya va bien.

El diseño de un lazo es una negociación entre error, velocidad, ruido y robustez. Por eso importa saber qué se está comprando con cada unidad de ganancia.

06 · Cómo decidir

Cuál usar

Criterios de elección.
SituaciónConvienePor qué
Proceso repetible, modelo estable y sin perturbacionesLazo abiertoMás simple y sin sensor
Perturbaciones o cambios de la plantaLazo cerradoDivide el error por 1 + K · Kp
Sin sensor posible o muy caroLazo abiertoNo hay con qué realimentar
Hay que mantener un valor (temperatura, velocidad)Lazo cerradoLa perturbación no se puede evitar
Un movimiento rápido y repetitivo con modelo conocidoCombinar (prealimentación + corrección)El lazo abierto aporta velocidad; el cerrado, precisión
Daño si el sensor fallaCerrado con validacionesPlausibilidad, límites y fallas enclavadas (tema 39)

El controlador de este tema ya es una combinación: la parte r / K_nom es lazo abierto (la prealimentación o feedforward, que usa el modelo para dar un buen punto de partida) y la parte Kp · e es realimentación (que corrige lo que el modelo no sabe). Es la estructura más común en robótica: el modelo hace el trabajo grueso y el lazo cerrado el fino.

07 · Código para la placa

Los dos lazos en Arduino

El sketch ejecuta el motor simulado con cuatro configuraciones. La diferencia entre lazo abierto y cerrado es una sola línea: el término kp * (REF - y). Con hardware, y sale de un encoder o un tacómetro y u se convierte en PWM.

// Lazo abierto contra lazo cerrado sobre un motor simulado: TAU * y' = -y + K*u - carga
// Lazo abierto: u = REF / K_NOM, calculado una sola vez con el modelo nominal.
// Lazo cerrado: u = REF / K_NOM + KP * (REF - y), corrige con lo medido en cada ciclo.
const float DT = 0.01, TAU = 0.5, T_FIN = 10.0;
const float K_NOM = 2.0, K_REAL = 1.6;                 // ganancia que cree el programa y ganancia real (rpm por % de potencia)
const float REF = 100.0, T_PERT = 5.0, PERT = 20.0;    // consigna (rpm) y carga que aparece a los 5 s

void ejecutar(const char *titulo, bool cerrado, float kp, float kReal) {
  float y = 0, u = 0, yAntes = 0, uAntes = 0;
  for (int k = 0; k < (int)(T_FIN / DT + 0.5); k++) {
    float t = k * DT;
    float carga = (t >= T_PERT) ? PERT : 0.0;
    u = REF / K_NOM;
    if (cerrado) u += kp * (REF - y);                  // en un robot real, y viene del encoder o del sensor
    u = constrain(u, 0.0, 100.0);                      // la salida tiene límites físicos
    y += DT * (-y + kReal * u - carga) / TAU;          // planta simulada
    if (k == (int)(T_PERT / DT + 0.5) - 1) { yAntes = y; uAntes = u; }
  }
  Serial.print(titulo);
  Serial.print(": antes de la carga "); Serial.print(yAntes, 2); Serial.print(" rpm (potencia "); Serial.print(uAntes, 1);
  Serial.print(" %); al final "); Serial.print(y, 2); Serial.print(" rpm (potencia "); Serial.print(u, 1); Serial.println(" %)");
}

void setup() {
  Serial.begin(115200);
  ejecutar("Lazo abierto, K=2,0 (nominal)", false, 0, K_NOM);
  ejecutar("Lazo abierto, K=1,6          ", false, 0, K_REAL);
  ejecutar("Lazo cerrado Kp=0,5, K=1,6   ", true, 0.5, K_REAL);
  ejecutar("Lazo cerrado Kp=2,0, K=1,6   ", true, 2.0, K_REAL);
}

void loop() {}

Salida esperada: Lazo abierto, K=2,0 (nominal): antes de la carga 100.00 rpm (potencia 50.0 %); al final 80.00 rpm (potencia 50.0 %); con K = 1,6, 80,00 y 60,00 rpm; con Kp = 0,5, 88,89 y 77,78 rpm (potencia de 55,6 % a 61,1 %); con Kp = 2,0, 95,24 y 90,48 rpm (de 59,5 % a 69,0 %).

Dos hábitos de este código valen para cualquier lazo: la potencia se limita con constrain() antes de aplicarla, y el cálculo se hace con un paso fijo DT (en un robot real, con el ciclo regular del tema 37).

08 · Exploración interactiva

Laboratorios del motor con carga

1. Motor con carga

La curva violeta es la velocidad, la línea lima la referencia (100 rpm) y la naranja la potencia aplicada. A los 5 s aparece la carga. Con Kp = 0 el controlador es de lazo abierto.

Respuesta del motor y potencia aplicadaArriba la velocidad en rpm con la referencia punteada y la carga marcada a los 5 segundos; abajo la potencia en porcentaje.
Error antes de la carga—
Error final—
Fórmula (antes / con carga)—
Ruido en la potencia—

Mové los controles.

Sugerencias. Con Kp = 0 y ganancia 2,0, sin carga: el lazo abierto es perfecto. Cambiá la ganancia a 1,6: el error aparece y no se corrige. Subí Kp a 2 y mirá cómo la potencia se ajusta sola. Después sumá ruido de 2 rpm y mirá la curva naranja temblar; Kp = 20 la satura. La fórmula del cuarto cuadro coincide con la simulación en cada combinación sin ruido.

2. Lo que se gana y lo que se paga

Para K = 1,6 y la carga de 20 rpm, la barra naranja es el error final (según la fórmula) y la lima el desvío que el ruido de 2 rpm produce en la potencia (simulado), para distintas ganancias.

Error y ruido según la gananciaPara cada valor de Kp, una barra naranja con el error final y una barra lima con el ruido inducido en la potencia. El error baja y el ruido sube al aumentar Kp.

El cruce es claro: pasar de Kp = 2 a Kp = 20 baja el error de 9,52 a 1,21 rpm, pero el ruido en la potencia pasa de 4,02 a 33,44 %. Rara vez conviene el extremo; el punto de equilibrio depende del ruido del sensor y de cuánto sufra el actuador.

09 · Código ejecutable

Comparar lazos en Python

La misma simulación, con el generador pseudoaleatorio determinista de los temas anteriores para el ruido, de modo que Python, JavaScript y Arduino den los mismos números.

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.

DT, TAU, T_FIN = 0.01, 0.5, 10.0
K_NOM, K_REAL = 2.0, 1.6        # ganancia nominal (la que usa el programa) y real de la planta, en rpm por % de potencia
REF, T_PERT, PERT = 100.0, 5.0, 20.0   # referencia (rpm), instante y tamaño de la perturbación de carga (rpm)
M = 0xFFFFFFFF

def mulberry32(semilla):
    estado = [semilla & M]
    def siguiente():
        estado[0] = (estado[0] + 0x6D2B79F5) & M
        t = ((estado[0] ^ (estado[0] >> 15)) * (estado[0] | 1)) & M
        t ^= (t + (((t ^ (t >> 7)) * (t | 61)) & M)) & M
        return ((t ^ (t >> 14)) & M) / 4294967296
    return siguiente

def simular(modo, kp=0.0, k_real=K_REAL, pert=PERT, ruido=0.0, semilla=7):
    """Motor con carga: TAU * y' = -y + K*u - carga. u es la potencia (0 a 100 %)."""
    azar = mulberry32(semilla)
    y, serie_y, serie_u = 0.0, [], []
    for k in range(round(T_FIN / DT)):
        t = k * DT
        carga = pert if t >= T_PERT else 0.0
        medida = y + ruido * (sum(azar() for _ in range(12)) - 6.0)
        if modo == "abierto":
            u = REF / K_NOM                      # el programa invierte el modelo nominal
        else:
            u = REF / K_NOM + kp * (REF - medida)   # estimación inicial + corrección con lo medido
        u = min(100.0, max(0.0, u))
        y += DT * (-y + k_real * u - carga) / TAU
        serie_y.append(y); serie_u.append(u)
    return serie_y, serie_u

def en(serie, t):
    return serie[round(t / DT) - 1]

def promedio(serie, a, b):
    seg = serie[round(a / DT):round(b / DT)]
    return sum(seg) / len(seg)

def desvio(serie, a, b):
    seg = serie[round(a / DT):round(b / DT)]
    m = sum(seg) / len(seg)
    return (sum((v - m) ** 2 for v in seg) / len(seg)) ** 0.5

def informe(titulo, modo, **kw):
    y, u = simular(modo, **kw)
    antes, despues = en(y, T_PERT), en(y, T_FIN)
    print(f"{titulo}: antes de la carga {antes:6.2f} rpm (error {REF - antes:6.2f}); al final {despues:6.2f} rpm (error {REF - despues:6.2f}); "
          f"potencia {en(u, T_PERT):5.1f} % -> {en(u, T_FIN):5.1f} %")

informe("Lazo abierto, planta nominal (K=2,0)", "abierto", k_real=K_NOM)
informe("Lazo abierto, planta envejecida (K=1,6) ", "abierto")
for kp in (0.1, 0.5, 2.0):
    informe(f"Lazo cerrado Kp={kp:3.1f}, K=1,6               ", "cerrado", kp=kp)
informe("Lazo cerrado Kp=2,0, planta nominal    ", "cerrado", kp=2.0, k_real=K_NOM)

print("\nTeoría del lazo cerrado: e = (r*(1 - K/K_NOM) + d) / (1 + K*Kp) en régimen: el error del lazo abierto, dividido por 1 + K*Kp; constante de tiempo = TAU / (1 + K*Kp)")
print("   Kp   e antes  e después  tau lazo   u: desvío con ruido 2 rpm")
for kp in (0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 20.0):
    k = K_REAL
    y, u = simular("cerrado", kp=kp, k_real=k, ruido=2.0)
    print(f"{kp:5.2f}  {REF * (1 - k / K_NOM) / (1 + k * kp):7.2f}  {(REF * (1 - k / K_NOM) + PERT) / (1 + k * kp):8.2f}  {TAU / (1 + k * kp):8.3f} s  {desvio(u, 1.0, T_PERT):6.2f} %")

La salida repite los números del texto: lazo abierto 100,00 / 80,00 / 60,00 rpm; cerrado con Kp = 2 en 95,24 y 90,48 rpm; y la tabla teórica de error, constante de tiempo y ruido para siete valores de Kp.

Probá cambiar K_REAL a 1,2 y comprobar que el error del lazo abierto crece, mientras que el del cerrado sigue siendo el de la fórmula. Después aumentá PERT y comprobá que el error con la carga se divide por el mismo factor.

10 · Método y diagnóstico

Pruebas y diagnóstico

  1. Identificá los elementos: ¿cuál es la referencia, qué sensor mide la salida, qué actuador y qué perturbaciones hay?
  2. Medí el lazo abierto primero (ganancia real, tiempo de respuesta): es la base para ajustar el lazo cerrado.
  3. Aplicá una perturbación conocida y compará la salida con y sin realimentación.
  4. Registrá referencia, medición, error y salida de control en cada ciclo; con esos cuatro canales se diagnostica casi todo.
  5. Observá la señal de control: si está pegada a un límite, el lazo está saturado; si tiembla, el ruido llega al actuador.
  6. Desconectá o falseá el sensor en una prueba y comprobá que el sistema pasa a un estado seguro (tema 39).
  7. Subí la ganancia de a poco, sin saltar a valores grandes.
«Con lazo cerrado el error siempre es cero»

Con control proporcional queda un residuo igual al error del lazo abierto dividido por 1 + K · Kp. Eliminarlo del todo exige una acción integral.

«Más ganancia siempre es mejor»

Reduce el error y acelera, pero amplifica el ruido, satura el actuador y, con retardo, puede volver inestable el sistema.

«El lazo abierto es un control malo»

Es el adecuado cuando el modelo es fiable y no hay perturbaciones. Es más simple, más barato y no puede oscilar. Muchas veces se combina con realimentación.

«Si el sensor mide algo, el lazo está bien»

El lazo corrige lo que el sensor dice. Si el sensor está desplazado, ruidoso o desconectado, corrige hacia el valor equivocado. Hay que validar la medición.

«La perturbación debe preverla el programa»

No: la realimentación existe justamente para lo imprevisto. Lo que se sabe de antemano se puede sumar como prealimentación.

«Cuando la salida satura, el lazo sigue corrigiendo»

Con la salida en el límite, aumentar el error no aumenta la acción: el lazo está abierto de hecho. Hay que diseñar con ese límite en cuenta.

11 · Práctica guiada

Predecí antes de comprobar

1. Lazo abierto envejecido

El programa cree que la ganancia es 2,0 y la real es 1,6. Pide 100 rpm. ¿Qué velocidad se obtiene y qué error?

Ver solución

Potencia 50 %, velocidad 1,6 · 50 = 80 rpm, error 20 rpm. El error relativo es 1 − K / K_nom = 20 %.

2. Con realimentación

Con el mismo motor se agrega Kp = 1. ¿Cuál es el error sin carga?

Ver solución

e = 20 / (1 + 1,6 · 1) = 20 / 2,6 = 7,69 rpm, el valor que aparece en la tabla del tema.

3. Cuánta ganancia hace falta

Con K = 1,6 y la carga de 20 rpm, el error con carga es 40 / (1 + 1,6 · Kp). ¿Qué Kp da un error de 2 rpm o menos?

Ver solución

Se necesita 1 + 1,6 · Kp ≥ 20, es decir, Kp ≥ 11,9. Con Kp = 10 el error es 2,35 y con Kp = 20, 1,21 rpm. Pero el ruido en la potencia ya es grande: conviene preguntarse si no hace falta una acción integral.

4. Velocidad del lazo

¿Cuánto vale la constante de tiempo del lazo cerrado con Kp = 5 y cómo se compara con el lazo abierto?

Ver solución

τ / (1 + K · Kp) = 0,5 / (1 + 1,6 · 5) = 0,5 / 9 = 0,056 s, nueve veces más rápido que los 0,5 s del lazo abierto.

5. Ruido

El sensor tiene 2 rpm de ruido (desvío). ¿Cuánto ruido aproximado llega a la potencia con Kp = 2?

Ver solución

Aproximadamente Kp · 2 = 4 % de desvío, que es lo que da la simulación (4,02 %). La proporcionalidad se pierde cuando la potencia satura en los límites.

6. Sensor desconectado

El tacómetro se desconecta y lee 0 rpm mientras el motor gira a 100. Con Kp = 2, ¿qué hace el controlador y cómo se evita?

Ver solución

Ve un error de 100 rpm y pide 50 + 2 · 100 = 250 %, que se limita a 100 %: el motor acelera sin control. Se evita con verificaciones de plausibilidad (lectura imposible, cambio brusco), una supervisión por tiempo y el paso a un estado seguro con falla enclavada (tema 39).

7. Elegir el lazo

Elegí abierto o cerrado: (a) un LED que se enciende 2 s al apretar un botón; (b) mantener 60 °C en un recipiente; (c) un servo de aeromodelismo a un ángulo; (d) un robot que debe girar 90°.

Ver solución

(a) Abierto: no hay nada que corregir. (b) Cerrado: las pérdidas de calor son perturbaciones. (c) Abierto desde el programa, aunque el servo ya tiene un lazo cerrado interno con su potenciómetro. (d) Cerrado con giróscopo o encoders: el deslizamiento de las ruedas hace que el lazo abierto se desvíe.

12 · Síntesis

Ideas para recordar

  • Un lazo abierto calcula la acción con un modelo y no mira el resultado; un lazo cerrado mide, compara con la referencia y corrige según el error.
  • Los elementos son: referencia, sensor, error, controlador, actuador, planta y perturbación.
  • El lazo abierto es perfecto con modelo exacto y sin perturbaciones, y no detecta ninguna de las dos desviaciones.
  • Con realimentación proporcional, el error en régimen es el del lazo abierto dividido por 1 + K · Kp.
  • La realimentación también acelera: la constante de tiempo pasa a τ / (1 + K · Kp).
  • Un controlador proporcional deja un error residual; eliminarlo requiere acción integral.
  • Subir la ganancia amplifica el ruido, satura el actuador y, con retardo, amenaza la estabilidad.
  • Se combina la prealimentación (modelo) con la realimentación (corrección), y se valida el sensor porque el lazo le cree.

¿Listo para el tema 41?

Antes de avanzar, elegí un sistema que conozcas (el calentador de una ducha, el control de crucero de un auto, un termostato) y dibujá su diagrama de bloques: ¿qué es la referencia, qué mide el sensor y cuáles son las perturbaciones?

En el próximo tema estudiamos la respuesta de un sistema: tiempo de subida, sobreimpulso, error en régimen permanente y estabilidad, es decir, qué ocurre entre la orden y el valor final.