R©gulation Vitesse Dun Moteur Courant Continu Ppt
Sommaire
-
-
- Introduction
- Matériel utilisé
- Mesure de la vitesse de rotation avec un codeur incrémental
- Calcul de la vitesse
- Comptage du nombre d'impulsions
- Asservissement en vitesse du moteur
- Code complet
-
Introduction
L'asservissement en vitesse d'un moteur à courant continu est la plupart du temps nécessaire pour les robots mobiles. On peut éventuellement se satisfaire d'un servomoteur à rotation continu dans le cas de petits robots mais dans un cas plus général (comme pour le robot gyropode Geeros), il sera préférable d'utiliser un moteur à courant continu avec réducteur, associé à un codeur incrémental (pour mesurer la vitesse de rotation). Le calcul de l'asservissement sera réalisé par un Arduino. Nous allons détailler tout ceci dans la suite de cet article.
Matériel utilisé
Ce tutoriel peut être mis en application facilement avec l'expérience « Commande de moteur électrique ».
La carte Romeo est intéressante car elle intègre de façon très compacte le micro-contrôleur (AVR Atmega328, le même cœur qu'un Arduino Uno) et un double pont en H permettant de contrôler deux moteurs à courant continu avec un courant max de 2 A en permanence (jusqu'à 3 A en pic). Ce composant est indispensable pour fournir suffisamment d'énergie au moteur et lui permettre de fonctionner à vitesse variable et dans les deux sens de rotation.
Si vous utilisez d'autres éléments pour ce tutoriel, faites un choix cohérent :
- La tension de la batterie doit correspondre à celle du moteur (pour ne pas griller ce dernier avec une fausse manipulation)
- Le courant max du moteur doit correspondre à ce que peut supporter le driver de puissance
Concernant ce dernier point, dans la pratique les risques sont très limités si vous ne bloquez pas le moteur. Le courant à vide est en général relativement faible ; le courant max est observé uniquement lorsque le moteur est bloqué alors qu'il est alimenté avec sa tension maximale.
Mesure de la vitesse de rotation avec un codeur incrémental
Le codeur incrémental fournit deux signaux carrés en quadrature, comme sur la capture ci-dessous:
Ces deux signaux permettent de mesurer à la fois la vitesse et le sens de rotation.
Calcul de la vitesse
La mesure de la vitesse se fait simplement en comptant le nombre d'impulsions pendant un temps fixe. Les données du problème sont les suivantes :
- Le codeur est fixé à l'arbre moteur et non pas à l'arbre de sortie du réducteur (celui utilisé pour l'entrainement). Le rapport de réduction étant 34:1, l'arbre moteur fait 34 tours lorsque l'arbre « principal » en fait 1
- Le codeur génère 48 impulsions à chaque fois qu'il fait un tour
- La cadence d'échantillonnage utilisée pour l'asservissement sera de 0.01 s
Par conséquent, lorsque l'arbre principal fait un tour, le codeur génère :
34 * 48 = 1632 impulsions.
Si N est le nombre d'impulsions comptées en 0.01 s, la vitesse est (en rad/s, l'unité standard, sachant qu'un tour fait 2*π radians) :
2*π*N/(0.01*1632)
Un point très important concerne la résolution de la mesure, c'est-à-dire la plus petite valeur qu'il est possible de calculer. La formule est la suivante (en rad/s) :
2*π/(Ts*CPR*ratio)
avec :
- Ts : cadence d'échantillonnage
- CPR : nombre d'impulsions par tour du codeur
- ratio : rapport de réduction du moteur
Le but étant d'avoir la plus faible résolution possible (pour avoir une bonne précision de mesure), il faut avoir Ts et/ou CPR et/ou ratio le plus grand possible. Cependant, ratio est fixé par le besoin en couple ou en vitesse maximale alors que Ts doit être plus petit que le temps de réponse souhaité pour l'asservissement du moteur (voir plus loin). Par conséquent, la seule réelle possibilité est de jouer sur CPR. Lors du choix d'un codeur incrémental, il est préférable de prendre celui qui permet d'obtenir le plus d'impulsions par tour (si le budget le permet).
Dans notre cas de figure, la résolution est la suivante
2*π/(0.01*1632) = 0.4 rad/s
Ce n'est pas exceptionnel, le seul moyen de faire mieux serait de réduire le temps de réponse de l'asservissement de vitesse du moteur (voir plus loin).
Comptage du nombre d'impulsions
Compter le nombre d'impulsions du codeur revient à compter le nombre de fronts montants et descendants des signaux jaune et bleu représentés sur l'image ci-dessus. Pour ce faire, la seule méthode viable consiste à brancher les deux signaux (les fils jaune et blanc sur le codeur utilisé) sur deux entrées « interruption » de la carte Arduino. Les deux autres fils (bleu et vert) seront respectivement branchés sur le 5 V et sur la masse de l'Arduino.
Sur une carte Romeo (comme sur un Arduino Uno d'ailleurs), il y a deux lignes d'interruption (numérotées 0 et 1), qui correspondent aux broches digitales 2 et 3. L'intérêt d'une ligne d'interruption est qu'elle permet, comme son nom l'indique, d'interrompre le déroulement des calculs sur le micro-contrôleur pour effectuer un traitement spécifique, en l'occurrence la mise à jour du compteur d'impulsions, avant de rendre la main à la boucle principale.
La seule « difficulté » est de savoir s'il faut incrémenter ou décrémenter le compteur dans le traitement de l'interruption. Il suffit pour cela d'observer les courbes ci-dessus, obtenues alors que le moteur tourne dans le sens positif. On constate que:
- Lorsque la voie A (en jaune) passe au niveau haut, la voie B (en bleu) est au niveau bas
- Lorsque la voie A passe au niveau bas, la voie B est au niveau haut
Quand le moteur tourne dans le sens positif, lors d'une interruption sur la voie A, les niveaux de A et B sont donc inversés. Le code correspondant sur l'Arduino sera le suivant :
1 2 3 4 5 6 7 8 9 | void GestionInterruptionCodeurPinA() { if (digitalReadFast2(codeurPinA) == digitalReadFast2(codeurPinB)) { ticksCodeur–; } else { ticksCodeur++; } } |
En ce qui concerne l'interruption liée à la voie B, c'est l'inverse :
- Lorsque la voie B passe au niveau haut, la voie A est au niveau haut
- Lorsque la voie B passe au niveau bas, la voie A est au niveau bas
Le code de traitement de l'interruption de la voie B sera donc le suivant :
1 2 3 4 5 6 7 8 9 | void GestionInterruptionCodeurPinB() { if (digitalReadFast2(codeurPinA) == digitalReadFast2(codeurPinB)) { ticksCodeur++; } else { ticksCodeur–; } } |
Vous aurez peut-être remarqué l'utilisation de la fonction digitalReadFast2() pour lire le niveau des entrées codeur, au lieu de la fonction standard digitalRead(). Cette fonction, issues de la librairie digitalWriteFast (téléchargeable à l'adresse http://www.3sigma.fr/telechargements/digitalWriteFast.zip) permet d'améliorer la rapidité de lecture et d'écriture des entrées digitales pour minimiser le temps passé dans la routine d'interruption (et donc rendre la main plus vite à la boucle de calcul principal). Pour utiliser cette bibliothèque dans votre code, vous devez faire deux choses simples
- décompresser l'archive que vous aurez téléchargée dans le sous-répertoire « librairies » de l'installation de votre environnement de développement Arduino
- ajouter au début de votre programme la ligne
1
#include <digitalWriteFast.h>
Le code permettant de calculer la vitesse est le suivant :
1 2 3 4 5 6 | // Nombre de ticks codeur depuis la dernière fois codeurDeltaPos = ticksCodeur; ticksCodeur = 0; // Calcul de la vitesse de rotation omega = ((2.*3.141592*((double)codeurDeltaPos))/1632.)/0.01; // en rad/s |
Pour que cela fonctionne correctement, ce code doit être exécuté à la cadence fixe de 0.01 s. L'utilisation de la fonction delay() pour faire ça est une mauvaise méthode. En effet, vous pourriez penser écrire le code suivant :
1 2 3 4 5 6 7 8 9 | void loop() { // Traitements divers // Lecture d'entrées + calculs par exemple // Attente de 10 ms delay(10); } |
Le problème est que les « traitements divers » vont prendre un certain temps, inconnu (T) et potentiellement variable (dans le cas où ils incluent des instructions conditionnelles). Par conséquent, la boucle sera exécutée toutes les T+0.01 s et non pas toutes les 0.01 s, et la mesure de la vitesse de rotation sera fausse.
La bonne méthode consiste à utiliser un timer du micro-contrôleur pour générer une interruption toutes les 0.01 s. Vous pouvez utiliser pour cela la bibliothèque FlexiTimer2 (téléchargeable ici: http://www.3sigma.fr/telechargements/FlexiTimer2.zip). Après l'avoir décompressée, il faut la placer dans le sous-répertoire « librairies » de l'installation de l'environnement de développement Arduino et ajouter au début du programme la ligne :
#include <FlexiTimer2.h>
Enfin, il faut ajouter les deux lignes suivantes dans la fonction startup() :
1 2 | FlexiTimer2::set(10, 1/1000., isrt); // résolution timer = 1 ms FlexiTimer2::start(); |
La première ligne crée une interruption qui se produit toutes les 10 ms (avec une résolution de 1 ms), ce qui exécute alors la fonction isrt() (contenant le code permettant de calculer la vitesse de rotation et de réaliser l'asservissement de vitesse). La seconde ligne démarre ce timer.
Asservissement en vitesse du moteur
Pour asservir le moteur en vitesse, nous allons utiliser un régulateur de type PID (Proportionnel Intégral Dérivé). Ce régulateur combine les 3 actions suivantes:
- Proportionnelle : action proportionnelle à l'écart entre la consigne de vitesse et la mesure
- Intégrale: cette action est basée sur l'intégrale par rapport au temps de l'écart entre la consigne et la mesure. Elle permet de compenser une erreur permanente (appelée « erreur statique ») entre la consigne et la mesure
- Dérivée: cette action est basée sur la dérivée par rapport au temps de l'écart entre la consigne et la mesure. Elle permet d'anticiper cet écart, ce qui est utile pour des systèmes naturellement instables, au prix d'une amplification des bruits de mesure
Il n'est pas utile d'associer systématiquement ces trois actions (la proportionnelle est cependant toujours présente). Dans notre cas de figure, un PI suffira, l'anticipation apportée par l'action dérivée n'étant pas utile.
Les gains proportionnel et l'intégral étant respectivement Kp=0.29 et Ki=8.93, le code du la régulation PID s'écrit:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /******* Régulation PID ********/ // Ecart entre la consigne et la mesure ecart = vref – omega; // Terme proportionnel P_x = Kp * ecart; // Calcul de la commande commande = P_x + I_x; // Terme intégral (sera utilisé lors du pas d'échantillonnage suivant) I_x = I_x + Ki * dt * ecart; /******* Fin régulation PID ********/ |
Code complet
Le code complet permettant de réaliser sur la carte Arduino Romeo cet asservissement de vitesse d'un moteur à courant continu avec codeur incrémental est le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | #include <FlexiTimer2.h> #include <digitalWriteFast.h> // Codeur incrémental #define codeurInterruptionA 0 #define codeurInterruptionB 1 #define codeurPinA 2 #define codeurPinB 3 volatile long ticksCodeur = 0; // Moteur CC #define directionMoteur 4 #define pwmMoteur 5 // Cadence d'envoi des données en ms #define TSDATA 100 unsigned long tempsDernierEnvoi = 0; unsigned long tempsCourant = 0; // Cadence d'échantillonnage en ms #define CADENCE_MS 10 volatile double dt = CADENCE_MS/1000.; volatile double temps = -CADENCE_MS/1000.; volatile double omega; volatile double commande = 0.; volatile double vref = 3.14; // PID volatile double Kp = 0.29; volatile double Ki = 8.93; volatile double P_x = 0.; volatile double I_x = 0.; volatile double ecart = 0.; // Initialisations void setup(void) { // Codeur incrémental pinMode(codeurPinA, INPUT); // entrée digitale pin A codeur pinMode(codeurPinB, INPUT); // entrée digitale pin B codeur digitalWrite(codeurPinA, HIGH); // activation de la résistance de pullup digitalWrite(codeurPinB, HIGH); // activation de la résistance de pullup attachInterrupt(codeurInterruptionA, GestionInterruptionCodeurPinA, CHANGE); attachInterrupt(codeurInterruptionB, GestionInterruptionCodeurPinB, CHANGE); // Moteur CC pinMode(directionMoteur, OUTPUT); pinMode(pwmMoteur, OUTPUT); // Liaison série Serial.begin(9600); Serial.flush(); // Compteur d'impulsions de l'encodeur ticksCodeur = 0; // La routine isrt est exécutée à cadence fixe FlexiTimer2::set(CADENCE_MS, 1/1000., isrt); // résolution timer = 1 ms FlexiTimer2::start(); } // Boucle principale void loop() { // Ecriture des données sur la liaison série ecritureData(); } void isrt(){ int codeurDeltaPos; double tensionBatterie; // Nombre de ticks codeur depuis la dernière fois codeurDeltaPos = ticksCodeur; ticksCodeur = 0; // Calcul de la vitesse de rotation omega = ((2.*3.141592*((double)codeurDeltaPos))/1632.)/dt; // en rad/s /******* Régulation PID ********/ // Ecart entre la consigne et la mesure ecart = vref – omega; // Terme proportionnel P_x = Kp * ecart; // Calcul de la commande commande = P_x + I_x; // Terme intégral (sera utilisé lors du pas d'échantillonnage suivant) I_x = I_x + Ki * dt * ecart; /******* Fin régulation PID ********/ // Envoi de la commande au moteur tensionBatterie = 7.2; CommandeMoteur(commande, tensionBatterie); temps += dt; } void ecritureData(void) { // Ecriture des données en sortie tous les TSDATA millisecondes tempsCourant = millis(); if (tempsCourant-tempsDernierEnvoi > TSDATA) { Serial.print(temps); Serial.print(« , »); Serial.print(omega); Serial.print(« \r »); Serial.print(« \n »); tempsDernierEnvoi = tempsCourant; } } void CommandeMoteur(double tension, double tensionBatterie) { int tension_int; // Normalisation de la tension d'alimentation par // rapport à la tension batterie tension_int = (int)(255*(tension/tensionBatterie)); // Saturation par sécurité if (tension_int>255) { tension_int = 255; } if (tension_int<-255) { tension_int = -255; } // Commande PWM if (tension_int>=0) { digitalWrite(directionMoteur, LOW); analogWrite(pwmMoteur, tension_int); } if (tension_int<0) { digitalWrite(directionMoteur, HIGH); analogWrite(pwmMoteur, -tension_int); } } // Routine de service d'interruption attachée à la voie A du codeur incrémental void GestionInterruptionCodeurPinA() { if (digitalReadFast2(codeurPinA) == digitalReadFast2(codeurPinB)) { ticksCodeur–; } else { ticksCodeur++; } } // Routine de service d'interruption attachée à la voie B du codeur incrémental void GestionInterruptionCodeurPinB() { if (digitalReadFast2(codeurPinA) == digitalReadFast2(codeurPinB)) { ticksCodeur++; } else { ticksCodeur–; } } |
Ce code intègre ce qui a été détaillé précédemment.
Source: http://www.robotique.ma/asservissement-en-vitesse-dun-moteur-a-courant-continu/
0 Response to "R©gulation Vitesse Dun Moteur Courant Continu Ppt"
Post a Comment