Control PID en WPILib
Nota
This article focuses on in-code implementation of PID control in WPILib. For a conceptual explanation of the working of a PIDController, see Introducción a PID
Nota
For a guide on implementing PID control through the command-based framework, see PID Control in Command-based.
WPILib supports PID control of mechanisms through the PIDController
class (Java, C++, Python
). This class handles the feedback loop calculation for the user, as well as offering methods for returning the error, setting tolerances, and checking if the control loop has reached its setpoint within the specified tolerances.
Usando la Clase PIDController
Construyendo un PIDController
Nota
Mientras que PIDController
puede ser usado de manera asincronica, este no provee ninguna función de seguridad de hilos - el asegurar una operación segura para hilos se deja completamente al usuario, y de este modo el uso asincronico es recomendado solamente para equipos avanzados.
Para usar las funcionalidades de control PID de WPILib, los usuarios deben primero construir un objeto PIDController
con las ganancias deseadas:
// Creates a PIDController with gains kP, kI, and kD
PIDController pid = new PIDController(kP, kI, kD);
// Creates a PIDController with gains kP, kI, and kD
frc::PIDController pid{kP, kI, kD};
from wpimath.controller import PIDController
# Creates a PIDController with gains kP, kI, and kD
pid = PIDController(kP, kI, kD)
Un cuarto parámetro opcional puede proveerse al constructor, especificando el periodo en el cual el controlador será ejecutado. El objeto PIDController
está echo principalmente para uso sincrónico desde el loop principal del robot, y este valor está puesto por default a 20ms.
Usando la salida del loop de retroalimentación.
Nota
El PIDController
asume que el método calculate()
es llamado regularmente en un intervalo consistente con el periodo configurado. La falla en esto resultará un resultado no deseado en el comportamiento del loop.
Usar un construido PIDController
es simple: Solo llame el método calculate()
desde el loop principal del robot (ejemplo: El método autonomousPeriodic()
del robot)
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
motor.set(pid.calculate(encoder.getDistance(), setpoint));
// Calculates the output of the PID algorithm based on the sensor reading
// and sends it to a motor
motor.Set(pid.Calculate(encoder.GetDistance(), setpoint));
# Calculates the output of the PID algorithm based on the sensor reading
# and sends it to a motor
motor.set(pid.calculate(encoder.getDistance(), setpoint))
Checando errores
Nota
getPositionError()
y getVelocityError()
son nombrados asumiendo que el loop está controlando una posición - para un loop que está controlando velocidad, estos regresan el error de velocidad y el error de la aceleración, respectivamente.
El error actual del proceso medido se regresa por medio de la función getPositionError()
, mientras que su derivada regresa por medio de la función getVelocityError()
:
Especificando y checando tolerancias
Nota
Si solo una tolerancia de posición es especificada, la tolerancia de velocidad se pone como default a infinito.
Nota
Tal como arriba, «position» se refiere al proceso de medición variable, y «velocity» a su derivada - de esta forma, para un loop de velocidad, estos son, de hecho, velocidad y aceleración respectivamente.
Ocasionalmente, es útil saber si un controlador ha rastreado el setpoint a una tolerancia dada - por ejemplo, para determinar si un comando debería ser finalizado, o (mientras siguiendo un perfil de movimiento) si el movimiento está siendo impedido y necesita ser planeado otra vez.
Para hacer esto, primero necesitamos especificar las tolerancias con el método setTolerance()
; entonces, podemos checarlo con el método atSetpoint()
.
// Sets the error tolerance to 5, and the error derivative tolerance to 10 per second
pid.setTolerance(5, 10);
// Returns true if the error is less than 5 units, and the
// error derivative is less than 10 units
pid.atSetpoint();
// Sets the error tolerance to 5, and the error derivative tolerance to 10 per second
pid.SetTolerance(5, 10);
// Returns true if the error is less than 5 units, and the
// error derivative is less than 10 units
pid.AtSetpoint();
# Sets the error tolerance to 5, and the error derivative tolerance to 10 per second
pid.setTolerance(5, 10)
# Returns true if the error is less than 5 units, and the
# error derivative is less than 10 units
pid.atSetpoint()
Reiniciando el controlador
Algunas veces es deseable aclarar el estado interno (más importante, el acumulador integral) de un PIDController
, teniendo en cuenta que puede que ya no sea valido (ejemplo: cuando el PIDController
ha sido desactivado y luego reactivado). Esto puede lograrse llamando el método reset()
.
Ajustando un valor integrador máximo.
Nota
Los integradores introducen inestabilidad e histéresis al sistema de retroalimentación del loop. Se recomienda fuertemente que los equipos eviten usar ganancias integrales a menos de que ninguna otra opción sirva - muy a menudo, los problemas que pueden ser resueltos con un integrador pueden ser resueltos por medio del uso de un feedforward más preciso.
A typical problem encountered when using integral feedback is excessive «wind-up» causing the system to wildly overshoot the setpoint. This can be alleviated in a number of ways - the WPILib PIDController
class enforces an integrator range limiter to help teams overcome this issue.
By default, the total output contribution from the integral gain is limited to be between -1.0 and 1.0.
The range limits may be increased or decreased using the setIntegratorRange()
method.
// The integral gain term will never add or subtract more than 0.5 from
// the total loop output
pid.setIntegratorRange(-0.5, 0.5);
// The integral gain term will never add or subtract more than 0.5 from
// the total loop output
pid.SetIntegratorRange(-0.5, 0.5);
# The integral gain term will never add or subtract more than 0.5 from
# the total loop output
pid.setIntegratorRange(-0.5, 0.5)
Disabling Integral Gain if the Error is Too High
Another way integral «wind-up» can be alleviated is by limiting the error range where integral gain is active. This can be achieved by setting IZone
. If the error is more than IZone
, the total accumulated error is reset, disabling integral gain. When the error is equal to or less than IZone, integral gain is enabled.
By default, IZone
is disabled.
IZone
may be set using the setIZone()
method. To disable it, set it to infinity.
// Disable IZone
pid.setIZone(Double.POSITIVE_INFINITY);
// Integral gain will not be applied if the absolute value of the error is
// more than 2
pid.setIZone(2);
// Disable IZone
pid.SetIZone(std::numeric_limits<double>::infinity());
// Integral gain will not be applied if the absolute value of the error is
// more than 2
pid.SetIZone(2);
# Disable IZone
pid.setIZone(math.inf)
# Integral gain will not be applied if the absolute value of the error is
# more than 2
pid.setIZone(2)
Ajustando entradas continuas
Advertencia
Si su mecanismo no es capaz de usar un movimiento de rotación continua completo (ejemplo: una torreta con cables que se giran mientras rota), no active la entrada continua a menos de que tenga implementada una característica de seguridad adicional para prevenir que el mecanismo se mueva más allá de sus limites.
Some process variables (such as the angle of a turret) are measured on a circular scale, rather than a linear one - that is, each «end» of the process variable range corresponds to the same point in reality (e.g. 360 degrees and 0 degrees). In such a configuration, there are two possible values for any given error, corresponding to which way around the circle the error is measured. It is usually best to use the smaller of these errors.
Para configurar un PIDController
para automáticamente hacer esto, use el método enableContinuousInput()
:
// Enables continuous input on a range from -180 to 180
pid.enableContinuousInput(-180, 180);
// Enables continuous input on a range from -180 to 180
pid.EnableContinuousInput(-180, 180);
# Enables continuous input on a range from -180 to 180
pid.enableContinuousInput(-180, 180)
Salida del controlador de sujeción
// Clamps the controller output to between -0.5 and 0.5
MathUtil.clamp(pid.calculate(encoder.getDistance(), setpoint), -0.5, 0.5);
// Clamps the controller output to between -0.5 and 0.5
std::clamp(pid.Calculate(encoder.GetDistance(), setpoint), -0.5, 0.5);
# Python doesn't have a builtin clamp function
def clamp(v, minval, maxval):
return max(min(v, maxval), minval)
# Clamps the controller output to between -0.5 and 0.5
clamp(pid.calculate(encoder.getDistance(), setpoint), -0.5, 0.5)