[ARDUINO] Asservissement grâce au PID

Le problème exposé dans ce sujet a été résolu.

Comme l’a signalé Aabu, sur le dernier graphe, on perd toute la vision sur les erreurs et les commandes négatives. Je suis volontairement très chiant sur les graphiques, parce que c’est un outil très important en sciences pour analyser et communiquer. En plus, savoir construire un graphique qui porte l’information qu’on veut communiquer, ça apprend aussi à repérer comment les graphiques des autres ont été construits, et ce qu’il veulent communiquer, ou au contraire, minimiser. Bref c’est aussi utile pour faire ses choix politiques.

Je vais reprendre la version précédente, on connais assez bien le sujet pour se débrouiller avec.

Image utilisateur Source:Jupiter41

Déjà en regardant le début de la courbe, on a une erreur faible, négative, et à peu près stable. La réponse est une commande négative qui augmente régulièrement.

Est-il normal d’avoir une commande négative quand l’erreur est négative ? En fait ça dépend du procédé. Concrètement, il suffit de retourner le capteur, et on inverse le signe de l’erreur. A ce stade, on ne peut pas dire quel signe sera le bon. on va juste vérifier si c’est cohérent.

là, globalement, l’erreur est faible, donc la réponse proportionnelle est faible, d’ailleurs on ne la voit pas (la commande est quasi nulle au début de la courbe), en regardant les chiffres, elle est à -57, donc négative, comme l’erreur est stable, on n’a pas de dérivée, et comme l’erreur n’est pas corrigée, l’intégrale augmente, et on fait -20 sur la commande à chaque pas, pour corriger l’erreur résiduelle. Tout a l’air d’aller dans le bon sens. Voyons quand l’erreur varie.

Là je vais regarder le pic vers 450ms. L’erreur est positive, donc le terme proportionnel est positif, l’intégral continue à intégrer de l’erreur, mais l’erreur fait un rapide retour vers 0. A ce moment, la courbe de commande devient quasiment horizontale, stable. Le terme dérivé a bien inhibé la variation de la commande, le système allant déjà vers la position de consigne.

Du coup, on a un système qui a l’air de fonctionner comme attendu. C’est une bonne nouvelle. Est ce que les valeurs de Kp, Ki, Kd sont bonnes ? On ne peux pas le savoir sans modèle du système, ou expérimentation.

Faire un modèle physique du système à asservir n’est à priori pas le but de l’exercice. On peut néanmoins imaginer quelques propriétés du système: à priori, ce qui va être construit va être à peu près équilibré, mais assez sensible aux perturbations (coups de vent, pression sur le système, …). L’erreur qu’on a ici, très faible, un passage à une grande valeur, et un retour quasiment à l’équilibre, le tout en une demi seconde, ça ressemble à ce qu’on attend quand quelqu’un appuie sur le système pour le déséquilibrer, pour voir si il est bien stabilisé. Ce à quoi on s’attend, c’est que le système force le temps de se remettre en position, puis se remette à un régime faible, comme au début.

Ce qu’on observe ici, c’est que le système, actionné à la main, est restabilisé vers environ t=950ms. A ce moment, la commande est toujours quasiment à son maximum, ce qui veut dire que le gros de la commande vient de l’intégrale. Sur certains systèmes, ça peut être souhaitable. Sur un drone, en imaginant que le début de la réponse ramène le drone à l’équilibre, on aurait ici un moteur qui continue de pousser à fond. Ce serait compensé après, mais, globalement, ça veut dire que le drone va faire de grosses oscillations. A ce stade, je pense qu’on peut diviser le Ki par 100, et avoir un comportement plus proche de ce qu’on voudra à la fin. On va diviser la commande par à environ 100 aussi, vue la prépondérance du Ki, mais comme la commande est pour le moment en unité arbitraire, ça n’est pas important. On réajustera les paramètres quand on aura une idée des valeurs qu’on souhaite réellement sortir.

Bonjour !

Je reviens vers vous (@Aabu et @Jacen notamment ^^) après avoir un peu avancé.

Mon professeur va se charger de trouver la fonction de transfert de notre moteur(un moteur brushless).

Mais du coup pour s’en servir, quand on l’aura, il faudrait que le PID renvoie une tension comme commande. Et j’aurais 2 questions :) :

  • Vaut-il mieux convertir l’angle en tension dès le début ou convertir la commande ?

  • Comment faire ? :) Nous avions d’abord pensez à faire un produit en croix pour trouver l’angle après avoir envoyé une certaine tension mais ne faut il pas prendre en compte le temps ? (On rechercherait l’angle parcourue par le drone pendant un temps t lorsque l’on envoie une certaine tension aux moteurs

Merci d’avance pour votre réponse !

+0 -0

Salut,

Je préfère répondre ici que sur l’autre sujet, pour garder tout au même endroit.

il faudrait que le PID renvoie une tension comme commande.

Tout à fait. Le PID calculera un tension à appliquer au moteur en fonction de l’erreur.

Vaut-il mieux convertir l’angle en tension dès le début ou convertir la commande ?

La question est un peu curieuse. Ton PID génère la tension à appliquer au moteur. La "conversion" se fait dans le PID. En fait, il ne faut pas voir ça comme une conversion, mais comme une génération de consigne. Le PID se dit "étant donné l’erreur actuelle et l’historique des erreurs, je décide qu’il faut contrôler le moteur avec telle tension".

Comment faire ? :) Nous avions d’abord pensez à faire un produit en croix pour trouver l’angle après avoir envoyé une certaine tension mais ne faut il pas prendre en compte le temps ? (On rechercherait l’angle parcourue par le drone pendant un temps t lorsque l’on envoie une certaine tension aux moteurs

Je pense que tu regardes le problème par le mauvais côté : tu essaies de faire le travail du contrôleur. En gros (et je fais des hypothèses), tu as une chaîne composée d’un contrôleur, d’un générateur de commande pour piloter le convertisseur, d’un convertisseur, d’un moteur et d’une hélice, le tout attaché au drone. Chaque composant transforme une chose en une autre :

  • le contrôleur transforme une erreur entre consigne de tension,
  • le générateur de commande transforme la tension en motif pour commander le convertisseur,
  • le convertisseur adapte l’énergie électrique pour le moteur en suivant le motif de tension,
  • le moteur traduit la commande en vitesse,
  • l’hélice traduit la vitesse en force,
  • la force est convertie en couple puis en angle par la géométrie du drone.

Si tu sais comment piloter le moteur (ton prof t’aide sur ce point si j’ai bien compris), tu n’as qu’à régler le contrôleur pour qu’il puisse agir in fine sur l’angle en générant un tension.

Je te conseille de faire un schéma-bloc, pour bien visualiser comment les différents éléments de ton système transforment les différentes grandeurs les unes vers les autres, si possible avec les fonctions de transfert.

Je pense que tu peux aussi t’aider en réfléchissant qualitativement : comment dois réagir le contrôleur si l’erreur augmente, quel est l’effet sur la chaîne et est-ce que cette réaction contre-balance bien l’augmentation de l’erreur ?

Merci de ta réponse !

Je ne pensais pas du tout que ma commande était déjà une tension .

J’avais trouvé un bon schéma bloc en cours (qui ressemble à ce que l’on veut faire) mais impossible de le retrouver, donc en voici un autre mais qui me parait un peut moins bien ^^ :

Image utilisateur
Image utilisateur

Je dois avouer que la suite de notre projet me parait un peu obscur du côté de l’asservissement :

  • Si j’ai bien compris avec mes recherches, la fonction de transfert (que va chercher notre professeur) va me servir à convertir la commande en "tension" pour le moteur (donc abaisser la valeur de notre commande ? etc ?), donc on aurait ensuite juste à envoyer le résultat renvoyé par cette fonction et cela serait bon ?

  • Mais même avec cette fonction de transfert qui s’occuperait selon moi d’adapter la commande aux moteurs, je ne vois pas trop quoi faire ensuite : je reparle ici de la vidéo du début de mon sujet qui montre ce que l’on veut faire, il y a deux moteurs, comment savoir lequel contrôler ?

Le matériel d e notre projet pour le moment : un banc pouvant bouger verticalement (voir vidéo), 2 moteurs (un de chaque côté) avec hélices + ECS (convertisseur), accéléromètre/gyroscope (pour récupérer l’angle), une carte Arduino.

Dans notre projet, je suis celui charger de l’asservissement par PID et je suis celui qui à le plus du mal (je n’ai fait que le calcul de la commande pour l’instant avec votre aide), si j’arrive à finir le PID notre projet seras quasiment finis…

Si j’ai bien compris avec mes recherches, la fonction de transfert (que va chercher notre professeur) va me servir à convertir la commande en "tension" pour le moteur (donc abaisser la valeur de notre commande ? etc ?), donc on aurait ensuite juste à envoyer le résultat renvoyé par cette fonction et cela serait bon ?

La fonction de transfert du moteur ne va pas te servir à transformer la commande en tension. C’est une représentation mathématique de comment ton moteur (ou esc+moteur, ça dépend de ce qu’on fait) transforme une grandeur d’entrée en autre chose. En fait, tu n’as pas vraiment besoin de la connaître pour régler ton PID avec une méthode essais-erreurs sur le système réel.

il y a deux moteurs, comment savoir lequel contrôler ?

Les deux ! Ce qui va faire pencher la balance, c’est la différence de poussée entre les deux moteurs. Donc ce que tu dois contrôler en toute rigueur, c’est le déséquilibre : si tu lèves trop d’un côté, il faut laisser tomber ce côté et tirer d’autant l’autre. Autrement dit, il faudra mettre plus de patate d’un côté, et moins de l’autre. On ne connaît pas comment est fait concrètement ton système d’ailleurs, un schéma serait sympathique, ça peut nous aider à t’aider. Note bien qu’il y a différentes solutions si ton système est attaché, vu que tirer plus fort sur les deux côtés ne fait rien, donc il faudra choisir à quoi correspond la poussée moyenne, pour pas que les moteurs tirent comme des dingues.

Aussi n’oublie pas ce qu’à dit @Jacen, ça pourrait être important à ce stade :

Votre commande doit être une différence de poussée entre les 2 moteurs. le plus simple pour un système réglé empiriquement, c’est de travailler directement avec des valeurs de rapport cyclique de PWM (le signal envoyé aux ESC des moteurs)

Jacen

Dans notre projet, je suis celui charger de l’asservissement par PID et je suis celui qui à le plus du mal (je n’ai fait que le calcul de la commande pour l’instant avec votre aide), si j’arrive à finir le PID notre projet seras quasiment finis…

À mon avis, l’asservissement sur un projet de ce type est de loin la partie la plus compliquée, surtout pour un débutant ! Demande un peu d’aide à tes collègues, que tu sois pas tout seul à galérer. ;)

+0 -0

Ah mince… Il faudra voir ce matin car mon professeur à peut-être passé son temps à la chercher, donc ça serais bien de l’utiliser si possible…

  • Du coup vous me reconseillez de faire des tests pour voir comment les moteurs réagissent en fonction de la commande renvoyée par le PID ?

  • Je verrai pour faire une photo du système lundi car il faut que l’on changé quelques trucs. Mais si j’ai bien compris il faudra d’abord faire tourner l’un des moteurs et si la commande du PID ne s’améliore pas arrêter ce moteur et faire tourner l’autre ?

  • Si la commande est une différence comment redistribuer à chaque moteurs la bonne poussée ? Tests ? (Refaire un graphique du signal PWM envoyé par les ECS en fonction de la commande ?

  • Oui je vais voir avec eux :) mais ils sont aussi occupés. Et dans le cahier des charges d’un projet en SI, les rôles sont attribués et on est censé faire que notre partie, mais bon notre professeur ne diras rien ^^.

  • Pour le schéma je vais essayer d’en faire un ce week-end (préparée vos yeux 😂) et je prendrais une photo du système Lundi.

Merci pour la photo, je vois bien comment votre installation est constituée maintenant.

Du coup vous me reconseillez de faire des tests pour voir comment les moteurs réagissent en fonction de la commande renvoyée par le PID ?

Honnêtement, il y a un stade où il faut mettre les mains dans le cambouis et arrêter de réfléchir dans le vent. :D

Mais si j’ai bien compris il faudra d’abord faire tourner l’un des moteurs et si la commande du PID ne s’améliore pas arrêter ce moteur et faire tourner l’autre ?

Pas forcément l’arrêter, mais l’un devra tourner moins vite que l’autre. Le plus simple, c’est de faire en sorte que le PID contrôle l’écart de poussée entre les deux moteurs. Si tu choisis cette solution, tu dois définir une méthode pour choisir la poussée de chaque moteur étant donné l’écart entre les deux. Je reste un peu vague, parce qu’il faut que tu le fasses toi-même, mais de manière imagée, si tu veux un écart de « poussée » de 5, tu peux pousser 10 d’un côté et 5 de l’autre ou encore 15 d’un côté et 10 de l’autre, tu vois le principe.

Si la commande est une différence comment redistribuer à chaque moteurs la bonne poussée ? Tests ? (Refaire un graphique du signal PWM envoyé par les ECS en fonction de la commande ?

À un moment donné oui, il faut tester. Est-ce que tu as déjà essayé de faire tourner tes moteurs (même sans PID) ? Est-ce que tu as fait des tests pour voir ce qu’il se passe quand tu donnes des consignes de vitesse différentes aux moteurs ? Est-ce que tu as moyen de « jouer au PID » en essayant de faire la régulation manuellement ? Toutes ces choses devraient t’aider à comprendre où aller au lieu de rester collé dans la théorie.

Merci beaucoup pour ton aide !

Notre système de maintien du drone étant quasiment fini je vais pouvoir faire des tests sur le modèle final. Je vais essayer d’avancer en faisant des tests.

Oui, on déjà essayer de faire tourner les moteurs. J’attendais que tout soit construit avant de commencer des tests avec le PID.

Je vais dire à mon professeur de ne pas regarder pour la fonction de transfert puisqu’elle ne servira pas dans mon cas si j’ai bien compris.

+0 -0

Re,

Je suis désolé de déjà revenir vers vous @Aabu, mais je n’ai pas trop compris l’une des choses que vous m’avez dit :’(.

Grâce à cela dans mon code :

    if (cmd < 0) cmd = 0 ;
    else if (cmd > 255) cmd = 255 ;

J’obtiens bien une commande comprise entre 0 et 255 (c’est déjà ça de fait…).

Mais vous me conseillez de travailler en différence de poussée entre les 2 moteurs mais je ne vois pas du tout comment faire :’(… : que faut-il faire pour trouver cette différence de poussée (je pense avoir compris pour tester de pousser de x d’un côté et de y de l’autre) et le lien avec le PID du coup… Auriez-vous une idée ? …

Re,

Grâce à cela dans mon code :

    if (cmd < 0) cmd = 0 ;
    else if (cmd > 255) cmd = 255 ;

J’obtiens bien une commande comprise entre 0 et 255 (c’est déjà ça de fait…).

C’est une bonne méthode ! C’est ce qu’on appelle une saturation : si on a plus que le maximum, alors on fait juste le maximum ; si on a moins que le minimum, alors on fait juste le minimum; sinon, on fait ce qu’on a, parce que c’est faisable.

Mais vous me conseillez de travailler en différence de poussée entre les 2 moteurs mais je ne vois pas du tout comment faire :’(… : que faut-il faire pour trouver cette différence de poussée (je pense avoir compris pour tester de pousser de x d’un côté et de y de l’autre) et le lien avec le PID du coup… Auriez-vous une idée ? …

Jupiter41

La différence de poussée, ce n’est rien d’autre que … la différence entre les poussées des deux moteurs. Si on note pgp_g la poussée du moteur de gauche et pdp_d la poussée du moteur de droite, la différence de poussée peut être définie par Δp=pgpd\Delta p = p_g - p_d.

Avec cette définition, on voit que si la différence de poussée est positive, alors cela veut dire que le moteur de gauche pousse plus que celui de gauche et donc qu’on aura tendance à lever le bras gauche et donc tourner dans le sens horaire. À l’inverse, une poussée négative correspondra à tourner dans le sens anti-horaire. On aurait pu définir la différence dans l’autre sens, et ça aurait changé la signification des signes.

Maintenant, ce qui nous intéresse, c’est trouver la poussée de chaque moteur (ou la commande à fournir au ESC, ce qui revient grossièrement au même dans cette application). On voit qu’avoir la différence de poussée ne suffit pas, parce que pour une différence de poussée donnée, on a une infinité de combinaisons de poussées possibles (par exemple 10 - 10 ou 5 - 5 donnent tous les deux 0). Il faut donc faire une hypothèse supplémentaire. Il est possible de se donner une poussée moyenne pmp_m, qui sera égale par définition à 12(pg+pd)\frac{1}{2}(p_g + p_d). Avec cette donnée en plus, on trouve les poussées des deux moteurs en fonction de Δp\Delta p et pmp_m en résolvant un système de deux équations à deux inconnues.

Ça devrait te débloquer un peu. Après, si j’en dis beaucoup plus, je vais te gâcher une partie du plaisir. :D

Merci beaucoup pour tes explications @Aabu ! :) Effectivement elle m’a débloquée un peu, mais vraiment pas beaucoup car je me retrouve bloqué dans mes calculs :’(.

Je ne vois pas trop le système d’équation que je dois résoudre pour exprimer pg et pd en fonction de Delta p et pm
J’ai réussi (bon ce n’était pas bien compliquer, mais c’est tout ce que j’ai réussi à faire…) à faire ça :

pm = 1/2 * (Delta p)

Delta p = 2pm

pg = (Delta p) + pd = 2pm + pd

pd = -(Delta p) + pg = -2pm + pg

Et c’est tout :'( (J’ai essayer d’exprimer toutes les variables selon les autres mais il me manque le système…)

Et j’aurais une petite question : Dans mon cas, Delta p correspond à la commande du PID, non ?

EDIT : Grosse bêtise de recopiage de ta formule : j’avais mis un - dans la formule de la poussée moyenne, je ne pouvais pas trouver…

+0 -0

Le système est le suivant :

Δp=pgpd\Delta p = p_g- p_d

pm=pg+pd2p_m = \frac{p_g + p_d}{2}

Tu peux le résoudre avec un système de calcul formel, si tu le souhaites. On n’est pas en cours de maths. ;)

Et j’aurais une petite question : Dans mon cas, Delta p correspond à la commande du PID, non ?

Oui.

Merci beaucoup pour ton aide @Aabu ! J’avais essayé de résoudre ce système mais j’avais obtenu des résultats qui me semblait bizarre, et je viens de me rendre compte que j’avais mis un moins partout pour la moyenne :B … Du coup c’est bon je les résolus (à la main, pour le plaisir :)) :

  • pg = 1/2 * (Delta p) + pm
  • pd = pm + 1/2 * (Delta p)

C’est partie pour la suite :lol: : Imaginons que j’ai une commande de 148, elle est dans l’intervalle 0–255 donc je peux l’utiliser directement. Ainsi, Delta p = 140 mais pm = ?. Je pense savoir ce que je vais devoir rajouter dans mon programme ensuite (tu m’as bien aidé :)) donc il me reste juste à trouver comment calculer pm, sans connaître pg et pd du coup.

+0 -0

Tu as un problème de signes dans ta solution. Avec ce que tu as écrit, les deux moteurs ont la même poussée.

pmp_m ne se calcule pas, c’est à toi de choisir sa valeur. Dans un drone complet, ce serait une sortie du contrôleur d’altitude, mais tu n’en as pas.

Bonne idée, je vais faire ça ! :lol: (des que l’on a projet, c’est à dire je ne sais pas trop quand, peut-être Samedi). Je te tiendrai au courant !

Merci énormément pour ton aide @Aabu en tout cas !

PS : Pour quelle raison, la valeur de la poussée moyenne peut-elle être choisie "aléatoirement" ? (pour que je puisse l’expliquer ;))

EDIT : Mon programme "final" :

////////////////////////////////////
// Les bibliothèques nécessaires  //
////////////////////////////////////

#include <Wire.h>
#include <ADXL345.h>

#define MOT_G ... // A REMPLACER
#define MOT_D ... // A REMPLACER

/* Variables */

double pitch = 0.00 ;

ADXL345 adxl ; // Variable adxl en relation avec la bibliothèque ADXL345 

float erreur = 0 ; // Erreur = consigne - mesure

float consigne = 90 ; // Valeur à atteindre (modifiable)

const float Kp = 10 ; // Constante du paramètre proportionnel
const float Ki = 0.4 ; // Constante du paramètre intégral
const float Kd = 20 ; // Constante du paramètre dérivée

float erreur_precedente = 0 ;
float somme_erreurs = 0 ;
float variation_erreur = 0 ;

float mesure = 0 ;

int poussee_g = 0 ; // Poussée du moteur gauche (à déterminer)
int poussee_d = 0 ; // Poussée du moteur droit (à déterminer)

float commande = 0 ; // Commande = différence de pousée entre les moteurs : cmd = poussee_g - poussee_d

const int periode = 15 ; // Période d'échantillonage = Pause à la fin du programme Arduino = Temps d'execution de la fonction loop()

const int poussee_moyenne = 100 ; // Poussée moyenne, déterminée aléatoirement mais dois rester logique

void setup() {

  pinMode(MOT_G, OUTPUT) ;
  pinMode(MOT_D, OUTPUT) ;

  Serial.begin(9600) ; // Moniteur série sur 9600 bauds (bits/s)

  adxl.powerOn() ; // Démarrage du capteur accéléromètre/gyroscope

}

void loop() {
  
  int x,y,z ;  
  adxl.readXYZ(&x, &y, &z) ; // Lecture des valeurs issues de  l'accéléromètre et stockage dans les variables x,y,z
  
  // Valeurs de sorties x, y, z 

  double x_Buff = float(x) ;
  double y_Buff = float(y) ;
  double z_Buff = float(z) ;

  pitch = atan2((- x_Buff) , sqrt(y_Buff * y_Buff + z_Buff * z_Buff)) * 63 ;
  mesure = pitch ; // Mesure finale
  
    erreur = consigne - mesure ; // Calcul de l'écart entre la consigne et la mesure (-> erreur) => Paramètre proportionnel
    somme_erreurs += erreur ; // Paramètre intégral
    variation_erreur = erreur - erreur_precedente ; // Paramètre dérivée

    commande = (Kp * erreur) + (Ki * somme_erreurs * periode) + ((Kd * variation_erreur) / periode) ; // Calcul de la commande

    erreur_precedente = erreur ; // Stockage de l'erreur actuelle

    // Conditions pour éviter la saturation (dépassage des valeurs limites (Démarrage du moteur/Moteur à fond)) :

    if (commande < ...) commande = ... ; // A REMPLACER par la valeur pour laquelle les moteurs démarrent /!\
    else if (commande > ...) commande = ... ; // A REMPLACER par la valeur pour laquelle les moteurs s'arrêtent /!\

    // Détermination de la pousée des moteurs :

    poussee_g = (1/2) * commande + poussee_moyenne ; // Poussee du moteur gauche
    poussee_d = poussee_moyenne - (1/2) * commande ; // Poussee du moteur droit

    // Envoie de ces poussées aux moteurs :

    analogWrite(MOT_G, poussee_g) ;
    analogWrite(MOT_D, poussee_d) ;

    /* Debug */

    //Serial.println(erreur) ;

    //Serial.print("Temps : ") ;
    //Serial.println(millis()) ;
    
    //Serial.print("Mesure : ") ;
    Serial.println(mesure) ;

    //Serial.print("Commande : ") ;
    //Serial.println(commande) ;

    
    delay(15) ; // Pause (= periode)

}
+0 -0
Ce sujet est verrouillé.