Contrôle PID dans WPILib

Note

This article focuses on in-code implementation of PID control in WPILib. For a conceptual explanation of the working of a PIDController, see Une introduction aux PIDs

Note

Pour un guide sur la mise en œuvre du contrôle PID via un environnement basé sur les commandes, regardez Contrôle PID via PIDSubsystems et PIDCommands.

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.

L’utilisation de la classe PIDController

La construction d’un objet PIDController

Note

Bien que l’objet PIDController puisse être utilisé de manière asynchrone, il ne fournit aucune fonctionnalité de sécurité des « threads » - la responsabilité du bon fonctionnement threadsafe est entièrement laissée à l’utilisateur. Par conséquant, l’utilisation asynchrone est recommandée uniquement pour les équipes avancées.

Afin d’utiliser la fonctionnalité de contrôle PID de WPILib, les utilisateurs doivent d’abord construire un objet PIDController avec les gains souhaités:

// 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 quatrième paramètre facultatif peut être fourni au constructeur, spécifiant la période durant laquelle le contrôleur sera exécuté. L’objet PIDController est principalement destiné à une utilisation synchrone à partir de la boucle du robot principal, et cette valeur par défaut est donc de 20 mSec.

Utilisation de la sortie de boucle de rétroaction

Note

Le PIDController assume que la méthode calculate() est appelée régulièrement à un intervalle cohérent avec la période configurée. Le non-respect de cette consigne entraînera un comportement de boucle involontaire.

L’utilisation de PIDController est simple: il suffit d’appeler la méthode calculate() depuis la boucle principale du robot (par exemple la méthode autonomousPeriodic() du 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))

La vérification des erreurs

Note

getPositionError() et getVelocityError() sont définis avec l’hypothèse que la boucle contrôle une position - si toutefois la boucle contrôle plutôt une vitesse, ces dernières renvoient respectivement l’erreur de vitesse et l’erreur d’accélération.

L’erreur actuelle de la variable de processus mesurée est retournée par la fonction getPositionError() , tandis que sa dérivée est retournée par la fonction getVelocityError ():

La spécification et la vérification des tolérances

Note

Si seulement une tolérance de position est spécifiée, la tolérance de vitesse par défaut est l’infini.

Note

Comme ci-dessus, «position» fait référence à la mesure de la variable de processus et «vitesse» à sa dérivée - ainsi, pour une boucle de vitesse, ce sont en fait la vitesse et l’accélération, respectivement.

Parfois, il est utile de savoir si un contrôleur a suivi le point de consigne dans une tolérance donnée - par exemple, pour déterminer si une commande doit être terminée ou (tout en suivant un profil de mouvement) si le mouvement est entravé et doit être re -prévu.

Pour ce faire, nous devons d’abord spécifier les tolérances avec la méthode setTolerance(); ensuite, nous pouvons le vérifier avec la méthode 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()

Réinitialisation du contrôleur

Il est parfois souhaitable d’effacer l’état interne (le paramètre le plus important étant l’accumulateur intégré) d’un PIDController, car il peut ne plus être valide (par exemple, lorsque le PIDController a été désactivé puis réactivé) . Cela peut être accompli en appelant la méthode reset().

La définition d’une valeur d’intégrateur maximale

Note

Les intégrateurs introduisent l’instabilité et l’hystérésis dans les systèmes de boucle de rétroaction. Il est fortement recommandé aux équipes d’éviter d’utiliser le gain intégral sauf si aucune autre solution ne fera l’affaire - très souvent, les problèmes qui peuvent être résolus avec un intégrateur peuvent être mieux résolus en utilisant un outil plus précis, comme le feedforward.

Un problème typique rencontré lors de l’utilisation de la rétroaction intégrale est un « emballement » excessif qui fait que le système dépasse largement le point de consigne. Cela peut être atténué de plusieurs manières - la classe WPILib PIDController applique un limiteur de plage d’intégrateur pour aider les équipes à surmonter ce problème.

Par défaut, la contribution de sortie totale du gain intégral est limitée entre -1,0 et 1,0.

Les limites de plage peuvent être augmentées ou diminuées à l’aide de la méthode setIntegratorRange().

// 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)

Le réglage de l’entrée continue

Avertissement

Si votre mécanisme n’est pas capable d’un mouvement de rotation entièrement continu (par exemple, une tourelle sans bague collectrice, dont les fils se tordent en tournant), il ne faut pas alors utiliser une entrée continue sauf si vous avez mis en place une fonction de sécurité supplémentaire pour empêcher le mécanisme de passer sa limite!

Avertissement

La fonction d’entrée continue ne limite pas automatiquement vos valeurs d’entrée - assurez-vous que vos valeurs d’entrée, lorsque vous utilisez cette fonction, ne sont jamais en dehors de la plage spécifiée!

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.

Pour configurer un PIDController pour le faire automatiquement, utilisez la méthode 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)

Le cramponnage de la sortie du contrôleur

// 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)