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.

rolesearmeard.blogspot.com

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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel