[ARDUINO] Asservissement grâce au PID

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

Je me suis juste dit que le temps ne devait pas influer sur la commande la première fois ^^ (donc c’est faux ?)

Je ne sais pas où tu as eu cette idée, mais c’est étrange.

La période d’échantillonnage intervient dans la mise à jour la commande, mais la commande est toujours mise à jour de la même manière, que ce soit la première ou la millionième fois. La seule différence, c’est que la première fois, certaines valeurs seront égales aux valeurs initiales.

Après, il faut garder en tête que l’initialisation n’arrive qu’une fois, ce qui fait qu’elle est en général peu importante, sauf si on fait un cas tordu exprès.

Et dans quel(s) cas devrais je convenir la période en seconde ?

Jupiter41

C’est à toi de choisir. Physiquement, c’est la même chose, c’est juste qu’il ne faut pas te mélanger les pinceaux. Dans ton programme, ta période semble être en millisecondes. Cela se répercute dans l’unité de Ki et Kd. Comme tu n’as précisé aucune unité nulle part, je pense que c’est utile que tu y réfléchisses brièvement.

D’accord merci pour tes précisions !

Du coup, j’ai modifié un peu mon code :) :

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

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

double pitch = 0.00;

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

float erreur = 0; //consigne - mesure

float consigne = 90;

const float Kp = 10 ;
const float Ki = 0.4 ;
const float Kd = 20 ;

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

float mesure = 0;
float commande = 0;

const int periode = 15 ;

void setup() {

  Serial.begin(9600);
  adxl.powerOn();

}

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;
  
    erreur = consigne - mesure ;
    somme_erreurs += erreur ;
    variation_erreur = erreur - erreur_precedente ;

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

    erreur_precedente = erreur ;

    
    Serial.print("Mesure : ") ;
    Serial.println(mesure) ;

    Serial.print("Commande : ") ;
    Serial.println(commande) ;
    
    delay(15) ;

}

Et nous avons pu réaliser quelques tests. Voici les "logs" :

Mesure : 95.38
Commande : -2064.44
Mesure Mesure : 93.33
Commande : -57.81
Mesure : 93.15
Commande : -70.13
Mesure : 93.75
Commande : -99.73
Mesure : 93.36
Commande : -114.60
Mesure : 93.36
Commande : -135.27
Mesure : 93.60
Commande : -159.58
Mesure : 93.33
Commande : -176.29
Mesure : 92.41
Commande : -180.61
Mesure : 93.60
Commande : -216.90
Mesure : 93.75
Commande : -239.57
Mesure : 93.09
Commande : -250.46
Mesure : 93.82
Commande : -282.45
Mesure : 92.74
Commande : -285.72
Mesure : 93.14
Commande : -310.53
Mesure : 93.51
Commande : -335.31
Mesure : 93.20
Commande : -350.47
Mesure : 93.14
Commande : -369.02
Mesure : 93.33
Commande : -391.33
Mesure : 93.38
Commande : -411.94
Mesure : 93.12
Commande : -427.53
Mesure : 93.72
Commande : -457.04
Mesure : 92.96
Commande : -465.35
Mesure : 95.74
Commande : -532.37
Mesure : 95.97
Commande : -566.98
Mesure : 86.94
Commande : -446.08
Mesure : 87.45
Commande : -448.63
Mesure : 86.50
Commande : -416.18
Mesure : 82.90
Commande : -333.96
Mesure : 75.59
Commande : -169.53
Mesure : 72.56
Commande : -40.21
Mesure : 58.49
Commande : 304.17
Mesure : 40.41
Commande : 787.92
Mesure : 48.38
Commande : 923.22
Mesure : 44.66
Commande : 1247.98
Mesure : 41.27
Commande : 1573.81
Mesure : 41.81
Commande : 1852.38
Mesure : 42.71
Commande : 2126.54
Mesure : 33.05
Commande : 2578.96
Mesure : 30.65
Commande : 2949.37
Mesure : 25.78
Commande : 3386.68
Mesure : 21.79
Commande : 3834.59
Mesure : 20.05
Commande : 4268.70
Mesure : 20.99
Commande : 4669.78
Mesure : 23.67
Commande : 5038.59
Mesure : 24.42
Commande : 5427.13
Mesure : 29.22
Commande : 5738.39
Mesure : 36.85
Commande : 5977.26
Mesure : 37.80
Commande : 6289.78
Mesure : 39.74
Commande : 6570.75
Mesure : 48.78
Commande : 6718.09
Mesure : 49.23
Commande : 6969.76
Mesure : 48.15
Commande : 7233.59
Mesure : 51.82
Commande : 7419.68
Mesure : 56.86
Commande : 7566.28
Mesure : 58.91
Commande : 7736.34
Mesure : 66.14
Commande : 7800.24
Mesure : 76.08
Commande : 7780.73
Mesure : 79.04
Commande : 7826.22
Mesure : 82.21
Commande : 7841.00
Mesure : 83.57
Commande : 7868.40
Mesure : 86.21
Commande : 7863.00
Mesure : 84.31
Commande : 7922.19
Mesure : 88.00
Commande : 7889.83
Mesure : 86.07
Commande : 7940.17
Mesure : 64.50
Commande : 8335.12
Mesure : 92.71
Commande : 7970.35
Mesure : 92.13
Commande : 8001.82
Mesure : 94.36
Commande : 7949.60
Mesure : 93.77
Commande : 7936.58
Mesure : 93.41
Commande : 7919.51
Mesure : 92.92
Commande : 7907.01
Mesure : 92.85
Commande : 7890.05
Mesure : 93.15
Commande : 7867.58
Mesure : 93.04
Commande : 7851.13
Mesure : 92.65
Commande : 7839.49
Mesure : 93.22
Commande : 7813.10
Mesure : 92.96
Commande : 7799.17
Mesure : 92.84
Commande : 7783.02
Mesure : 92.87
Commande : 7765.27
Mesure : 92.85
Commande : 7748.48
Mesure : 93.42
Commande : 7721.42
Mesure : 93.51
Commande : 7700.08
Mesure : 93.54
Commande : 7678.74
Mesure : 93.06
Commande : 7665.82
Mesure : 92.92
Commande : 7649.24
Mesure : 93.89
Commande : 7614.68
Mesure : 93.16
Commande : 7605.26
Mesure : 93.77
Commande : 7574.75
Mesure : 93.41
Commande : 7559.27
Mesure : 94.06
Commande : 7527.04
Mesure : 92

Le truc c’est que je n’arrive pas à me rendre compte si ce sont des valeurs correctes :p

Quand pensez-vous ?

+0 -0

Quand pensez-vous ?

Jupiter41

Aussi souvent qu’il me sied.

Pour ce qui est des valeurs, elles peuvent être correcte, mais j’ai l’impression que l’intégrateur est trop élevé. Pour mieux voir ce qu’il se passe, fait un graphe avec l’erreur en fonction du temps, et la commande en fonction du temps (avec deux échelles d’ordonnée différentes, y’a une donnée qui va de -70 à +10 et l’autre de -2000 à 8000).

Malheureusement je n’ai pas eu le temps de le faire… Il faudra attendre jeudi :(

Mais j’ai préparé le programme pour ne plus avoir qu’à faire tourner le programme puis de copier les valeurs dans Excel ^^

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

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

double pitch = 0.00;

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

float erreur = 0; //consigne - mesure

float consigne = 90;

const float Kp = 10 ;
const float Ki = 0.4 ;
const float Kd = 20 ;

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

float mesure = 0;
float commande = 0;

const int periode = 15 ;

int tab_err[200] ;
int tab_com[200] ;
int tab_t[200] ;
int i = 0 ;
int j = 0 ;

void setup() {

  Serial.begin(9600);
  adxl.powerOn();

}

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;
  
    erreur = consigne - mesure ;
    somme_erreurs += erreur ;
    variation_erreur = erreur - erreur_precedente ;

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

    erreur_precedente = erreur ;

    tab_err[i] = erreur ;
    tab_com[i] = commande ;
    tab_t[i] = millis() ;

     //Serial.print(“Erreur : “) ;
    //Serial.println(erreur) ;

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

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

    if (millis() > 5000) {

      for (j = 0 ; j <= i ; j++) {

          Serial.println(tab_err[j]) ;
        
      }

      Serial.print("\nCommande :\n") ;

      for (j = 0 ; j <= i ; j++) {

          Serial.println(tab_com[j]) ;
        
      }

      Serial.print("\n Time : \n") ;

      for (j = 0 ; j <= i ; j++) {

          Serial.println(tab_t[j]) ;
        
      }

    Serial.println("-----------------------------") ;
       
    }

    i++ ;
    
    delay(15) ;

}

Ou alors pour le temps je peux aussi prendre les valeurs que je vous ai envoyez précédemment et je considère que la première fois t = 0ms, 2ème t = 15 ms, 3ème t = 30 ms etc… ^^ et dans ce cas là je peux faire le graphique avant jeudi.

+0 -0

Ben non, tu as la mesure, pour avoir l’erreur, il faut lui soustraire la consigne. La consigne étant constante, tu peux calculer ton erreur.

Bon, par contre, ton dernier programme devrait planter assez méchamment. Tu accumules des données toutes les 15ms, tu prévoies de la mémoire pour en accumuler 200, et tu commence à les afficher après 5s. A ce moment, ton programme sera déjà en train d’écrire n’importe où depuis environ 2s (un peu moins, parce qu’on se décale du temps de traitement à chaque itération, avec cette implémentation).

PS: par contre, je viens de regarder, avec la liaison série à 9600 bauds, il y en avait précédemment pour une 30aine de ms pour afficher les logs. La base de temps est donc totalement fausse, il va falloir repenser l’implémentation

+0 -0

Du coup j’ai fait les graphiques avec le temps qui augmentent de 15ms à chaque fois, les voici :

L’erreur en fonction d temps :

Image utilisateur
Image utilisateur

La commande en fonction du temps :

Image utilisateur
Image utilisateur

Même si les graphiques sont bizarres cela me semble cohérent, et vous ? :p

est ce qu’on peut avoir le temps en abscisse (c’est important, pour les mathématiques et la lisibilité) et les deux courbes sur le même graphique (avec des échelles différentes en ordonnée). Ce que je veux voir c’est comment la commande réagit aux variations de l’erreur.

j’essaye de rédiger ce que je peux en tirer ASAP

Pour le rapport (j’imagine que le projet ne consiste pas juste à jouer avec l’arduino), ça peut être bien de préciser sur le graphe que le temps est en ms, l’erreur en degrés, la commande en unité arbitraire, et, si possible d’aligner le 0 des deux échelles d’ordonnées. On est sur un système traité comme linéaire (on le modélise par un système d’équations différentielles linéaires), en général, aligner le 0 de la grandeur d’entrée et de celle de sortie facilite la lecture du graphe (là on a l’impression que l’erreur est positive quand elle est légèrement négative).

Et du coup comme on le voit sur le graphique, toujours ce même "problème" le paramètre intégral fait que la commande reste beaucoup trop haute alors que le système à atteint la consigne demandé…

(Et one ne peut pas le retirer car le principe c’est que l’on utilise le PID :p)

Bonsoir,

Le dernier graphe est super dur à lire ! On ne voit pas les erreurs négatives, ce qui est mauvais, parce qu’on a du mal à interpréter (sans parler du fait qu’on ne sait pas à quoi correspondent les axes).

Et du coup comme on le voit sur le graphique, toujours ce même "problème" le paramètre intégral fait que la commande reste beaucoup trop haute alors que le système à atteint la consigne demandé…

Tu oublies que tu n’es pas entrain de commander le système réel, mais de faire des tests à la main… La chose que tu visualises actuellement, c’est l’entrée du correcteur (l’erreur) et sa sortie (la commande). Que l’erreur soit bizarre est normal : tu ne commandes pas le véritable système ! Ce que tu dois vérifier, c’est la cohérence entre les deux : est-ce que la sortie correspond au résultat attendu pour l’entrée donnée ? Si oui, cela signifie que ton programme implémente correctement le correcteur, sinon, tu t’es planté.

Dans un second temps, tu devras tester ce correcteur sur le système réel, et seulement à ce moment là tu pourras avoir des courbes d’erreurs qui ont un rapport avec la réalité. Notamment, dans ce cas là, il s’agira d’observer que l’erreur devient nulle à la fin.

Actuellement, rien qu’avec la courbe, tu peux vérifier qualitativement si ton programme est bon ou pas. Tu peux aussi essayer de comparer tes données avec le même calcul fait avec Excel par exemple (il faut évidemment ne pas se tromper dans Excel non plus…), l’objectif étant de démontrer que le programme de l’Arduino est correct.


D’ailleurs, ce genre de processus de comparaison est utilisé dans l’industrie (et ailleurs) :

  1. On implémente le correcteur dans un logiciel de simulation et on compare au résultat attendu (gros du boulot de réflexion se fait là).
  2. On implémente le correcteur en C (par exemple), on compile et on exécute sur l’ordinateur, puis on compare avec la simulation. Si on a des différences, soit le code est faux (souvent), soit le compilateur à un problème (rare).
  3. On compile le code et on l’exécute sur le microcontrôleur. Si on a une différence avec l’étape 2, cela peut être un bug de compilateur (plus courant qu’on ne l’aimerait) ou un soucis avec le microcontrôleur (rare).

À la fin, on a une chaîne de comparaison qui nous dit que ce que fait le micro est bien la même chose que ce qu’on a vu en simulation. Dans ton cas, la chaîne est plus courte, mais tu fais la même chose : tu compares ton programme par rapport à une référence (qualitative parce que tu n’as pas d’implémentation de référence).

D’accord merci beaucoup pour tes explications très claires ! :).

Personnellement le correcteur me paraît bien implanté donc du coup il faut que l’on attende d’avoir construite le système pour pouvoir vérifier la mise en place du PID. Et la nouvelle grande question c’est comment faire pour convertir la commande en tension pour le moteur :p (mais ça nous n’avons pas encore regardé)

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.

Ce sujet est verrouillé.