Bucles de control#

Los bucles de control son programas informáticos que se utilizan para accionar sistemas de transmisión de potencia (como un chasis o un deslizador lineal) de forma rápida y controlada. Los bucles de control no solo permiten accionar mecanismos con rapidez sin temor a perder el control, sino que, en muchos casos, ayudan a preservar la longevidad de los mecanismos al reducir los cambios rápidos de la tensión aplicada al motor.

¿Qué es un Error?#

Lo primero que hay que definir cuando se habla de bucles de control es el concepto de error.

El error se define como la diferencia entre donde estás y donde quieres estar. Por ejemplo, digamos que le dices a tu chasis que conduzca a 30 pulgadas por segundo, pero en realidad, en un momento dado, el chasis está conduciendo a 28 pulgadas por segundo. Como \(30-28=2\), el error de la velocidad del chasis en ese momento \(T\) es de 2 pulgadas por segundo. En otras palabras, en un tiempo :math:e`t=T`, \(e(t)=2\).

PID#

Un controlador PID (o controlador Proporcional Integral Derivativo) es un lazo de control que utiliza únicamente el error para controlar el sistema. PID es una forma de lazo de control de realimentación, o control de lazo cerrado. Esto significa que se necesitan datos sobre la variable que se está controlando para que el lazo controle esa variable. En este caso, se requiere información sobre el error del sistema para controlar el sistema con un controlador PID.

El cálculo opcional#

La siguiente ecuación representa la definición matemática rigurosa de la salida de un controlador PID \(f\) en cualquier tiempo \(t\):

\[f(t) = K_p e(t) + K_i \int_o^t e(t) \mathrm{d}t + K_d \frac{\mathrm{d}e(t)}{mathrm{d}t}\]

donde \(K_p\), \(K_i\) y \(K_d\) son constantes y \(e(t)\), como ya se ha dicho, es el error del sistema.

Si no tiene experiencia con el cálculo, no se preocupe; aunque el PID tiene sus raíces fundamentalmente en el cálculo, no necesita ninguna experiencia en cálculo para poder entenderlo, sólo álgebra básica. Sin embargo, te recomendamos que leas el resto de la sección independientemente de tu experiencia con el cálculo, ya que la fórmula por sí sola no te explica por qué funciona.

Simplificación de la fórmula PID#

He aquí una versión simplificada de la fórmula PID: \(f(t)=K_p P(t)+K_i I(t)+K_d D(t)\)

Todo lo que hemos hecho es simplemente tomar la fórmula completa y sustituir parte de los términos por funciones: \(P(t)\), \(I(t)\), y \(D(t)\).

El término proporcional#

El primer componente de la función, \(K_p P(t)\), es con diferencia el más sencillo y fácil de entender, ya que \(P(t) = e(t)\). Por poner un ejemplo, supongamos que \(K_i=0\) y \(K_d=0\) (un controlador PID con sólo una constante proporcional se conoce como controlador P). ¿Cómo se comportará el sistema? Pues bien, si el error es grande, la salida será grande. Del mismo modo, si el error es pequeño, la salida será pequeña. Además, idealmente, dado el tiempo suficiente, el sistema siempre se aproxima a su destino, suponiendo que \(K_p\) sea del signo correcto.

Digamos que aplicamos esto a un chasis. Quieres conducir una distancia \(D\), y decides ajustar las potencias de tus motores utilizando un controlador P para lograrlo. En este caso, tu error es la distancia a la que se encuentra el robot de la ubicación deseada. Cuando empiezas a avanzar, tu error es grande, por lo que avanzas rápidamente, lo cual es deseable. Después de todo, no estás preocupado por sobrepasar el objetivo si estás lejos de él.

Pero a medida que la distancia del robot al objetivo se acerque a 0, empezarás a reducir la velocidad, ganando más control sobre el robot. En el mejor de los casos, cuando el error sea cero, el robot se detendrá y habrás llegado a tu destino. Si te pasas, el error será negativo y el robot retrocederá, repitiendo el proceso.

El término derivado#

Este término, \(K_d D(t)\), pretende amortiguar la tasa de cambio del error. En otras palabras, intenta mantener el error constante. ¿Cómo se consigue esto?

Bueno, para aquellos de ustedes con el cálculo en su haber, \(D(t)=\frac{de(t)}{dt}\). Para aquellos sin experiencia en cálculo, representa la rapidez con que el error está cambiando. Gráficamente, \(D(t)\) es simplemente la pendiente del error en cualquier momento dado \(t\).

Está pendiente puede calcularse haciendo un seguimiento del error a lo largo de iteraciones sucesivas del bucle de control. Una iteración se produce en el tiempo \(t_n\) con un error de \(e(t_n)\). En la siguiente iteración, el tiempo es \(t_{n+1}\) con un error de \(e(t_{n+1})\). Por lo tanto, para encontrar \(D(t)\), basta con encontrar la pendiente de \(e(t)\) dados estos dos puntos.

El término integral#

Hay que reconocer que el término integral es el menos importante para los bucles de control PID FTC®. Con un ajuste adecuado de \(K_p\) y \(K_d\), a menudo se puede poner \(K_i\) a 0 y listo.

Sin embargo, puede seguir siendo útil en algunos casos. Al igual que el término derivativo, el término integral pretende corregir el rebasamiento. Si el sistema cree que ha llegado a su destino, se detendrá, incluso cuando, de hecho, el error aún no sea 0. Tal vez el motor ya no reciba suficiente potencia para moverse. Pues bien, dado el tiempo suficiente, el término integral aumentará la salida (en este caso, la potencia del motor), provocando el movimiento hacia el destino.

Para explicarlo sin cálculos, el término integral básicamente suma el error en un intervalo de tiempo específico. Para ello, el error en cada iteración del bucle se suma a una variable (en este caso, \(I(t)\)).

Sin embargo, sumar el error de esta forma tiene un desafortunado efecto secundario: cuanto más tarda el bucle en completar una iteración, más lentamente aumenta esta suma, lo que obviamente no es deseable, ya que no queremos que el retraso afecte a cómo se mueve el robot. Para compensar esto, antes de que el error se añada a \(I(t)\), se multiplica por el tiempo que tardó el bucle anterior en completarse, o \(t_{n+1}-t_n\), evitando que el retraso haga que la suma del sistema sea más lenta.

Supongamos que el robot se detiene cerca del objetivo. La combinación P y D no son lo suficientemente fuertes como para moverlo hacia delante hasta el destino. Puedes ajustar \(K_p\) y \(K_d\) para compensar (esto es lo recomendado), o puedes añadir el término integral para aumentar el rendimiento (esto también funciona, pero requiere más atención y ajuste para conseguir el mismo resultado).

PID Pseudocódigo#

while True:
   current_time = get_current_time()
   current_error = desire_position-current_position

   p = k_p * current_error

   i += k_i * (current_error * (current_time - previous_time))

   if i > max_i:
       i = max_i
   elif i < -max_i:
       i = -max_i

   D = k_d * (current_error - previous_error) / (current_time - previous_time)

   output = p + i + d

   previous_error = current_error
   previous_time = current_time

Ajuste de un bucle PID#

Lo más importante que hay que saber al sintonizar un bucle PID es cómo afecta cada uno de los términos a la salida. Esto puede permitirle ver qué ganancias necesitan ser ajustadas.

Por ejemplo, si no se alcanza el objetivo, sino que la consigna empieza a oscilar en torno al objetivo, significa que no hay suficiente ganancia D. Si finalmente se alcanza el objetivo, aunque muy lentamente, significa que no hay suficiente ganancia P o que la ganancia D es demasiado alta.

En resumen, la variable P conduce el error hacia cero, la variable I corrige el error de estado estacionario y la variable D amortigua los efectos de la variable P, más a medida que el error se aproxima a cero, lo que evita el rebasamiento.

El método más común para sintonizar un controlador PID es el siguiente:

  1. Poner a cero las ganancias I y D

  2. Aumenta la ganancia P hasta que haya oscilaciones alrededor del objetivo

  3. Aumentar la ganancia D hasta que no se produzca sobreimpulso

  4. Si hay error de estado estacionario, aumente la ganancia I hasta que se corrija

Algo importante a tener en cuenta es que la mayoría de los sistemas no necesitan tanto control I como D. Generalmente, los sistemas sin mucha fricción no necesitan un término I, pero necesitarán más control D. Los sistemas con mucha fricción, por otro lado, generalmente no necesitan control D porque la fricción facilita la deceleración, pero necesitan control I porque la fricción impide que el sistema alcance el objetivo de otro modo.

Para una explicación más detallada, haga clic aquí

Controlador PID integrado#

Para situaciones en las que se necesita controlar la velocidad o la posición de un solo motor, se puede utilizar el controlador PID incorporado. El PID puede activarse cambiando el modo de ejecución a RUN_USING_ENCODER.

Consejo

Muchos malinterpretan el uso de RUN_USING_ENCODER, muchos pueden confundir que es necesario utilizar este modo para que los encoders funcionen en absoluto, pero esto no es cierto. En su lugar, RUN_USING_ENCODER habilita la realimentación de velocidad utilizando el encoder. Si estás utilizando un controlador PID externo como el que implementas, generalmente, se recomienda que utilices RUN_WITHOUT_ENCODER.

Para la documentación oficial sobre el controlador PID integrado, vea aquí

Depuración del controlador PID integrado#

Problema

Solución

El motor va a máxima velocidad independientemente de la consigna de velocidad

La mayoría de las veces esto ocurre cuando ocurre una de estas dos cosas:

#1: Su codificador no está conectado correctamente.

Diagnóstico: Registre la posición de su codificador en telemetría, si la posición oscila entre 0 - 1 asegúrese de que tiene el cable correcto y que está asentado correctamente.

#2: Su motor va en la dirección equivocada.

Diagnóstico: Registra tu velocidad en telemetría, si tienes una velocidad de referencia positiva y la salida es negativa o viceversa entonces tu motor está enchufado al revés.

El motor no alcanza la velocidad máxima con .setPower

Usa el método .setVelocity como parte de DcMotorEx o usa RUN_WITHOUT_ENCODER con un controlador PID externo.

Controlador PID Frecuencia de muestreo#

Para los equipos que desean obtener el máximo rendimiento de su controlador PID, es esencial tener en cuenta la Frecuencia de muestreo del controlador. La frecuencia de muestreo es cuando el controlador actualiza su salida dados los nuevos datos del sensor. Tasas de muestreo más altas permiten un control más estable y permiten el uso de coeficientes PID más significativos para reducir el tiempo de asentamiento. Vea este video para ver cómo la frecuencia de muestreo afecta a la estabilidad en un ejemplo práctico de control de motores. El controlador PID incorporado está bloqueado a una frecuencia de refresco de 20hz (frecuencia de muestreo de 50ms). Muchos de los mejores equipos de FTC optimizan sus bucles de robot para que funcionen hasta a 80hz, consiguiendo un control mucho más estable con un PID externo.

Control Feedforward#

El control feedforward es un método de lo que se conoce como control «en bucle abierto». Es lo contrario del control en bucle cerrado y la principal diferencia es que el control directo no utiliza sensores para controlar el sistema. En su lugar, «predice» la entrada deseada basándose en un modelo.

Normalmente, el feedforward se utiliza para controlar las tasas de cambio o para combatir las perturbaciones conocidas del sistema.

El feedforward es muy potente porque es inmune al ruido o a otros errores de los sensores. Esto se debe a que no mide activamente el sistema, sino que predice la entrada deseada. Sin embargo, esto también significa que no es muy bueno para corregir errores. Por eso se suele utilizar junto con un controlador de bucle cerrado, como el PID.

Modelo Feedforward Kv Ka#

El feedforward más común y el utilizado por librerías como road-runner es el modelo feedforward Kv-Ka:

\[f(t) = K_v \cdot \text{Velocity} + K_a \cdot \text{Acceleration}\]

Donde \(K_v\) es la ganancia de velocidad, \(K_a\) es la ganancia de aceleración, y \(f(t)\) es la salida feedforward enviada a tus motores.

Estas ganancias pueden estimarse proporcionando al controlador una serie de rampas de entrada (como las calculadas con un perfil de movimiento), midiendo la salida y cambiando las ganancias hasta que el robot alcance el movimiento deseado.

Nota

Las ganancias cambiarán en función de la masa del robot, la fricción y otros factores. Se recomienda volver a calcular estas ganancias cada vez que se realice un cambio significativo en el robot.

Kv Ka Feedforward Pseudocódigo#

while True:
     targetVelocity = getTargetVelocity(time)
     targetAcceleration = getTargetAcceleration(time)
     output = targetVelocity * Kv + targetAcceleration * Ka;

Alimentación por fricción estática#

En todos los sistemas existe cierta fricción estática. Esto significa que el mecanismo del robot no se moverá hasta que se aplique una cierta cantidad de energía. Esto se puede modelar añadiendo un término feedforward constante en la dirección en la que quieres moverte.

while True:
     error = desire_position - current_position;
     sign = signum(error) # sign of error, will be -1, 0, or 1
     output = sign * staticFriction + PID(error); # PID Controller + Friction Feedforward

Perfiles de movimiento#

Truco

Los perfiles de movimiento no son un tipo específico de control loop, sino más bien una técnica que funciona bien en combinación con otros lazos de control, como el PID y el feedforward.

La creación de perfiles de movimiento es una técnica popularizada en FRC® que está empezando a abrirse camino en FTC. Un perfil de movimiento es una función utilizada para modificar la velocidad de un sistema de transmisión de potencia de forma controlada y coherente, cambiando la velocidad deseada gradualmente en lugar de instantáneamente.

Vamos a ilustrar esto con un ejemplo: digamos que usted quiere que su chasis, que inicialmente está inmóvil, avance a toda velocidad. Normalmente, en el código se pondrían todos los motores de la transmisión a máxima potencia. Sin embargo, esto puede ser problemático porque, aunque le digas a los motores que se muevan a toda velocidad instantáneamente, la transmisión tarda un tiempo en alcanzar la velocidad máxima. Esto puede conducir a movimientos incontrolados que tienen el potencial de hacer que la autonomía sea menos consistente y, quizás más importante, dañar los mecanismos.

Los perfiles de movimiento intentan resolver este problema.

Ventajas#

  • Movimientos más controlados y predecibles

  • Reduce el cambio rápido de la tensión aplicada al motor

Desventajas#

  • Puede ser más lento

Existen dos tipos principales de perfiles de movimiento: **Los perfiles trapezoidales y los perfiles de curva S. Los perfiles trapezoidales aceleran el sistema a un ritmo constante, mientras que los perfiles de curva S suponen que la sacudida (la velocidad a la que cambia la aceleración) es constante. Dado que los perfiles S-Curve no son óptimos para controlar trayectorias en 2d (como la conducción) y existen para reducir el deslizamiento (que normalmente sólo se produce al conducir en FTC), se recomiendan los perfiles trapezoidales para la mayoría de las aplicaciones FTC.

Los perfiles trapezoidales deben su nombre a la forma del gráfico de la velocidad en función del tiempo:

Los gráficos de posición en el tiempo, velocidad en el tiempo y aceleración con el tiempo se grafican para un perfil de movimiento trapezoidal.

Estas son las «funciones mágicas» para la velocidad y la aceleración en el tiempo a las que se aludió en la sección feedforward.#

He aquí un pseudocódigo para un perfil trapezoidal:

while True:
   current_velocity = get_current_velocity()
   current_time = get_current_time()

   direction_multiplier = 1

   if position_error < 0:
       direction_multiplier = -1

   # if maximum speed has not been reached
   if MAXIMUM_SPEED > abs(current_velocity):
       output_velocity = current_velocity + direction_multiplier * MAX_ACCELERATION * (current_time - previous_time)
       output_acceleration = MAX_ACCELERATION

   #if maximum speed has been reached, stay there for now
   else:
       outputVelocity = MAXIMUM_SPEED
       outputAcceleration = 0

   #if we are close enough to the object to begin slowing down
   if position_error <= (output_velocity * output_velocity) / (2 * MAX_ACCELERATION)):
       output_velocity = current_velocity - direction_multiplier * MAX_ACCELERATION * (current_time - previous_time)
       output_acceleration = -MAX_ACCELERATION

   previous_time = current_time

Los resultados del pseudocódigo anterior se utilizan a continuación en un bucle feedforward y/o PID para controlar la posición del sistema de forma suave y predecible.

En este Jupyter Notebook se puede encontrar un ejemplo más avanzado de las matemáticas para la generación de perfiles de movimiento tal y como se utilizan en la Librería Road Runner.