Tutorial · Iniciante · 20 min de leitura
Construa um robô seguidor de linha: dos sensores ao controle PID
Como funciona um robô seguidor de linha: sensores infravermelhos, erro ponderado e um laço PID que comanda a tração diferencial com suavidade.
Um robô seguidor de linha é o primeiro robô autônomo clássico: ele lê uma linha sob seus sensores e comanda a direção para permanecer sobre ela. Por baixo, há um pequeno problema de controle — transformar leituras dos sensores em uma correção de direção — o que torna essa uma ótima maneira de aprender controle por realimentação. Acompanhe no simulador de robô seguidor de linha.
Detecte a linha
Um sensor de reflectância infravermelha projeta luz infravermelha para baixo e mede quanto dela retorna. Uma linha escura reflete menos que a superfície clara ao redor, portanto cada sensor informa se está sobre a linha. Uma barra com vários sensores na frente do robô informa não apenas se ele está sobre a linha, mas também onde a linha está em relação ao centro.
Cinco sensores são um bom ponto de partida. Três funcionam em baixa velocidade; robôs de competição costumam usar oito para obter maior resolução.
Transforme as leituras em um único número de erro
Em vez de reagir a sensores individuais, combine-os em uma única posição ponderada. Dê a cada sensor um valor de posição (negativo à esquerda, zero no centro, positivo à direita) e calcule a média ponderada pela intensidade com que cada sensor vê a linha:
// positions: {-2, -1, 0, 1, 2}; readings: reflectance per sensor
float weightedPosition = sum(position[i] * reading[i]) / sum(reading[i]);
O resultado é seu erro: zero significa que a linha está centralizada, um valor negativo significa que ela está à esquerda e um valor positivo, à direita. Um sinal contínuo e suave é exatamente o que um controlador precisa.
Controle a direção com PID
Alimente esse erro a um controlador PID. A correção acelera uma roda e desacelera a outra, fazendo o robô voltar para a linha:
float correction = kp * error + ki * integral + kd * derivative;
setMotorSpeeds(baseSpeed - correction, baseSpeed + correction);
O ganho proporcional realiza a maior parte da correção de direção; o ganho derivativo amortece as oscilações nas curvas; e um pequeno termo integral elimina desvios constantes. Se você está começando a escolher ganhos, veja primeiro como ajustar um controlador PID.
Corrija as falhas comuns
- Oscilação em torno da linha: o ganho proporcional está alto demais ou o derivativo baixo demais. Reduza primeiro a velocidade e depois reajuste. Na implementação real, um
delay()longo no laço adiciona tempo morto que prejudica o termo derivativo. - Perda da linha em curvas fechadas: o robô percorre uma distância maior do que seus sensores conseguem atualizar. Diminua a velocidade, aumente a largura da barra ou reutilize a última direção conhecida da linha para que o robô vire de volta, em vez de seguir em frente e sair da pista.
- Desvio para um lado: calibre os sensores. Passe-os sobre a linha e o fundo, registre o valor mínimo e o máximo de cada sensor e normalize as leituras em tempo real em relação a essa faixa.
Todo o laço em um único lugar
Os fragmentos acima só formam um robô funcional quando a temporização, a calibração e o caso de perda de linha são tratados em conjunto:
const int N = 5;
const int PIN[N] = {A0, A1, A2, A3, A4};
const int POS[N] = {-2000, -1000, 0, 1000, 2000}; // weights, in the error's units
int minv[N], maxv[N];
long lastError = 0;
float integral = 0;
unsigned long lastMicros = 0;
const float KP = 0.10f, KI = 0.0f, KD = 1.6f;
const int BASE = 140; // out of 255
void loop() {
// 1. Real elapsed time — never assume the loop period.
const unsigned long now = micros();
const float dt = (now - lastMicros) * 1e-6f;
if (dt < 0.002f) return; // cap the rate at 500 Hz
lastMicros = now;
// 2. Position from the weighted average.
long weighted = 0, total = 0;
for (int i = 0; i < N; i++) {
const int v = normalized(analogRead(PIN[i]), i);
if (v > 100) { weighted += (long)v * POS[i]; total += v; }
}
// 3. Line lost: hold the last error, do NOT zero it.
const long error = (total == 0) ? lastError : (weighted / total);
// 4. PID, with derivative on measurement and conditional integration.
const float derivative = -(float)(error - lastError) / dt;
const float candidate = integral + error * dt;
const float raw = KP * error + KI * candidate + KD * derivative;
const float correction = constrain(raw, -BASE, BASE);
if (raw == correction) integral = candidate;
lastError = error;
// 5. Differential steering.
setMotors(BASE - correction, BASE + correction);
}
Cinco detalhes fazem a diferença entre um robô que segue uma linha e outro que quase segue.
dt real. Tanto I quanto D dividem pelo tempo decorrido. Um laço cuja duração muda à medida que você adiciona recursos — e vai mudar — calcularia os dois termos com base em uma informação falsa se assumisse um período constante.
O limiar v > 100. Sem ele, cinco canais que registram um pequeno valor de fundo arrastam a média ponderada para o centro, independentemente de onde a linha esteja. O sintoma é um robô que acompanha bem uma linha forte, mas se comporta mal em uma linha apagada.
Manter o último erro ao perder a linha. Zero significa “centralizado”, então um robô que zera o erro segue em frente à velocidade máxima — afastando-se da linha, que quase certamente estava curvando. Manter o último erro faz o robô continuar virando na direção em que já estava virando.
Derivativo sobre a medição. Derivar o erro faz uma alteração na referência produzir um pico enorme. Aqui a referência é sempre zero, então a diferença é pequena, mas vale a pena criar o hábito.
Integração condicional. Quando a correção satura, continuar acumulando o termo integral apenas armazena um sobrepasso que será compensado mais tarde.
Uma ordem de ajuste que funciona
Não ajuste os três ao mesmo tempo. Defina KI = 0 e KD = 0 e então:
| Etapa | Mudança | Observe | Pare quando |
|---|---|---|---|
| 1 | Aumente KP a partir de um valor pequeno |
O robô começa a responder à linha | Ele acompanha uma curva suave, com uma pequena serpentação |
| 2 | Continue aumentando KP |
A serpentação cresce e se transforma em oscilação | Ele oscila de forma constante — anote este valor como Ku |
| 3 | Defina KP como aproximadamente metade de Ku |
A resposta é rápida e há sobrepasso | — |
| 4 | Aumente KD |
A serpentação é amortecida | As retas ficam estáveis; aumentar mais KD torna o robô agitado |
| 5 | Somente se ele continuar acompanhando a linha fora do centro, adicione um pouco de KI |
O robô se recentraliza | Ele se recentraliza — KI raramente é necessário em um robô seguidor de linha |
| 6 | Aumente a velocidade BASE |
Todo o comportamento se degrada | Reajuste KP e KD na nova velocidade |
A etapa 6 é a que as pessoas esquecem: os ganhos dependem da velocidade. Um ajuste perfeito com ciclo de trabalho de 140 oscila com 220, porque a mesma correção de direção agora produz uma mudança maior na orientação por unidade de tempo. Espere reajustar sempre que mudar a velocidade de forma significativa.
O termo integral realmente raramente é útil aqui. A referência de um robô seguidor de linha é zero e as perturbações são transitórias, portanto normalmente não existe um desvio constante que KI precise eliminar. Se você vir um desvio persistente, suspeite de ajuste desigual dos motores ou sensor não calibrado antes de aumentar o ganho integral.
A taxa do laço limita tudo silenciosamente
| Taxa do laço | Intervalo | Distância percorrida a 0,4 m/s |
|---|---|---|
| 20 Hz | 50 ms | 20 mm entre decisões |
| 50 Hz | 20 ms | 8 mm |
| 200 Hz | 5 ms | 2 mm |
| 500 Hz | 2 ms | 0,8 mm |
Um robô que decide a cada 20 mm não consegue acompanhar uma curva com precisão, por mais bem ajustados que estejam os ganhos, e nenhum termo derivativo recupera informação que nunca foi amostrada.
As causas habituais são fáceis de eliminar: um delay() em qualquer lugar do laço, Serial.print de mais de alguns valores a 9600 baud e a leitura de sensores que você não usa. Removê-las normalmente leva uma primeira montagem de 50 Hz para várias centenas de Hz, e o acompanhamento melhora sem alterar nenhum ganho. Meça a taxa antes de ajustar, não depois.
Solução de problemas
| Sintoma | Causa provável | Correção |
|---|---|---|
| Serpenteia nas retas | KP alto demais ou KD baixo demais |
Divida KP pela metade e depois aumente KD |
| Oscila cada vez mais rápido | KP acima do ganho-limite |
Reduza até estabilizar |
| Corta curvas | Velocidade alta demais para o avanço do sensor | Diminua a velocidade ou mova a matriz para a frente |
| Sai da pista ao perder a linha | Erro zerado ao perder a linha | Mantenha o último erro |
| Acompanha bem em uma direção e mal na outra | Matriz não nivelada ou motores desajustados | Nivele a matriz e ajuste cada motor |
| Desvia para um lado | Sensores não calibrados ou motores diferentes | Calibre cada canal e depois faça o ajuste |
| Funciona na bancada, mas vagueia no chão | A distância do sensor ao piso mudou | Use 3–8 mm, suporte rígido e recalibre |
| Funcionou ontem, mas está lento hoje | Queda de tensão da bateria | Os ganhos são propriedades do sistema — reajuste ou feche um laço de velocidade |
| Vibra apenas em baixa velocidade | Abaixo da zona morta dos motores | Meça o ciclo de trabalho de partida de cada motor e compense |
Nada responde a KD |
Há um delay() no laço |
O tempo morto prejudica completamente a ação derivativa |
Tente por sua conta
No simulador de robô seguidor de linha você pode desenhar uma pista, alterar a quantidade de sensores e a velocidade e ajustar os ganhos enquanto observa o erro e o tempo de uma volta completa. Tudo o que você aprender sobre o erro ponderado e o laço de controle se aplica diretamente a um robô Arduino.
Explore o grafo
Faz parte destas montagens
Projetos e caminhos de aprendizagem que incluem este tutorial.
Leitura complementar