Aller au contenu


Photo
- - - - -

PID - un banc de test


  • Veuillez vous connecter pour répondre
4 réponses à ce sujet

#1 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 644 messages
  • Gender:Male

Posté 04 novembre 2023 - 12:04

Le PID, c'est comme le piano. Ce n'est pas parce que l'on sait comment cela fonctionne que l'on sait en jouer.

 

J'ai découvert une vidéo de Ian Carey qui propose un banc de test extrêmement facile à réaliser qui permet de s'entrainer à modifier les variables pour bien les maitriser.

L'idée de Ian Carey, avec un Arduino Nano, est de générer une tension de référence (target) sur une sortie PWM au travers d'un filtre Passe Bas. Puis cette tension est réinjectée dans une entrée analogique. C'est ça, le truc génial !

Dans cette vidéo, j'ai également découvert l'influence très importante de dt (delta time) dans le calcul du PID.

 

J'ai modifié son filtre en reprenant un filtre calculé par Sando, avec R=470 et C=100µF. Ce filtre fonctionne parfaitement et génère très peu d'oscillations.

 

J'ai réécrit le programme de Ian Carey à ma manière. Avec 25 lignes de code, j'ai un PID qui fonctionne très bien.

 

Il y a quand même des choses que je ne m'explique pas dans le programme Ian Carey.

Dans la fonction setup(), il initialise l'abscisse à 0 et target , dans une boucle. J'ai voulu supprimer cette boucle, mais le "dessin" caractéristique du début du feedback, disparaissait. Il s'agit de cette oscillation à peu près symétrique sur la ligne de target. En fait, sa boucle camouflait ce problème.

J'ai alors eu l'idée de remplacer cette boucle par une temporisation de 5000ms,  init_time .

J'aimerais bien savoir pourquoi, il est nécessaire de patienter pendant 5 secondes pour avoir ce début d'oscillations.

C'est important, car dans la vraie vie, on peut se demander si on lance le processus immédiatement ou si on doit attendre un petit moment avant que le feedback atteigne la bonne position.

 

Je génère donc l'axe des abscisses et le target dans la fonction loop(). De plus, je génère une ligne symétrique aux abscisses par rapport à la ligne du target. Cela pour que le Plotter Arduino ne zoom pas en fonction des valeurs du feedback.

Je paramètre également un délai à la fin de la fonction loop()loop_time . Ce paramètre est très important car, il détermine le dt , et donc le calcul du PID.

 

Le Plotter Arduino est assez sensible, mais je pense avoir trouver le bon code.

Après avoir lancer le programme, il faut attendre les 5 secondes d'initialisation avant de voir apparaitre le feedback. Il est préférable de cocher "Interpolate" pour avoir une oscillation plus "fluide".

Je pense que l'utilisation du Plotter a une grande influence dans le dt à cause du temps d'exécution des Serial.print() , mais ce délai n'impacte pas négativement le calcul du PID, car si je diminue la variable loop_time , le résultat est impacté négativement.

Il me semble donc nécessaire que le PID soit déterminer dans un lapse de temps minimum, incompressible.

 

Le schéma du banc de test sur une breadbord.

Fichier joint  Schema01_bb.jpg   151,71 Ko   1 téléchargement(s)

 

Le résultat avec mon code.

Fichier joint  PID01.jpg   107,5 Ko   1 téléchargement(s)

 

Mon code :


// Program inspired from the code of Ian Carey https://www.youtube.com/@careyian - 03/11/2023
const int INPUT_PIN = A0;
const int OUTPUT_PIN = DD5;
 
float dt, last_time = 0, init_time = 5000, loop_time = 200, target = 60.00, kp = 0.76, ki = 0.191, kd = 0.0009;
 
void setup(){
  Serial.begin(115200);
  analogWrite(OUTPUT_PIN, 0);
  delay(init_time);
}
 
void loop(){
  float now = millis(); dt = (now - last_time) /1000.00; last_time = now;
  float feedback = map(analogRead(INPUT_PIN), 0, 1024, 0, 255);
  float error = target - feedback;
  float output = pid(error);
  analogWrite(OUTPUT_PIN, (int)output);
  Serial.print(target); Serial.print(","); Serial.print(target*2); Serial.print(","); Serial.print(feedback); Serial.print(","); Serial.println(0);
  delay(loop_time);
}
 
float pid(float error){
  static float integral, previous = error;
  float P = kp * error;
  float I = ki * (integral = integral + error * dt);
  float D = kd * (error - previous) / dt;
  return P + I + D;
}
 
La vidéo de Ian Carey
 
Son code :

// code de Ian Carey https://www.youtube.com/@careyian
// https://youtu.be/RZW1PsfgVEI?si=yrka40X2lhyUlicM
// https://gist.github.com/careyi3/02a57dfd3a62a96d46171489c83488bd#file-pid-ino
 
const int INPUT_PIN = A0;
const int OUTPUT_PIN = DD3;
 
double dt, last_time;
double integral, previous, output = 0;
double kp, ki, kd;
double setpoint = 75.00;
 
void setup()
{
  kp = 0.8;
  ki = 0.20;
  kd = 0.001;
  last_time = 0;
  Serial.begin(9600);
  analogWrite(OUTPUT_PIN, 0);
  for(int i = 0; i < 50; i++)
  {
    Serial.print(setpoint);
    Serial.print(",");
    Serial.println(0);
    delay(100);
  }
  delay(100);
}
 
void loop()
{
  double now = millis();
  dt = (now - last_time)/1000.00;
  last_time = now;
 
  double actual = map(analogRead(INPUT_PIN), 0, 1024, 0, 255);
  double error = setpoint - actual;
  output = pid(error);
 
  analogWrite(OUTPUT_PIN, output);
 
  // Setpoint VS Actual
  Serial.print(setpoint);
  Serial.print(",");
  Serial.println(actual);
 
  // Error
  //Serial.println(error);
 
  delay(300);
}
 
double pid(double error)
{
  double proportional = error;
  integral += error * dt;
  double derivative = (error - previous) / dt;
  previous = error;
  double output = (kp * proportional) + (ki * integral) + (kd * derivative);
  return output;
}

 



#2 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 644 messages
  • Gender:Male

Posté 13 novembre 2023 - 02:43

Faire un filtre passe-bas pourrait en rebuter, quelques uns.

Alors, toujours, en restant dans le même esprit minimaliste que la version précédente, je me suis dit qu'il était peut-être possible de faire encore plus simple.

J'ai donc eu l'idée de générer une fréquence sur une pin, et de la réinjecter sur une autre pin.

Pour générer cette fréquence, j'ai choisi la pin 12 en sortie, et pour la pin en entrée, j'ai choisi la pin 2 qui autorise les interruptions.

Et plutôt que d'utiliser un Arduino Nano et un Breadboard, j'ai préféré un Arduino Uno qui permet de fixer un petit câble de connexions entre ces deux pins.

 

Difficile de faire plus minimaliste pour se lancer dans l'étude du PID. 

 

J'ai également légèrement modifié mon programme pour qu'il soit encore plus simple.

D'autant que, plutôt que de se fixer sur la fréquence, j'ai préféré me fixer sur la période qui est légèrement plus simple à programmer.

 

Le résultat de la courbe du PID est plus conforme à l'orthodoxie et dessine une courbe qui part de 0 et se stabilise rapidement en atteignant la consigne.

Ci-dessous, on peut voir un résultat pour une période 90ms, mais le résultat est tout aussi correcte dans une plage que j'ai testée, de 20ms à 100ms.

 

J'ai bien envie de faire une vidéo, alors avant de publier une bêtise, n'hésitez à me faire part de vos remarques, sur le code.

 

Fichier joint  Plotter01.jpg   99,07 Ko   0 téléchargement(s)Fichier joint  DSC_4082-S-2.jpg   100,27 Ko   0 téléchargement(s)

 

Le code.

// Minimalist workbench PID, based on a frequency period - 11/11/2023
volatile unsigned long feedback, tic, tac=millis();
float prvTime = 0, dt;
float periodTarget = 90.00, kp = 0.30, ki = 6.00, kd = 0.002;
 
void setup(){
  Serial.begin(115200);
  pinMode(12, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(2), interrupt, RISING);
}
 
void loop(){
  float time = millis(); dt = (time - prvTime) /1000.00; prvTime = time;
  float pid = PID();
  digitalWrite(12, HIGH); delay(pid/2); digitalWrite(12, LOW); delay(pid/2);
  Serial.print(periodTarget); Serial.print(","); Serial.print(feedback); Serial.print(","); Serial.print(140); Serial.print(","); Serial.println(0);
}
 
float PID(){
  static float errorSum, previousError;
  float error = periodTarget - feedback;
  float P = kp * error;
  float I = ki * (errorSum = errorSum + (error * dt));
  float D = kd * (error - previousError) / dt;  previousError = error;
  return P + I + D;
}
 
void interrupt(){
  tic = millis();
  feedback = tic - tac;
  tac = tic;
}


#3 Mike118

Mike118

    Staff Robot Maker

  • Administrateur
  • PipPipPipPipPip
  • 9 867 messages
  • Gender:Male
  • Location:Anglet
  • Interests:Robotique, Entrepreneuriat, Innovation, Programmation, Résolution de problème, Recherche de solutions, Mécanique, Electronique, Créer, Concevoir

Posté 13 novembre 2023 - 10:33

 

 

Et plutôt que d'utiliser un Arduino Nano et un Breadboard, j'ai préféré un Arduino Uno qui permet de fixer un petit câble de connexions entre ces deux pins.

 

Difficile de faire plus minimaliste pour se lancer dans l'étude du PID. 

 

 

Tu aurais pu utiliser une nano et mettre un fil femelle femelle ;) 
En changeant de pin et en prenant le pin 3 à la place du 12 tu aurais même pu mettre un simple cavalier entre 2 et 3 vu qu'ils sont côte à côte ;) 

Ensuite si tu veux quelque chose d'un peu plus " drôle " tu peux choisir une broche PWM ( comme par hasard 3 est aussi une broche PWM ;) ) et chercher à modifier la valeur de pwm pour avoir un temps à l'état haut qui correspond à ce que tu souhaites ( par contre oui dans ce cas il va te falloir mettre ton interruption en " CHANGE " et non plus en RISING ... Et bien compter que le temps à l'état haut dans ton interruption ... ) Il va aussi peut être te falloir changer la fréquence de PWM sur le Timer 2 dans ce cas ... Bref tu peux éventuellement proposer cela dans un deuxième temps ;)


Si mon commentaire vous a plus laissez nous un avis  !  :thank_you:

Nouveau sur Robot Maker ? 

Jetez un oeil aux blogs, aux tutoriels, aux ouvrages, au robotscope  aux articles,  à la boutique  et aux différents services disponible !
En attendant qu'une bibliothèque de fichiers 3D soit mise en place n'hésitez pas à demander si vous avez besoin du fichier 3D d'un des produits de la boutique... On l'a peut être ! 

 

Les réalisations de Mike118  

 

 

 


#4 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 644 messages
  • Gender:Male

Posté 14 novembre 2023 - 08:30

Pour le Nano, il me semble que les broches sont vraiment trop courtes pour un fil femelle/femelle. Et puis, le Uno est souvent considéré comme étant l'Arduino de référence de tout débutant.

J'ai choisi la pin 12, parce que le fil se positionne naturellement dessus. Bien sûr, la pin 3 est plus proche, mais la photo aurait été moins spectaculaire avec un cavalier.

 

J'avais bien pensé au PWM, mais le code aurait été légèrement plus compliqué.

Et puis, ici, je voulais axer la démo sur le PID et non sur le PWM qui est, me semble t-il, un sujet en soi. Mais pour mon UGV, c'est effectivement, le sujet !

 

Sinon, le code ? Pas de loup caché ?



#5 Oracid

Oracid

    Pilier du forum

  • Modérateur
  • PipPipPipPipPip
  • 6 644 messages
  • Gender:Male

Posté 15 novembre 2023 - 08:57

Et voilà, la vidéo :

 






0 utilisateur(s) li(sen)t ce sujet

0 members, 0 guests, 0 anonymous users