Le principe est simple : un châssis motorisé suit une ligne noire. Des détecteurs observent la position de la ligne et donnent les ordres de correction correspondants au robot.
Version 1 : ça eut marché...
Je me suis d'abord lancé avec ce que j'avais sous la main.
Pour la motorisation, j'ai récupéré deux moteurs à courant continu avec moto-réducteurs qui me restaient, toujours suite à l'amélioration d'un char radio-commandé. Ce sont des moteurs très bas de gamme ce qui ne m'a pas facilité la tâche.
Pour les capteurs, j'avais acheté des petits récepteurs-émetteurs infrarouges QRD1114 pour faire de la détection de sol pour mon robot éviteur d'obstacles mais ils se sont révélés totalement inadaptés à cet usage. Par contre ils sont idéaux pour la détection d'une ligne noire sur fond blanc, ce qui est précisément ce qu'on leur demande.
Pour relier tout ça, j'ai fabriqué un châssis en matériau de récup.
Le châssis porte les moteurs à un bout, l'accu pour les alimenter à l'autre bout et par-dessus la carte de commande des moteurs et l'Arduino. Les capteurs peuvent être fixés aux extrémités. Un patin téflon sert de "troisième roue" à l'extrémité de l'ensemble.
Les moteurs sont pilotés via une carte de commande basée sur un pont en H L298 pilotée par l'Arduino. C'est un peu un marteau pour écraser une mouche vu que deux transistors auraient aussi bien fait l'affaire.
Ce projet ne devait me prendre qu'une après-midi, mon idée étant de coller deux capteurs devant le robot (un de part et d'autre de la ligne), écrire les 10 lignes de code correspondantes (ligne sur le capteur droit : on tourne à gauche et vice-versa) et hop!
Mais ça s'est révélé un tantinet plus difficile...
Premier essai
Pour ce premier essai, on a deux capteurs à l'avant du robot, assez loin des moteurs, et un code simple : on avance à vitesse constante et si la ligne est détectée on bloque le moteur situé du côté du capteur. Ça marche... plus ou moins.
Déjà, la démarche du robot est très hachée puisqu'il zigzague en "butant" sur la ligne en permanence. Ensuite, comme les capteurs sont loin des moteurs, ils font de grandes embardées et perdent régulièrement la ligne. Enfin, le robot a la curieuse manie de tourner très bien à gauche, mais très mal à droite... Il fallait donc revoir tout ça.
Deuxième essai
Pour le deuxième essai, j'ai tout d'abord modifié les capteurs : au lieu de 2, j'en ai 4 en ligne. J'ai tout réalisé à la main mais ça peut aussi s'acheter tout fait. De plus les capteurs sont placés à l'arrière du robot, qui devient donc l'avant, bien plus près des moteurs, réduisant la vitesse de leur mouvement latéral par rapport à l'axe d’avancée.
Le comportement du robot est plus "intelligent" : si la ligne est détectée par un des capteurs centraux (petite erreur), on tourne doucement en freinant légèrement une roue et en accélérant l'autre. Les petites corrections sont donc bien plus douces. Par contre si la ligne est détectée par un des capteurs latéraux (grande erreur) on braque en bloquant une roue et en accélérant l'autre. Ca fait une embardée mais comme un des capteurs centraux passe très vite sur la ligne, initiant le comportement "petite erreur", l'embardée est assez vite lissée.
Le robot marche beaucoup mieux! Les lignes droites sont bien mieux négociées et les tournants ne génèrent plus de perte de la ligne intempestive... Dans un sens seulement. En effet, tout comme dans la version précédente, le robot tourne très bien à droite mais très mal à gauche (c'est inversé puisqu'on a retourné le robot).
Après moult recherches dans le code, les branchements, les engrenages, j'ai fini par comprendre qu'un des moteurs était plus faiblard que l'autre et tournait moins vite avec la même valeur de PWM. J'ai donc introduit une variable de trim pour égaliser leur vitesse.
Et enfin, j'ai eu un comportement efficace et cohérent dans les deux sens pendant toute une soirée!
Et le lendemain matin, patatras... La ligne était très mal détectée et le robot la perdait au premier tournant.
J'ai fini par me rendre à l'évidence : moteurs trop bas de gamme, masse (plus de 800g) et donc inertie trop importante, je n'allais pas m'en sortir avec ce matériel. J'ai donc recommencé.
Version 2
Le châssis
J'ai tout d'abord changé les moteurs. J'ai opté pour deux servos à rotation continue : ce n'est pas cher et ça marche très bien, même si ça n'atteint pas des vitesses bien grandes.
Exit également la batterie NiMH, j'alimente désormais l'ensemble avec un boîtier contenant 6 piles AA.
Côté châssis, il a bien réduit en taille. J'ai fait au plus simple : les servos sont collés avec du double-face sur une plaque qui accueille également le pack de piles sur lequel s'ajoute l'Arduino, le tout fixé avec du velcro pour pouvoir être transporté d'un projet à l'autre.
Sur le dos de l'Arduino, un shield maison porte un régulateur de tension LM317 délivrant 6 volts pour alimenter les servos ainsi que leurs deux connecteurs. Le LM317 est connecté au pack de piles via une broche. On aurait peut-être pu utiliser le Vin de l'Arduino pour l'alimenter mais je n'ai trouvé nulle part de renseignement fiable sur l'intensité maximale qui peut y passer, j'ai donc joué la sécurité.
A l'avant, on retrouve le capteur maison inchangé avec ses quatre détecteurs.
Au final la structure est plus simple et surtout plus légère avec à peine 400g, soit la moitié du poids de la version précédente.
Le comportement
J'ai à quelques détails près repris le comportement de la première version.
Si un des capteurs centraux détecte la ligne, on freine le moteur situé du même côté pour recentrer. Si un des capteurs latéraux voit la ligne, on bloque complètement la roue pour corriger plus violemment. Petite amélioration, la roue reste bloquée jusqu'à ce qu'un des capteurs centraux ait vu la ligne et prenne le relais avec la correction douce. Ainsi on s'assure que la ligne est toujours recentrée.
J'ai également doté le robot de la capacité à négocier des croisements de ligne. Si les quatre capteurs voient la ligne en même temps, c'est qu'on est sur un croisement. Le robot continue alors tout droit jusqu'à ce que les deux capteurs extrêmes revoient le blanc, indiquant qu'on est sorti du croisement. Le comportement par défaut reprend alors son cours.
Comme le montre la vidéo en tête d'article, ça marche à merveille. Pour améliorer encore, on pourrait prendre des moteurs plus rapides et un détecteur à 5 capteurs pour toujours détecter la position exacte de la ligne.
Le code
La fonction "tourne" accepte deux arguments, la vitesse des moteurs gauche et droite. L'arrêt correspond à la valeur 90, la vitesse maximum à environ 105 dans un sens (j'ai pris 110 par sécurité) et 75 dans l'autre sens. Comme les servos sont tête-bêche et pour simplifier la gestion des valeurs de vitesse dans le reste du code, on "retourne" la valeur envoyée au servo droit avec la fonction map().
#include <Servo.h> // bibliotheque servo
// capteurs
int capteurDDroite = A2; // analog pin 2
int capteurDroite = A3; // analog pin 3
int capteurGauche = A4; // analog pin 4
int capteurGGauche = A5; // analog pin 5
// valeurs des capteurs
int droite = 0;
int gauche = 0;
int ddroite = 0;
int ggauche = 0;
int seuil = 450; // seuil de détection de la bande noire, à régler en fonction de la luminosité ambiante
// servos
Servo myservo1; // création de l'objet servo droit
Servo myservo2; // création de l'objet servo gauche
// vitesses
int avance = 110; // avance normale
int ralenti = 95; // vitesse pour tourner lentement
int stoppe = 90; // arret
void setup(){
// Serial.begin(9600); // démarrage port série
// servo
myservo1.attach(9); // servo droite sur le pin 9
myservo2.attach(10); // servo gauche sur le pin 10
myservo1.write(stoppe); // on arrete les servos
myservo2.write(stoppe);
delay(5000); // petit délai avant démarrage
}
void loop(){
lecture();
while ((gauche <=seuil && droite <= seuil && ddroite <= seuil && ggauche <= seuil)){ // si tout est ok on avance
tourne(avance,avance);
lecture();
}
if(gauche >= seuil && droite >= seuil && ddroite >= seuil && ggauche >= seuil){ // croisement
while (ddroite >= seuil || ggauche >= seuil){ // on attend que les capteurs extremes soient à nouveau dans le blanc
tourne(avance,avance);
lecture();
}
}
if (ggauche > seuil){ // si capteur extreme gauche on tourne a fond jusqu'à ce que le capteur central gauche ait récupéré la ligne
while (gauche < seuil) {
tourne(stoppe,avance);
gauche = analogRead(capteurGauche);
}
}
if (ddroite > seuil){ // // si capteur extreme droit on tourne a fond jusqu'à ce que le capteur central droit ait récupéré la ligne
while (droite < seuil) {
tourne(avance,stoppe);
droite = analogRead(capteurDroite);
}
}
if (gauche > seuil && ggauche <= seuil && ddroite <= seuil){ // si le capteur centre gauche et lui seul voit la ligne on tourne doucement
tourne(ralenti,avance);
}
if(droite > seuil && ggauche <= seuil && ddroite <= seuil){ // si capteur centre droit on tourne doucement
tourne(avance,ralenti);
}
}
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);
// Serial.print(vitesseGauche);
// Serial.print("\t");
// Serial.println(vitesseDroite);
}
void lecture(){ // on lit les capteurs
ggauche = analogRead(capteurGGauche);
droite = analogRead(capteurDroite);
gauche = analogRead(capteurGauche);
ddroite = analogRead(capteurDDroite);
}
8 réactions
1 De alex - 03/04/2012, 09:47
bonjour,
j'aimerais refaire le robot suiveur de ligne avec arduino mais je bloque sur la PWM, pourriez-vous me montrer votre premier code avec la PWM, s'il vous plait ?
merci
dans l'attente de votre réponse
cordialement
2 De Vincent Becker - 03/04/2012, 20:04
Bonjour,
Voici le code pour la version PWM. Les moteurs sont commandés par un module avec un pont en H qui utilise deux pins par moteur : un en HIGH ou LOW (digitalWrite) pour le sens de rotation, et un en analogique (analogWrite) pour la vitesse avec une valeur comprise entre 0 (stop) et 255 (à fond).
Comme mes moteurs étaient de très mauvaise qualité, j'ai introduit un trim appliqué aux valeurs envoyées à la fonction tourne() pour en accélérer un par rapport à l'autre.
La fonction hardstop met les moteurs en marche arrière pendant 5ms pour arrêter net et éviter que le robot parte sur son inertie.
Enfin, la fonction lancement() met brièvement les moteurs à fond en marche avant pour les faire décoller après un arrêt, sinon il leur arrivait de se bloquer si on leur envoyait une vitesse lente.
Bref tout ceci s'est résolu avec les servos à rotation libre. Mais si vous avez de bons moteurs, le PWM peut être très efficace.
// capteurs
int capteurDDroite = A2; //analog pin 0
int capteurDroite = A3; //analog pin 0
int capteurGauche = A4; //analog pin 1
int capteurGGauche = A5; //analog pin 1
int droite = 0; // valeurs des capteurs
int gauche = 0;
int ddroite = 0;
int ggauche = 0;
int seuil = 100; // seuil de détaection de la bande noire
// moteurs
int moteurDroite = 6; // PWM Pin Motor 1
int PoMD = 7; // Polarity Pin Motor 1
int moteurGauche = 5; // PWM Pin Motor 2
int PoMG = 4; //Polarity Pin Motor 2
// vitesses
int trim = 30; // trim moteur gauche
int avance = 110; // avance normale
int deltaV = 35; // constante de variation de vitesse des moteurs pour chg de direction
int ralenti = avance-deltaV; // ralentissement pour tourner
int accelere = avance+deltaV; // accélération pour tourner
int demarrage = 255; // coup de pouce pour décoller les moteurs après un arret
int stoppe= 1; // variable qui se rappelle si on s'est arreté
int hardstopped = 0;
void setup(){
// Serial.begin(9600);
pinMode(moteurDroite, OUTPUT);
pinMode(PoMD, OUTPUT);
pinMode(moteurGauche, OUTPUT);
pinMode(PoMG, OUTPUT);
digitalWrite(PoMD, HIGH) ; // On met les moteurs en marche avant
digitalWrite(PoMG, HIGH) ;
/*
tourne(255,255); // on lance les moteurs
delay(2000);
tourne(0,0);
delay(2000);
*/
}
void loop(){
lecture();
/*
Serial.print(droite);
Serial.print("\t");
Serial.println(gauche);
Serial.print("\t");
Serial.println(gauche);
Serial.print("\t");
Serial.println(gauche);
delay(100);
*/
if (gauche <=seuil && droite <= seuil && ddroite <= seuil && ggauche <= seuil){ // si tout est ok on lance les moteurs si ils sortent d'un arret et on avance
lancement();
tourne(avance,avance);
// lecture();
}
if (ggauche > seuil){ // si capteur extreme gauche on tourne a fond
// hardstop(PoMG,moteurGauche);
tourne(0,accelere);
// ggauche = analogRead(capteurGGauche);
stoppe = 1;
}
if (ddroite > seuil){ // si capteur extreme droit on tourne a fond
// hardstop(PoMD,moteurDroite);
tourne((accelere+trim),0);
// ddroite = analogRead(capteurDDroite);
stoppe = 1;
}
if (gauche > seuil && ggauche <= seuil && ddroite <= seuil){ // si capteur centre gauche on tourne doucement pondéré par rapport aux capteurs extremes
lancement();
tourne((ralenti+trim),accelere);
// gauche = analogRead(capteurGauche);
// Serial.println("Droite");
// droite = analogRead(capteurDroite);
}
if(droite > seuil && ggauche <= seuil && ddroite <= seuil){ // si capteur gauche on tourne doucement
lancement();
tourne((accelere+trim),ralenti);
// droite = analogRead(capteurDroite);
// Serial.println("Gauche");
// gauche = analogRead(capteurGauche);
}
hardstopped = 0;
}
void lancement() {
if (stoppe == 1){
analogWrite(moteurDroite, demarrage);
analogWrite(moteurGauche, demarrage);
stoppe=0;
delay (5);
}
}
void hardstop(int polarite,int moteur){
if (!hardstopped){
digitalWrite(polarite, LOW) ; // On met les moteurs en marche arrière
analogWrite(moteur, demarrage);
delay (5);
digitalWrite(polarite, HIGH) ;
hardstopped = 1;
}
}
void tourne(int vitesseGauche, int vitesseDroite){
analogWrite(moteurDroite, vitesseDroite);
analogWrite(moteurGauche, vitesseGauche);
}
void lecture(){
ggauche = analogRead(capteurGGauche);
droite = analogRead(capteurDroite); // on lit les capteurs
gauche = analogRead(capteurGauche);
ddroite = analogRead(capteurDDroite);
}
3 De alex - 05/04/2012, 17:21
Merci beaucoup d'avoir répondu aussi rapidement.
Une dernière question : je n'ai pas de module pont en H, puis-je raccorder les moteurs directement sur les pins 7,6,5,4 ??
Alexandre
4 De Vincent Becker - 08/04/2012, 11:17
Surtout pas! Vous allez détruire votre Arduino. Les pins peuvent fournir au maximum 40 mA, vos moteurs vont demander beaucoup plus et l'Atmega va griller.
Le pont en H permet de connecter les moteurs à une alimentation extérieure. On en trouve des capables de donner 1 ou 2 ampères, il faut savoir ce que demandent vos moteurs et dimensionner en fonction.
De plus le pont en H permet de faire varier la direction de rotation des moteurs.
Sur la première version j'ai utilisé celui-ci:
http://www.zartronic.fr/module-cont...
Si vous n'avez besoin que de la marche avant, vous pouvez vous contenter de simples transistors type TIP120 ou des Mosfet (c'est la même chose). C'est facile à câbler et ça ne coûte presque rien :)
Un tutoriel (en anglais) ici :
http://itp.nyu.edu/physcomp/Tutoria...
Cordialement,
Vincent Becker
5 De alex - 08/04/2012, 13:02
merci beaucoup de vos explications, il ne me reste plus qu'a créer ce petit robot !
Si vous voulez je pourrais poster le résultat sur votre site ?
encore merci
cordialement
6 De Vincent Becker - 08/04/2012, 15:48
Pourquoi pas! N'hésitez pas à me tenir au courant de vos travaux :)
7 De alex - 19/04/2012, 17:48
bonjour,
j'ai réalisé la platine capteur et branché les transistors à l'arduino puis mis le tout sous tension.....et RIEN.
je pense que cela viens de la platine capteur, j'ai du mal réaliser le schéma de la plaque.
Pourriez-vous m'indiquer comment avez-vous realisé votre montage des capteurs ?
De plus avec les transistors je n'ai plus besoin des pins 4 et 7 ? Les pins 6 et 5 suffisent ?
Encore désoler pour le dérangement.
Cordialement
8 De Vincent Becker - 20/04/2012, 14:22
Pour la platine, je me suis inspiré de cette page qui montre comment brancher un QRD1114 sur une Arduino : http://bildr.org/2011/03/various-pr... Il suffit de les brancher en parallèle en les connectant chacun à un pin.
Pour les servos, il vaut mieux utiliser les pins 9 et 10 : en effet la librairie Servo neutralise ces deux pins par défaut, qu'on y branche un servo ou pas. Autant les utiliser pour garder les autres pins libres!
Le mieux avant de brancher la platine capteur est d'essayer de faire tourner les servos tous seuls. Essayez avec un puis avec deux. Une fois que ça tourne, vous pourrez implémenter les capteurs.
Et en effet, vous n'avez besoin que d'un pin par servo : la valeur de PWM transmise au servo par le pin donne la vitesse et le sens, avec l'arrêt à 90.