Contrôle PID: L'ajout d'un délai avant la prochaine boucle est-il une bonne idée?
J'implémente le contrôle PID en C ++ pour faire tourner un robot à entraînement différentiel d'un nombre précis de degrés, mais j'ai de nombreux problèmes.
Sortie prématurée de la boucle de contrôle en raison de l'exécution rapide de la boucle
Si le robot mesure son erreur comme étant inférieure à 0,5 degré, il sort de la boucle de contrôle et considère le virage comme «terminé» (le 0,5 est une valeur aléatoire que je pourrais changer à un moment donné). Il semble que la boucle de contrôle fonctionne si rapidement que le robot peut tourner à une vitesse très élevée, dépasser le point de consigne et quitter la boucle / couper les puissances du moteur, car il était au point de consigne pendant un court instant. Je sais que c'est tout le but du contrôle PID, pour atteindre avec précision le point de consigne sans dépassement, mais ce problème rend très difficile le réglage des constantes PID. Par exemple, j'essaie de trouver une valeur de kp telle qu'il y ait une oscillation constante, mais il n'y a jamais d'oscillation car le robot pense qu'il a "fini" une fois qu'il a passé le point de consigne. Pour résoudre ce problème,J'ai implémenté un système où le robot doit être au point de consigne pendant un certain temps avant de sortir, et cela a été efficace, permettant une oscillation, mais le problème de la sortie précoce de la boucle semble être un problème inhabituel et ma solution peut être incorrect.
Le terme D n'a aucun effet en raison de la rapidité d'exécution
Une fois que j'ai fait osciller le robot de manière contrôlée en utilisant uniquement P, j'ai essayé d'ajouter D pour éviter le dépassement. Cependant, cela n'a eu aucun effet la plupart du temps, car la boucle de contrôle tourne si vite que 19 boucles sur 20, le taux de changement d'erreur est de 0: le robot ne bougeait pas ou ne bougeait pas assez pour cela. à mesurer à ce moment-là. J'ai imprimé le changement d'erreur et le terme dérivé de chaque boucle pour le confirmer et j'ai pu voir que ceux-ci seraient tous les deux à 0 pendant environ 20 cycles de boucle avant de prendre une valeur raisonnable, puis de revenir à 0 pendant 20 autres cycles. Comme je l'ai dit, je pense que c'est parce que les cycles de boucle sont si rapides que le robot n'a littéralement pas assez bougé pour une sorte de changement notable d'erreur.C'était un gros problème car cela signifiait que le terme D n'avait essentiellement aucun effet sur le mouvement du robot car il était presque toujours 0. Pour résoudre ce problème, j'ai essayé d'utiliser la dernière valeur non nulle du dérivé à la place de toute valeur 0, mais cela ne fonctionnait pas bien, et le robot oscillerait de manière aléatoire si le dernier dérivé ne représentait pas le taux actuel de changement d'erreur.
Remarque: J'utilise également une petite anticipation pour le coefficient de frottement statique, et j'appelle cette anticipation "f"
Dois-je ajouter un délai?
J'ai réalisé que je pense que la source de ces deux problèmes est la boucle qui s'exécute très très rapidement, donc j'ai pensé à ajouter une instruction d'attente à la fin de la boucle. Cependant, cela semble être une mauvaise solution pour ralentir intentionnellement une boucle. Est-ce une bonne idée?
turnHeading(double finalAngle, double kp, double ki, double kd, double f){
std::clock_t timer;
timer = std::clock();
double pastTime = 0;
double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
const double initialHeading = getHeading();
finalAngle = angleWrapDeg(finalAngle);
const double initialAngleDiff = initialHeading - finalAngle;
double error = angleDiff(getHeading(), finalAngle);
double pastError = error;
double firstTimeAtSetpoint = 0;
double timeAtSetPoint = 0;
bool atSetpoint = false;
double integral = 0;
double derivative = 0;
double lastNonZeroD = 0;
while (timeAtSetPoint < .05)
{
updatePos(encoderL.read(), encoderR.read());
error = angleDiff(getHeading(), finalAngle);
currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
double dt = currentTime - pastTime;
double proportional = error / fabs(initialAngleDiff);
integral += dt * ((error + pastError) / 2.0);
double derivative = (error - pastError) / dt;
//FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
// if(epsilonEquals(derivative, 0))
// {
// derivative = lastNonZeroD;
// }
// else
// {
// lastNonZeroD = derivative;
// }
double power = kp * proportional + ki * integral + kd * derivative;
if (power > 0)
{
setMotorPowers(-power - f, power + f);
}
else
{
setMotorPowers(-power + f, power - f);
}
if (fabs(error) < 2)
{
if (!atSetpoint)
{
atSetpoint = true;
firstTimeAtSetpoint = currentTime;
}
else //at setpoint
{
timeAtSetPoint = currentTime - firstTimeAtSetpoint;
}
}
else //no longer at setpoint
{
atSetpoint = false;
timeAtSetPoint = 0;
}
pastTime = currentTime;
pastError = error;
}
setMotorPowers(0, 0);
}
turnHeading(90, .37, 0, .00004, .12);
Réponses
Ne désengagez pas votre manette.
Le but d'un contrôleur n'est pas seulement de diriger votre système vers le point de consigne souhaité en fonction d'une réponse dynamique prédéterminée, mais également de contrer les facteurs externes potentiels qui peuvent entraver cette tâche. Pensez à une perturbation qui éloignera le système du point de consigne une fois qu'il a été atteint. Ainsi, le contrôleur doit toujours être opérationnel (sauf si vous devez changer la tâche elle-même et par conséquent changer également le contrôleur).
Pour cela, il faudrait certainement additionner la partie intégrale, qui est responsable de l'atteinte d'une erreur nulle en régime permanent en présence de quantités non modélisées et de perturbations externes.
C'est une ressource assez liée: https://robotics.stackexchange.com/a/19198/6941.
Oubliez le dérivé.
95% des contrôleurs PID dans les industries sont des contrôleurs PI (voir "Feedback Systems" Astrom, Murray) car la partie D ne peut jouer un rôle significatif qu'avec des processus lents (comme ceux concernés par la régulation de la température et du niveau du réservoir). Ce n'est certainement pas votre cas. D'une manière ou d'une autre, les ressources liées aux difficultés des termes dérivés sont:
- https://robotics.stackexchange.com/a/21556/6941.
- https://robotics.stackexchange.com/a/21555/6941.
Une fois que j'ai fait osciller le robot de manière contrôlée en utilisant uniquement P, j'ai essayé d'ajouter D pour éviter le dépassement.
Eh bien, il semble que vous suivez les prescriptions de Ziegler-Nichols pour régler votre contrôleur. Vous devez vous en tenir à des tableaux qui vous donnent des estimations des gains. Cependant, ce sont des heuristiques et il est probable que cela ne fonctionnera pas dans votre cas.
Déposez simplement la partie D et concentrez-vous sur le contrôleur PI. Il existe également une autre variante ZN qui ne repose pas sur des oscillations générées intentionnellement:https://robotics.stackexchange.com/a/21616/6941.
N'ajoutez jamais de retards au système.
Les retards sont des méchants à gérer dans une boucle de contrôle, et les pires bêtes que les ingénieurs doivent combattre ( regardez ça 🎥 ) car ils réduisent considérablement la marge de phase poussant l'ensemble du système vers l'instabilité.
Si vous estimez que votre boucle est trop rapide, appliquez la mise en forme d'entrée au point de consigne (avec par exemple une trajectoire à secousse minimale ) pour lisser le début de pas net. Le système en boucle fermée réagira plus gracieusement. Une autre possibilité est que votre gain P soit trop élevé: il suffit alors de désaccorder le contrôleur.
À cet égard, si vous insérez le terme intégral I, vous devrez raisonner sur le temps d'échantillonnage $T_s$ ainsi que.
Il serait préférable de déterminer une fréquence d'échantillonnage fixe pour votre contrôleur. Une règle empirique très approximative est que quel que soit le temps de stabilisation dont vous avez besoin pour sortir de la boucle une fois qu'elle fonctionne dans le régime linéaire , votre intervalle d'échantillonnage doit être entre 10 fois et 100 fois inférieur au temps de stabilisation. En d'autres termes, la fréquence d'échantillonnage doit être 10 à 100 fois plus rapide que la bande passante de boucle souhaitée.
Comme petit bonus supplémentaire, cela signifie que vous pouvez calculer dt en dehors de la boucle
De préférence, la fréquence d'échantillonnage doit être dictée par le matériel, par exemple par un temporisateur matériel, et encore mieux l'échantillonnage de la position doit être fait par le matériel, déclenché par un temporisateur. Cela facilite considérablement les contraintes en temps réel sur le logiciel.
Si vous utilisez une fréquence d'échantillonnage raisonnable et que vous n'enregistrez généralement pas les changements dans votre encodeur, alors votre encodeur n'a pas assez d'étapes.
Je ne suis pas d'accord avec @Ugo_Pattachini, en ce sens que si la boucle implique un moteur et que votre contrôleur est suffisamment propre, une action différentielle peut être bénéfique. Mais il y a de fortes chances qu'il doive être limité en bande (c'est-à-dire que vous avez besoin d'un contrôleur de retard), et si vous effectuez un réglage PID au siège du pantalon, il y a de fortes chances que vous n'ayez pas les outils pour réglez correctement la limitation de bande.
Cependant, si vous ne pouvez pas appliquer un timing très serré lors de l'échantillonnage de l'encodeur, n'essayez même pas d'utiliser le contrôle dérivé. Des temps d'échantillonnage irréguliers et une certaine vitesse sur votre moteur entraîneront un bruit supplémentaire, et les contrôleurs dérivés ont tendance à amplifier le bruit. Si vous ne pouvez pas organiser un signal propre à contrôler, le contrôle dérivé ne conviendra pas.