Guides · 18 min de lecture

Robot suiveur de ligne Arduino : capteurs TCRT5000 et PID

Robot suiveur de ligne avec Arduino : barrette TCRT5000, placement et étalonnage, code if-else et PID, réglage Kp/Kd, driver moteur, batterie et règles.

Un robot suiveur de ligne est un véhicule autonome qui détecte une ligne noire (ou blanche) au sol grâce à des capteurs infrarouges et ajuste en permanence la vitesse de ses deux roues pour rester dessus. C'est l'un des premiers vrais projets de robotique que l'on réalise avec Arduino, car il réunit dans un seul projet la lecture de capteurs, la prise de décision et la commande de moteurs. Les chariots autonomes (AGV) qui transportent des pièces dans les usines fonctionnent sur le même principe, et le suiveur de ligne est l'une des catégories les plus disputées des concours de robotique scolaires.

Dans ce guide, tu vas apprendre comment fonctionne le capteur TCRT5000, ce qui distingue les barrettes de 2, 3 et 5 capteurs, à quelle hauteur et avec quel écartement monter les capteurs, et comment étalonner le seuil. Tu feras d'abord rouler le robot avec un programme if-else simple, puis tu passeras à une version avec régulation PID qui suit la ligne sans zigzaguer ni sortir de la piste dans les virages. En plus du code pour Arduino Uno, ESP32 et Raspberry Pi Pico, tu trouveras le choix du driver moteur et de la batterie, les règles courantes des concours et les solutions aux problèmes les plus fréquents.

Capteur infrarouge à réflexion TCRT5000
TCRT5000 : une LED infrarouge à boîtier bleu et un phototransistor noir sont côte à côte dans le même boîtier ; les modules capteurs de ligne sont construits autour de ce composant.

Comment fonctionne un robot suiveur de ligne ?

Une surface blanche renvoie l'essentiel de la lumière infrarouge, alors qu'une surface noire mate en absorbe la plus grande partie. Les capteurs placés sous le robot mesurent cette différence pour savoir où se trouve la ligne par rapport au robot. Le robot n'a pas de direction : il tourne grâce à la propulsion différentielle. Si la roue gauche tourne plus vite que la droite, le robot part vers la droite, et si elle tourne moins vite, vers la gauche. Le programme exécute la même boucle des centaines de fois par seconde : lire les capteurs, calculer de combien la ligne s'est décalée, puis corriger la vitesse des deux moteurs. Plus cette boucle fermée est rapide et stable, plus le robot avance de façon fluide.

ComposantConseilléRemarque
Carte de commandeArduino Uno ou NanoLa Nano tient plus facilement sur un petit châssis ; un ESP32 ou une Pico conviennent aussi
Capteur de ligneBarrette TCRT5000 à 3 ou 5 voies, QTR-8AAvec le PID, une barrette à sorties analogiques donne de meilleurs résultats
Driver moteurL298N ou TB6612FNGLe TB6612FNG est plus petit et plus efficace
Moteurs2 motoréducteurs CC (TT 1:48 ou N20)Les deux moteurs doivent être du même modèle et de même vitesse
Roues2 roues motrices et 1 roue folleLes roues de 65 mm de diamètre sont les plus courantes
Alimentation2 accus Li-ion 18650 (7,4 V) et un interrupteurUne pile 9 V est trop faible pour les moteurs
DiversChâssis, bouton-poussoir, fils de connexionUn bouton simplifie l'étalonnage et le départ
Arduino Nano
Arduino Nano
L298N
L298N
N20
N20
18650 Li-ion
18650 Li-ion

Le capteur TCRT5000 : barrettes de 2, 3 et 5 capteurs

Le TCRT5000 se compose d'une LED infrarouge (950 nm) et d'un phototransistor placés côte à côte dans le même boîtier. Plus la lumière de la LED se réfléchit sur le sol et atteint le phototransistor, plus celui-ci conduit. Le capteur fonctionne au mieux à environ 2,5 mm ; au-delà de 1 cm, l'écart entre le noir et le blanc diminue vite. Les modules tout prêts intègrent un comparateur LM393 : sur la plupart d'entre eux, la broche DO donne HIGH sur le noir et LOW sur le blanc selon le seuil réglé au potentiomètre, et la LED de la carte s'allume sur une surface blanche. La broche AO fournit la valeur analogique brute : élevée sur le noir, faible sur le blanc. Tous les détails sur le capteur sont dans l'article Capteur infrarouge TCRT5000.

CapteursDispositionAlgorithmeQuand le choisir ?
2De part et d'autre de la ligne ; les deux voient normalement du blancif-elsePremier essai, piste large aux virages doux
3Celui du milieu sur la ligne, ceux des côtés sur ses bordsif-else ou PID simpleProjets scolaires, concours de niveau débutant
5Rangée régulière, celui du milieu sur la lignePID (moyenne pondérée)Suivi rapide et fluide
8Rangée large (comme la QTR-8A)PID et détection des intersectionsPistes de concours avec virages serrés et croisements

Placement des capteurs : hauteur, écartement et avancée

Aussi bon que soit ton code, le robot ne roulera pas correctement si les capteurs sont mal placés. Monte la barrette de capteurs devant l'axe des roues, parallèle au sol et symétrique par rapport à l'axe central du robot. Ces mesures sont un bon point de départ :

  • Hauteur : Laisse 3 à 5 mm entre le dessous du capteur et le sol. Pololu indique pour sa barrette QTR-8A une distance optimale de 3 mm et une distance maximale conseillée de 6 mm. Plus haut, l'écart entre le noir et le blanc diminue et le robot perd la ligne.
  • Écartement : Sur les barrettes analogiques, la distance entre deux capteurs doit se situer entre la moitié et les deux tiers de la largeur de la ligne ; pour une ligne de 20 mm, 10 à 13 mm conviennent bien. La ligne se trouve ainsi le plus souvent sous deux capteurs et la position est calculée en douceur.
  • Disposition numérique à 3 capteurs : Le capteur du milieu se place juste au-dessus de la ligne et ceux des côtés juste à l'extérieur de ses bords ; pour une ligne de 20 mm, environ 18 à 22 mm d'entraxe.
  • Avancée : Place les capteurs 4 à 8 cm devant l'axe des roues. Trop près, le robot voit les virages trop tard ; trop loin, la moindre rotation balaie fortement les capteurs et le robot tremble.
  • Lumière ambiante : Le soleil et les lampes halogènes émettent aussi de l'infrarouge. Entourer les capteurs d'une jupe ou d'un ruban noir rend les mesures bien plus stables.
Une erreur fréquente consiste à monter les capteurs de travers ou trop haut. Si le robot perd la ligne au premier virage, vérifie la hauteur des capteurs avant le code : quelques millimètres suffisent à diviser les valeurs lues par deux. Le ruban isolant brillant réfléchit aussi l'infrarouge, si bien qu'une ligne noire peut parfois paraître blanche ; utilise si possible du ruban mat ou une ligne imprimée.

Câblage : Arduino Uno, ESP32 et Pico

Le tableau ci-dessous correspond exactement au programme de test des capteurs et aux programmes PID ; le câblage de l'exemple if-else à 3 capteurs est décrit dans sa propre section. Numérote les capteurs de gauche à droite en regardant le robot de l'arrière : le capteur le plus à gauche porte le numéro 1. Nous utilisons un L298N comme driver moteur ; le rôle de ses broches et le principe du pont en H sont détaillés dans Commande d'un moteur CC avec le L298N. La Pico n'a que trois entrées analogiques, nous y utilisons donc les capteurs 1, 3 et 5.

LiaisonArduino UnoESP32Raspberry Pi Pico
Capteur 1 (le plus à gauche) AOA0GPIO 36GP26
Capteur 2 AOA1GPIO 39-
Capteur 3 (milieu) AOA2GPIO 34GP27
Capteur 4 AOA3GPIO 35-
Capteur 5 (le plus à droite) AOA4GPIO 32GP28
VCC des capteurs5V3V33V3
L298N ENA (vitesse moteur gauche)D9GPIO 25GP15
L298N IN1, IN2D8, D7GPIO 26, 27GP14, GP13
L298N ENB (vitesse moteur droit)D10GPIO 33GP10
L298N IN3, IN4D5, D4GPIO 18, 19GP11, GP12
Bouton de départ (autre patte à GND)D2GPIO 23GP16
GNDGNDGNDGND (commune avec batterie et driver)
Le pôle négatif de la batterie, le GND du driver moteur et le GND de la carte doivent impérativement être reliés ; sans masse commune, les moteurs ont un comportement aléatoire. N'alimente jamais les moteurs par la broche 5V de la carte. Sur l'ESP32 et la Pico, alimente les capteurs en 3V3 : la sortie AO d'un module alimenté en 5 V peut monter jusqu'à 5 V et endommager les entrées analogiques en 3,3 V.

Choisir le driver moteur et la batterie

Le L298N est bon marché et robuste, mais ses transistors bipolaires absorbent 1,5 à 3 V : avec un pack de 7,4 V, il ne reste qu'environ 5 à 5,5 V aux moteurs. Sur les petits robots rapides, un TB6612FNG ou un DRV8833 à MOSFET donne de meilleurs résultats. Avec le TB6612FNG, le code ne change pas : tu branches les fils de ENA, IN1 et IN2 sur PWMA, AIN1 et AIN2 (ceux du canal B sur PWMB, BIN1 et BIN2), puis tu relies la broche STBY à VCC. Le DRV8833 n'a pas de broche de vitesse (enable) séparée : le signal PWM va directement sur les broches IN, il faut donc adapter la fonction drive(). Pour l'alimentation, le plus pratique reste deux accus Li-ion 18650 en série : 8,4 V chargés, 7,4 V nominaux, et ils fournissent sans peine un courant élevé. Les piles 9 V ne supportent pas le courant des moteurs et se vident vite. Tu peux calculer l'autonomie de ta batterie avec l'outil Autonomie de batterie.

DriverTension moteurCourant par voieChute de tensionAvis
L298N5–35 V2 A1,5–3 VRobuste mais encombrant et peu efficace
TB6612FNG4,5–13,5 V1,2 A (pointe 3,2 A)Très faibleMeilleur rapport qualité-prix pour un suiveur
DRV88332,7–10,8 V1,5 A (pointe 2 A)Très faiblePetits robots à moteurs N20
L9110S2,5–12 V0,8 AFaibleUniquement robots légers et lents
Motoréducteur jaune TT et sa roue
Motoréducteur jaune TT avec roue de 65 mm : le réducteur diminue la vitesse et augmente le couple ; c'est le moteur le plus courant des suiveurs de ligne et des voitures robots.

Un exemple de vitesse : les moteurs jaunes TT (1:48) tournent à environ 200 tr/min à vide sous 6 V. Une roue de 65 mm a une circonférence de π × 65 ≈ 204 mm, donc le robot peut rouler au maximum à 200 × 0,204 ≈ 41 m par minute, soit environ 0,7 m par seconde. C'est largement suffisant pour la plupart des pistes scolaires ; la vraie difficulté est de ne pas perdre la ligne à cette vitesse. Les robots plus rapides utilisent des micromotoréducteurs N20 et un châssis léger. Quand la tension de la batterie baisse, les moteurs ralentissent et tes réglages PID se décalent ; mesurer la tension avec un pont diviseur de tension et adapter la vitesse en conséquence est une amélioration de niveau avancé.

Étape 1 : teste les capteurs et trouve le seuil

Avant de brancher les moteurs, regarde ce que voient les capteurs. Le programme ci-dessous affiche la valeur analogique brute des cinq capteurs séparée par des tabulations ; ouvre Outils > Traceur série dans l'IDE Arduino à 115200 bauds et chaque capteur apparaîtra comme une courbe distincte. Fais passer lentement le robot sur la ligne à la main et observe l'évolution des valeurs.

Arduino (C++)
1// 5 sensörün ham değerlerini okur / reads the raw values of 5 sensors
2const int N = 5;
3const int SENSOR[N] = {A0, A1, A2, A3, A4};   // A0 en solda / A0 is the leftmost
4
5void setup() {
6  Serial.begin(115200);
7}
8
9void loop() {
10  for (int i = 0; i < N; i++) {
11    Serial.print(analogRead(SENSOR[i]));
12    Serial.print(i < N - 1 ? '\t' : '\n');    // Serial Plotter için sekme / tabs for the Serial Plotter
13  }
14  delay(50);
15}

Sous 5 V, un module TCRT5000 typique renvoie 30 à 200 sur une surface blanche et 600 à 1000 sur la ligne noire, mais ces valeurs dépendent de la hauteur du capteur, du matériau du sol et de la lumière ambiante. Le seuil le plus simple est : seuil = (valeur sur blanc + valeur sur noir) / 2. Par exemple, si tu lis 80 sur le blanc et 820 sur le noir, le seuil vaut 450. Comme chaque capteur lit un peu différemment, le programme PID le fera automatiquement : pendant l'étalonnage, il enregistre la valeur minimale et maximale de chaque capteur, puis ramène chaque mesure entre 0 (blanc) et 1000 (noir) avec (mesure − min) × 1000 / (max − min). Le seuil vaut alors 500 pour tous les capteurs.

Sur les modules à sortie numérique (DO), le seuil se règle avec le potentiomètre de la carte. Tourne le trimmer pour que la LED du module reste éteinte quand le capteur est au-dessus de la ligne noire et allumée au-dessus de la surface blanche ; la position située pile au milieu de ces deux points est la plus stable. Fais ce réglage sur la piste du concours et sous le même éclairage.

Étape 2 : algorithme if-else simple (3 capteurs)

Pour un premier robot qui fonctionne, le plus clair est de choisir parmi quelques mouvements prédéfinis selon l'état des trois capteurs. Relie les sorties DO d'un module à 3 voies à A0 (gauche), A1 (milieu) et A2 (droite) ; les broches analogiques de l'Uno fonctionnent aussi comme entrées numériques. La logique est résumée dans ce tableau (1 signifie que le capteur voit la ligne) :

GaucheMilieuDroiteSignificationMouvement
010Ligne au milieuTout droit
110Ligne un peu à gaucheLa roue gauche ralentit, léger virage à gauche
100Ligne très à gaucheLa roue gauche recule, virage serré à gauche
011Ligne un peu à droiteLa roue droite ralentit, léger virage à droite
001Ligne très à droiteLa roue droite recule, virage serré à droite
111Intersection ou ligne d'arrivéeContinuer tout droit
000Ligne perdueTourner vers le dernier côté vu
Arduino (C++)
1// 3 sensörlü basit çizgi izleyen / simple 3-sensor line follower
2const int ENA = 9, IN1 = 8, IN2 = 7;      // sol motor / left motor
3const int ENB = 10, IN3 = 5, IN4 = 4;     // sağ motor / right motor
4const int S_LEFT = A0, S_MID = A1, S_RIGHT = A2;   // sensörlerin DO çıkışları / sensor DO outputs
5const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
6
7const int SPEED = 150;                    // düz gidiş hızı / straight-line speed
8const int SOFT = 80;                      // hafif dönüşte iç teker / inner wheel on a soft turn
9const int BACK = -90;                     // sert dönüşte iç teker geri / inner wheel reverses on a sharp turn
10int lastSide = 0;                         // -1 sol, 1 sağ / -1 left, 1 right
11
12void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
13  left = constrain(left, -255, 255);
14  right = constrain(right, -255, 255);
15  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
16  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
17  analogWrite(ENA, abs(left));
18  analogWrite(ENB, abs(right));
19}
20
21void setup() {
22  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4};
23  for (int p : outputs) pinMode(p, OUTPUT);
24  pinMode(S_LEFT, INPUT);
25  pinMode(S_MID, INPUT);
26  pinMode(S_RIGHT, INPUT);
27}
28
29void loop() {
30  bool l = digitalRead(S_LEFT) != WHITE_LINE;    // true = sensör çizgide / true = sensor on the line
31  bool m = digitalRead(S_MID) != WHITE_LINE;
32  bool r = digitalRead(S_RIGHT) != WHITE_LINE;
33
34  if (m && !l && !r) {                    // tam ortada: düz git / centered: go straight
35    drive(SPEED, SPEED);
36  } else if (l && r) {                    // kavşak ya da bitiş çizgisi: düz geç / junction or finish line: go straight
37    drive(SPEED, SPEED);
38  } else if (l && m) {                    // çizgi biraz solda / line slightly to the left
39    drive(SOFT, SPEED);  lastSide = -1;
40  } else if (r && m) {                    // çizgi biraz sağda / line slightly to the right
41    drive(SPEED, SOFT);  lastSide = 1;
42  } else if (l) {                         // çizgi çok solda / line far to the left
43    drive(BACK, SPEED);  lastSide = -1;
44  } else if (r) {                         // çizgi çok sağda / line far to the right
45    drive(SPEED, BACK);  lastSide = 1;
46  } else {                                // çizgi kayıp: son görülen tarafa dön / line lost: turn to the last seen side
47    if (lastSide < 0)      drive(BACK, SPEED);
48    else if (lastSide > 0) drive(SPEED, BACK);
49    else                   drive(SOFT, SOFT);
50  }
51}

La variable lastSide mémorise de quel côté le robot a vu la ligne en dernier ; si dans un virage serré les trois capteurs la perdent, le robot tourne de ce côté pour la retrouver au lieu de partir tout droit au hasard. Avec deux capteurs, la logique est encore plus simple : les capteurs encadrent la ligne, le robot va tout droit tant qu'ils voient tous deux du blanc, tourne à gauche quand celui de gauche voit du noir et à droite quand c'est celui de droite. Le point faible de cette méthode, ce sont les mouvements par paliers : le robot ne passe que d'une vitesse fixe à une autre, il zigzague donc légèrement même en ligne droite et sort de la piste dans les virages dès que tu augmentes la vitesse.

Étape 3 : suivi de ligne fluide avec un PID

Le PID applique une correction proportionnelle à l'écart du robot par rapport à la ligne. On calcule d'abord la position de la ligne à partir des cinq capteurs par une moyenne pondérée : la valeur de chaque capteur (0–1000) est multipliée par sa propre position (0, 1000, 2000, 3000, 4000), on additionne le tout et on divise par la somme des valeurs. On obtient un nombre entre 0 et 4000, égal à 2000 quand la ligne est pile au milieu. Par exemple, si les capteurs lisent 0, 0, 300, 900, 0, la position vaut (300 × 2000 + 900 × 3000) / 1200 = 2750. L'erreur = position − 2000 : une valeur positive signifie que la ligne est à droite, une valeur négative qu'elle est à gauche.

La correction est la somme de trois termes : correction = Kp × erreur + Ki × somme des erreurs + Kd × (erreur − erreur précédente). Le terme P braque d'autant plus fort que l'écart est grand. Le terme D regarde la vitesse à laquelle l'erreur change et freine quand le robot revient vers la ligne, ce qui amortit les oscillations. Le terme I corrige les petites erreurs qui restent longtemps du même côté, mais sur un suiveur de ligne on le laisse le plus souvent à zéro. Le moteur gauche tourne ensuite à vitesse de base + correction et le moteur droit à vitesse de base − correction. Par exemple, avec une erreur de 800 et Kp = 0,06, la correction vaut 48 ; avec une vitesse de base de 140, le moteur gauche tourne à 188, le droit à 92, et le robot vire doucement à droite.

Arduino (C++)
1// PID çizgi izleyen: Arduino Uno + 5 analog sensör + L298N
2// PID line follower: Arduino Uno + 5 analog sensors + L298N
3const int ENA = 9, IN1 = 8, IN2 = 7;      // sol motor / left motor
4const int ENB = 10, IN3 = 5, IN4 = 4;     // sağ motor / right motor
5const int BUTTON = 2;                     // diğer bacağı GND'ye / other leg to GND
6const int N = 5;
7const int SENSOR[N] = {A0, A1, A2, A3, A4};   // A0 en solda / A0 is the leftmost
8const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
9const int CENTER = (N - 1) * 500;         // orta konum: 2000 / center position: 2000
10
11float Kp = 0.06, Ki = 0.0, Kd = 0.6;      // başlangıç değerleri / starting values
12int baseSpeed = 140;                      // düzlük hızı (0-255) / straight-line speed (0-255)
13const unsigned long PERIOD_MS = 5;        // sabit döngü süresi / fixed loop period
14
15int minVal[N], maxVal[N];
16int lastPosition = CENTER;
17float integral = 0, lastError = 0;
18unsigned long lastTime = 0;
19
20void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
21  left = constrain(left, -255, 255);
22  right = constrain(right, -255, 255);
23  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
24  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
25  analogWrite(ENA, abs(left));
26  analogWrite(ENB, abs(right));
27}
28
29void waitButton() {
30  while (digitalRead(BUTTON) == HIGH) {}  // basılmasını bekle / wait for a press
31  delay(30);
32  while (digitalRead(BUTTON) == LOW) {}   // bırakılmasını bekle / wait for the release
33  delay(30);
34}
35
36void calibrate() {                        // 5 sn: robotu çizginin üstünde sağa sola gezdir / 5 s: sweep the robot across the line
37  for (int i = 0; i < N; i++) { minVal[i] = 1023; maxVal[i] = 0; }
38  digitalWrite(LED_BUILTIN, HIGH);
39  unsigned long start = millis();
40  while (millis() - start < 5000) {
41    for (int i = 0; i < N; i++) {
42      int v = analogRead(SENSOR[i]);
43      if (v < minVal[i]) minVal[i] = v;
44      if (v > maxVal[i]) maxVal[i] = v;
45    }
46  }
47  digitalWrite(LED_BUILTIN, LOW);
48}
49
50int readPosition() {                      // 0 (en sol) … 4000 (en sağ) / 0 (leftmost) … 4000 (rightmost)
51  long sum = 0, weighted = 0;
52  bool onLine = false;
53  for (int i = 0; i < N; i++) {
54    int range = max(maxVal[i] - minVal[i], 1);
55    long v = (long)(analogRead(SENSOR[i]) - minVal[i]) * 1000 / range;
56    v = constrain(v, 0L, 1000L);          // 0 = beyaz, 1000 = siyah / 0 = white, 1000 = black
57    if (WHITE_LINE) v = 1000 - v;
58    if (v > 500) onLine = true;           // eşik: kalibrasyonun ortası / threshold: middle of the calibration
59    if (v > 50) {                         // küçük gürültüyü yok say / ignore small noise
60      weighted += v * (i * 1000L);
61      sum += v;
62    }
63  }
64  if (!onLine) {                          // çizgi kayıp: son görülen tarafa dön / line lost: turn to the last seen side
65    return (lastPosition < CENTER) ? 0 : (N - 1) * 1000;
66  }
67  lastPosition = weighted / sum;
68  return lastPosition;
69}
70
71void setup() {
72  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4, LED_BUILTIN};
73  for (int p : outputs) pinMode(p, OUTPUT);
74  pinMode(BUTTON, INPUT_PULLUP);
75  drive(0, 0);
76  waitButton();                           // 1. basış: kalibrasyon / 1st press: calibration
77  calibrate();
78  waitButton();                           // 2. basış: yarış / 2nd press: race
79  lastTime = millis();
80}
81
82void loop() {
83  if (millis() - lastTime < PERIOD_MS) return;
84  lastTime += PERIOD_MS;
85
86  float error = readPosition() - CENTER;  // artı: çizgi sağda / positive: line is to the right
87  integral = constrain(integral + error, -20000.0f, 20000.0f);   // birikmeyi sınırla / anti-windup
88  float derivative = error - lastError;
89  lastError = error;
90
91  int correction = (int)(Kp * error + Ki * integral + Kd * derivative);
92  drive(baseSpeed + correction, baseSpeed - correction);
93}

Après le téléversement, appuie une fois sur le bouton : la LED de la carte s'allume pendant 5 secondes. Pendant ce temps, fais glisser le robot à la main plusieurs fois à gauche et à droite au-dessus de la ligne pour que chaque capteur voie à la fois le noir et le blanc. Quand la LED s'éteint, pose le robot sur la ligne et appuie de nouveau sur le bouton. La boucle s'exécute toutes les 5 ms grâce à millis() ; une période fixe garde le terme D toujours à la même échelle. Si la ligne disparaît complètement, readPosition() renvoie 0 ou 4000 selon le dernier côté où elle a été vue, et le robot la cherche en tournant au maximum. Pour câbler le bouton, consulte Comment brancher un bouton sur Arduino, et pour temporiser sans delay, Arduino millis() et delay().

Comment régler Kp et Kd

  1. Garde une vitesse de base faible (100 par exemple) et mets Ki et Kd à zéro.
  2. Commence avec un Kp faible (0,02) et augmente-le petit à petit jusqu'à ce que le robot prenne les virages. Quand le robot se met à osciller vite et régulièrement autour de la ligne, tu as atteint la limite haute de Kp ; reste un peu en dessous.
  3. Démarre Kd à environ 10 fois Kp et augmente-le jusqu'à ce que l'oscillation disparaisse. Si Kd devient trop grand, le robot tremble et les moteurs sifflent.
  4. Quand le robot roule bien droit en ligne droite et de façon fluide en virage, augmente la vitesse de base de 10 à 20 et répète les étapes 2 et 3.
  5. N'essaie Ki, avec une valeur très faible (0,0001 par exemple), que si le robot dérive toujours du même côté dans les longues lignes droites.
  6. Note les valeurs trouvées et la tension de la batterie à ce moment-là ; quand la batterie se vide, les mêmes valeurs se comportent autrement.
À l'échelle de cet article (position 0–4000, PWM 0–255, boucle de 5 ms), une bonne plage de départ est 0,04–0,08 pour Kp et 0,4–1,2 pour Kd. Ne mets pas de Serial.print dans la boucle pendant le réglage : chaque affichage ralentit la boucle et perturbe le terme D. Si tu as besoin de voir des valeurs, affiche-les au plus une fois toutes les 200 ms.

Suiveur de ligne avec l'ESP32 : règle le PID depuis ton téléphone

Le grand avantage de l'ESP32, c'est que tu peux modifier les valeurs du PID depuis ton téléphone sans téléverser le code à chaque fois. La logique est la même que pour l'Uno ; voici les différences : l'entrée analogique de l'ESP32 est sur 12 bits (0–4095), les capteurs doivent être branchés sur des broches ADC1, soit GPIO 32–39 (les broches ADC2 ne peuvent pas être lues quand le Wi-Fi est actif), et les capteurs sont alimentés en 3,3 V. analogWrite() fonctionne avec les versions 2.x et 3.x du core Arduino pour ESP32. Installe sur ton téléphone Android une application comme Serial Bluetooth Terminal, connecte-toi à l'appareil "CizgiRobot" et envoie des commandes comme p0.07, d0.9 ou s160 ; le robot utilise aussitôt la nouvelle valeur et te la renvoie. Le Bluetooth classique n'existe que sur l'ESP32 d'origine (pas sur les S3 ni C3), et les iPhone ne gèrent pas ce type de liaison série. Si tu découvres l'ESP32, lis d'abord Qu'est-ce que l'ESP32 ?.

Arduino (C++)
1// ESP32 PID çizgi izleyen + Bluetooth ile canlı ayar
2// ESP32 PID line follower + live tuning over Bluetooth
3#include "BluetoothSerial.h"
4
5BluetoothSerial SerialBT;
6
7const int ENA = 25, IN1 = 26, IN2 = 27;   // sol motor / left motor
8const int ENB = 33, IN3 = 18, IN4 = 19;   // sağ motor / right motor
9const int BUTTON = 23;                    // diğer bacağı GND'ye / other leg to GND
10const int LED_PIN = 2;                    // DevKit üzerindeki LED / onboard LED on the DevKit
11const int N = 5;
12const int SENSOR[N] = {36, 39, 34, 35, 32};   // yalnız ADC1 pinleri / ADC1 pins only
13const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
14const int CENTER = (N - 1) * 500;
15
16float Kp = 0.06, Ki = 0.0, Kd = 0.6;
17int baseSpeed = 140;
18const unsigned long PERIOD_MS = 5;
19
20int minVal[N], maxVal[N];
21int lastPosition = CENTER;
22float integral = 0, lastError = 0;
23unsigned long lastTime = 0;
24
25void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
26  left = constrain(left, -255, 255);
27  right = constrain(right, -255, 255);
28  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
29  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
30  analogWrite(ENA, abs(left));
31  analogWrite(ENB, abs(right));
32}
33
34void waitButton() {
35  while (digitalRead(BUTTON) == HIGH) {}
36  delay(30);
37  while (digitalRead(BUTTON) == LOW) {}
38  delay(30);
39}
40
41void calibrate() {                        // ESP32 ADC 12 bit: 0-4095 / ESP32 ADC is 12-bit: 0-4095
42  for (int i = 0; i < N; i++) { minVal[i] = 4095; maxVal[i] = 0; }
43  digitalWrite(LED_PIN, HIGH);
44  unsigned long start = millis();
45  while (millis() - start < 5000) {
46    for (int i = 0; i < N; i++) {
47      int v = analogRead(SENSOR[i]);
48      if (v < minVal[i]) minVal[i] = v;
49      if (v > maxVal[i]) maxVal[i] = v;
50    }
51  }
52  digitalWrite(LED_PIN, LOW);
53}
54
55int readPosition() {
56  long sum = 0, weighted = 0;
57  bool onLine = false;
58  for (int i = 0; i < N; i++) {
59    int range = max(maxVal[i] - minVal[i], 1);
60    long v = (long)(analogRead(SENSOR[i]) - minVal[i]) * 1000 / range;
61    v = constrain(v, 0L, 1000L);
62    if (WHITE_LINE) v = 1000 - v;
63    if (v > 500) onLine = true;
64    if (v > 50) {
65      weighted += v * (i * 1000L);
66      sum += v;
67    }
68  }
69  if (!onLine) {
70    return (lastPosition < CENTER) ? 0 : (N - 1) * 1000;
71  }
72  lastPosition = weighted / sum;
73  return lastPosition;
74}
75
76void readTuning() {                       // telefondan "p0.07", "d0.9", "s160" gibi komutlar / commands like "p0.07", "d0.9", "s160" from the phone
77  if (!SerialBT.available()) return;
78  char cmd = SerialBT.read();
79  if (cmd != 'p' && cmd != 'i' && cmd != 'd' && cmd != 's') return;   // satır sonlarını atla / skip line endings
80  float value = SerialBT.parseFloat();
81  if (cmd == 'p') Kp = value;
82  if (cmd == 'i') { Ki = value; integral = 0; }
83  if (cmd == 'd') Kd = value;
84  if (cmd == 's') baseSpeed = (int)value;
85  SerialBT.printf("Kp=%.3f Ki=%.4f Kd=%.3f speed=%d\n", Kp, Ki, Kd, baseSpeed);
86}
87
88void setup() {
89  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4, LED_PIN};
90  for (int p : outputs) pinMode(p, OUTPUT);
91  pinMode(BUTTON, INPUT_PULLUP);
92  drive(0, 0);
93  SerialBT.begin("CizgiRobot");           // telefonda görünecek ad / name shown on the phone
94  SerialBT.setTimeout(20);                // parseFloat robotu bekletmesin / keep parseFloat from blocking
95  waitButton();
96  calibrate();
97  waitButton();
98  lastTime = millis();
99}
100
101void loop() {
102  readTuning();
103  if (millis() - lastTime < PERIOD_MS) return;
104  lastTime += PERIOD_MS;
105
106  float error = readPosition() - CENTER;
107  integral = constrain(integral + error, -20000.0f, 20000.0f);
108  float derivative = error - lastError;
109  lastError = error;
110
111  int correction = (int)(Kp * error + Ki * integral + Kd * derivative);
112  drive(baseSpeed + correction, baseSpeed - correction);
113}
Certains règlements de concours interdisent les modules Wi-Fi, Bluetooth ou RF sur le robot, voire toute carte équipée du Wi-Fi. Le plus sûr est d'utiliser l'ESP32 pour le réglage et l'entraînement, puis de concourir avec une Arduino Uno, une Nano ou une Pico sans Wi-Fi ; en cas de doute, demande aux organisateurs.

Suiveur de ligne avec la Raspberry Pi Pico et MicroPython

La Pico n'offre que trois entrées analogiques utilisables (GP26, GP27, GP28) : le code ci-dessous fonctionne donc avec trois capteurs analogiques et calcule la position entre 0 et 2000. Pour utiliser cinq capteurs, tu peux brancher un module à 5 voies à sorties numériques sur cinq GPIO quelconques et compter chaque capteur comme 0 ou 1000 ; la moyenne pondérée est plus grossière mais fonctionne quand même. read_u16() renvoie une valeur de 0 à 65535 et la vitesse des moteurs est donnée en pourcentage (de -100 à 100). Pour l'installation et les premiers pas, consulte Qu'est-ce que la Raspberry Pi Pico ?.

MicroPython
1from machine import Pin, PWM, ADC
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000)        # sol motor hızı / left motor speed
5in1 = Pin(14, Pin.OUT); in2 = Pin(13, Pin.OUT)
6enb = PWM(Pin(10)); enb.freq(1000)        # sağ motor hızı / right motor speed
7in3 = Pin(11, Pin.OUT); in4 = Pin(12, Pin.OUT)
8button = Pin(16, Pin.IN, Pin.PULL_UP)     # diğer bacağı GND'ye / other leg to GND
9led = Pin("LED", Pin.OUT)
10sensors = [ADC(26), ADC(27), ADC(28)]     # sol, orta, sağ / left, middle, right
11WHITE_LINE = False                        # siyah zemin + beyaz çizgi ise True / True for a white line on black
12
13N = len(sensors)
14CENTER = (N - 1) * 500                    # orta konum: 1000 / center position: 1000
15Kp, Ki, Kd = 0.05, 0.0, 0.4
16BASE = 45                                 # taban hız (%) / base speed (%)
17min_val = [65535] * N
18max_val = [0] * N
19last_pos = CENTER
20
21def drive(left, right):                   # her biri -100 … 100 / each -100 … 100
22    left = max(-100, min(100, left))
23    right = max(-100, min(100, right))
24    in1.value(left >= 0); in2.value(left < 0)
25    in3.value(right >= 0); in4.value(right < 0)
26    ena.duty_u16(int(abs(left) * 655.35))
27    enb.duty_u16(int(abs(right) * 655.35))
28
29def wait_button():
30    while button.value():                 # basılmasını bekle / wait for a press
31        time.sleep_ms(10)
32    time.sleep_ms(30)
33    while not button.value():             # bırakılmasını bekle / wait for the release
34        time.sleep_ms(10)
35    time.sleep_ms(30)
36
37def calibrate():                          # 5 sn sağa sola gezdir / sweep for 5 s
38    led.on()
39    start = time.ticks_ms()
40    while time.ticks_diff(time.ticks_ms(), start) < 5000:
41        for i, s in enumerate(sensors):
42            v = s.read_u16()
43            min_val[i] = min(min_val[i], v)
44            max_val[i] = max(max_val[i], v)
45    led.off()
46
47def read_position():                      # 0 (sol) … 2000 (sağ) / 0 (left) … 2000 (right)
48    global last_pos
49    total = weighted = 0
50    on_line = False
51    for i, s in enumerate(sensors):
52        rng = max(1, max_val[i] - min_val[i])
53        v = max(0, min(1000, (s.read_u16() - min_val[i]) * 1000 // rng))
54        if WHITE_LINE:
55            v = 1000 - v
56        if v > 500:
57            on_line = True
58        if v > 50:
59            weighted += v * i * 1000
60            total += v
61    if not on_line:                       # çizgi kayıp / line lost
62        return 0 if last_pos < CENTER else (N - 1) * 1000
63    last_pos = weighted // total
64    return last_pos
65
66drive(0, 0)
67wait_button()                             # 1. basış: kalibrasyon / 1st press: calibration
68calibrate()
69wait_button()                             # 2. basış: yarış / 2nd press: race
70integral = 0
71last_error = 0
72while True:
73    t0 = time.ticks_ms()
74    error = read_position() - CENTER
75    integral = max(-10000, min(10000, integral + error))
76    correction = Kp * error + Ki * integral + Kd * (error - last_error)
77    last_error = error
78    drive(BASE + correction, BASE - correction)
79    while time.ticks_diff(time.ticks_ms(), t0) < 5:   # 5 ms sabit döngü / fixed 5 ms loop
80        pass

Règles des concours et piste

Les règles des concours de suiveurs de ligne varient selon l'organisateur et l'année, mais l'idée de base reste la même : le robot doit boucler la piste le plus vite possible sans quitter la ligne. Par exemple, dans le règlement 2024 niveau débutant de la catégorie suiveur de ligne du concours national de robotique MEB en Turquie, la piste est une ligne blanche de 20 ± 2 mm sur fond noir. Le robot doit mesurer au plus 280 × 180 mm roues comprises et peser au plus 1 kg batterie comprise ; la tension de fonctionnement ne doit pas dépasser 12 V, avec deux motoréducteurs CC 6–12 V en L à engrenages plastique, des roues de 65 mm de diamètre au plus et une barrette de 8 capteurs au maximum. La piste comporte un pont incliné à 13 degrés et une section carrée où couper par le milieu coûte 20 secondes de pénalité ; un robot dont le corps se retrouve entièrement sur le fond noir est considéré comme sorti de piste, et un robot immobile sur la ligne plus de 30 secondes est disqualifié. D'autres concours scolaires et régionaux utilisent l'inverse, une ligne noire de 20 ± 2 mm sur fond blanc, et exigent des robots plus petits ne dépassant pas 500 grammes. Bref, lis toujours le règlement de l'année avant de concevoir ton robot.

  • Piste inversée : Pour une ligne blanche sur fond noir, mets la variable WHITE_LINE à true dans les programmes. Si les couleurs changent au cours de la piste, tu peux ajouter un test qui bascule de mode quand les capteurs extérieurs voient du noir et celui du milieu du blanc.
  • Virages à 90 degrés : Réduis la vitesse de base avant le virage ou laisse la roue intérieure reculer ; dans les programmes de cet article, la roue intérieure recule déjà quand la correction dépasse la vitesse de base.
  • Intersections : Quand tous les capteurs voient la ligne en même temps, continue tout droit ; si tu dois choisir une direction, crée une machine à états qui compte les intersections.
  • Lignes pointillées : Quand la ligne disparaît brièvement, essaie de continuer tout droit pendant 100 à 200 ms au lieu de tourner tout de suite.
  • Rampe et pont : Dans la pente, la distance entre capteur et sol varie ; garde la barrette de capteurs basse et proche des roues, et ralentis un peu dans la rampe.
  • Piste d'entraînement : Colle du ruban isolant noir mat de 19 mm sur du carton blanc mat ou du carton plume ; pour les premiers essais, ne fais pas de virages d'un rayon inférieur à 15 cm.

Problèmes fréquents et solutions

ProblèmeCause probableSolution
Le robot zigzague en ligne droiteKp trop grand, Kd trop petit ou vitesse trop élevéeBaisse Kp d'environ 20 %, augmente Kd ; réduis la vitesse de base si besoin
Il sort de la piste dans les viragesVitesse trop élevée, barrette trop étroite ou mauvaise réaction quand la ligne est perdueRéduis la vitesse de base, laisse la roue intérieure reculer, tourne vers le dernier côté vu
Le robot tire toujours d'un côtéLes moteurs ne tournent pas à la même vitesse ou la barrette n'est pas centréeCentre les capteurs, ralentis le moteur le plus rapide avec un petit coefficient (0,95 par exemple)
Les capteurs voient du noir ou du blanc partoutPas d'étalonnage, capteur trop haut ou trimmer mal régléRamène la hauteur à 3–5 mm, refais l'étalonnage sur la piste
L'Arduino redémarre au démarrage des moteursLa tension de la batterie chute un instant ou la masse commune est mauvaiseAlimente la carte séparément ou ajoute un condensateur de 470 µF sur l'alimentation, raccourcis les fils
Le robot se comporte autrement quand la batterie faiblitLa vitesse des moteurs baisse avec la tensionMesure la tension de la batterie et adapte la vitesse, ou mets une batterie pleine avant chaque passage
Ça marche à la maison, pas au concoursLa lumière ambiante ou un sol brillant perturbe l'infrarougeProtège les capteurs avec une jupe noire, étalonne sous l'éclairage du concours
Le moyen le plus rapide d'isoler un problème est de soulever les roues et de déplacer le robot à la main au-dessus de la ligne : si les roues accélèrent dans le bon sens, les capteurs et le code sont corrects et le problème vient de la mécanique ou de l'alimentation. Pour un diagnostic détaillé des redémarrages, consulte L'Arduino redémarre tout seul.

Étapes suivantes et idées de projets

  • Suiveur qui évite les obstacles : Ajoute un HC-SR04 à l'avant pour que le robot contourne les obstacles de la piste ; la logique de décision est dans Robot éviteur d'obstacles.
  • Mode Bluetooth : Passe du suivi de ligne à la commande par téléphone d'un appui sur un bouton ; facile à ajouter avec le module Bluetooth HC-05.
  • Chronomètre au tour : Un robot qui détecte la ligne de départ et affiche le temps au tour sur un écran OLED.
  • Résolveur de labyrinthe : Un robot qui compte et mémorise les intersections, puis suit le chemin le plus court au deuxième passage.
  • Robot d'entrepôt : Un petit modèle d'AGV qui s'arrête à des stations le long de la ligne et dépose une charge avec un servo.
  • Une base solide : Si tu n'as pas encore monté le châssis, les moteurs et la batterie, commence par le guide Fabriquer une voiture robot Arduino.

Un robot suiveur de ligne fait travailler en même temps les capteurs, les algorithmes, les moteurs et l'alimentation, et il va un peu plus vite à chaque séance de réglage. Fais d'abord rouler le robot avec la version if-else simple, puis passe au PID et règle les valeurs avec patience. Si tu veux comprendre comment le PWM règle la vitesse des moteurs, Qu'est-ce que le PWM ? est une bonne suite. Dans l'application BlueGrays, tu trouveras pas à pas, avec schémas et code, les projets de détection de surface noire et blanche avec le TCRT5000, de voiture pilotée en Bluetooth avec le L298N et de robot éviteur d'obstacles.

Questions fréquentes

Combien de capteurs faut-il pour un robot suiveur de ligne ?

Deux capteurs suffisent pour un robot qui fonctionne, mais trois donnent un résultat bien plus stable avec un simple programme if-else. Pour un suivi rapide et fluide, on conseille 5 ou 8 capteurs analogiques avec un PID. De nombreux concours limitent le nombre de capteurs à 8.

Pourquoi mon robot suiveur de ligne zigzague-t-il ?

La cause la plus fréquente est un Kp trop grand et un Kd trop petit : le robot corrige trop fort chaque écart et passe de l'autre côté de la ligne. Baisser un peu Kp, augmenter Kd, réduire la vitesse de base et avancer les capteurs de 4 à 8 cm devant les roues réduit les oscillations. Avec un code if-else simple, un léger zigzag est normal, car le robot passe d'une vitesse fixe à une autre.

Peut-on faire un suiveur de ligne sans PID ?

Oui. Avec un programme if-else simple et 2 ou 3 capteurs, le robot suit la ligne, ce qui suffit souvent pour un projet scolaire. Mais quand la vitesse augmente, il perd la ligne dans les virages et oscille en ligne droite. Si tu vises une bonne place en concours, il faudra passer au PID.

Quelle batterie choisir pour un robot suiveur de ligne ?

Le plus pratique est deux accus Li-ion 18650 en série : ils fournissent 7,4 V nominaux et délivrent sans peine le courant élevé des moteurs. Les robots légers utilisent aussi une batterie LiPo 2S. Une pile 9 V ne peut pas fournir le courant des moteurs et se vide très vite.

Que changer dans le code pour une ligne blanche sur fond noir ?

Il suffit d'inverser les valeurs des capteurs : dans les programmes de cet article, mettre WHITE_LINE à true transforme la valeur normalisée en 1000 − valeur, et le robot suit la ligne blanche. Sur les modules numériques, la même variable inverse la lecture de DO. L'étalonnage doit quand même se faire sur la piste du concours.

À quelle hauteur placer le capteur TCRT5000 ?

Le TCRT5000 fonctionne au mieux à environ 2,5 mm ; en pratique, laisser 3 à 5 mm entre le capteur et le sol donne de bons résultats. Au-delà de 1 cm, l'écart entre le noir et le blanc devient très faible et le robot perd la ligne. Sur les pistes avec rampe, placer la barrette près des roues limite la variation de distance.

Voir tous les projets
TCRT5000 Détection de surface noire et blanche
#78Intermédiaire
Premium

TCRT5000 Détection de surface noire et blanche

Projet qui détecte la couleur du sol avec le capteur TCRT5000 et allume une LED rouge sur un sol noir et une LED verte sur un sol blanc. Il enseigne la logique de détection du seuil de couleur qui con

4.7(40)
49
Mesure de distance avec TCRT5000
#77Intermédiaire
Premium

Mesure de distance avec TCRT5000

Projet qui mesure la quantité de lumière réfléchie par la surface avec le capteur infrarouge TCRT5000 et l'affiche sous forme de valeur analogique sur l'écran LCD I2C. Enseigne les bases de la détecti

4.6(33)
43
Voiture contrôlée par Bluetooth avec L298N
#80Avancé
Premium

Voiture contrôlée par Bluetooth avec L298N

Projet de voiture robot 4WD (quatre moteurs, quatre roues motrices) qui peut être contrôlée depuis le téléphone avec Arduino et le module Bluetooth HC-05. Un total de quatre roues est entraîné simulta

5.0(38)
64
Robot évitant les obstacles
#81Avancé
Premium

Robot évitant les obstacles

Que diriez-vous de construire un véhicule intelligent qui trouve son propre chemin ? Le Robot Évitant les Obstacles scrute en permanence son environnement grâce au capteur ultrasonique situé devant lu

5.0(38)
49
HC-SR04 Mesure de la distance (affichage en série)
#27Débutant

HC-SR04 Mesure de la distance (affichage en série)

Le capteur ultrasonique HC-SR04 permet de mesurer la distance et d'afficher le résultat en cm sur le moniteur série. Il enseigne le principe du calcul de la distance à l'aide d'une onde sonore.

4.6(44)
66
Allumer une LED avec un bouton-poussoir (Push)
#9Débutant

Allumer une LED avec un bouton-poussoir (Push)

Projet de commande instantanée (push) qui allume la LED lorsque le bouton est pressé et l'éteint lorsqu'il est relâché. Enseigne l'utilisation de la lecture de bouton et de l'expression conditionnelle

4.9(112)
190
ESP32 LED Fade avec PWM
#110Débutant
Premium

ESP32 LED Fade avec PWM

Modifiez la luminosité de la LED en douceur de 0 à max, de max à 0. Nous contrôlons le PWM avec analogWrite() comme dans Arduino - la même syntaxe avec ESP32 Arduino Core 3.0+ fonctionne sans problème. La définition du pin + pinMode + analogWrite est suffisante.

4.8(36)
54
Raspberry Pi Pico PWM LED Fade (Effet Respiration, GP14)
#160Débutant
Premium

Raspberry Pi Pico PWM LED Fade (Effet Respiration, GP14)

La LED ne clignote plus — elle respire ! 🌬️ Grâce au **PWM** (modulation de largeur d'impulsion) du Raspberry Pi Pico, la luminosité monte puis descend lentement. Apprends la classe machine.PWM, duty_u16() (luminosité 16 bits 0–65535), la fréquence PWM, les boucles range() et la sortie quasi analogique. Base du mélange de couleurs RGB, du contrôle moteur, des servos et du buzzer.

0.0(0)
4