Bonjour à tous !

J'ai rédigé, il y a quelques temps déjà, un article expliquant comment implémenter un asservissement de type PID sans faire de calculs ! Après plusieurs retours de lecteurs, je me suis rendu compte que cet article, qui se voulait simple et compréhensible, n'était pas assez pratique. Il manquait un exemple concret ! C'est donc pour remédier à ce manque que je rédige l'article suivant.

Je me baserai donc sur ce que j'ai écrit dans l'article Implémenter un PID sans faire de calculs pour implémenter réellement l'asservissement d'un moteur à courant continu à l'aide d'une Arduino.

Présentation du moteur et de sa codeuse

Le moteur que je vais utilisé est un motoréducteur 29:1 avec une roue codeuse monté sur l'arbre moteur. La documentation est consultable ici.

Les caractéristiques intéressantes à noter sont :

  • 350 tours de roue minutes
  • 10150 tours d'arbre moteur minute (environ 170 par seconde)
  • 32 transitions montante et descendante de la codeuse par tour d'arbre moteur
  • 928 transitions montante ou descendante de la codeuse par tour de roue

Câblage du moteur à l'Arduino

Comme je n'ai pas à disposition de shield Arduino pour contrôler un moteur à courant continue, j'ai donc refait une petite interface de puissance très basique, avec les composants à ma disposition, pour les besoins de l'article.

Le transistor Q2 vient driver le MOSFET de puissance. Lorsque la sortie 9 de l'arduino est à l'état haut, Q1 est bloqué et le moteur ne tourne pas. A l'inverse, lorsque la sortie 9 de l'arduino est à l'état bas, alors Q1 devient passant et le moteur tourne à plein régime. Le moteur se contrôle donc en "tout ou rien", ce qui tombe bien, car la sortie "analogique" de l'Arduino est en réalité une sortie PWM de rapport cyclique ajustable.

Au niveau du câblage, j'utilise une Arduino Mega. La sortie 9 commande mon moteur. La codeuse (je n'utilise qu'une seule des deux sorties de la codeuse) est branché sur la pin 2 de l'arduino mega (pin qui correspond à l'interruption 0).

Si vous voulez utiliser une autre Arduino, pensez à brancher la codeuse, non plus sur la pin 2, mais sur la pin qui correspond à une pin d'interruption !

Récupération des informations codeuses

Je souhaite connaitre le nombre de tours de l'arbre moteur grâce à la codeuse. Comme nous l'avons vu dans une première partie, il y a 32 transitions montantes ET descendante qui s'opèrent pendant un seul tour de l'arbre moteur.

Ainsi, il suffit de mettre une interruption qui se déclenche à chaque transition de la codeuse et qui exécute une fonction qui viendra simplement incrémenter un compteur.

const int _MOTEUR =  9;            // Digital pin pour commande moteur
unsigned int tick_codeuse = 0;     // Compteur de tick de la codeuse

/* Routine d'initialisation */
void setup() {
    pinMode(_MOTEUR, OUTPUT);   // Sortie moteur
    analogWrite(_MOTEUR, 255);  // Sortie moteur à 0
    delay(5000);                // Pause de 5 sec pour laisser le temps au moteur de s'arréter si celui-ci est en marche

    attachInterrupt(0, compteur, CHANGE);    // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
}

/* Fonction principale */
void loop(){
    delay(10);
}

/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++;  // On incrémente le nombre de tick de la codeuse
}

Création de la fonction d'asservissement

Pour mettre en place un asservissement numérique, il faut que les calculs de la commande du moteur se fassent à un intervalle de temps régulier. Pour cela, j'ai utilisé un timer qui permet d'exécuter une fonction précise tous les x millisecondes. Le timer n'est pas un objets inclut de base à Arduino, j'ai donc installé une bibliothèque externe, SimpleTimer, qui remplit cette tâche. Vous pourrez trouver cette bibliothèque sur cette page.

#include <SimpleTimer.h>           // http://arduino.cc/playground/Code/SimpleTimer

SimpleTimer timer;                 // Timer pour échantillonnage
const int _MOTEUR =  9;            // Digital pin pour commande moteur
unsigned int tick_codeuse = 0;     // Compteur de tick de la codeuse
int cmd = 0;                       // Commande du moteur
const int frequence_echantillonnage = 50;  // Fréquence d'exécution de l'asservissement

/* Routine d'initialisation */
void setup() {
    Serial.begin(115200);         // Initialisation port COM
    pinMode(_MOTEUR, OUTPUT);   // Sortie moteur
    analogWrite(_MOTEUR, 255);  // Sortie moteur à 0

    delay(5000);                // Pause de 5 sec pour laisser le temps au moteur de s'arréter si celui-ci est en marche

    attachInterrupt(0, compteur, CHANGE);    // Interruption sur tick de la codeuse  (interruption 0 = pin2 arduino mega)
    timer.setInterval(1000/frequence_echantillonnage, asservissement);  // Interruption pour calcul du PID et asservissement
}

/* Fonction principale */
void loop(){
    timer.run();
    delay(10);
}

/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++;  // On incrémente le nombre de tick de la codeuse
}

/* Interruption pour calcul du PID */
void asservissement()
{
    // DEBUG
    Serial.println(tick_codeuse);

    // Réinitialisation du nombre de tick de la codeuse
    tick_codeuse=0;
}

Ici, la fonction asservissement() est exécutée toutes les 20ms (50Hz) et la fonction compteur() est exécuté à chaque fois que la codeuse change d'état.

Mise en place d'un asservissement P

Essayons maintenant de mettre en place un asservissement proportionnel. Pour cela, il nous faut trois choses.

D'abord, la consigne, qui correspondra, dans notre exemple, au nombre de tours de roue par seconde. Je vais fixer cette consigne à 5, ce qui veut dire que je souhaite que le moteur effectue 5 tours de roue par seconde.

Ensuite, il nous faut le nombre de tour de roue qu'a effectué le moteur durant les dernière 20ms. Rien de bien compliqué. On a notre variable tick_codeuse qui compte le nombre de changement d'état de la codeuse durant les 20 dernières millisecondes. On sait qu'il y a 32 changements d'état de la codeuse par tour de l'arbre moteur. On sait qu'il faut 29 tours d'arbre moteur pour faire un tour de roue.

On en déduit donc que la roue à fait tick_codeuse/32/29 tours de roues durant les 20 dernières milliseconde, et donc 50*tick_codeuse/32/29 tours de roue par seconde !

Avec cette information, on peut donc calculer l'erreur qui est la différence entre la consigne (le nombre de tour de roue par seconde voulu) et la réponse à cette consigne (le nombre de tours de roue par seconde réalisé).

Il ne nous reste plus qu'a trouver notre coefficient de proportionnalité de notre régulation, ce qui nous donne la fonction d'asservissement suivante :

/* Interruption pour calcul du P */
void asservissement()
{
    // Calcul de l'erreur
    int frequence_codeuse = frequence_echantillonnage*tick_codeuse;
    float nb_tour_par_sec = (float)frequence_codeuse/(float)tick_par_tour_codeuse/(float)rapport_reducteur;
    float erreur = consigne_moteur_nombre_tours_par_seconde - nb_tour_par_sec;

    // Réinitialisation du nombre de tick de la codeuse
    tick_codeuse=0;

    // P : calcul de la commande
    cmd = kp*erreur;

    // Normalisation et contrôle du moteur
    if(cmd < 0) cmd=0;
    else if(cmd > 255) cmd = 255;
    analogWrite(_MOTEUR, 255-cmd);

    // DEBUG
    /*
    Serial.print(nb_tour_par_sec,8);
    Serial.print(" : ");
    Serial.print(erreur,4);
    Serial.println();
    //*/
}

Notons que lors de l'envoie du signal de commande au transistor, il faut inverser le résultat trouvé avec notre asservissement proportionnel car dans notre cas, le moteur tourne pour une commande de 0 et s'arrête pour une commande de 255 ! (d'où la ligne analogWrite(_MOTEUR, 255-cmd); )

Il faut donc maintenant définir le coefficient de proportionnalité. J'ai tracé, ci-dessous, différentes réponses de mon moteur en fonction du temps pour des coefficients de proportionnalité différents. On remarque bien que quand kp augmente, la réponse se rapproche de plus en plus à la consigne voulu, mais que quand kp est trop grand, la réponse oscille fortement autour de la consigne.

Amélioration PI

D'après les résultats précédent, j'ai décidé de prendre un coefficient de proportionnalité kp égal à 300. Ainsi, l'erreur statique sera  d'environ 5%. Pour améliorer le comportement de notre asservissement et pour annuler notre erreur statique, j'ai décidé de rajouter un terme intégrateur afin d'obtenir un asservissement PI.

Pour cela, je vais garder en mémoire la somme de toutes les erreurs de mon système. Plus cette somme des erreurs est importante et plus j'essaye de corriger ma commande. Ainsi, il faut que j'ajoute ce nouveau terme intégrale à la ligne calculant la commande.

somme_erreur += erreur;
// PI : calcul de la commande
cmd = kp*erreur + ki*somme_erreur;

Dans cette seconde phase, nous avons donc à régler ce coefficient intégrateur ki.

Plus on augmente le coefficient d'intégration ki, est plus le système répond vite, mais en contre partie, le système devient de plus en plus instable. Le but est donc de trouver un compromis entre temps de réponse et stabilité. On voit que pour des ki trop grand, le système part en oscillation.

D'après ces graphiques, un coefficient proportionnel de 300 et un coefficient intégrateur de 5 ou 6 nous permet d'atteindre une réponse quasiment optimale, avec un dépassement très faible et un temps de réponse d'envirion 120 milliseconde. Le système se stabilise donc après 6 exécutions de la fonction asservissement.

Asservissement final PID

Afin de mettre en place un asservissement PID complet, je vais maintenant rajouté le terme dérivateur, bien que dans notre cas, celui-ci n'ai pas beaucoup d'influence car un simple asservissement PI nous permet d'atteindre une réponse quasi parfaite.

float delta_erreur = erreur-erreur_precedente;
erreur_precedente = erreur;
// PID : calcul de la commande
cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur;

Il faut faire attention de ne pas prendre un coefficient dérivateur kd trop grand car le temps de réponse augmente.

Après plusieurs essais, je suis arrivé à un triplet assez performant. Voici l'allure de la réponse :

Et voici le programme final :

/**
* Asservissement d'un moteur à l'aide d'un régulateur PID
* Avril 2012 - Ferdinand Piette
*/

#include <SimpleTimer.h>           // http://arduino.cc/playground/Code/SimpleTimer
#define _DEBUG false

SimpleTimer timer;                 // Timer pour échantillonnage
const int _MOTEUR =  9;            // Digital pin pour commande moteur
unsigned int tick_codeuse = 0;     // Compteur de tick de la codeuse
int cmd = 0;                       // Commande du moteur

const int frequence_echantillonnage = 50;  // Fréquence du pid
const int rapport_reducteur = 29;          // Rapport entre le nombre de tours de l'arbre moteur et de la roue
const int tick_par_tour_codeuse = 32;      // Nombre de tick codeuse par tour de l'arbre moteur

float consigne_moteur_nombre_tours_par_seconde = 5.;  //  Nombre de tours de roue par seconde

float erreur_precedente = consigne_moteur_nombre_tours_par_seconde;
float somme_erreur = 0;   // Somme des erreurs pour l'intégrateur
float kp = 300;           // Coefficient proportionnel
float ki = 5.5;           // Coefficient intégrateur
float kd = 100;           // Coefficient dérivateur

/* Routine d'initialisation */
void setup() {
    Serial.begin(115200);         // Initialisation port COM
    pinMode(_MOTEUR, OUTPUT);     // Sortie moteur
    analogWrite(_MOTEUR, 255);    // Sortie moteur à 0

    delay(5000);                  // Pause de 5 sec pour laisser le temps au moteur de s'arréter si celui-ci est en marche

    attachInterrupt(0, compteur, CHANGE);    // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
    timer.setInterval(1000/frequence_echantillonnage, asservissement);  // Interruption pour calcul du PID et asservissement
}

/* Fonction principale */
void loop(){
    timer.run();
    delay(10);
}

/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++;  // On incrémente le nombre de tick de la codeuse
}

/* Interruption pour calcul du PID */
void asservissement()
{
    // Réinitialisation du nombre de tick de la codeuse
    int tick = tick_codeuse;
    tick_codeuse=0;

    // Calcul des erreurs
    int frequence_codeuse = frequence_echantillonnage*tick;
    float nb_tour_par_sec = (float)frequence_codeuse/(float)tick_par_tour_codeuse/(float)rapport_reducteur;
    float erreur = consigne_moteur_nombre_tours_par_seconde - nb_tour_par_sec;
    somme_erreur += erreur;
    float delta_erreur = erreur-erreur_precedente;
    erreur_precedente = erreur;

    // PID : calcul de la commande
    cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur;

    // Normalisation et contrôle du moteur
    if(cmd < 0) cmd=0;
    else if(cmd > 255) cmd = 255;
    analogWrite(_MOTEUR, 255-cmd);

    // DEBUG
    if(_DEBUG)  Serial.println(nb_tour_par_sec,8);
}

84 commentaires à “Asservissement en vitesse d'un moteur avec Arduino”

  1. Super ! Les explications sont ultra claires ! Merci infiniment !

  2. Bonjour,

    J'ai une question. La variable somme_erreur ne risque pas un dépassement? Ne devrait elle pas etre remis a zero, tous les n boucles? sinon le tuto est une bonne application du tuto, le PID sans calculs.

    TeRRy

    • Bonjour,

      Normalement, si le PID est bien fait, il n'y aura pas de dépassement vu que la somme des erreurs oscillera autour de 0 🙂

      EDIT : Sinon, on pourrait imaginer "brider" cette valeur dans un intervalle et que si la somme des erreurs dépasse une des borne de l’intervalle, on la ramène toujours à la borne.

      double borne_min = -50, borne_max = 50;
      if(somme_erreur > borne_max) somme_erreur = borne_max;
      if(somme_erreur < borne_min) somme_erreur = borne_min;

  3. bonjour,

    Je ne comprends pas le delay(10) dans void loop() { ...

    Merci beaucoup

    Bastof

  4. Bonjour, j'ai une question concernant Ki.

    Pour moi Ki dépend des conditions initiales. Sauriez vous m'expliquer pourquoi si ce n'est pas le cas ?

    Par exemple :
    Le moteur tourne à 20 tours de roue par minute. Je lui demande d'aller à 100 tours. la somme des écarts au moment de la stabilisation vaudra l'aire sous (et sur) la consigne mettons x.
    Le moteur tourne à 40 tous de roue par minute. Je lui demande d'aller à 100 tours. la somme des écarts au moment de la stabilisation vaudra à nouveau l'aire sous (et sur) la consigne mettons y.

    Partant d'un écart plus grand, x sera supérieur à y. Un unique Ki introduira un surplus différent dans ma commande pour x et y. L'état stable ne sera donc pas à la même "hauteur" en fonction de mes conditions initiales.

    Je pense avoir raté quelquechose. Quand vous dites que la somme va osciller autour de 0, je ne suis pas d'accord, elle va osciller autour d'une valeur constante. La somme serait nulle soit en la faisant sur une fenetre soit en ayant un régulateur qui passe autant de "temps * amplitude" sous la consigne que sur la consigne. Et si c'est le cas, ce terme intégrateur ne compense pas l'erreur statique puisqu'il est nul.

    Je ne sais pas suis je suis clair dans l'explication de mon problème. J'espère que vous saurez m'aider à comprendre.

    Merci d'avance

    Jean

    • Pour moi Ki dépend des conditions initiales. Sauriez vous m'expliquer pourquoi si ce n'est pas le cas ?

      Non non, Ki est bien indépendant des conditions initiales. Ki ne dépend que des caractéristiques du moteur.

      Partant d'un écart plus grand, x sera supérieur à y.

      Non, c'est bien y qui est supérieur à x puisque y est la somme de x plus un terme d'erreur (qui ici est de même signe).
      L'erreur grandit donc.

      Un unique Ki introduira un surplus différent dans ma commande pour x et y. L'état stable ne sera donc pas à la même "hauteur" en fonction de mes conditions initiales.

      Le terme d'intégration sert à dire "Si je reste longtemps avec une erreur importante, alors je modifie violemment la commande pour essayer de me stabilisé".
      Dit autrement : "Avoir une petite erreur pendant un laps de temps important équivaut à avoir une grande erreur pour un laps de temps cours".

      Je pense avoir raté quelquechose. Quand vous dites que la somme va osciller autour de 0, je ne suis pas d'accord, elle va osciller autour d'une valeur constante.

      Hum... en effet, il semble que je me sois quelque peu fourvoyé dans mes explications. La somme va osciller autour d'une valeur constante qui vaudra l'erreur statique du système P. C'est l'erreur qui oscille autour de zéro.

      • Non, c'est bien y qui est supérieur à x puisque y est la somme de x plus un terme d'erreur (qui ici est de même signe).
        L'erreur grandit donc.

        Je ne suis pas d'accord. ^^
        Si je commence à 20 tour et que je veux aller à 100, j'ai plus de "chemin" à faire que si je commence de 40, j'aurais donc une somme d'écart plus faible vu que mon écart initial est plus faible.

        Je vois plus le terme d'intégration par son effet : compenser l'erreur statique. C'est peut être là que je me fourvoie.
        Mon objectif est d'atteindre la consigne sans écart statique quelque soit la consigne et quelque soit T0 sans changer Kp, Ki(, et Kd). Ce n'est pas possible avec un Ki constant, c'est ce que je retiens de mes expérimentations.

        Par exemple :
        T0 = 24°C, C = 50°, Ki = 0.0001 -> Somme = 3500 environ
        T0' = 28°C, C = 50°, Ki = 0.0001 -> Somme' = 3700 environ
        Du coup ma température de stabilisation est différente entre T0 et T0'
        Ce qui est logique, mais ne m'arrange pas. ^^

        C'est surtout pour le cas où je dois descendre en température que ça ne m'arrange pas vu que la somme oscille autour d'une constante négative ce qui augmente encore plus mon écart statique. ^^

        Je réfléchis actuellement à transformer la constante Ki en une fonction de quelques paramètres (écart, commande précédente, somme, peut être d'autres ...).

        Merci de votre aide.

        • Je ne suis pas d'accord. ^^
          Si je commence à 20 tour et que je veux aller à 100, j'ai plus de "chemin" à faire que si je commence de 40, j'aurais donc une somme d'écart plus faible vu que mon écart initial est plus faible.

          En effet, mais les constantes Kp, Ki et Kd sont définies afin d'obtenir une réponse ayant toujours la même forme, quelque soit la consigne de départ et l'état initial du système.

          Ce qu'on cherche, c'est bien d'avoir une forme toujours identique pour la réponse.
          Si vous changez dynamiquement le terme intégrateur, la forme de la réponse changera aussi et dans certains cas, vous aurez un système oscillant et dans d'autre, un système apériodique... ce qui peut être dangereux dans certaines situations (exemple, asservir un bras pour écrire sur une feuille de papier. Dans ce cas, la réponse doit toujours être apériodique, sinon, s'il y a un léger dépassement, la pointe s'écrase sur la table (ou transperce la feuille))

  5. MERCI !
    Je crois que j'ai enfin compris comment mettre simplement en place un asservissement PID avec un Arduino, un moteur et une roue codeuse.

    En revanche, comment avez-vous obtenu vos courbes pour déterminer les constantes kp, ki et kd ? (Processing ?)

    • En revanche, comment avez-vous obtenu vos courbes pour déterminer les constantes kp, ki et kd ? (Processing ?)

      Non, j'ai activé le mode débug, ce qui m'a permis d'envoyer sur le port série le nombre de tours par seconde du moteur à chaque fois que je recalcule la commande.
      #define _DEBUG true

      [...]

      void asservissement()
      {

      [...]

      if(_DEBUG) Serial.println(nb_tour_par_sec,8);
      }

      Ensuite, un simple copier/coller dans Excel.

      Attention néanmoins à bien régler le baudrate pour que la vitesse de transmission soit suffisamment rapide afin de ne pas perturber la boucle d'asservissement !

  6. comment avez vous tracez les graphiques??

    Merci.

  7. Merci de m'avoir répondu si vite.

    Mais qu'avez-vous copier-coller dans votre graphique et comment avez vous fait?

    Merci de votre réponse.

    • J'ai copié les valeurs que j'obtiens sur le port série en mode debug dans un tableur Excel.
      if(_DEBUG) Serial.println(nb_tour_par_sec,8);

      Ces valeurs représentent le nombre de tours de roues par seconde estimé toutes les 50ms.
      Je trace ensuite le graph.

  8. Salut,

    Tout d'abord merci beaucoup pour ces 2 tutos sur l'asservissement en vitesse c'est vraiment utile.
    J'ai essayé aujourd'hui de le mettre en oeuvre sur mon robot mais j'ai un problème (Mon moteur avec encodeur intégré). En fait quand je lit la réponse de mon encodeur avec analogueWrite, il me donne soit 0 soit 5, or 5 avec analogueWrite ça veut bien dire 5/1024*5 Volt non ?
    Ensuite j'ai quand même réussi a lire les tick de mon codeur avec analogueWrite :
    if(analogRead(54) == 5 && verif == 0)
    {
    tickCodeuseG++;
    //Serial.println(tickCodeuseG);
    verif = 1;
    }

    if(analogRead(54) == 0 && verif == 1)
    {
    verif = 0;
    }

    Mais j'ai l'impression qu'il saute des tick, genre quand mon moteur tourne très vite il me dit qu'il y a autant que tick par senconde que lorsque qu'il tourne a moitier de puissance mais pour d'autre vitesse il me répond d'autre nombres de tick par seconde et ça semble a peu près cohérent.
    Je sais que je suis pas très clair car c'est pas très clair dans ma tête non plus le fonctionnement d'un encodeur ><, je récapitule :

    _ Est t'il normale que la réponse de mon encodeur soit si faible en tension ?
    _ Faut-il absolument utiliser la fonction attachInterrupt ? (Elle a un meilleur temps de réaction peut-être et elle capte tous les tick elle)
    _ Faut-il absolument utiliser la fonction attachInterrupt sur les pin Interrupt de la arduino ?

    J'te remercie d'avance pour ton aide.
    A bientot 😉

    • Bonsoir,

      _ Est t'il normale que la réponse de mon encodeur soit si faible en tension ?

      Non, ce n'est pas normal. Déjà, sur une Arduino Méga, les entrées analogiques sont les pin 0 à 15. La pin 54 n'existe pas.

      Sinon, pourquoi utiliser une entrée alors qu'un tick codeuse est un signal numérique ? Autant utiliser une entrée numérique.

      _ Faut-il absolument utiliser la fonction attachInterrupt ? (Elle a un meilleur temps de réaction peut-être et elle capte tous les tick elle)

      Oui, l'interruption se déclenchera à chaque fois que tu auras un tick (montant, descendant ou les deux). Ainsi, tu n'en louperas pas.

      _ Faut-il absolument utiliser la fonction attachInterrupt sur les pin Interrupt de la arduino ?

      Uniquement sur les pins 2 (interrupt 0), 3 (interrupt 1), 18 (interrupt 5), 19 (interrupt 4), 20 (interrupt 3), et 21 (interrupt 2) pour l'arduino méga : http://arduino.cc/en/Main/arduinoBoardMega

      • Bonjour, merci pour ta réponse très rapide :).
        J'essaye ça cet aprem j'te tiens au courant.

      • Salut,
        Merci pour ton aide maintenant ça marche très bien !
        J'ai mis les réponses des encodeurs sur les bon pin (20 et 21) avec la bonne fonction attachInterrupt et ça marche très bien.

        Merci encore !

  9. Bonjour,

    j'ai grasse a votre tutorial réussi a implémenter un PID pour des tètes de lecture HDD avec capteur optique.
    Entre mon code de gestion d'erreurs et cette technique, c'est le jour et la nuit (un angle bien propre !)

    J'avais quasiment implémenter un PID sans m'en rendre compte, une sorte de PD agissant comme suit :

    cmd += kp*erreur + kd*delta_erreur;
    Comme si en quelque sorte, la consigne "corrigé" un delta sur la commande.

    Précis, stable, mais temps de réponse extrêmement long....

    Ceci dit, j'ai quelques questions :

    cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur;

    Ce ne devrait pas etre

    cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur +128;

    Pour la phase de test, ki et kd ==0:
    En sachant qu'erreur tend vers -inf lorsque la mesure est trop haute , cmd (avant
    // Normalisation et contrôle du moteur), cmd est négatif.
    donc, dans votre code, si l'erreur est négative, cmd vas être automatiquement ==0.
    Afin de profiter d'un ralentissement, (a l'inverse d'un stop moteur si l'erreur est un tant soit peu négative) ne devrait ton pas ajouter (255/2) a cmd avant normalisation ?

    Est-ce assez clair? Ai-je loupé quelque chose ?

    • Bonjour.

      En sachant qu'erreur tend vers -inf lorsque la mesure est trop haute

      Justement non. L'erreur ne tend jamais vers -inf. C'est la somme des erreurs qui peut potentiellement tendre à l'infinie si l'asservissement se fait mal.

      Ici, une commande à 0 signifie que le moteur n'est pas alimenté. Une commande positive signifie que le moteur est alimenté pour tourner dans un sens. Une commande négative signifierai que le moteur puisse tourner dans le sens inverse, ce qui est impossible dans mon exemple.
      Ici, si l’erreur est négative, on n'alimente plus le moteur. Ce n'est pas un stop moteur : ne pas alimenter le moteur ne signifie pas que celui-ci ne tourne pas !

  10. Bonjour Ferdinand Piette !

    D'abord je tiens à vous dire que je suis nouveau dans arduino. Je voudrais compiler votre code PID complet avec arduino uno r3. Le compilateur me dit erreur :(SimpleTimer Timer 😉 does not the name type.
    Et donc je sais pas quoi faire ? S'il vous plait, pouvez vous m'aider comment il faut faire pour résoudre cette problème,
    je vous mercis infiniment.

  11. Bonjour monsieur Ferdinant !
    J'ai crée la bibliothèque dans sketche arduino comme la recommandation, et j'ai enregistré deux fichiers nommés SimpleSimtimer.h et SimpleTimer.ccp. Et voilà le résultat comme le text en dessous :

    C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avr-g++ -c -g -Os -w -fno-exceptions -ffunction-sections -fdata-sections -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=154 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -IC:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino -IC:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\standard C:\Users\VX7\AppData\Local\Temp\build2312663445330094959.tmp\sketch_nov09a.cpp -o C:\Users\VX7\AppData\Local\Temp\build2312663445330094959.tmp\sketch_nov09a.cpp.o

    sketch_nov09a:9: error: 'SimpleTimer' does not name a type
    sketch_nov09a.ino: In function 'void setup()':
    sketch_nov09a:35: error: 'timer' was not declared in this scope
    sketch_nov09a.ino: In function 'void loop()':
    sketch_nov09a:40: error: 'timer' was not declared in this scope

    S'il vous plais je ne sais pas comment il faut faire ?

  12. Rebonjour encore monsieur Ferdinand !
    Oubliez le text précédent au dessus, j'ai finalement réussir à compiler le programme car j'ai fait erreur en enregistrment
    SimpleTimer.ccp au lieu de SimpliTimer.cpp.
    Merci à vous pour me dirigez vers bibliothèque SimpleTimer. A la suite

  13. Salut, super détaillé, excellent site 😉

  14. Les explications sont claires.j'aime.

  15. Bonjour Ferdinand.
    Je développe actuellement un fauteuil roulant électrique pour personne handicapée.
    Mon cahier des charges: autonomie supérieure à 6 heures, capacité de franchissement de marches de 16 cm, et donc des trottoirs, et fonction tout terrain, l'ensemble devant rester dans les dimensions d'un fauteuil roulant, et à un prix abordable.
    J'ai un moteur 24v, DC par roue, soit 4 moteurs. Tous les contrôleurs pilotés par joystick que j'ai trouvés à ce jour ne peuvent contrôler que 2 moteurs. Je pensais coupler les moteurs droits ensembles et gauches ensembles, mais je risque d'avoir une vitesse différente entre les deux moteurs, et donc un comportement chaotique du fauteuil. J'ai donc pensé à asservir le moteur arrière droit au moteur avant droit afin qu'il tourne à la même vitesse de manière automatique, et idem à gauche, et ainsi je pourrais utiliser le joystick. Pourriez-vous m'aider à réaliser cette partie électronique que je ne maitrise absolument pas. A quel coût?
    Voila. Si cela retient votre attention, merci de m'envoyer un email pour poursuivre.
    Merci, Cordialement
    Rémy

    • excellentes explications du PID,
      j'avais deja mis en oeuvre des asservissements, mais pas aussi efficace.

      bonjour Rémy
      avez vous trouvé quelqu'un pour vous aider?
      Jean

  16. Tout simplement excellent Ferdinand...
    Supers explications du PID pour tous !
    Merci.
    Bruno.

  17. Merci, c'est très utile!

  18. Bonjour,

    Très beau travail!

    Je veux réaliser une pompe à eau en fonction d'une consigne de débit. Pour cela je dispose d'un pompe dc12v et d'un débitmètre qui me sort un signal sinusoïdal à fréquence variable. n'ayant pas de roue codeuse puis je me servir des informations fournis par le débitmètre pour réaliser un asservissement PID?

    Si c'est réalisable pouvez vous m'expliquer les grandes lignes SVP?

    Merci de votre aide.

    • Bonjour,

      Cela me semble tout à fait réalisable.
      Pour cela, il suffit de convertir la fréquence du signal en sortie du débitmètre en débit (à l'aide des informations contenues dans la datasheet du capteur où grâce à un calibrage).

      Il faut donc réaliser le PID sur cette information de débit : il faut calculer l'erreur et la somme des erreurs par rapport à un débit de consigne. Le résultat du PID servira à commander le moteur.

  19. Merci du temps passé !!! Tres utile ! 😛

  20. Bonjour et merci pour les explications.
    Je débute en essayant de fabriquer un robot gyropode. J'ai testé votre programme sur un motoréducteur et bien sur : cela fonctionne parfaitement.

    Du coup, j'ai essayé de le modifier pour faire tourner 2 moteurs avec la même consigne et là... problème. Le deuxième moteur tourne à pleine vitesse. Apparemment, les interruptions sur la broche 3 (int1) ne fonctionne pas.

    Vous accepteriez de jeter un petit coup d’œil à mon sketch ?

  21. C'est encore moi... En fait le programme était bon mais j'avais un faux contact. Merci encore

  22. Bonjour , j'aurais 2 questions . C'est lorsque j'essaie de compiler le code , j'ai une erreur dans arduino : "class simple timer has no menber named 'set interval'", j''ai donc un problème de compilation sachant que j'ai bien inclu la bibliotheque simpletimer .
    Utilisie-tu simplement ta carte arduino et ton moteur codeur ou tu utilise à la place une carte romeo ?. Car j'ai fait des recherche , le codeur incrémentale delivre bcp trop d'impulsions pour la carte arduino donc elle est incapable de gerer toutes et ces impusions , il faut donc utiliser une carte romeo qui inclus une sorte de diviseur de frequence qui a pour but de diminuer la frequence des impulsions et donc d'avoir une frequence plus basse pour que arduino puisse la gérer correctement , merci d'avance

    • Bonjour,

      Pour la première question, je ne sais pas. La bibliothèque contient toujours un membre setInterval (http://playground.arduino.cc/Code/SimpleTimer#F_setInterval). Il doit y avoir un problème dans le code : avez-vous bien fait l'include ? avez vous bien instancié un objet timer ?

      Pour la seconde question, oui, j'ai testé mon code sur une arduino uno et mega et ça marche parfaitement.
      La uno est tout à fait capable de gérer les quelques 5500 impulsions par secondes que peut délivrer la codeuse lorsque le moteur est à plein régime.
      Pas besoin de "diminuer la fréquence des impulsions". Ce n'est d'ailleurs possible qu'en utilisant une codeuse moins précise ou en mettant la codeuse après le réducteur et non sur l'arbre moteur. Mais dans les deux cas, on perd en précision.

      • Merci de m'avoir répondu aussi rapidement , j'ai donc laisser tomber le diviseur de frequence , mais aurait-tu une idée de la capacité de la carte arduino à recevoir des données , peut-elle gérer plus de 6000 impulsions par sec(en moyenne)? J'ai tester ton code , j'ai bien inclus le fichier SimpleTimer.h et SimpleTimer.cpp , le code marche mais mon problème concernant le 'set interval' et 'run'(dans le loop)persiste , le code se compile très bien sans ces deux lignes , arduino ne semble pas les connaitre . Aurait -tu une idée ? cordialement

  23. Bonjour M ferdinand , je tiens tous d'abord à vous remercier pour le travail que vous avez réalisé . Mais si je vous envoie ce message , c'est parce que j'ai un véritable problème qui me bloque complètement .
    Je précise tous d'abord que mon moteur à un régime nominal de 6v , une reduction de 1/53 et 48 impulsions pour chaque tour de moteur effectuer .
    Lorsque je lance le programme , et que j'essaie d'afficher les impulsions stoker dans la variable compteur , il m'affiche 300 impulsions alors que moteur en sortie de reducteur n'a fait que 1 tour (il devrait m'afficher environ 2544 impulsion car 53 * 48 =2544)
    2)Comment pouvons nous déterminer les coeficients kp , ki , kd ?
    merci

    • Je me permet ce petit commentaire car je suis confronté en ce moment au même genre de problèmes.
      Tout d'abord un grand merci à M.Piette pour ses tutoriaux.
      @dakito : Attention quand un fabricant/fournisseur donne un codeur à 48 impulsions par tour cela sous entend 2 fronts (montant et descendant) sur chacune des voies A et B (cas d'un encodeur à 2 voies en quandrature). Si on n’échantillonne seulement un des 2 signaux et seulement un seul front, on se retrouve avec 4 fois moins d'impulsions. Cela peut être une piste dans votre cas.

  24. Bonjour,

    Si je ne me trompe pas, c'est l’asservissement I qui permet d'avoir un système précis, et non l'asservissement P, pour un système d'ordre 0.

  25. est ce ke j peux avoir votre compte personnel parce que je veux que tu m'aide dans mon projet

  26. bonjour. je suis un novice et je m'intéresse à tout ce qui touche le handicap. je me bat pour comprendre le langage et la programmation. pour le côté matériel hardware et mécanique ce là devrait aller pour moi. à la lecture des commentaires aux quels j'arrive à comprendre. une idée me viens:pourquoi ne pas utiliser un moteur pas à pas . avec réducteur devant un différentiel des deux arbre de roues. y aurait-il un problème de puissance pour ne pas utiliser un pas a pas.
    merci par avance de votre reponse
    cordialement
    Roger

    • Bonjour,
      Oui, c'est possible, mais dans ce cas là, la régulation PID et la roue codeuse ne sert plus à rien.
      Le propos de l'article était justement d'illustrer l'utilisation d'un PID.
      L'utilisation d'un moteur à courant continue ou pas à pas dépend de l'application.

  27. Bonjour,

    Je ne comprend pas à quoi servent les résistances R1 et R2 exactement. Comment elles ont été choisies ? Il s'agit de limiter la tension ? le courant ?

    Je ne comprend pas non plus pourquoi on a besoin d'un transistor Bipolaire ? pourquoi ne pas directement utiliser le transistor MOFSET ? Pourquoi passer par l’intermédiaire d'un transistor bipolaire avant ?

    La diode est une diode de roue libre ? pour eviter les surtensions sur le moteur ?

    Je ne comprend pas exactement le choix des composants sur le circuit electrique.

    Cordialement

    Michel

    Et Merci pour se site !

    • Bonjour,

      J'avais réalisé à l'époque l'interface de commande rapidement avec les composants à ma disposition.
      Il y a donc surement moyen de faire mieux (notamment, cette interface ne peut pas faire tourner le moteur dans les deux sens). Néanmoins, compte tenu de mes besoins, celle-ci est plus que suffisante.

      R1 est juste une résistante de pull-up pour commander le MOFSET à 12V lorsque le bipolaire est bloqué.
      Lorsque le bipolaire est passant, la gate du MOFSET est forcé à 0V et R1 sert à éviter un court-circuit (il y a seulement 12mA qui passent dans R1 et dans le bipolaire du coup).
      La valeur de R1 a été choisi au pifomètre, mais on pourrait l'augmenter encore afin de limiter le courant traversant le bipolaire (aucun courant ne passe dans la gate du MOFSET qui est un transistor commandé en tension et non en courant comme le bipolaire).

      R2, quant à elle, sert à limiter le courant entrant à la base du bipolaire (vu que c'est un transistor commandé en courant cette fois).
      Lorsque la pin9 de l'Arduino est en état haut (5V), le bipolaire conduit. Sa base est donc environ à 0.7V, ce qui fait que le courant de commande du bipolaire est de (5-0.7)/1000 soit 4.3mA environ.

      On ne peut pas commander directement le MOFSET via l'Arduino car celle-ci délivre du 5V alors que le MOFSET doit être commandé en 12V.
      Le bipolaire permet donc de passer d'une commande 0-5V à une commande 12V-0V (ici, la commande est inversée, mais on pourrait imaginer une interface qui n’inverse pas la commande).
      Donc l'Arduino, R1, R2 et le bipolaire forment la partie commande alors que le MOFSET et D1 forment la partie puissance.

      La diode D1 est en effet une diode de roue libre afin d'évacuer le courant du moteur lorsque le MOFSET se coupe. Le moteur étant composé d'une bobine et donc une inductance, on ne peut pas avoir de discontinuité dans le courant sous peine de sur-tension au moment où l'on active ou coupe le MOFSET.
      En théorie le MOFSET inclut déjà une diode de roue libre, mais en pratique elle est souvent sous dimensionnée.

  28. Bonjour
    Je souhaite mettre en place une surveillance de mon regulateur Pid vitesse afin d'eviter tout emballement en cas de panne-panne encodeur par exemple.en surveillant que ma composante integrale (sur consigne elevee)ne depasse pas un seuil pendant plus de 2sec jarrive a empecher cette survitesse. Oui mais voila ,des que je rentre une consigne tres petite et que je simule de nouveau cette panne d'encodeur le systeme de secu ne se declenche que 3 min apres le debut- le robot ayant deja pris beaucoup de vitesse....je cherche une idee donc pour que la surveillance soit efficace aussi bien en consigne haute quant consigne basse...si vous avez une idee?!CDT

  29. Bonjour
    Les explications sont très claires . Je suis un projet de construction de gyropode (type segway) à base d accelometre et gyroscope sous arduino et j ai pu lire certains projets sur le net mais en anglais
    Il y a la notion de pid par rapport à la mesure de l angle d inclinaison et la vitesse moteur associée
    Pouvez m aider sur ces notions ?
    Merci et encore bravo !

  30. Bonjour,

    merci pour votre article tres interressant et tres clair. Pour illustrer celui ci vous avez des graphes, pouvez vous mexpliquer comment vous les avez obtenus car pour le projet que je développe j'ai besoins de tracer les sorties obtenues.
    Cordialement.

  31. Bonjour,

    Je suis désolé de vous poser la question ici car elle n'a pas forcément un très grand rapport avec le sujet du dessus. J'aurais préféré vous contacter autrement ... Veuillez m'en excuser.

    Je cherche à contrôler avec un Arduino si c'est possible plusieurs multiplexeurs. Je vous explique le contexte:

    Je veux réaliser une mesure 4 fils de faible résistance à l'aide d'un ohmmètre que je pense acheter. Le problème est que j'ai une bonne centaine de résistance à mesurer et j'aimerai bien automatiser la chose... ( La valeur des résistances varient au cours du temps ).

    J'aimerais donc pouvoir aiguiller mes pistes de façon à ce que mon ohmmètre mesure la résistance 1 puis la résistance 2 ainsi de suite ...

    j'aimerais savoir si vous avez déjà réalisé un montage arduino avec un multiplexeur. Si cela vous semble possible car il me faut plusieurs multiplexeur et que en tout j'aurais je pense avoir au moins 200 pistes à gérer. Ce qui me ferait utiliser au moins 8 sorties numériques de l'arduino pour coder la positions des multiplexeurs ? je pense que 8 sorties numériques c'est vraiment un minimum ... Sachant qu'il y 13 sorties numériques sur l'arduino dont 6 qui sont des PWM . Bon après rien ne m'empèche d'utiliser des PWM en guise de sortie numérique non ?

    Voila merci beaucoup ! Si vous avez des idées à me suggérer, n'hésitez pas 🙂

    Encore une fois désolé de m'adresser sur un sujet qui parle d'asservissement.

  32. comment faire commande 5 moteurs a courant continu dans des deffernts sens a l'aide d'un clavier pour donner l'angle et un ecrant LCD 😕

  33. Salut

    Déja , beau boulot pour tes différentes explications sur la régul PID .
    Je suis entrain d'essayer de régler la mienne pour le chauffage d'une pièce (salle de bain de 5m2).
    En commençant mes tests de Kp , j'arrive à trouver une valeur qui me satisfait , sans trop de d'oscillations mais en étant un peu éloigné de ma consigne (env. 0,5 °C) , du coup j'ai voulu faire intervenir le Kd mais le soucis est que la pièce a une certaine inertie et donc elle met un peu de temps à atteindre une tempêrature proche de ma consigne et que pendant ce temps ma somme d'erreurs augmente tout le temps ... Et donc une fois la consigne dépassée, avec l'inertie , elle met du temps à revenir proche de zéro . Je me suis dit que j'allais augmenter le Kd , sauf que ça fait s'emballer le système et si à l'inverse je le diminue .. bah il me sert plus à rien ^^ !

    Aurais tu une piste pour éviter ce phénomène ? Je pensais imposer des limites au produit Kd*somme_erreurs ou alors commencer à compter la somme d'erreurs "le plus tard possible" et non dès le début ...

    Merci

    • Bonjour,

      Après Kp, tu as essayé d'intégrer Ki je suppose (et non Kd) ?
      Dans un système avec beaucoup d'inertie, en effet, le Ki peut faire très rapidement osciller la réponse.
      Normalement, le fait de rajouter le Kd permet justement d'anticiper ces dépassements.
      Dans mon exemple avec le moteur, Kd ne servait quasiment à rien (le système étant très réactif).
      Par contre, dans un système avec beaucoup d'inertie comme le tiens, Kd devrait être très utile. C'est étrange que le système s'emballe avec du coup...

  34. Bonjour,

    Tout d’abord merci pour ce tuto,

    Je viens solliciter votre aide car cela fait 3 mois que mes neurones surchauffe sur mon projet et là je sature....

    Pour faire vite, j'ai un plateau tournant actionner par un petit moteur mais la partie réducteur ce fait avec une vis sans fin. Ce plateau est fixé à la vertical avec un bras en porte à faux ce qui entraîne un freinage en monté et donc une accélération en descente...
    Le problème que je rencontre, contrairement à votre projet c'est que quand le moteur n'est pas alimenté, il s'arrête quasi instantanément.
    Ceci entraîne donc une rotation saccadée qui ne permet pas de régler le PID correctement.
    J'ai chercher à commander le plateau en degré par minute car cela correspond plus à la vitesse rechercher qui est au maximum de 3 où 4 tours par minute.

    Auriez vous une piste pour créé un asservissement angulaire qui ne coupe pas complètement le moteur en cas de dépassement de la consigne ??

    Je suis novice mais je ne cherche pas de solution "toute faite" mais juste un p'tit coup pouce pour ne pas finir de m'arracher les cheveux 🙂

    Merci d'avance pour votre aide

    Cordialement,
    Pierro

    Ps : Je vous met à dispo l'ébauche de de mon programme
    [PHP]
    #include // http://arduino.cc/playground/Code/SimpleTimer

    SimpleTimer timer; // Timer pour echantillonnage

    const int potar = A1; //la broche pour regler la vitesse
    int cmd = 0 ;

    const int LED = 3; // Constante pour la broche 3

    const int _MOTEUR = 10; // Digital pin en PWM pour commande moteur

    unsigned int tick_codeuse = 0; // Compteur de tick de la codeuse

    const int frequence_echantillonnage = 200; // Frequence du pid
    const int rapport_reducteur = 468; // Rapport entre le nombre de tours de l'arbre moteur et de la roue
    const int tick_par_tour_codeuse = 2; // Nombre de tick codeuse par tour de l'arbre moteur

    // gestion vitesse angulaire
    float consigne_vitesse_angulaire = analogRead(potar);

    float erreur_precedente = (float) consigne_vitesse_angulaire;
    float somme_erreur = 0; // Somme des erreurs pour l'integrateur
    float kp = 200; // Coefficient proportionnel
    float ki = -1; // Coefficient integrateur
    float kd = 300; // Coefficient derivateur

    /* Routine d'initialisation */
    void setup() {
    Serial.begin(250000); // Initialisation port COM
    pinMode(_MOTEUR, OUTPUT); // Sortie moteur
    analogWrite(_MOTEUR, 255); // Sortie moteur a 0

    delay(1000); // Pause de 1 sec pour laisser le temps au moteur de s'arreter si celui-ci est en marche

    attachInterrupt(0, compteur, RISING); // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
    timer.setInterval(1000/frequence_echantillonnage, asservissement); // Interruption pour calcul du PID et asservissement
    }

    /* Fonction principale */
    void loop() {
    timer.run();
    delay(10);
    }

    /* Interruption sur tick de la codeuse */
    void compteur() {
    tick_codeuse++; // On incremente le nombre de tick de la codeuse
    }

    /* Interruption pour calcul du PID */
    void asservissement()
    {
    // Réinitialisation du nombre de tick de la codeuse
    int tick = tick_codeuse;
    tick_codeuse = 0;

    // Calcul des erreurs
    float frequence_codeuse = frequence_echantillonnage*tick;
    float nb_degre_par_sec = (float)frequence_codeuse/(float)tick_par_tour_codeuse/(float)rapport_reducteur*360;
    float erreur = consigne_vitesse_angulaire - nb_degre_par_sec;
    somme_erreur += erreur;
    float delta_erreur = erreur - erreur_precedente;
    erreur_precedente = erreur;

    // PID : calcul de la commande
    cmd = kp * erreur + ki * somme_erreur + kd * delta_erreur;

    // Normalisation et controle du moteur
    if (consigne_vitesse_angulaire 500) cmd = 255;
    consigne_vitesse_angulaire = analogRead(potar); // mesure tension du potar
    consigne_vitesse_angulaire = map(consigne_vitesse_angulaire, 510, 750, 0, 500); // adaptation echelle valeur

    analogWrite(_MOTEUR, 255 - cmd);

    // affiche sur le moniteur les données voulues
    Serial.print("Erreur : "); Serial.print(erreur,5);
    Serial.print(" ");
    Serial.print("consigne vitesse ang. : ");Serial.print(consigne_vitesse_angulaire,3);
    Serial.print(" ");
    Serial.print("Commande : "); Serial.print(cmd,3);
    Serial.print(" ");
    Serial.print("nb_degre_par_sec : ");Serial.print(nb_degre_par_sec,6);
    Serial.print(" ");
    Serial.print("erreur precedente : ");Serial.print(erreur_precedente,5);
    Serial.print(" ");
    Serial.print("Freq. Codeuse : ");Serial.println(frequence_codeuse,6);

    }

    [/PHP]

  35. salut,
    j'ai un mini projet sur asservissement en vitesse linéaire , si et possible donnez-moi quelques exemples de vitesse linéaire! je trouve des machines en vitesse angulaire mais je ne trouve pas en vitesse linéaire!!

  36. Bonjour,

    Merci et bravo pour ces deux articles clairs et qui expliquent simplement les PID et leurs mise en oeuvre pratique. M'amusant beaucoup avec des drones je m'explique mieux leurs comportement et cela facilite grandement les réglages.

  37. Merci pour votre cours, mais quelles sont les puissances des résistances et quel type de diode ?

  38. Salut,

    ton article est vraiment très intéressant et à du en aider plus d'un 🙂
    J'essais pour ma part de l'utiliser pour l'asservissement d'un moteur Brushless avec un signal en Spwm.
    La vitesse de rotation varie avec la fréquence d’exécution de la fonction loop dans laquelle on fait varier la valeur des sorties PWM.

    Ma roue codeuse fait 2400 tick par tour et j'essai d'avoir une vitesse constante de 137 ticks toutes les 100 ms.
    c'est ce que j'obtiens quand ma fréquences de boucle est à 13µs environ. delayMicroseconds(cmd);

    Ce que je ne comprends pas c'est que les valeurs de CMD issue du cacul de PID sont bien au dessus du chiffre 13 ce qui fait que mon moteur se retrouve aux vitesses de seuil fixés. Soit vitesse maxi, soit à l'arret.

    Pour info voici ma fonction, je n'ai pas à traduire les tick en frequence de rotation car ma consigne est le nombre de tick lui même sur 100ms

    void asservissement()
    {

    int tick = ticksCodeur;
    ticksCodeur = 0;

    // Calcul de l'erreur
    int erreur = consnbticks - tick;
    somme_erreur += erreur;
    float delta_erreur = erreur - erreur_precedente;
    erreur_precedente = erreur;

    // PID : calcul de la commande
    cmd = 500 - kp*erreur + ki*somme_erreur + kd*delta_erreur;

    // Normalisation et contr�le du moteur
    if (cmd 200) cmd = 500;

    Serial << tick << ";" << erreur << ";" << somme_erreur << ";" << delta_erreur << ";" << cmd << endl;
    }

    Merci de votre aide si vous suivez toujours ce poste 🙂

  39. Bonjour, est ce que vous pouvez me donner une methode pour convertir la commande calculée par le PID en des signaux PWM? quelle relation entre cette commande et les rapports cycliques des Timers?

  40. Bonjour,

    en fait c'est la question que je me pose aussi... une mise à l'échelle entre le résultat du pid et la commande moteur est obligatoire en fait non ?

    Par exemple cela fonctionnerait t'il si on souhaitait avoir comme consigne le nombre de tick codeur par 100ms ?

    l'échelle n'est pas la même et il n'y a pas de rapport précis entre le dutycycle du pwm et la vitesse de rotation.

  41. Bonjour et simplement merci pour ces démonstrations et ces aides que vous nous apportez.
    Je reconnais que j'avais oublié ces notions et donc abandonné un projet mais ces petits rappels me donnent l'envie de m'y remettre : donc MERCI Ferdinand.

  42. Bonjour,
    Est que vous pouvez m'aider a faire un organigramme pour ce programme. Merci

  43. Bonsoir
    Je trouve votre tuto très intéressant merci beaucoup , en fait je voulais demander autre chose , les graphes vous les obtenez comment ? est ce un logiciel ou bien vous les tracez vous même ?
    Merci

  44. Bonjour, un grand merci pour ce super tuto.

  45. J'arrive pas a faire un logigramme pour ce programme

  46. Bonsoir
    J ai un projet de construction d un gyropode type "segway" , et je pense que la mise en place d'un pid indispensable au sujet du calcul angulaire de la plate forme et faire réagir les moteurs en conséquence
    Par contre je ne sais pas comment déterminer les consignes !
    Pourriez.vous m'aider sur le sujet ?
    Merci d avance
    Giloris

  47. Bonjour, je travaille actuellement sur une maquette d'ascenseur et je suis bloquer au niveau de sin asservissement en vitesse svp faite un schéma complet qui marche avec le programme final de ferdinand. Merci

  48. bonjour
    je ne comprends pas comme fonctionne cet asservissement de vitesse, si il s'agit bien d'un asservissement de vitesse?
    Pour faire simple on va se placer dans le cas ou on ne fait que du proportionnel (donc pas de Ki et pas de Kd)
    j'ai donc cmd qui vaut : Kp * erreur
    la commande qui est envoyée avec analogwrite est : analogwrite(broche, 255-cmd)

    Lorsque la vitesse réelle est égale à la consigne j'ai cmd=0 car erreur =0 donc une valeur de 255 est envoyée ce qui correspond à la vitesse max indépendamment de ma valeur de consigne. Il n'y a donc pas d'asservissement par rapport à la consigne de vitesse? ou alors il y a quelque chose de gros qui m'échappe...

  49. mrc pour les eplication j ai an dynamotachemitrique a la place de l encodeur j'arrive pas a faire son asservissement

  50. Bonsoir, moi j'essaie d'utiliser ce code mais j'ai besoin du schéma si possible

  51. Bonsoir,

    Merci pour ce processus détaillé de mise en œuvre d'une régul PID sur ARDUINO, avec un moteur cc pour actionneur. Retraité de la pétrochimie en centrale thermique, j'étais toujours en admiration devant les personnes en charge de régler les boucles de régulation avec la phase identification, et réglages. C'était quelques fois du PI seul.
    Je vais peut-être mettre en œuvre ce code pour une appli de rotation de tube en phase de soudure pour un ami ferronnier. L'entrainement du tube en rotation devant se faire à vitesse constante et stable, l'actionneur serait un moteur d'essuie-glace via un réducteur 50:1.

    Merci à vous pour ce brillant développement sur un cas concret, aboutissant à du code directement applicable, sous réserve de trouver les coefficients appropriés.

    Bonne continuation!!

    .

  52. Salut,

    je ne comprends pas comment tu calcules la vitesse de rotation en tours/s a partir du nombre d impulsions.

    ( ligne 57 et 58 du code )

    A partir de ma roue codeuse je peux directement obtenir la vitesse de rotation en tours par seconde , et je pourrais directement passer a la ligne du code 59 . est ce possible?

    Merci de votre reponse

  53. bonjour,
    c'est étrange mais je ne vois à aucun moment l'utilisation de la codeuse O_o.
    Comment faites vous pour mesurez quoi que ce soit sans même l'instancier?

  54. Bonjour,

    Merci de votre tutoriel, j'ai fais le même asservissement que celui de votre code mais pour deux moteurs, chacun ayant deux capteurs, j'ai donc quatre interruptions au total. Les interruptions de chaque moteur incrémente une fonction différente (une fonctions pour les interruptions de chaque moteur). Je trace les courbes et je vois que la consigne est atteinte à merveille mais en testant su terrain, ça marche souvent en ligne droite mais par moment le robot se met à tourner à droite ou à gauche (une des roues est donc au dessus ou en dessous de la consigne) signe pour moi que une des interruption est n'est pas bien comptée ou une des interruptions n'est pas comptée, mais ce n'est qu'une hypothèse. Avez vous été confronté à ce problème? avez vous des pistes à exploiter.

    Ah j'ai mis un condo de 100u sur le L298N partie puissance.

    Je vous remercie.

  55. bonjour
    j ai un montage pour faire fonctionner diviseur sur 360° avec 72 000 pas au tours du diviseur avec un arduino
    un moteur pas a pas un driver commander par un clavier un joystik une resistance ajustable avec ecran lcd 2004
    en plus je voudrais commander le moteur pas a pas avec un codeur incrementale 2500 pas au tour
    entre +/- 5 a 20 volts sortie A B Z
    j ai trouver q un asservissement en position avec des moteur en cc
    j ai repiquer sur le net pour faire mon programme

    avez vous dans vos archive une base programmable pour un moteur pas a pas
    merci d avance

  56. Enfin un article qui traite en détail l'sservissement en vitesse d'un moteur avec Arduino!

  57. Bonjour je souhaite réguler la température et humidité dans un incubateur éclosior et je ne sais pas comment commencer j'ai vraiment besoin que vous m'aider... Si possible

    • Bonjour,
      - Si c'est un projet qui va être réalisé en vrai et que ce n'est pas un projet universitaire, je vous conseille d'utiliser des régulateurs du commerce comme par exemple Inkbird ITC-308 pour la température et Inkbird ITC-306A pour l'humidité. J'utilise ça pour mon propre incubateur et ça marche relativement bien. La fluctuation n'est pas nulle (un degré ou plus en fonction de l'inertie du système) car ce ne sont pas des régulateurs PID mais c'est plus que suffisant pour un incubateur.
      - Si vous souhaitez absolument réaliser un PID, il faut vous assurer que le système de chauffage et de régulation d'humidité puisse être réglable en intensité. Si on peut uniquement les allumer et les éteindre sans régler l'intensité alors le PID n'est pas le moyen de régulation approprié. Ensuite, vous pouvez régler les différents paramètres du PID exactement comme dans cet article : http://www.ferdinandpiette.com/blog/2012/04/asservissement-en-vitesse-dun-moteur-avec-arduino/
      Seulement, l'inertie de votre système sera surement assez long ce qui veut dire que le tracé des courbes et l'ajustement des paramètres va prendre pas mal de temps.
      - Si c'est un projet universitaire, le mieux est de commencer à modéliser le système : brancher le chauffage, relever à intervalle de temps régulier, tracer le graphe de température en fonction du temps ; couper le chauffage et relever aussi le graphe pour le refroidissement. A partir du graphe, établir l'ordre du système (1, 2, autre) et en déduire les paramètres qui vont bien (système du premier ordre de type exp(-t/tau) : relever le tau, système du second ordre : relever le facteur de qualité, le facteur d'amortissement, etc.). A partir de cette modélisation, il existe des techniques pour avoir une première approximation des paramètres d'un PID (une petite recherche bibliographique s'impose). Il faut modéliser le système en température, mais aussi en humidité. Attention toutefois : le taux d'humidité peut potentiellement faire changer l'inertie en température du système, la modélisation en température du système à 20% d'humidité peut être différente de la modélisation en température à 80% d'humidité, le mieux est de modéliser le système en températures pour plusieurs taux d'humidité pour être sûr.
      Bon amusement 😉

Laisser un commentaire pour dakito Annuler le commentaire

Vous pouvez utiliser ces tags et attributs HTML&nsbp;: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

© 2011-2012 Ferdinand Piette