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.

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.
| Composant | Conseillé | Remarque |
|---|---|---|
| Carte de commande | Arduino Uno ou Nano | La Nano tient plus facilement sur un petit châssis ; un ESP32 ou une Pico conviennent aussi |
| Capteur de ligne | Barrette TCRT5000 à 3 ou 5 voies, QTR-8A | Avec le PID, une barrette à sorties analogiques donne de meilleurs résultats |
| Driver moteur | L298N ou TB6612FNG | Le TB6612FNG est plus petit et plus efficace |
| Moteurs | 2 motoréducteurs CC (TT 1:48 ou N20) | Les deux moteurs doivent être du même modèle et de même vitesse |
| Roues | 2 roues motrices et 1 roue folle | Les roues de 65 mm de diamètre sont les plus courantes |
| Alimentation | 2 accus Li-ion 18650 (7,4 V) et un interrupteur | Une pile 9 V est trop faible pour les moteurs |
| Divers | Châssis, bouton-poussoir, fils de connexion | Un bouton simplifie l'étalonnage et le départ |




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.
| Capteurs | Disposition | Algorithme | Quand le choisir ? |
|---|---|---|---|
| 2 | De part et d'autre de la ligne ; les deux voient normalement du blanc | if-else | Premier essai, piste large aux virages doux |
| 3 | Celui du milieu sur la ligne, ceux des côtés sur ses bords | if-else ou PID simple | Projets scolaires, concours de niveau débutant |
| 5 | Rangée régulière, celui du milieu sur la ligne | PID (moyenne pondérée) | Suivi rapide et fluide |
| 8 | Rangée large (comme la QTR-8A) | PID et détection des intersections | Pistes 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.
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.
| Liaison | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Capteur 1 (le plus à gauche) AO | A0 | GPIO 36 | GP26 |
| Capteur 2 AO | A1 | GPIO 39 | - |
| Capteur 3 (milieu) AO | A2 | GPIO 34 | GP27 |
| Capteur 4 AO | A3 | GPIO 35 | - |
| Capteur 5 (le plus à droite) AO | A4 | GPIO 32 | GP28 |
| VCC des capteurs | 5V | 3V3 | 3V3 |
| L298N ENA (vitesse moteur gauche) | D9 | GPIO 25 | GP15 |
| L298N IN1, IN2 | D8, D7 | GPIO 26, 27 | GP14, GP13 |
| L298N ENB (vitesse moteur droit) | D10 | GPIO 33 | GP10 |
| L298N IN3, IN4 | D5, D4 | GPIO 18, 19 | GP11, GP12 |
| Bouton de départ (autre patte à GND) | D2 | GPIO 23 | GP16 |
| GND | GND | GND | GND (commune avec batterie et driver) |
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.
| Driver | Tension moteur | Courant par voie | Chute de tension | Avis |
|---|---|---|---|---|
| L298N | 5–35 V | 2 A | 1,5–3 V | Robuste mais encombrant et peu efficace |
| TB6612FNG | 4,5–13,5 V | 1,2 A (pointe 3,2 A) | Très faible | Meilleur rapport qualité-prix pour un suiveur |
| DRV8833 | 2,7–10,8 V | 1,5 A (pointe 2 A) | Très faible | Petits robots à moteurs N20 |
| L9110S | 2,5–12 V | 0,8 A | Faible | Uniquement robots légers et lents |

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.
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.
É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) :
| Gauche | Milieu | Droite | Signification | Mouvement |
|---|---|---|---|---|
| 0 | 1 | 0 | Ligne au milieu | Tout droit |
| 1 | 1 | 0 | Ligne un peu à gauche | La roue gauche ralentit, léger virage à gauche |
| 1 | 0 | 0 | Ligne très à gauche | La roue gauche recule, virage serré à gauche |
| 0 | 1 | 1 | Ligne un peu à droite | La roue droite ralentit, léger virage à droite |
| 0 | 0 | 1 | Ligne très à droite | La roue droite recule, virage serré à droite |
| 1 | 1 | 1 | Intersection ou ligne d'arrivée | Continuer tout droit |
| 0 | 0 | 0 | Ligne perdue | Tourner vers le dernier côté vu |
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.
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
- Garde une vitesse de base faible (100 par exemple) et mets Ki et Kd à zéro.
- 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.
- 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.
- 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.
- 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.
- 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.
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 ?.
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}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 ?.
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 passRè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ème | Cause probable | Solution |
|---|---|---|
| Le robot zigzague en ligne droite | Kp trop grand, Kd trop petit ou vitesse trop élevée | Baisse Kp d'environ 20 %, augmente Kd ; réduis la vitesse de base si besoin |
| Il sort de la piste dans les virages | Vitesse trop élevée, barrette trop étroite ou mauvaise réaction quand la ligne est perdue | Ré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ée | Centre les capteurs, ralentis le moteur le plus rapide avec un petit coefficient (0,95 par exemple) |
| Les capteurs voient du noir ou du blanc partout | Pas 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 moteurs | La tension de la batterie chute un instant ou la masse commune est mauvaise | Alimente 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 faiblit | La vitesse des moteurs baisse avec la tension | Mesure la tension de la batterie et adapte la vitesse, ou mets une batterie pleine avant chaque passage |
| Ça marche à la maison, pas au concours | La lumière ambiante ou un sol brillant perturbe l'infrarouge | Protège les capteurs avec une jupe noire, étalonne sous l'éclairage du concours |
É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.
Projets à réaliser après cet article
Voir tous les projets
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

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

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

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

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.

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

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.

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.

