Guias · 17 min de leitura

Robô seguidor de linha com Arduino: sensores TCRT5000 e PID

Monte um robô seguidor de linha com Arduino: sensores TCRT5000, posicionamento e calibração, código if-else e PID, ajuste de Kp/Kd, driver de motor, bateria e regras.

Um robô seguidor de linha é um veículo autônomo que detecta uma linha preta (ou branca) no chão com sensores infravermelhos e fica ajustando a velocidade das suas duas rodas para continuar sobre ela. É um dos primeiros projetos de robô de verdade que as pessoas montam com Arduino, porque junta leitura de sensores, tomada de decisão e controle de motores em um único projeto. Os carrinhos autônomos (AGVs) que movimentam materiais nas fábricas funcionam com a mesma ideia, e seguir linha é uma das categorias mais disputadas nas competições escolares de robótica.

Neste guia você vai aprender como o sensor TCRT5000 funciona, como diferem as barras de 2, 3 e 5 sensores, a que altura e espaçamento montar os sensores e como calibrar o limite. Primeiro você coloca o robô para andar com um sketch if-else simples e depois passa para uma versão com controle PID, que segue a linha sem zigue-zague e sem sair da pista nas curvas. Junto com código que funciona para Arduino Uno, ESP32 e Raspberry Pi Pico, você também encontra como escolher o driver de motor e a bateria, regras típicas de competição e soluções para os problemas mais comuns.

Sensor de refletância infravermelho TCRT5000
TCRT5000: um LED infravermelho azulado e um fototransistor preto ficam lado a lado em um mesmo invólucro; os módulos de sensor de linha são construídos em torno dessa peça.

Como funciona um robô seguidor de linha?

Uma superfície branca reflete de volta a maior parte da luz infravermelha, enquanto uma superfície preta fosca absorve a maior parte dela. Os sensores embaixo do robô medem essa diferença para descobrir onde a linha está em relação ao robô. O robô não tem direção; ele faz curvas com o que se chama de tração diferencial: se a roda esquerda gira mais rápido que a direita, o robô vira para a direita, e se gira mais devagar, vira para a esquerda. O programa executa o mesmo laço centenas de vezes por segundo: lê os sensores, calcula quanto a linha se desviou e corrige a velocidade dos dois motores de acordo. Quanto mais rápido e estável esse laço fechado roda, mais suave o robô se move.

PeçaRecomendadoObservação
Placa controladoraArduino Uno ou NanoO Nano cabe mais fácil em um chassi pequeno; um ESP32 ou um Pico também servem
Sensor de linhaBarra TCRT5000 de 3 ou 5 canais, QTR-8AUma barra com saídas analógicas dá melhores resultados com PID
Driver de motorL298N ou TB6612FNGO TB6612FNG é menor e mais eficiente
Motores2 motores CC com redução (TT 1:48 ou N20)Os dois motores devem ser do mesmo modelo e velocidade
Rodas2 rodas de tração e 1 roda bobaRodas de 65 mm são as mais comuns
Alimentação2 × células Li-ion 18650 (7,4 V) e uma chaveUma bateria de 9 V (PP3) é fraca demais para motores
OutrosChassi, botão, jumpersUm botão facilita a calibração e a largada
Placa Arduino Nano
Placa Arduino Nano
Driver de motor L298N
Driver de motor L298N
Motor N20 com redução
Motor N20 com redução
Bateria Li-ion 18650
Bateria Li-ion 18650

O sensor TCRT5000: barras de 2, 3 e 5 sensores

O TCRT5000 é formado por um LED infravermelho (950 nm) e um fototransistor lado a lado no mesmo invólucro. Quanto mais luz do LED bate no chão e chega ao fototransistor, mais o transistor conduz. O sensor funciona melhor a uma distância de cerca de 2,5 mm; acima de 1 cm, a diferença entre preto e branco cai rápido. Os módulos prontos trazem um comparador LM393: na maioria dos módulos, o pino DO fornece HIGH sobre o preto e LOW sobre o branco, conforme o limite ajustado no potenciômetro, e o LED da placa acende sobre uma superfície branca. O pino AO dá o valor analógico bruto: alto sobre o preto, baixo sobre o branco. Você encontra todos os detalhes do sensor em Sensor infravermelho TCRT5000.

SensoresPosicionamentoAlgoritmoQuando escolher
2Um de cada lado da linha; os dois normalmente veem brancoif-elsePrimeira tentativa, pista larga com curvas suaves
3O do meio sobre a linha, os de fora nas bordas delaif-else ou PID simplesProjetos escolares, competições de nível iniciante
5Fileira com espaçamento igual, o do meio sobre a linhaPID (média ponderada)Seguir linha rápido e suave
8Fileira larga (como a QTR-8A)PID mais detecção de cruzamentosPistas de competição com curvas fechadas e cruzamentos

Posicionamento dos sensores: altura, espaçamento e distância à frente

Por melhor que seja o seu código, o robô não anda bem se os sensores estiverem no lugar errado. Monte a barra de sensores à frente do eixo das rodas, paralela ao chão e simétrica em relação à linha central do robô. Estas medidas são um bom ponto de partida:

  • Altura: deixe 3–5 mm entre a parte de baixo do sensor e o chão. A Pololu indica 3 mm como a distância ideal e 6 mm como a máxima recomendada para a sua barra QTR-8A. Mais alto que isso, a diferença entre preto e branco diminui e o robô perde a linha.
  • Espaçamento: em barras analógicas, mantenha a distância entre dois sensores entre metade e dois terços da largura da linha; 10–13 mm funciona bem para uma linha de 20 mm. Assim a linha normalmente fica sob dois sensores e a posição é calculada de forma suave.
  • Disposição digital com 3 sensores: coloque o sensor do meio bem sobre a linha e os laterais logo fora das bordas dela; para uma linha de 20 mm, isso dá cerca de 18–22 mm de centro a centro.
  • Distância à frente: coloque os sensores 4–8 cm à frente do eixo das rodas. Perto demais e o robô percebe as curvas tarde; longe demais e até uma pequena virada desloca muito os sensores, fazendo o robô balançar.
  • Luz ambiente: a luz do sol e as lâmpadas halógenas também têm luz infravermelha. Proteger os sensores com uma saia preta ou fita deixa as leituras bem mais estáveis.
Um erro comum é montar os sensores inclinados ou altos demais. Se o robô perde a linha na primeira curva, confira a altura dos sensores antes do código: até alguns milímetros podem reduzir as leituras pela metade. Fita isolante brilhante também reflete infravermelho, então uma linha preta às vezes pode parecer branca; use fita fosca ou uma linha impressa, se puder.

Ligação: Arduino Uno, ESP32 e Pico

A tabela abaixo corresponde exatamente aos sketches de teste dos sensores e de PID; a ligação do exemplo if-else com 3 sensores está descrita na seção dele. Numere os sensores da esquerda para a direita, olhando o robô por trás: o sensor mais à esquerda é o número 1. Usamos um L298N como driver de motor; o que cada pino faz e como a ponte H funciona estão explicados em detalhe em Controle de motor CC com L298N. O Pico só tem três entradas analógicas, então nele usamos os sensores 1, 3 e 5.

LigaçãoArduino UnoESP32Raspberry Pi Pico
AO do sensor 1 (mais à esquerda)A0GPIO 36GP26
AO do sensor 2A1GPIO 39-
AO do sensor 3 (meio)A2GPIO 34GP27
AO do sensor 4A3GPIO 35-
AO do sensor 5 (mais à direita)A4GPIO 32GP28
VCC dos sensores5V3V33V3
ENA do L298N (velocidade do motor esquerdo)D9GPIO 25GP15
IN1, IN2 do L298ND8, D7GPIO 26, 27GP14, GP13
ENB do L298N (velocidade do motor direito)D10GPIO 33GP10
IN3, IN4 do L298ND5, D4GPIO 18, 19GP11, GP12
Botão de largada (outra perna no GND)D2GPIO 23GP16
GNDGNDGNDGND (comum com a bateria e o driver)
O negativo da bateria, o GND do driver de motor e o GND da placa precisam estar todos ligados; sem um terra comum, os motores se comportam de forma errática. Nunca alimente os motores pelo pino 5V da placa. No ESP32 e no Pico, alimente os sensores pelo 3V3: a saída AO de um módulo alimentado com 5 V pode subir até 5 V e danificar as entradas analógicas de 3,3 V.

Como escolher o driver de motor e a bateria

O L298N é barato e resistente, mas os seus transistores bipolares engolem 1,5–3 V: de um pacote de baterias de 7,4 V, só cerca de 5–5,5 V chegam aos motores. Em robôs pequenos e rápidos, um TB6612FNG ou um DRV8833, baseados em MOSFET, funcionam melhor. Com o TB6612FNG o código continua o mesmo: você liga as conexões ENA, IN1 e IN2 a PWMA, AIN1 e AIN2 (PWMB, BIN1 e BIN2 no canal B) e liga o pino STBY ao VCC. O DRV8833 não tem pino separado de velocidade (enable); o sinal PWM vai direto aos pinos IN, então você vai precisar adaptar a função drive(). Para a alimentação, a escolha mais prática são duas células Li-ion 18650 em série: 8,4 V cheias, 7,4 V nominais, e elas fornecem corrente alta com facilidade. Baterias de 9 V (PP3) não aguentam a corrente dos motores e descarregam rápido. Você pode calcular quanto tempo a sua bateria vai durar com a ferramenta Duração da bateria.

DriverTensão dos motoresCorrente por canalQueda de tensãoVeredito
L298N5–35 V2 A1,5–3 VResistente, mas grande e ineficiente
TB6612FNG4,5–13,5 V1,2 A (pico de 3,2 A)Muito baixaMelhor custo-benefício para um seguidor de linha
DRV88332,7–10,8 V1,5 A (pico de 2 A)Muito baixaRobôs pequenos com motores N20
L9110S2,5–12 V0,8 ABaixaSó robôs leves e lentos
Motor CC TT amarelo com redução e roda
Motor TT amarelo com redução e roda de 65 mm: a caixa de redução diminui a rotação e aumenta o torque; é o motor mais comum em projetos de seguidor de linha e de carrinho robô.

Um exemplo rápido de velocidade: motores TT amarelos (1:48) giram a cerca de 200 rpm sem carga em 6 V. Uma roda de 65 mm tem circunferência de π × 65 ≈ 204 mm, então o robô percorre no máximo 200 × 0,204 ≈ 41 m por minuto, ou cerca de 0,7 m por segundo. Isso é mais que suficiente para a maioria das pistas escolares; o verdadeiro desafio é não perder a linha nessa velocidade. Robôs mais rápidos usam micromotores N20 com redução e um chassi leve. Conforme a tensão da bateria cai, os motores ficam mais lentos e os seus valores de PID ajustados mudam de comportamento; medir a tensão da bateria com um divisor de tensão e escalar a velocidade de acordo é uma melhoria avançada.

Passo 1: teste os sensores e encontre o limite

Antes de ligar os motores, descubra o que os sensores veem. O sketch abaixo mostra o valor analógico bruto dos cinco sensores separados por tabulação; abra Ferramentas > Plotter Serial na Arduino IDE em 115200 baud e você vai ver cada sensor como uma linha própria. Passe o robô devagar sobre a linha com a mão e observe como os valores mudam.

C++ (Arduino IDE)
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}

Em 5 V, um módulo TCRT5000 típico lê 30–200 em uma superfície branca e 600–1000 em uma linha preta, mas esses números mudam com a altura do sensor, o material do chão e a luz ambiente. O limite mais simples é limite = (valor do branco + valor do preto) / 2. Por exemplo, se você lê 80 no branco e 820 no preto, o limite é 450. Como cada sensor lê um pouco diferente, o sketch de PID faz isso automaticamente: durante a calibração, ele registra o menor e o maior valor de cada sensor e depois escala cada leitura para entre 0 (branco) e 1000 (preto) com (leitura − mín) × 1000 / (máx − mín). Isso deixa o limite em 500 para todos os sensores.

Em módulos com saída digital (DO), você ajusta o limite no potenciômetro da placa. Gire o trimpot de modo que o LED do módulo fique apagado com o sensor sobre a linha preta e aceso sobre a superfície branca; a posição bem no meio desses dois pontos é a mais estável. Faça o ajuste na pista em que vai competir e com a mesma iluminação.

Passo 2: um algoritmo if-else simples (3 sensores)

Para um primeiro robô funcionando, a abordagem mais clara é escolher entre alguns movimentos prontos com base no estado de três sensores. Ligue as saídas DO de um módulo de 3 canais ao A0 (esquerda), ao A1 (meio) e ao A2 (direita); os pinos analógicos do Uno também funcionam como entradas digitais. A lógica está nesta tabela (1 significa que o sensor vê a linha):

EsquerdaMeioDireitaSignificadoMovimento
010Linha no meioSeguir reto
110Linha um pouco à esquerdaRoda esquerda desacelera, curva suave à esquerda
100Linha bem à esquerdaRoda esquerda dá ré, curva fechada à esquerda
011Linha um pouco à direitaRoda direita desacelera, curva suave à direita
001Linha bem à direitaRoda direita dá ré, curva fechada à direita
111Cruzamento ou linha de chegadaAtravessar reto
000Linha perdidaVirar para o último lado visto
C++ (Arduino IDE)
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}

A variável lastSide lembra de que lado o robô viu a linha por último; se os três sensores perderem a linha em uma curva fechada, o robô vira para esse lado para encontrá-la de novo, em vez de seguir reto sem rumo. Com dois sensores a mesma lógica é ainda mais simples: os sensores ficam um de cada lado da linha, o robô segue reto enquanto os dois veem branco, vira à esquerda quando o esquerdo vê preto e vira à direita quando é o direito. A fraqueza desse método é que os movimentos vêm em degraus: o robô só alterna entre algumas velocidades fixas, então faz um leve zigue-zague até nas retas e sai da pista nas curvas quando você aumenta a velocidade.

Passo 3: seguir linha de forma suave com PID

O PID aplica uma correção proporcional ao quanto o robô se desviou da linha. Primeiro, a posição da linha é calculada a partir dos cinco sensores como uma média ponderada: o valor de 0–1000 de cada sensor é multiplicado pela sua posição (0, 1000, 2000, 3000, 4000), os resultados são somados e divididos pela soma dos valores. O resultado é um número entre 0 e 4000, que vale 2000 quando a linha está exatamente no meio. Por exemplo, se os sensores leem 0, 0, 300, 900, 0, a posição é (300 × 2000 + 900 × 3000) / 1200 = 2750. O erro = posição − 2000: um valor positivo significa que a linha está à direita, um negativo, que está à esquerda.

A correção é a soma de três termos: correção = Kp × erro + Ki × soma dos erros + Kd × (erro − erro anterior). O termo P vira com mais força quanto maior o desvio. O termo D olha a rapidez com que o erro muda e freia conforme o robô se aproxima da linha, amortecendo a oscilação. O termo I corrige erros pequenos que ficam muito tempo de um lado só, mas em um seguidor de linha normalmente fica em zero. Depois, o motor esquerdo roda a velocidade base + correção e o direito a velocidade base − correção. Por exemplo, com um erro de 800 e Kp = 0.06, a correção é 48; com velocidade base de 140, o motor esquerdo roda a 188 e o direito a 92, e o robô faz uma curva suave para a direita.

C++ (Arduino IDE)
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}

Depois de carregar, aperte o botão uma vez: o LED da placa acende por 5 segundos. Nesse tempo, deslize o robô com a mão algumas vezes para a esquerda e para a direita sobre a linha, para que cada sensor veja tanto o preto quanto o branco. Quando o LED apagar, coloque o robô sobre a linha e aperte o botão de novo. O laço roda a cada 5 ms usando millis(); um período fixo mantém o termo D sempre na mesma escala. Se a linha sumir completamente, readPosition() retorna 0 ou 4000, conforme o último lado em que foi vista, e o robô a procura com a curva mais fechada. Para a ligação do botão, veja Como ligar um botão ao Arduino, e para temporizar sem delay, veja millis() e delay() no Arduino.

Como ajustar Kp e Kd

  1. Mantenha a velocidade base baixa (100, por exemplo) e coloque Ki e Kd em zero.
  2. Comece com um Kp pequeno (0.02) e aumente aos poucos até o robô conseguir fazer as curvas. Quando o robô começar a oscilar rápido e de forma regular em torno da linha, você chegou ao limite superior do Kp; fique um pouco abaixo desse valor.
  3. Comece o Kd com cerca de 10 vezes o Kp e aumente até a oscilação sumir. Se o Kd ficar grande demais, o robô treme e os motores chiam.
  4. Quando o robô andar perfeitamente reto nas retas e suave nas curvas, aumente a velocidade base em 10–20 e repita os passos 2 e 3.
  5. Só experimente o Ki, com um valor bem pequeno (como 0.0001), se o robô continuar puxando para um lado em retas longas.
  6. Anote os valores que encontrar e a tensão da bateria naquele momento; conforme a bateria descarrega, os mesmos valores se comportam de outro jeito.
Na escala usada neste artigo (posição 0–4000, PWM 0–255, laço de 5 ms), uma boa faixa inicial é 0.04–0.08 para o Kp e 0.4–1.2 para o Kd. Não coloque Serial.print dentro do laço durante o ajuste: cada print deixa o laço mais lento e bagunça o termo D. Se precisar ver valores, mostre-os no máximo uma vez a cada 200 ms.

Seguidor de linha com ESP32: ajuste o PID pelo celular

A grande vantagem do ESP32 é que você pode mudar os valores do PID pelo celular, sem carregar o código de novo toda vez. A lógica é a mesma da versão do Uno; as diferenças são: a entrada analógica do ESP32 é de 12 bits (0–4095), os sensores precisam ir em pinos do ADC1, GPIO 32–39 (os pinos do ADC2 não podem ser lidos com o Wi-Fi ligado), e os sensores são alimentados com 3,3 V. analogWrite() funciona nas versões 2.x e 3.x do core Arduino do ESP32. Instale um app como o Serial Bluetooth Terminal no seu celular Android, conecte ao dispositivo “CizgiRobot” e envie comandos como p0.07, d0.9 ou s160; o robô usa o novo valor na hora e o devolve como confirmação. O Bluetooth clássico só existe no ESP32 original (não no S3 nem no C3), e iPhones não suportam esse tipo de conexão serial. Se o ESP32 é novidade para você, leia antes O que é o ESP32?.

C++ (Arduino IDE)
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}
Alguns regulamentos de competição proíbem módulos Wi-Fi, Bluetooth ou RF no robô, ou até placas com Wi-Fi de qualquer tipo. A abordagem mais segura é usar o ESP32 para ajustes e treinos e competir com um Arduino Uno, um Nano ou um Pico sem Wi-Fi; se tiver dúvida, pergunte aos organizadores.

Seguidor de linha com Raspberry Pi Pico e MicroPython

O Pico só tem três entradas analógicas disponíveis para o usuário (GP26, GP27, GP28), então o código abaixo trabalha com três sensores analógicos e calcula a posição entre 0 e 2000. Se quiser cinco sensores, você pode ligar um módulo de 5 canais com saídas digitais a quaisquer cinco GPIOs e contar cada sensor como 0 ou 1000; a média ponderada fica mais grosseira, mas ainda funciona. read_u16() retorna um valor de 0 a 65535, e a velocidade dos motores é dada em porcentagem (de -100 a 100). Para a configuração e os primeiros passos, veja O que é o 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

Regras de competição e a pista

As regras das competições de seguidor de linha variam conforme o organizador e o ano, mas a ideia básica é a mesma: o robô precisa completar a pista no menor tempo sem sair da linha. Por exemplo, no regulamento de 2024 da categoria seguidor de linha de nível iniciante da Competição Nacional de Robôs do MEB, na Turquia, a pista é uma linha branca de 20 ± 2 mm sobre fundo preto. O robô pode medir no máximo 280 × 180 mm, incluindo as rodas, e pesar no máximo 1 kg, incluindo as baterias; a tensão de operação não pode passar de 12 V, e ele precisa usar dois motores CC de 6–12 V com redução em L e engrenagens de plástico, rodas de no máximo 65 mm de diâmetro e uma barra de até 8 sensores. A pista tem uma ponte com inclinação de 13 graus e um trecho quadrado em que cortar caminho pelo meio custa uma penalidade de 20 segundos; um robô cujo corpo fique totalmente sobre o fundo preto conta como fora da pista, e um que fique parado sobre a linha por mais de 30 segundos é desclassificado. Outras competições escolares e regionais usam o contrário, uma linha preta de 20 ± 2 mm sobre fundo branco, e pedem robôs menores, de no máximo 500 gramas. Resumindo, leia sempre o regulamento daquele ano antes de projetar o seu robô.

  • Pista invertida: para uma linha branca sobre fundo preto, coloque a variável WHITE_LINE em true nos sketches. Se as cores se invertem no meio da pista, você pode acrescentar uma verificação que troca de modo quando os sensores de fora começam a ver preto e o do meio, branco.
  • Curvas de 90 graus: diminua a velocidade base antes da curva ou deixe a roda interna dar ré; nos sketches deste artigo, a roda interna já dá ré quando a correção é maior que a velocidade base.
  • Cruzamentos: quando todos os sensores veem a linha ao mesmo tempo, atravesse reto; se precisar escolher uma direção, monte uma máquina de estados que conta os cruzamentos.
  • Linhas tracejadas: quando a linha some por um instante, experimente seguir reto por 100–200 ms em vez de virar na hora.
  • Rampas e pontes: em uma inclinação, a distância entre o sensor e o chão muda; mantenha a barra de sensores baixa e perto das rodas e reduza um pouco a velocidade na rampa.
  • Pista de treino: cole fita isolante preta fosca de 19 mm em cartolina branca fosca ou placa de espuma; nas primeiras tentativas, não faça curvas com raio menor que 15 cm.

Problemas comuns e soluções

ProblemaCausa provávelSolução
O robô faz zigue-zague nas retasKp alto demais, Kd baixo demais ou velocidade alta demaisDiminua o Kp em cerca de 20%, aumente o Kd; diminua a velocidade base, se precisar
Sai da pista nas curvasVelocidade alta demais, barra de sensores estreita demais ou reação errada quando a linha se perdeDiminua a velocidade base, deixe a roda interna dar ré, vire para o último lado visto quando a linha se perder
O robô sempre puxa para um ladoOs motores giram em velocidades diferentes ou a barra de sensores não está centralizadaCentralize os sensores, reduza o motor mais rápido com um pequeno fator (0.95, por exemplo)
Os sensores veem preto ou branco em todo lugarSem calibração, sensor alto demais ou trimpot não ajustadoBaixe a altura para 3–5 mm, repita a calibração na pista
O Arduino reinicia quando os motores ligamA tensão da bateria cai por um instante ou o GND comum está ruimAlimente a placa separadamente ou acrescente um capacitor de 470 µF à alimentação, encurte os fios
O robô muda de comportamento conforme a bateria descarregaA velocidade dos motores cai junto com a tensãoMeça a tensão da bateria e escale a velocidade, ou coloque uma bateria cheia antes de cada corrida
Funciona em casa, falha no local da competiçãoA luz ambiente ou um piso brilhante atrapalham o infravermelhoProteja os sensores com uma saia preta, calibre sob a iluminação da competição
O jeito mais rápido de localizar um problema é levantar as rodas do chão e passar o robô sobre a linha com a mão: se as rodas acelerarem no sentido certo, os sensores e o código estão bons e o problema está na mecânica ou na alimentação. Para um diagnóstico detalhado de problemas de reset, veja Arduino reiniciando sozinho.

Próximos passos e ideias de projetos

  • Seguidor de linha que desvia de obstáculos: acrescente um HC-SR04 na frente para o robô contornar obstáculos na pista; a lógica de decisão está em Robô que desvia de obstáculos.
  • Modo Bluetooth: alterne entre seguir linha e controle pelo celular com um botão; é fácil de acrescentar com o módulo Bluetooth HC-05.
  • Cronômetro de volta: um robô que detecta a linha de largada e mostra o tempo da volta em um display OLED.
  • Resolvedor de labirinto: um robô que conta e memoriza os cruzamentos e segue o caminho mais curto na segunda volta.
  • Robô de armazém: uma pequena maquete de AGV que para em estações ao longo da linha e solta uma carga com um servo.
  • Uma base sólida: se você ainda não montou o chassi, os motores e a bateria, comece pelo guia Carrinho robô com Arduino.

Um robô seguidor de linha ensina sensores, algoritmos, motores e alimentação de uma vez só, e fica um pouco mais rápido a cada rodada de ajustes. Coloque o robô para andar primeiro com a versão if-else simples, depois passe para o PID e ajuste os valores com paciência. Se tem curiosidade sobre como o PWM define a velocidade dos motores, O que é PWM? é uma boa próxima leitura. No app BlueGrays você encontra passo a passo, com esquemas do circuito e código, os projetos de detecção de superfície preta e branca com TCRT5000, do carrinho L298N controlado por Bluetooth e do robô que desvia de obstáculos.

Perguntas frequentes

Quantos sensores um robô seguidor de linha precisa?

Dois sensores bastam para um robô funcionar, mas três dão resultados bem mais estáveis com um sketch if-else simples. Para seguir a linha de forma suave e rápida, recomendam-se 5 ou 8 sensores analógicos com PID. Muitas competições permitem no máximo 8 sensores.

Por que o meu robô seguidor de linha faz zigue-zague?

O motivo mais comum é um Kp alto demais e um Kd baixo demais: o robô corrige demais cada desvio e cruza para o outro lado da linha. Diminuir um pouco o Kp, aumentar o Kd, reduzir a velocidade base e colocar os sensores 4–8 cm à frente das rodas reduzem a oscilação. Com código if-else simples, um leve zigue-zague é normal, porque o robô alterna entre algumas velocidades fixas.

Dá para montar um seguidor de linha sem PID?

Sim. Com um sketch if-else simples e 2 ou 3 sensores, o robô segue a linha, e isso muitas vezes basta para projetos escolares. Conforme a velocidade aumenta, porém, o robô perde a linha nas curvas e balança nas retas. Se você quer uma boa colocação em uma competição, vai precisar passar para o PID.

Qual é a melhor bateria para um robô seguidor de linha?

A opção mais prática são duas células Li-ion 18650 em série: elas dão 7,4 V nominais e fornecem com facilidade a corrente alta que os motores puxam. Robôs leves também usam um pacote LiPo 2S. Uma bateria de 9 V (PP3) não fornece a corrente dos motores e descarrega rápido.

Como mudo o código para uma linha branca sobre fundo preto?

Basta inverter os valores dos sensores: nos sketches deste artigo, colocar WHITE_LINE em true faz o valor normalizado virar 1000 − valor, então o robô segue a linha branca. Em módulos digitais, a mesma variável inverte a leitura do DO. Mesmo assim, faça a calibração na pista em que vai competir.

A que altura do chão o sensor TCRT5000 deve ficar?

O TCRT5000 funciona melhor a cerca de 2,5 mm; na prática, deixar 3–5 mm entre o sensor e o chão dá bons resultados. Acima de 1 cm, a diferença entre preto e branco diminui muito e o robô perde a linha. Em pistas com rampas, manter a barra de sensores perto das rodas reduz a variação da distância.

Ver todos os projetos
Detecção de superfícies pretas e brancas com TCRT5000
#78Intermediário
Premium

Detecção de superfícies pretas e brancas com TCRT5000

Com o pequeno sensor que funciona como o olho dos robôs seguidores de linha, você identifica se o piso é preto ou branco. O TCRT5000 emite luz invisível em direção ao piso e verifica quanto dela é ref

4.7(40)
49
Leitura de reflexão (proximidade) com TCRT5000
#77Intermediário
Premium

Leitura de reflexão (proximidade) com TCRT5000

Com um pequeno sensor que emite luz invisível e mede quanto dela é refletido, você detecta a superfície à frente dele. Quanto mais o sensor se aproxima de um objeto ou de uma superfície clara, mais lu

4.6(33)
43
Carrinho controlado por Bluetooth com L298N
#80Avançado
Premium

Carrinho controlado por Bluetooth com L298N

Transforme seu celular em um controle remoto e dirija seu próprio carrinho robô. O Arduino recebe os comandos de um aplicativo de controle remoto no celular por meio de um módulo Bluetooth e aciona as

5.0(38)
65
Robô que desvia de obstáculos
#81Avançado
Premium

Robô que desvia de obstáculos

Que tal construir um veículo inteligente que encontra seu próprio caminho? O robô que desvia de obstáculos monitora o ambiente constantemente com o sensor ultrassônico na parte da frente. Ao encontrar

5.0(38)
49
Medição de distância com HC-SR04 (Monitor Serial)
#27Iniciante

Medição de distância com HC-SR04 (Monitor Serial)

O sensor HC-SR04 funciona como um morcego que se orienta no escuro: emite um som que você não consegue ouvir, escuta o eco e mede a distância até o objeto à sua frente. O Arduino escreve essa distânci

4.6(46)
71
Controle de LED com botão (momentâneo)
#9Iniciante

Controle de LED com botão (momentâneo)

É como uma campainha: a luz fica acesa enquanto você pressiona o botão e apaga quando você solta. O Arduino monitora o botão continuamente; enquanto ele está pressionado, o LED fica aceso e apaga assi

4.8(113)
229
Variação gradual do brilho do LED com PWM no ESP32
#110Iniciante

Variação gradual do brilho do LED com PWM no ESP32

Varie suavemente o brilho do LED de 0 ao máximo e do máximo a 0. Controlamos o PWM com analogWrite(), como no Arduino. A mesma sintaxe funciona sem problemas com ESP32 Arduino Core 3.0+. Basta definir o pino e usar pinMode e analogWrite.

4.8(36)
54
Raspberry Pi Pico: variação de brilho do LED com PWM (efeito de respiração)
#160Iniciante

Raspberry Pi Pico: variação de brilho do LED com PWM (efeito de respiração)

O LED não só pisca — ele respira! Com o PWM (modulação por largura de pulso) do Raspberry Pi Pico, o brilho do LED aumenta e diminui lentamente, como a luz de um MacBook em repouso. Aprenda a usar a classe machine.PWM, duty_u16() (brilho de 16 bits, de 0–65535), frequência PWM (freq(1000) — 1.000 ciclos de ligar/desligar por segundo, sem cintilação visível), laços com passos usando range() (crescentes e decrescentes) e saída semelhante à analógica em um pino digital.

0.0(0)
4