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é...

suiveur_v1_600.JPG

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

suiveur_v2_600.JPG

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);
}