Moteur avec encodeur – asservissement

On a vu que les programmes précédents ne permettaient pas une super exactitude de positionnement car, bien que le positionnement soit suivi avec précision grâce aux encodeurs, le moteur continue de tourner une fois la position atteinte et l’alimentation coupée.

Première façon assez « bourrine » mais efficace, si on a besoin de 720 pas de rotation, par exemple, retirer dans le programme une centaine de pas qui seront faits moteur coupé. Cette correction est constante pour une valeur pwm donnée et une tension donnée à condition de ne pas s’occuper de rotations de moins d’un tour.

Deuxième façon, recourir à une fonction d’asservissement. Je me suis inspiré de cet article qui m’a bien aidé car je ne connaissais rien de ce domaine.

On utilise un timer pour lancer une action d’échantillonnage à intervalles réguliers et suivant l’écart entre la position actuelle et la cible, on ajuste la vitesse du moteur et, éventuellement, on fait marche arrière si on a été trop loin.

Les paramètres ont été trouvés par ajustements successifs, si vous avez du matériel différent il faudra sans doute les modifier. La précision obtenue est satisfaisante avec des erreurs maxi autour de 3%. Les déplacements inférieurs à un tour sont à éviter.


/*
   Moteur encodeur V6

avec asservissement de type P  
 

*/
 
 #include <Encoder.h>
 
 // le moteur a deux encodeurs
 // en quadrature et
 // chaque pin est controlee
 // par interruption
 
 int A = 2; // pin encodeur A
 int B = 3; // pin encodeur B
 
Encoder position_moteur1(A,B);

// position cible exprimée en 
// nb de ticks
// 1 tour arbre moteur = 360 ticks

long position_cible_M1=0;

// un utilise une librairie
// pour commander l'echantillonage

#include <SimpleTimer.h>

SimpleTimer timer;

// Intervalle de temps entre deux mesures
// ici 80 ms

int intervalle_echantillonage=80;

// constante de correction
// coeff proportionnel
// asservissement de type P

float kp=0.95; 
 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation

// valeur mini et maxi pwm
// si trop bas degradation des balais
// et certains moteurs ne tournent pas


int pwm_max=255;

int pwm_min=120;

// fonction de controle des moteurs
 
void marche_avant(int a) //Marche avant
{
     if ( a >= pwm_min) {
      analogWrite (E1,a); // PWM (reglage vitesse)
      digitalWrite(M1,HIGH); // sens de rotation
     } 
  else analogWrite(E1,0);; 
 
} 
 
void marche_arriere (int a) //Marche arrière
{
     
   if ( a >= pwm_min) {
       analogWrite (E1,a); // PWM (reglage vitesse)
       digitalWrite(M1,LOW); // sens de rotation
       } 
   else analogWrite(E1,0);
}

 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
   
   // pin moteur
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   // pin encodeurs
   
   pinMode(A,INPUT); 
   pinMode(B,INPUT); 
   
   // pour que les capteurs à effet Hall fonctionnent
   // il faut activer la resistance pull-up
   // de l'Arduino
   
   digitalWrite(A,HIGH);
   digitalWrite(B,HIGH);
   
   // initailiser la position de l'axe moteur
   
   position_moteur1.write(0); 
   
   // la position que l'on veut atteindre
   // exprimée en degrés
   // remarquer que ce n'est pas un appel de fonction
   
   position_cible_M1=5000; 
   
   //demander au timer de lancer l'echantillonage
   
   timer.setInterval(intervalle_echantillonage,asservissement);    
    
}
 
// executee en boucle
 
void loop() {
  
   timer.run();              
    
   Serial.print("*** Dans loop, position actuelle :");
   
   Serial.println(position_moteur1.read());        
   
   // la position finit par se stabiliser
   // mais la fonction d'asservissement continue
   // cependant sans relache
   // à envoyer des ordres au moteur
   // Ceux-ci ne font pas tourner le moteur
   // car la vitesse demandee est trop faible  
}

// la fonction appelée par le timer

void asservissement()

{
   
  long erreur;
  
  int valeur_pwm;
    
  erreur = position_cible_M1 -  position_moteur1.read()  ;      
  
  valeur_pwm = kp * erreur;
  
  if (valeur_pwm >= 255) {
     valeur_pwm = pwm_max;
  }

  else   
  if (valeur_pwm <= -255) {
     valeur_pwm = -pwm_max ;
  }
  
  // petits ajustements avec map sur
  // les valeurs pwm
  // pour obtenir davantage de valeurs
  // actives
  
    if (valeur_pwm > 0) {
       valeur_pwm=map(valeur_pwm,0,255,80,255);   
       marche_avant(valeur_pwm);
     }
     
     else { valeur_pwm=map(valeur_pwm,-255,0,-255,-80);         
            marche_arriere(-valeur_pwm) ; 
          }
    
}

Moteur avec encodeur

Il existe des moteurs avec encodeur incorporé. J’ai trouvé celui-ci chez Lextronic, le modèle est un DCM2. Il y a 6 fils connectés au moteur. Le rouge et le noir sont les alimentations habituelles d’un moteur. Les 4 autres fils s’occupent de capteurs intégrés au moteur. Ce ne sont pas des capteurs optiques comme dans les montages précédents mais des capteurs magnétiques à effet Hall.

Les capteurs sont à alimenter en 5 v :

– le fil marron est l’alimentation des capteurs
– le fil vert est la masse pour les capteurs

– le fil bleu est la sortie du capteur A
– le fil violet est la sortie du capteur B

On peut utiliser un seul capteur et travailler dans le même esprit que précédemment. On peut aussi utiliser les deux capteurs ce qui est intéressant car cela nous permet de connaître le sens de rotation du moteur. (Les capteurs envoient des signaux carrés, en quadrature et cela permet de retrouver le sens de rotation du moteur).

On peut écrire un programme analogue à ceux utilisés jusqu’ici :


/*
   Moteur encodeur V0
   
   on utilise ici uniquement 
   
   le capteur A
    
       
 */
 
int ENC_A = 2; // encodeur A à la pin 2 
 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation

// on va stocker les chgts de l'encodeur 

volatile long chgt_encodeur = 0;


// la fonction de traitement de l'interruption

void compte_chgt()
{

chgt_encodeur ++ ;

}

 
void marche_avant(char a) //Marche avant
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,HIGH); // sens de rotation
} 
 
void marche_arriere (char a) //Marche arrière
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,LOW); // sens de rotation
}

void stop_moteur () 
{
  analogWrite (E1,0); // PWM (reglage vitesse)
  delay(100);
}
 
 
/////////////////////////////////////////////////////////////////////
void marche_nb_pas( int vitesse, int nb_pas, char sens)
/////////////////////////////////////////////////////////////////////

{


chgt_encodeur = 0;


// lancer le moteur

 if (sens == 'A') {
      marche_avant (vitesse);
     }
     
 if (sens == 'R') {
      marche_arriere (vitesse);
     }
     

while ( chgt_encodeur < nb_pas  ) {        
  
  // on ne fait rien
  // chgt_encodeur est incrémentée
  // par la fonction de
  // traitement de l'interruption
     
  }      

stop_moteur();
}
 
 
 
// Executee une seule fois
 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie
    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   pinMode(ENC_A,INPUT); 
   
   // pour que le capteur à effet Hall fonctionne
   // il faut activer la resistance pull-up
   // de l'Arduino
   
   digitalWrite(ENC_A,HIGH);
   
   // l'interruption 0 (pin 2) déclenche la fonction compte_chgt
   
   attachInterrupt(0, compte_chgt, CHANGE);        
  
}
 
// executee en boucle
 
void loop() {
  
    
   Serial.println("On avance de 300 pas");
     
   marche_nb_pas(200,300,'A');
   
   delay(3000);
    
   
  Serial.println("On recule de 300  pas");
   
   marche_nb_pas(200,300,'R');
   
   delay(3000);
   
  
}


On peut aussi recourir à des bibliothèques optimisées et qui simplifient l’écriture , ce que l’on a fait dans le programme ci-dessous :


/*
   Moteur encodeur V1   
    
       
 */
 
 #include <Encoder.h>
 
 int A = 2; // pin encodeur A
 int B = 3; // pin encodeur B
 
Encoder position_moteur1(A,B);
 
 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation



 
void marche_avant(char a) //Marche avant
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,HIGH); // sens de rotation
} 
 
void marche_arriere (char a) //Marche arrière
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,LOW); // sens de rotation
}

void stop_moteur () 
{
  analogWrite (E1,0); // PWM (reglage vitesse)
  delay(100);
}
 
 
/////////////////////////////////////////////////////////////////////
void marche_nb_pas( int vitesse, int nb_pas, char sens)
/////////////////////////////////////////////////////////////////////

{

position_moteur1.write(0);

long pos_M1  = 0;

// lancer le moteur

 if (sens == 'A') {
      marche_avant (vitesse);      
      
     }
     
 if (sens == 'R') {
      marche_arriere (vitesse);      
         
     }
     

 while (  abs(position_moteur1.read()) < nb_pas  ) {        
  
  }      

stop_moteur();

} 
 
 
// Executee une seule fois
 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie
    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   pinMode(A,INPUT); 
   pinMode(B,INPUT); 
   
   // pour que le capteur à effet Hall fonctionne
   // il faut activer la resistance pull-up
   // de l'Arduino
   
   digitalWrite(A,HIGH);
   digitalWrite(B,HIGH);
          
  
}
 
// executee en boucle
 
void loop() {
  
    
   Serial.println("On avance de 720 pas");
     
   marche_nb_pas(200,720,'A');
   
   delay(3000);
    
   
   Serial.println("On recule de 720  pas");
   
   marche_nb_pas(200,720,'R');
   
   delay(3000);
   
  
}

On peut enfin décider de raisonner non pas en nombre de pas à faire dans un sens ou dans l’autre mais en position à atteindre.


/*
   Moteur encodeur V2   
    
       
 */
 
 #include <Encoder.h>
 
 int A = 2; // pin encodeur A
 int B = 3; // pin encodeur B
 
Encoder position_moteur1(A,B);

 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation

 
void marche_avant(char a) //Marche avant
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,HIGH); // sens de rotation
} 
 
void marche_arriere (char a) //Marche arrière
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,LOW); // sens de rotation
}

void stop_moteur () 
{
  analogWrite (E1,0); // PWM (reglage vitesse)
  delay(100);
}
 
 
/////////////////////////////////////////////////////////////////////
void marche_vers_position( int vitesse, int position_cible_M1)
/////////////////////////////////////////////////////////////////////

{
   
       while (position_moteur1.read() < position_cible_M1) {
              marche_avant(vitesse);           
       }
      
       while (position_moteur1.read() > position_cible_M1) {
              marche_arriere(vitesse);
        }           
        

stop_moteur();

}

 
// Executee une seule fois
 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie
    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   pinMode(A,INPUT); 
   pinMode(B,INPUT); 
   
   // pour que le capteur à effet Hall fonctionne
   // il faut activer la rsistance pull-up
   // de l'Arduino
   
   digitalWrite(A,HIGH);
   digitalWrite(B,HIGH);
   
          
  
}
 
// executee en boucle
 
void loop() {
  
   position_moteur1.write(0);     
      
   Serial.println("On avance position 300");
     
   marche_vers_position(200,300);
   
   Serial.println(position_moteur1.read());
   
   delay(3000);    
   
  Serial.println("On retourne position 0");
   
   marche_vers_position(200,0);
   
   Serial.println(position_moteur1.read());
   
   delay(3000);
   
   Serial.println("******************");
   
  
}

Ce dernier programme met en lumière un souci : on demande la position 300 mais on l’atteint pas forcément… En effet si l’alimentation moteur est bien coupée à la position 300, le moteur continue à tourner par inertie et va jusqu’à la position 305 (alim moteur 4,5v) ou 370 (alim moteur 12v).

Nous verrons comment améliorer la situation dans un prochain article.

Compter les tours avec une fourche optique – utilisation des interruptions

Pour être sûr de ne pas louper un événement (par exemple plus de lumière reçue par le détecteur de la fourche optique), il faut trouver un moyen de le traiter immédiatement, même quand le programme est occupé à tout autre chose, par exemple faire un calcul ou attendre par un appel à delay() … En effet, lorsqu’il n’est pas précisément en train d’exécuter les quelques lignes du programme qui sont à l’affût du changement d’état, l’Arduino est sourd !

Les interruptions permettent, toutes affaires cessantes, de traiter un événement dès qu’il se produit, en interrompant provisoirement l’exécution du programme. Une fois l’interruption traitée, le programme reprend son cours là où il en était resté.

Si vous ne savez pas du tout ce que c’est qu’une interruption, jetez un coup d’oeil ici.

Le programme précédent est simplifié, il va supporter des vitesses de rotation plus élevées… que des avantages !


/*
   Compte-tours avec fourche optique
   et roue encodeuse 10 dents
   DFrobot SEN0038
   utilisation des interruptions
    
       
 */
 
int FO = 2; // fourche optique relié à la pin 2 
 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation

// on va stocker les chgts d'état de la fourche

volatile long chgt_fo = 0;


// la fonction de traitement de l'interruption

void compte_chgt()
{

chgt_fo ++ ;

}

 
void marche_avant(char a) //Marche avant
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,HIGH); // sens de rotation
} 
 
void marche_arriere (char a) //Marche arrière
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,LOW); // sens de rotation
}

void stop_moteur () 
{
  analogWrite (E1,0); // PWM (reglage vitesse)
  delay(100);
}
 
 
/////////////////////////////////////////////////////////////////////
void marche_nb_pas( int vitesse, int nb_pas, char sens)
/////////////////////////////////////////////////////////////////////

{

int nb_top = 0;

chgt_fo = 0;


// lancer le moteur

 if (sens == 'A') {
      marche_avant (vitesse);
     }
     
 if (sens == 'R') {
      marche_arriere (vitesse);
     }
     

while ( nb_top < nb_pas  ) {     
   
   // pour une dent passée,
   // deux changements d'état :
   // lumière=> pas de lumière
   // pas de lumière => lumière
   
   // c'est la fonction de traitement
   // de l'interruption qui s'occupe
   // d'incrémenter la variable chgt_fo
     
    nb_top = chgt_fo / 2;
    
  //  Serial.print ("*************** nb_top : ");
    
   // Serial.println(nb_top);    
  
     
  }     
 

stop_moteur();
}
 
 
// Executee une seule fois
 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie
    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   pinMode(FO,INPUT); // fourche optique reliée pin 2
   
   // l'interruption 0 (pin 2) déclenche la fonction compte_chgt
   
   attachInterrupt(0, compte_chgt, CHANGE);     
   
  
}
 
// executee en boucle
 
void loop() {
  
    
   Serial.println("On avance de 20 pas");
   // 10 pas = 1 tour
   
   marche_nb_pas(200,20,'A');
   
   delay(3000);
    
   
  Serial.println("On recule de 5  pas");
   
   marche_nb_pas(200,5,'R');
   
   delay(3000);
   
  
  Serial.println("On recule de 15 pas");
   
   marche_nb_pas(200,15,'R');    
    
  delay(3000);
}

Compter les tours avec une fourche optique

Voici une variante qui utilise une roue encodeuse de 10 dents et une fourche optique (Voir Dfrobot SEN0038).

Ici pas de contrainte mécanique comme un fin de course peut poser, il n’y a pas de contact.

Pour des vitesses de rotations élevées, ce type de programme ne suffit pas, on perd des événements ; pour s’en sortir, il faut faire appel aux interruptions comme nous le verrons dans le prochain article.

Le programme est pratiquement le même :



/*
   Compte-tours avec fourche optique
   et roue encodeuse 10 dents
   DFrobot SEN0038
    
       
 */
int FO = 2; // fourche optique relié à la pin 2 
 
int E1 = 5; //M1 Controle de vitesse PWM
 
int M1 = 4; //M1 Controle du sens de rotation
 
void marche_avant(char a) //Marche avant
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,HIGH); // sens de rotation
} 
 
void marche_arriere (char a) //Marche arrière
{
  analogWrite (E1,a); // PWM (reglage vitesse)
  digitalWrite(M1,LOW); // sens de rotation
}

void stop_moteur () 
{
  analogWrite (E1,0); // PWM (reglage vitesse)
  delay(100);
}
 
 
/////////////////////////////////////////////////////////////////////
void marche_nb_pas( int vitesse, int nb_pas, char sens)
/////////////////////////////////////////////////////////////////////

{
// un axe comporte une roue
// encodeuse 
// 10 "dents" par roue
// on compte les changements d'etat
// de la pin reliée :
// pour une dent passée,
// deux changements d'état
// lumière=> pas de lumière
// pas de lumière => lumière

boolean etat_fo = false;

boolean etat_fo_precedent = false ;

int chgt_fo = 0 ;

int nb_top = 0;


// lancer le moteur

 if (sens == 'A') {
      marche_avant (vitesse);
     }
     
 if (sens == 'R') {
      marche_arriere (vitesse);
     }
     

while ( nb_top < nb_pas  ) {
  
      
 etat_fo = 0;
  
 if (digitalRead(FO)) {       
        
      delay(5);
      
      etat_fo = 1;
            
 }             
 
  
   // est-ce qu'on a changé d'état ?
  
   if ( etat_fo != etat_fo_precedent ) {
     
       chgt_fo++;
   }
   
  
   
   // 1 passage de dent entraine
   // 2 changements d'état
     
    nb_top = chgt_fo / 2;
    
  //  Serial.print ("*************** nb_top : ");
    
   // Serial.println(nb_top);
    
    etat_fo_precedent= etat_fo;     
     
  }     
 

stop_moteur();
}
 
 
 
// Executee une seule fois
 
void setup() {
   
   // initialiser la communication serie
    
   Serial.begin(9600);
    
   pinMode(E1, OUTPUT); // E1 est utilisee en sortie
    
   pinMode(M1, OUTPUT); // M1 est utilisee en sortie
   
   pinMode(FO,INPUT); // fourche optique reliée pin 2
   
   
  
}
 
// executee en boucle
 
void loop() {
  
    
   Serial.println("On avance de 20 pas");
   // 10 pas = 1 tour
   
   marche_nb_pas(200,20,'A');
   
   delay(3000);
    
   
  Serial.println("On recule de 5  pas");
   
   marche_nb_pas(200,5,'R');
   
   delay(3000);
   
  
  Serial.println("On recule de 15 pas");
   
   marche_nb_pas(200,15,'R');    
    
  delay(3000);
}