Cet encodeur est très simple, il permet de déterminer ou programmer la distance parcourue ou mesurer la vitesse de rotation d'une roue. Il n'est cependant pas à quadrature, il ne permet donc pas de déterminer le sens de rotation. On pourrait assez facilement faire l'adaptation nécessaire.
Le principe consiste à coller sur la roue à encoder un réseau de bandes noires et blanches alternatives en rayons. Avec un capteur infrarouge, on compte les bandes qui passent. Il suffit ensuite d'écrire le code autour pour faire ce qu'on veut.
Ingrédients
Pour cet encodeur, il vous faudra en plus d'un microcontrôleur de votre choix (Arduino dans mon cas) :
- 1 émetteur-récepteur infrarouge du genre QRD1114 (voir cette page pour les instructions de connexion à l'Arduino)
- 1 transistor ou MOSFET qui bascule dans les 2v (par exemple un BC547)
- 1 résistance de 10 kOhm
- 1 imprimante.
Le coût de revient est donc inférieur à 3 euros :)
Phase 1 : le capteur
La roue codeuse
Tout d'abord, il faut créer le cercle encodeur.
Pour cela, j'ai utilisé cette feuille excel trouvée sur cette page. Je l'ai ouverte avec LibreOffice, réalisé un cercle encodeur à 40 rayons (à recolorer en noir et blanc à la main, c'est un peu fastidieux), exporté en PDF, ouvert avec The Gimp et imprimé.
Il n'y a plus ensuite qu'à coller le cercle sur la roue.
La lecture
Pour lire la roue, il faut placer le capteur tout près mais pas trop : tout près car le capteur étant très peu directionnel il faut qu'il puisse bien différencier les bandes noires et blanches et pas trop parce que s'il touche le cercle il devient aveugle et rate des bandes. J'ai placé les miens à 2mm environ.
En l'état, ça marche déjà! Il suffit de brancher le capteur sur une entrée analogique de l'Arduino, calibrer pour voir les valeurs correspondant aux bandes noires et blanches et compter les changements avec un poil d'hystérésis pour éliminer les valeurs intermédiaires. Ca donne ceci :
#include <Servo.h> // bibliotheque servo
Servo myservo1; // création de l'objet servo droit
Servo myservo2; // création de l'objet servo gauche
// encodeur
int encodeurGauche = 4; //encodeur gauche
int encodeurDroit = 5; //encodeur droit
int seuil = 200; // seuil de détection de la bande noire
int detect[6]; // compteur de bandes, on met les valeurs dans un tableau avec le numéro de pin comme index
int bande[6]; // tableau de la couleur des bandes en fonction des capteurs (idem)
int val = 0; // valeur lue sur le capteur
int mesureGauche = 0; // comptage des bandes
int mesureDroit = 0;
// vitesse du servo
int avance = 110;
int stoppe = 90; // arret
void setup(){
// servo
myservo1.attach(9); // servo droite sur le pin 9
myservo2.attach(10); // servo gauche sur le pin 10
myservo1.write(stoppe);
myservo2.write(stoppe);
// on initialise les encodeurs
mesureGauche=encode(encodeurGauche);
mesureDroit=encode(encodeurDroit);
detect[encodeurDroit] = 0;
detect[encodeurGauche] = 0;
}
void loop(){
while(mesureGauche < 60){ // exemple : on avance de 60 graduations
tourne(avance,avance);
mesureGauche=encode(encodeurGauche);
mesureDroit=encode(encodeurDroit);
}
tourne(stoppe,stoppe); // et on s'arrete
}
// fonction d'encodage
int encode(int encodeur){ // on sélectionne l'encodeur en cours
val = analogRead(encodeur);
while(val > (seuil+30) && bande[encodeur] == 1){ // bande noire + hysteresis et venant de bande blanche
val = analogRead(encodeur);
detect[encodeur] = detect[encodeur]+1; // on incrémente le compteur
bande[encodeur] = 0; // on note le changement d'état
}
while(val < (seuil-30) && bande[encodeur] == 0){ // bande blanche - hysteresis et venant de bande noire
val = analogRead(encodeur);
detect[encodeur] = detect[encodeur]+1; // on incrémente le compteur
bande[encodeur] = 1; // on note le changement d'état
}
return(detect[encodeur]);
}
// fonction de gestion de la vitesse des servos
void tourne(int vitesseGauche, int vitesseDroite) { // on transmet la vitesse de rotation aux servos
vitesseDroite=map(vitesseDroite,180,0,0,180); // on inverse le sens de rotation du servo droit
myservo1.write(vitesseDroite);
myservo2.write(vitesseGauche);
}
Cette approche a l'avantage de l'extrême simplicité matérielle mais l'inconvénient d'avoir à lire les capteurs en permanence dans la boucle. Si la boucle est très longue à exécuter ou si la roue tourne très vite, on risque de perdre des impulsions. D'où la nécessité de se brancher sur les interruptions matérielles de la carte.
Phase 2 : les interruptions
Pour utiliser les interruptions de l'Arduino, le signal analogique ne convient pas : il faut un signal numérique, en LOW ou HIGH. On va donc transformer le signal analogique du capteur en signal numérique via un transistor.
Un petit tour par le voltmètre m'a montré que le capteur envoie environ 0,2v sur une bande blanche et 2,2v sur une bande noire. Il y a tout un tas de transistors et MOSFET qui réagissent dans ces valeurs là. J'ai testé avec un transistor NPN BC547 et un Mosfet comme celui-ci, ça marche pareil.
On branche la sortie du capteur sur la base du transistor, le collecteur sur le pin correspondant à l'interruption (pin 2 pour l'interruption 0 par exemple) ainsi que sur la sortie 5V de la carte avec une résitance "pull-up" de 10 kOhms en série et l'émetteur sur la masse.
Lorsque le transitsor est inactif (bande blanche), le pin 2 est en état HIGH grâce à la résistance pull-up qui le relie au 5v. Quand le transistor est activé, il relie le pin 2 à la masse, celui-ci passe donc à LOW. Il reste à activer l'interruption en surveillant les changements d'état (option CHANGE) et on peut compter les bandes.
Le code est beaucoup plus simple puisqu'on n'a plus qu'à incrémenter le compteur à chaque interruption, et l'ensemble est bien plus fiable grâce à l'interruption : on n'a plus à se soucier de la longueur du code dans loop(). Voici donc le nouveau code qui fait la même chose que le précédent :
#include <Servo.h> // bibliotheque servo
Servo myservo1; // création de l'objet servo droit
Servo myservo2; // création de l'objet servo gauche
// encodeur
int mesureGauche = 0; // comptage des bandes
int mesureDroite = 0;
// vitesses
int avance = 110; // avance normale
int stoppe = 90; // arret
void setup(){
// interruptions
attachInterrupt(0, rayonGauche, CHANGE); // encodeur gauche
attachInterrupt(1, rayonDroite, CHANGE); // encodeur droit
// servo
myservo1.attach(9); // servo droite sur le pin 9
myservo2.attach(10); // servo gauche sur le pin 10
myservo1.write(stoppe);
myservo2.write(stoppe);
}
void loop(){
while(mesureGauche < 60){ // exemple : on avance de 60 graduations
tourne(avance,avance);
}
tourne(stoppe,stoppe);
}
// fonctions en cas d'interruption
void rayonGauche(){
mesureGauche = mesureGauche+1;
}
void rayonDroite(){
mesureDroite = mesureDroite+1;
}
// fonction de gestion de la vitesse des servos
void tourne(int vitesseGauche, int vitesseDroite) { // on transmet la vitesse de rotation aux servos
vitesseDroite=map(vitesseDroite,180,0,0,180); // on inverse le sens de rotation du servo droit
myservo1.write(vitesseDroite);
myservo2.write(vitesseGauche);
}
On peut désormais s'amuser avec notre robot qui peut être programmé pour des trajectoires complexes.
