Guias · 19 min de leitura

Robô que desvia de obstáculos com Arduino: guia com HC-SR04 e servo

Monte um robô que desvia de obstáculos com Arduino: varredura com HC-SR04 sobre um servo, ligação do L298N ou L293D, lógica de parar-recuar-virar, sensores IR e modo manual por Bluetooth.

Um robô que desvia de obstáculos é um carrinho robô autônomo que percebe uma parede, uma cadeira ou uma caixa à frente sem bater nela e encontra um novo caminho. A montagem mais comum usa um Arduino Uno, um sensor de distância ultrassônico HC-SR04, um servo motor SG90 que gira o sensor para a esquerda e para a direita, dois motores CC com redução e um driver de motor L298N (ou L293D). O robô anda em linha reta, para quando chega perto de um obstáculo, recua um pouco, vira a “cabeça” para os dois lados para medir e depois segue na direção mais livre.

Neste guia transformamos o carrinho 2WD do artigo carrinho robô com Arduino em um robô autônomo, mantendo a mesma pinagem. Passo a passo, vemos as tabelas de ligação, como os limites de distância são calculados, um código que cresce em três etapas, as superfícies que o sensor não enxerga e como cobrir essas falhas com sensores infravermelhos e, por fim, a troca para o modo manual por Bluetooth pelo celular. O código para controlar o mesmo robô com um ESP32 ou um Raspberry Pi Pico está no fim do artigo.

Sensor de distância ultrassônico HC-SR04
HC-SR04: o transdutor marcado com T emite um som de 40 kHz e o marcado com R escuta o eco. Este é o olho do robô que desvia de obstáculos; ele mede 2–400 cm dentro de um ângulo estreito de cerca de 15 graus.

Como funciona um robô que desvia de obstáculos?

Todo robô autônomo repete dezenas de vezes por segundo o mesmo ciclo de três passos: sentir, decidir, agir. O HC-SR04 cuida de sentir: ele emite um som curto de 40 kHz que seu ouvido não escuta e mede quanto tempo o eco leva para voltar; divida esse tempo em microssegundos por 58 e você tem a distância em centímetros. O Arduino decide, comparando a distância medida com os limites que você definiu. O L298N e os motores cuidam de agir. Como o sensor fica sobre um servo, o robô consegue “olhar” para a esquerda e para a direita sem girar o corpo, o que permite decisões de curva muito melhores do que as de um robô com sensor fixo. Os detalhes de como o sensor funciona estão no artigo sensor de distância HC-SR04.

PeçaFunçãoObservação
Arduino Uno (ou Nano)Cérebro: mede, decide e comanda os motoresUm ESP32 ou Pico também serve
HC-SR04Olho: mede a distância até o obstáculo à frenteFunciona com 5 V, 2–400 cm
Servo SG90Pescoço: gira o sensor para a esquerda e para a direitaO sensor é preso ao braço do servo com um suporte plástico
L298N ou shield L293DMúsculo: dá direção e velocidade aos motoresUm pino do Arduino não fornece a corrente de um motor
2 motores TT com redução e uma roda bobaTração diferencialAs curvas são feitas girando as duas rodas em sentidos opostos
2 células 18650 (7,4 V)AlimentaçãoUma bateria de 9 V não basta
2 sensores de obstáculo IR (opcional)Detectam obstáculos nos cantos e nas lateraisMódulos com LM393, como o FC-51
HC-05 (opcional)Controle manual pelo celularFunciona só com Android
Arduino Uno
Arduino Uno
Driver de motor L298N
Driver de motor L298N
Motor TT
Motor TT
Bateria Li-ion 18650
Bateria Li-ion 18650

Alimentação: não deixe o servo reiniciar o robô

Como o robô que desvia de obstáculos acrescenta um servo aos motores, a distribuição da alimentação importa mais do que em um carrinho robô comum. Um SG90 consome cerca de 10 mA parado; no instante em que começa a se mover, sobe para 100–250 mA e, quando forçado, chega perto de meio ampère. Se esse pico coincidir com a corrente de partida dos motores, o barramento de 5 V do Arduino cai por um instante e a placa reinicia. A solução é simples: os motores funcionam com a bateria via L298N, o Arduino funciona com a bateria via VIN, e o servo, o HC-SR04, os sensores IR e o HC-05 dividem o pino 5V do Arduino, com um capacitor de 470 µF colocado junto aos fios de alimentação do servo. Mesmo com o servo em movimento, a carga total no barramento de 5 V fica abaixo de meio ampère e, como é breve, o regulador do Uno dá conta. Para calcular a autonomia da bateria com os seus números, use a ferramenta Autonomia da bateria.

SeçãoAlimentado porCorrente típica
2 motores TTTerminal 12V do L298N ← 2 × 18650 (7,4 V)0,3–0,6 A andando, até 2 A na partida
Arduino UnoVIN ← positivo da bateria (depois da chave)Cerca de 50 mA
Servo SG905V do Arduino + capacitor de 470 µF~10 mA parado, 100–250 mA em movimento
HC-SR045V do ArduinoCerca de 15 mA
2 sensores IR5V do Arduino20–40 mA cada
HC-055V do Arduino30–40 mA durante o pareamento, cerca de 10 mA conectado
Ligue todos os GNDs juntos (negativo da bateria, L298N, Arduino, servo, sensores); sem GND comum, o sensor lê distâncias sem sentido e o servo treme. Não alimente o servo pelo mesmo jumper fino dos motores. Se você usar um servo mais forte, como um MG90S ou MG996R, alimente-o não pelo Arduino, mas por um módulo abaixador separado ajustado em 5 V (por exemplo, um LM2596).

Esquema de ligação: Arduino Uno, ESP32 e Pico

Os pinos dos motores são os mesmos do guia do carrinho robô, então você pode partir para este projeto só acrescentando a cabeça com o sensor, sem desmontar o carrinho. Colocar os pinos de velocidade no 5 e no 6 é proposital: no Uno, a biblioteca Servo usa o Timer1 e por isso desativa o PWM nos pinos 9 e 10; se as velocidades dos motores estivessem nesses pinos, o controle de velocidade quebraria assim que o servo fosse conectado. O HC-SR04 e os sensores IR vão em A0–A3. Os pinos analógicos do Uno também podem funcionar como os pinos digitais 14–19 quando necessário, e essa escolha facilita usar o mesmo código com um shield L293D, porque o shield não usa esses pinos. Você pode ver a disposição dos pinos da sua placa na ferramenta Pinagem.

LigaçãoArduino UnoESP32Raspberry Pi Pico
ENA do L298N (velocidade do motor esquerdo)D5GPIO 25GP15
IN1, IN2 do L298ND7, D8GPIO 27, 33GP14, GP13
ENB do L298N (velocidade do motor direito)D6GPIO 26GP10
IN3, IN4 do L298ND9, D4GPIO 32, 4GP12, GP11
Sinal do servo (laranja)D3GPIO 13GP16
Servo + (vermelho), − (marrom)5V, GND5V do L298N, GND5V do L298N, GND
TRIG do HC-SR04A0GPIO 18GP17
ECHO do HC-SR04A1 (direto)GPIO 19, por um divisor de 1 kΩ / 2 kΩGP18, por um divisor de 1 kΩ / 2 kΩ
VCC do HC-SR045V5V do L298N5V do L298N
OUT do IR esquerdo, OUT do IR direitoA2, A3GPIO 23, 22GP19, GP20
VCC dos sensores IR5V3V33V3
TXD, RXD do HC-05D10, D11 (RXD por divisor)Não precisa (Bluetooth embutido)GP1, GP0 (UART0)
Alimentação da placaVIN ← bateria (depois da chave)5V (VIN) ← 5V do L298NVSYS ← 5V do L298N
O pino ECHO do HC-SR04 fornece um sinal de 5 V, enquanto os pinos do ESP32 e do Pico trabalham em 3,3 V e não toleram 5 V. Monte um divisor de tensão assim: ECHO → 1 kΩ → ponto que vai para a placa → 2 kΩ → GND: 5 V × 2 / 3 ≈ 3,3 V. O TRIG não precisa de divisor; um sinal de 3,3 V basta para disparar o sensor. No ESP32 e no Pico, a placa, o servo e o HC-SR04 dividem a mesma saída 5V do L298N, então você pode chegar perto do limite de cerca de 0,5 A desse regulador pequeno; se notar reinícios, alimente o servo por uma fonte de 5 V separada.

Monte e teste a cabeça com o sensor

Micro servo motor SG90
SG90: um micro servo de 9 gramas que gira 60 graus em cerca de 0,1 segundo a 4,8 V. O suporte plástico que segura o HC-SR04 é parafusado no braço do servo.
  1. Ligue o servo sem o braço e carregue o sketch de teste abaixo. No fim de cada varredura, o código leva o servo a 90 graus e espera 2 segundos; desconecte o cabo USB durante essa pausa e o servo fica onde está. Encaixe o braço no eixo de modo que o sensor aponte bem para a frente; assim, 90 graus significam “em frente”.
  2. Prenda o HC-SR04 ao braço com um suporte plástico ou cola quente. Os fios do sensor precisam ser longos o bastante para não esticar quando o servo gira; um cabo enroscando no chassi durante a varredura de 140 graus é um defeito comum.
  3. Monte o sensor a 8–12 cm do chão e perto do ponto mais à frente do corpo. Baixo demais, ele capta ecos do chão e da textura do tapete; alto demais, não enxerga obstáculos baixos.
  4. Incline o sensor 3–5 graus para cima. Um sensor perfeitamente nivelado e baixo pode confundir um eco do chão com um obstáculo, principalmente em pisos ásperos.
  5. Abra o Monitor Serial em 9600 baud. Se você vir valores pequenos nas linhas de 125 e 160 graus quando põe a mão à esquerda do sensor, o lado esquerdo corresponde aos ângulos grandes; se for o contrário, defina SERVO_DIR como -1 no sketch avançado e troque os valores de LEFT_ANGLE e RIGHT_ANGLE no primeiro sketch.
C++ (Arduino IDE)
1#include <Servo.h>
2
3const int SERVO_PIN = 3;
4const int TRIG = A0, ECHO = A1;        // analog pinler dijital pin olarak / analog pins used as digital pins
5Servo head;
6
7int pingCm() {
8  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
9  digitalWrite(TRIG, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
10  digitalWrite(TRIG, LOW);
11  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);    // en fazla 25 ms bekle (~4,3 m) / wait at most 25 ms (~4.3 m)
12  return t / 58;                                     // 0 = yankı yok / 0 = no echo
13}
14
15void setup() {
16  Serial.begin(9600);
17  pinMode(TRIG, OUTPUT);
18  pinMode(ECHO, INPUT);
19  head.attach(SERVO_PIN);
20  head.write(90);                                    // önce ortala / center first
21  delay(1000);
22}
23
24void loop() {
25  const int angles[] = {20, 55, 90, 125, 160};
26  for (int a : angles) {
27    head.write(a);
28    delay(400);                                      // servo yerine otursun / let the servo settle
29    Serial.print(a);
30    Serial.print(F(" derece / degrees: "));
31    Serial.print(pingCm());
32    Serial.println(F(" cm"));
33  }
34  Serial.println();
35  head.write(90);
36  delay(2000);
37}

Cada linha mostra um ângulo e a distância naquela direção. Se não houver nada a cerca de 4 metros do sensor, você vai ler 0 cm: o pulseIn() retorna 0 quando nenhum eco chega em 25 ms. No código principal vamos tratar 0 como “caminho livre”, mas um sensor com fio solto ou com TRIG e ECHO trocados também costuma ler 0. Por isso, antes de colocar o robô no chão, ponha a mão a 10, 30 e 60 cm e confira se os valores realmente mudam; um robô colocado para andar sem esse teste vai direto na parede sem diminuir a velocidade.

O algoritmo de decisão: parar, recuar, virar para a direção mais livre

Dividir o comportamento do robô em zonas por distância deixa o código simples e confiável. Sem obstáculo à frente, ele anda em velocidade máxima; vai reduzindo conforme o obstáculo se aproxima e, ao chegar ao limite de parada, para e faz a varredura. Durante a varredura, o sensor olha 70 e 35 graus para a esquerda e 35 e 70 graus para a direita; são feitas três leituras em cada direção e usa-se a mediana delas. O robô vira para a direção com a maior distância, naquele ângulo. Se nenhuma direção estiver livre, por exemplo quando ele entra num canto, ele recua um pouco e gira 180 graus.

Distância medidaZonaO que o robô faz
Acima de 60 cm (ou sem eco)LivreEm frente em velocidade máxima (PWM 200)
25–60 cmAproximaçãoReduz a velocidade conforme a distância (PWM de 200 até 130)
12–25 cmObstáculoPara, confirma a leitura, faz a varredura dos dois lados, vira para a direção mais livre
Abaixo de 12 cmPerto demaisPrimeiro recua por 350 ms, depois faz a varredura
Todas as direções abaixo de 35 cmBeco sem saídaRecua e gira 180 graus

Esses limites não foram escolhidos ao acaso. A distância de parada é a soma de três margens: a margem de reação (distância percorrida entre duas leituras), a margem de frenagem (quanto o robô desliza depois que os motores são cortados) e a margem de giro (para o nariz não encostar no obstáculo enquanto ele gira no lugar). Um carrinho com motores TT em 6 V anda a cerca de 0,5–0,7 m/s e percorre 3–4 cm em um intervalo de medição de 60 ms. A margem de frenagem é de uns 5 cm e, para um corpo de 15 cm de largura que gira no lugar, a margem de giro fica em torno de 10 cm. Isso soma cerca de 18–20 cm; com alguns centímetros de folga de segurança, STOP_CM = 25 é um bom ponto de partida. Se o seu robô for mais rápido ou o piso for escorregadio, aumente o limite para 30–35 cm. Em corredores estreitos, por outro lado, um limite grande demais faz o robô parar e varrer o tempo todo, então faça o ajuste final no seu próprio percurso.

Código passo a passo 1: seu primeiro robô que desvia de obstáculos

A primeira versão é o esqueleto do algoritmo: seguir em frente, parar a 25 cm, recuar, olhar para a esquerda e para a direita, virar para o lado mais livre. A função de motor drive(left, right) é a mesma do guia do carrinho robô e aceita valores de -255 a 255; positivo é para a frente, negativo é para trás. Depois de carregar o código, primeiro coloque o carrinho sobre uma caixa, com as rodas fora do chão, e observe como ele para e vira quando você aproxima a mão do sensor; se tudo parecer certo, coloque o robô no chão.

C++ (Arduino IDE)
1#include <Servo.h>
2
3const int ENA = 5, IN1 = 7, IN2 = 8;            // sol motor / left motor
4const int ENB = 6, IN3 = 9, IN4 = 4;            // sağ motor / right motor
5const int SERVO_PIN = 3;
6const int TRIG = A0, ECHO = A1;
7
8const int STOP_CM = 25;                         // bu mesafede dur / stop at this distance
9const int SPEED = 170;                          // düz gidiş hızı / cruising speed
10const int TURN = 170;                           // dönüş hızı / turning speed
11const int LEFT_ANGLE = 160, RIGHT_ANGLE = 20;   // ters ise yer değiştir / swap if reversed
12
13Servo head;
14
15void drive(int left, int right) {               // her biri -255 … 255 / each -255 … 255
16  digitalWrite(IN1, left > 0);  digitalWrite(IN2, left < 0);
17  digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
18  analogWrite(ENA, abs(left));
19  analogWrite(ENB, abs(right));
20}
21
22int pingCm() {
23  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
24  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
25  digitalWrite(TRIG, LOW);
26  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
27  if (t == 0) return 400;                       // yankı yok: önü açık say / no echo: treat as open
28  return t / 58;
29}
30
31int lookAt(int angle) {
32  head.write(angle);
33  delay(400);                                   // servo dönsün ve dursun / let the servo turn and settle
34  return pingCm();
35}
36
37void setup() {
38  int pins[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
39  for (int p : pins) pinMode(p, OUTPUT);
40  pinMode(ECHO, INPUT);
41  drive(0, 0);
42  head.attach(SERVO_PIN);
43  head.write(90);
44  delay(3000);                                  // yere koyman için süre / time to put it on the floor
45}
46
47void loop() {
48  int front = pingCm();
49  if (front > STOP_CM) {
50    drive(SPEED, SPEED);                        // yol açık: ileri / path clear: forward
51    delay(60);                                  // ölçümler arası 60 ms / 60 ms between readings
52    return;
53  }
54
55  drive(0, 0);                                  // 1) dur / 1) stop
56  delay(150);
57  drive(-SPEED, -SPEED);                        // 2) biraz geri çekil / 2) back off a little
58  delay(300);
59  drive(0, 0);
60
61  int left = lookAt(LEFT_ANGLE);                // 3) iki yana bak / 3) look both ways
62  int right = lookAt(RIGHT_ANGLE);
63  head.write(90);
64  delay(300);
65
66  if (left < STOP_CM && right < STOP_CM) {      // iki yan kapalı: geri dön / both blocked: turn around
67    drive(-TURN, TURN);
68    delay(700);
69  } else if (left > right) {                    // 4) daha açık tarafa dön / 4) turn to the more open side
70    drive(-TURN, TURN);
71    delay(350);
72  } else {
73    drive(TURN, -TURN);
74    delay(350);
75  }
76  drive(0, 0);
77  delay(100);
78}

Este código cumpre a tarefa, mas tem alguns pontos fracos: uma única leitura ruim faz o robô parar à toa, ele sempre se aproxima dos obstáculos na mesma velocidade, só olha em duas direções e o tempo de giro não depende do ângulo. E como ele espera com delay(), não consegue reagir a nada durante a varredura. Os detalhes da lógica de espera estão no artigo millis() e delay(); a próxima versão corrige a maioria desses problemas.

Código passo a passo 2: filtragem, redução gradual, IR e Bluetooth

A versão avançada funciona com a mesma ligação, e não tem problema se você ainda não instalou os sensores IR e o HC-05. Como as entradas IR são configuradas com INPUT_PULLUP, um sensor que não está ligado lê “sem obstáculo” (HIGH); sem um HC-05, o robô simplesmente começa no modo automático. A tabela abaixo explica o que cada parte do código faz.

C++ (Arduino IDE)
1#include <Servo.h>
2#include <SoftwareSerial.h>
3
4// Pinler / Pins
5const int ENA = 5, IN1 = 7, IN2 = 8;     // sol motor / left motor
6const int ENB = 6, IN3 = 9, IN4 = 4;     // sağ motor / right motor
7const int SERVO_PIN = 3;
8const int TRIG = A0, ECHO = A1;
9const int IR_LEFT = A2, IR_RIGHT = A3;   // LOW = engel (takılı değilse HIGH) / LOW = obstacle (HIGH if not fitted)
10SoftwareSerial bt(10, 11);               // RX, TX → HC-05 TXD, RXD (RXD bölücüyle / RXD via divider)
11
12// Ayarlar / Settings
13const int BACK_CM = 12;                  // daha yakınsa geri çekil / closer: back off first
14const int STOP_CM = 25;                  // dur ve tara / stop and scan
15const int SLOW_CM = 60;                  // yavaşlamaya başla / start slowing down
16const int GUARD_CM = 15;                 // manuel modda çarpışma koruması / collision guard in manual mode
17const int MAX_CM = 400;
18const int CRUISE = 200, SLOW = 130, TURN = 170;   // PWM hızları / PWM speeds
19const int SERVO_CENTER = 90;             // sensör öne bakmıyorsa düzelt / adjust if not facing ahead
20const int SERVO_DIR = 1;                 // +70 sola bakmıyorsa -1 yap / set to -1 if +70 doesn't look left
21const float MS_PER_DEG = 4.0;            // derece başına ms, kalibre et / ms per degree, calibrate
22const unsigned long BT_TIMEOUT = 600;    // manuelde komut kesilirse dur / stop if commands stop
23
24Servo head;
25bool autoMode = true;
26int manualLeft = 0, manualRight = 0, manualSpeed = 200;
27unsigned long lastCmd = 0, lastPing = 0;
28
29void drive(int left, int right) {        // her biri -255 … 255 / each -255 … 255
30  left = constrain(left, -255, 255);
31  right = constrain(right, -255, 255);
32  digitalWrite(IN1, left > 0);  digitalWrite(IN2, left < 0);
33  digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
34  analogWrite(ENA, abs(left));
35  analogWrite(ENB, abs(right));
36}
37
38int pingCm() {
39  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
40  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
41  digitalWrite(TRIG, LOW);
42  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);  // ~4,3 m zaman aşımı / ~4.3 m timeout
43  return t == 0 ? MAX_CM : (int)(t / 58);          // yankı yok = açık alan / no echo = open space
44}
45
46int distanceCm() {                       // 3 ölçümün ortancası / median of 3 readings
47  int a = pingCm(); delay(60);
48  int b = pingCm(); delay(60);
49  int c = pingCm();
50  return max(min(a, b), min(max(a, b), c));
51}
52
53bool irBlocked(int pin) {
54  return digitalRead(pin) == LOW;
55}
56
57int lookAt(int offset) {                 // + sol, - sağ (derece) / + left, - right (degrees)
58  head.write(SERVO_CENTER + SERVO_DIR * offset);
59  delay(300);                            // servo yerine otursun / let the servo settle
60  return distanceCm();
61}
62
63void turnBy(int deg) {                   // + sola, - sağa / + left, - right
64  if (deg > 0) drive(-TURN, TURN);
65  else drive(TURN, -TURN);
66  delay((unsigned long)(abs(deg) * MS_PER_DEG));
67  drive(0, 0);
68  delay(100);
69}
70
71void avoid() {
72  drive(0, 0);                           // 1) dur / 1) stop
73  delay(120);
74  int front = distanceCm();              // ortancayla doğrula / confirm with the median
75  bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
76  if (front >= STOP_CM && !sides) return;          // yanlış alarm / false alarm
77
78  if (front < BACK_CM || sides) {        // 2) çok yakın: geri çekil / 2) too close: back off
79    drive(-TURN, -TURN);
80    delay(350);
81    drive(0, 0);
82  }
83
84  const int offsets[] = {70, 35, -35, -70};        // 3) tara / 3) scan
85  int bestOffset = 0, bestCm = 0;
86  for (int off : offsets) {
87    int cm = lookAt(off);
88    if (cm > bestCm) { bestCm = cm; bestOffset = off; }
89  }
90  head.write(SERVO_CENTER);
91  delay(200);
92
93  if (bestCm < STOP_CM + 10) {           // her yön kapalı: çıkmaz / every way blocked: dead end
94    drive(-TURN, -TURN);
95    delay(400);
96    turnBy(180);
97  } else {
98    turnBy(bestOffset);                  // 4) en açık yöne dön / 4) turn to the most open direction
99  }
100}
101
102void autoStep() {
103  if (millis() - lastPing < 60) return;  // 60 ms'de bir ölç / measure every 60 ms
104  lastPing = millis();
105  int d = pingCm();
106  bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
107
108  if (d < STOP_CM || (l && r)) {
109    avoid();
110  } else if (l) {                        // sol köşede engel: sağa kaç / front-left: dodge right
111    drive(TURN, -TURN);
112    delay(150);
113  } else if (r) {                        // sağ köşede engel: sola kaç / front-right: dodge left
114    drive(-TURN, TURN);
115    delay(150);
116  } else if (d < SLOW_CM) {
117    int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE);   // yaklaştıkça yavaşla / slow down as you get closer
118    drive(s, s);
119  } else {
120    drive(CRUISE, CRUISE);
121  }
122}
123
124void manualStep() {
125  if (millis() - lastCmd > BT_TIMEOUT) {           // komut yok: dur / no commands: stop
126    manualLeft = 0;
127    manualRight = 0;
128  }
129  static int front = MAX_CM;
130  if (millis() - lastPing >= 60) {
131    lastPing = millis();
132    front = pingCm();
133  }
134  bool forward = manualLeft > 0 && manualRight > 0;
135  if (forward && front < GUARD_CM) drive(0, 0);    // çarpışma koruması / collision guard
136  else drive(manualLeft, manualRight);
137}
138
139void handleBluetooth() {
140  while (bt.available()) {
141    char c = bt.read();
142    Serial.println(c);                   // gelen harfi göster / show the incoming letter
143    lastCmd = millis();
144    if (c == 'A') {                      // otomatik mod / auto mode
145      autoMode = true;
146      continue;
147    }
148    if (c == 'M') {                      // manuel mod / manual mode
149      autoMode = false;
150      manualLeft = manualRight = 0;
151      drive(0, 0);
152      head.write(SERVO_CENTER);
153      continue;
154    }
155    if (autoMode) continue;              // otomatikte yön harflerini yok say / ignore in auto mode
156    int s = manualSpeed;
157    switch (c) {
158      case 'F': manualLeft = s;  manualRight = s;  break;   // ileri / forward
159      case 'B': manualLeft = -s; manualRight = -s; break;   // geri / backward
160      case 'L': manualLeft = -s; manualRight = s;  break;   // yerinde sola / spin left
161      case 'R': manualLeft = s;  manualRight = -s; break;   // yerinde sağa / spin right
162      case 'S': manualLeft = 0;  manualRight = 0;  break;   // dur / stop
163      default:
164        if (c >= '0' && c <= '9') manualSpeed = map(c - '0', 0, 9, 100, 240);   // hız kademesi / speed level
165    }
166  }
167}
168
169void setup() {
170  int outs[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
171  for (int p : outs) pinMode(p, OUTPUT);
172  drive(0, 0);
173  pinMode(ECHO, INPUT);
174  pinMode(IR_LEFT, INPUT_PULLUP);
175  pinMode(IR_RIGHT, INPUT_PULLUP);
176  Serial.begin(9600);
177  bt.begin(9600);                        // HC-05 veri modu hızı / HC-05 data mode baud rate
178  head.attach(SERVO_PIN);
179  head.write(SERVO_CENTER);
180  delay(3000);                           // yere koyman için süre / time to put it on the floor
181}
182
183void loop() {
184  handleBluetooth();
185  if (autoMode) autoStep();
186  else manualStep();
187}
Função ou configuraçãoO que faz
pingCm()Faz uma leitura; retorna 400 cm (espaço livre) se nenhum eco chegar em 25 ms
distanceCm()Faz 3 leituras com 60 ms de intervalo e retorna a mediana; descarta um valor fora da curva
autoStep()Mede a cada 60 ms; conforme a distância e os sensores IR, segue em frente, reduz, desvia ou chama avoid()
avoid()Para, confirma a leitura, recua se necessário, olha em 4 direções e vira para a mais livre
turnBy(degrees)Gira o robô no lugar pelo ângulo dado; o tempo é calculado com MS_PER_DEG
manualStep()Aplica a direção enviada pelo celular; para a 15 cm quando anda para a frente e para se nenhum comando chegar em 600 ms
SERVO_CENTER, SERVO_DIRO ângulo em que o sensor aponta para a frente e qual lado é a esquerda
STOP_CM, SLOW_CM, BACK_CMOs limites de parada, de redução e de recuo

Para um robô que desvia de obstáculos, o filtro de mediana é melhor que a média: para três leituras como 24, 26 e 400 cm, a média dá 150 cm e engana o robô, enquanto a mediana escolhe 26 cm. Se quiser se aprofundar, veja o artigo filtragem de dados de sensores. A divisão entre modo automático e manual também é, na verdade, uma pequena máquina de estados; explicamos essa abordagem em detalhes no guia máquina de estados no Arduino. Depois de carregar o código, ajuste o robô ao seu cômodo nesta ordem:

  1. Centro do servo: com o robô de frente e alinhado a uma parede lisa, se o sensor não apontar bem para ela, mude SERVO_CENTER em passos pequenos, como 85 ou 95.
  2. Tempo de giro: acrescente temporariamente uma linha turnBy(360); no fim do setup() e observe se o robô dá uma volta completa. Se ele girar 300 graus em vez de 360, defina MS_PER_DEG como 4.0 × 360 / 300 = 4.8. Como esse valor muda com a bateria e com o piso, ajuste-o com a bateria cheia no piso em que o robô vai andar.
  3. Limite de parada: mande o robô em direção a uma parede e meça a que distância o nariz dele fica da parede onde ele para. Se for menos de 10 cm, aumente STOP_CM em 5 cm.
  4. Velocidade lenta: se SLOW estiver abaixo do limite de partida dos motores, o robô empaca na zona de aproximação; nesse caso, aumente SLOW para 150.

Pontos cegos do HC-SR04: superfícies macias, paredes inclinadas e obstáculos finos

Um sensor ultrassônico enxerga superfícies que refletem bem o som, mas nem todo obstáculo devolve o som ao sensor. O fabricante recomenda um alvo de pelo menos 0,5 m², o mais plano possível, para bons resultados. Numa sala de verdade, porém, o robô encontra cortinas, sofás, pés de cadeira e cantos de parede. O som ricocheteia no mesmo ângulo em que chega, como uma bola de bilhar: um sensor de frente para uma parede recebe um eco forte, mas se a linha de visão dele se desviar mais de uns 30–40 graus da perpendicular, o som resvala para outra direção e nunca volta. Nesse caso, o pulseIn() retorna 0 e o código acha que o caminho está livre. Esse é o motivo mais comum de robôs raspando nas paredes.

Obstáculo ou superfíciePor que não é vistoSolução
Cortinas, sofás, almofadas, roupas, pelo de animaisSuperfícies macias e porosas absorvem o som e o eco volta muito fracoAdicione um sensor IR para curta distância e faça o robô andar devagar
Aproximação de uma parede em ânguloO som é refletido para longe e o eco não chega ao sensorAdicione sensores IR inclinados para fora nos cantos dianteiros e varra em ângulos mais próximos
Pés de cadeira e mesa, postes finosA superfície refletora é minúscula e o eco é fracoReduza a velocidade, use o filtro de mediana, adicione sensores IR nas laterais
Obstáculos baixos, abaixo do sensor (cabos, soleiras, chinelos)Ficam abaixo do cone de somAdicione um sensor IR voltado para baixo ou uma microchave no para-choque dianteiro
Bordas de mesa, vãos de escadaO sensor ultrassônico olha para a frente e não vê um vão no chãoDetecção de queda com um TCRT5000 voltado para o chão
Objetos a menos de 2 cmZona morta do sensor: o eco volta antes de o pulso de som terminarConfigure o robô para recuar a 12 cm
Cantos e corredores estreitosO som rebate entre duas paredes e volta atrasado, então a distância parece maiorFiltro de mediana e leituras em vários ângulos
Cobrir o que um sensor não enxerga com outro sensor é sempre mais eficaz do que tentar deixar um único sensor perfeito. Mas o seu primeiro passo deve ser reduzir a velocidade: um robô a 0,3 m/s não bate tão forte quanto um a 0,7 m/s depois da mesma leitura ruim, e ainda sobra tempo para uma segunda leitura. Reunimos os pontos fortes e fracos de diferentes sensores de distância no artigo comparação de sensores de distância para Arduino.

Adicionando sensores IR

Os sensores ultrassônicos e os infravermelhos (IR) compensam as fraquezas uns dos outros. Sensores de obstáculo IR como o FC-51 têm um LED infravermelho e um receptor; quando a luz refletida é forte o bastante, o comparador LM393 coloca a saída em LOW e o LED da placa acende. O alcance é ajustável com um trimpot de cerca de 2 a 30 cm, mas ele não mede distância, só diz “obstáculo” ou “sem obstáculo”. Se você colocar dois sensores IR nos cantos dianteiros do robô, inclinados 30–45 graus para fora, eles detectam pés de cadeira e paredes inclinadas que ficam fora do cone estreito do sensor ultrassônico. No código avançado, o robô desvia rapidamente para a direita quando o IR esquerdo dispara e para a esquerda quando o IR direito dispara; se os dois dispararem ao mesmo tempo, ele para e faz a varredura como se houvesse um obstáculo à frente.

CaracterísticaHC-SR04 (ultrassônico)FC-51 (obstáculo IR)TCRT5000 (IR reflexivo)
SaídaDistância (cm)Sim / não (digital)Valor analógico e saída digital
Alcance2–400 cmCerca de 2–30 cm (ajustável)Cerca de 1–15 mm
Ângulo de detecçãoCerca de 15°Cerca de 35°Muito estreito
Ponto fracoSuperfícies macias, inclinadas e finasSuperfícies pretas e foscas, luz do solMudanças de altura, luz do sol
Enxerga bemVidro, objetos pretos, superfícies grandes e planasTecido, cantos, obstáculos próximosCor do chão e bordas de mesa
Posição no robôOlho principal, girado pelo servoCantos dianteirosSob o para-choque dianteiro, voltado para o chão
Sensor infravermelho reflexivo TCRT5000
TCRT5000: montado voltado para baixo, a 5–10 mm do chão, ele percebe bordas de mesa e vãos de escada; no instante em que o reflexo some, o robô pode parar e recuar.

Para robôs que andam sobre uma mesa, o obstáculo mais perigoso é, na verdade, uma queda. Se você instalar um módulo TCRT5000 voltado para o chão sob o para-choque dianteiro, vai saber no instante em que o chão some embaixo dele. Ligue a saída digital do módulo no D2, que ainda está livre no Uno; primeiro confira no Monitor Serial para qual nível a saída vai quando não há chão, depois leia esse pino logo no início de autoStep() e, se o chão sumir, pare os motores na hora e faça o robô recuar. Essa verificação precisa vir antes da medição de distância; cair sempre custa mais caro que bater.

Passando para o modo manual por Bluetooth

Às vezes você vai querer dirigir o robô autônomo com as próprias mãos: para levá-lo de um cômodo a outro, colocá-lo diante de um obstáculo e testar a reação dele ou só para brincar. A conexão com o HC-05 no código avançado torna isso possível. Quando o celular envia a letra A, o robô passa para o modo automático; quando envia M, passa para o modo manual. No modo manual são usadas as letras de controle do guia do carrinho robô (F, B, L, R, S), com uma diferença: se o robô vir um obstáculo a menos de 15 cm enquanto anda para a frente, ele para, apesar do seu comando. Essa proteção contra colisão permite aproveitar os sensores do robô autônomo também na direção manual. Os passos de pareamento do HC-05 e os comandos AT estão no artigo módulo Bluetooth HC-05.

LetraAçãoEm qual modo?
APassar para o modo automáticoNos dois modos
MPassar para o modo manual, parar o robôNos dois modos
F / BPara a frente / para trásManual
L / RGirar no lugar para a esquerda / direitaManual
SPararManual
0–9Nível de velocidade (PWM 100–240)Manual
  1. Ligue o pino TXD do HC-05 no D10, o pino RXD no D11 por um divisor de 1 kΩ / 2 kΩ, o VCC no 5V e o GND no GND.
  2. Pareie o HC-05 nas configurações de Bluetooth do seu celular Android com o PIN 1234 (ou 0000, se não funcionar).
  3. Instale pela Play Store um app de controle remoto de carrinho Bluetooth e conecte-o ao HC-05. Se você deixar o Arduino na USB e abrir o Monitor Serial em 9600 baud, o código mostra cada letra que chega do celular.
  4. A maioria dos apps de controle tem botões extras, como luzes ou buzina, e cada um envia sua própria letra. Descubra essas letras no Monitor Serial e coloque-as no lugar de 'A' e 'M' no código; se preferir definir os botões você mesmo, pode criar seu próprio controle com o guia App Inventor com Arduino e Bluetooth.
  5. Desconecte o cabo USB e ligue a chave; depois de 3 segundos, o robô parte no modo automático. Passe para o modo manual e dirija-o pelo celular.
No Uno, a SoftwareSerial segura as outras interrupções por cerca de 1 ms enquanto recebe um byte; nesse tempo, os pulsos da biblioteca Servo podem se alongar e a cabeça com o sensor pode dar uma leve tremida. Isso não afeta o comportamento do robô, mas, se incomodar, você pode passar para o Bluetooth embutido do ESP32. O HC-05 e o HC-06 não funcionam com iPhone; para iPhone, você precisa de um módulo BLE ou de um ESP32 programado para BLE.

Montando com um shield de motor L293D

Shield driver de motor L293D
Shield de motor L293D: ele encaixa direto em um Arduino Uno; quatro motores CC se ligam aos terminais M1–M4 e dois servos às barras de pinos no canto.

Se você não gosta de um emaranhado de fios, pode usar no lugar do L298N um shield de motor L293D (o Adafruit Motor Shield V1 e seus clones), que encaixa direto no Arduino Uno. O shield aciona quatro motores CC, tem duas barras de pinos para servos e alimenta os servos pelo barramento de 5 V do Arduino. Os motores não são ligados diretamente aos pinos do Arduino, mas pelo registrador de deslocamento 74HC595 da placa, então você os controla com a biblioteca Adafruit Motor Shield (a da V1, não a da V2), ou seja, a AFMotor, instalada pelo Gerenciador de Bibliotecas. O shield usa D3, D4, D5, D6, D7, D8, D11 e D12 para os motores e D9 e D10 para os servos; A0–A5, D2 e D13 ficam livres. Como o HC-SR04 e os sensores IR já estão em A0–A3, você só precisa mudar o código dos motores, o pino do servo e os pinos do HC-05.

ConfiguraçãoCom o L298NCom o shield L293D
Ligação dos motoresTerminais OUT1–OUT4Terminais M1 (esquerdo) e M2 (direito)
Código dos motoresdigitalWrite e analogWrite dentro de drive()AFMotor dentro de drive()
SERVO_PIN39 ou 10, conforme a barra de pinos em que você ligar o servo
HC-05D10, D11A4 (RX) e A5 (TX): SoftwareSerial bt(A4, A5)
Corrente por canal2 A contínuos0,6 A contínuos, 1,2 A de pico
Alimentação dos motoresTerminal 12VTerminal EXT_PWR; com o jumper PWR colocado, o Arduino funciona com a mesma fonte
C++ (Arduino IDE)
1// L293D shield: ENA–IN4 tanımlarını ve setup()'taki pinMode listesinden bu pinleri sil, drive() fonksiyonunu aşağıdakiyle değiştir
2// L293D shield: delete the ENA–IN4 definitions, remove them from the pinMode list in setup() and replace drive() with the one below
3// Ayrıca / Also: SERVO_PIN = 9 ya da / or 10, SoftwareSerial bt(A4, A5)
4#include <AFMotor.h>                     // Adafruit Motor Shield V1 kütüphanesi / library
5
6AF_DCMotor leftMotor(1);                 // M1 klemensi / M1 terminal
7AF_DCMotor rightMotor(2);                // M2 klemensi / M2 terminal
8
9void drive(int left, int right) {        // her biri -255 … 255 / each -255 … 255
10  leftMotor.setSpeed(min(abs(left), 255));
11  rightMotor.setSpeed(min(abs(right), 255));
12  leftMotor.run(left > 0 ? FORWARD : (left < 0 ? BACKWARD : RELEASE));
13  rightMotor.run(right > 0 ? FORWARD : (right < 0 ? BACKWARD : RELEASE));
14}

O limite de 0,6 A por canal do L293D basta para a direção normal de um robô 2WD com motores TT, mas quando uma roda empurra um obstáculo e trava, o motor pode puxar mais de 1 A e o chip esquenta rápido. Por isso, um algoritmo de varredura que para o robô antes de ele forçar contra um obstáculo também aumenta a vida útil do shield. Os ajustes de jumper do shield e um sketch de carrinho com quatro motores estão no artigo shield de motor L293D, e o funcionamento interno do L298N no guia controle de motor CC com L298N.

Robô que desvia de obstáculos com ESP32

Há duas diferenças importantes no ESP32. Primeiro, a biblioteca Servo do Arduino não funciona no ESP32, então geramos o sinal do servo diretamente com o LEDC, a unidade de PWM da placa: em um sinal de 50 Hz, um pulso de 0,544–2,4 ms corresponde a 0–180 graus. Como os motores funcionam a 1 kHz e o servo a 50 Hz, os canais dos motores são configurados primeiro e o canal do servo depois, para que o servo tenha seu próprio timer. Segundo, o ESP32 tem Bluetooth clássico embutido, então não é preciso um HC-05; o robô aparece no celular como “BlueGrays-Robot” e as letras de direção e de modo são as mesmas da versão do Uno. O código é para a versão 3.x do core Arduino do ESP32 (ledcAttach e ledcWrite trabalham com números de pino) e, como precisa de Bluetooth clássico, não compila para o ESP32-S3 nem para o ESP32-C3.

C++ (Arduino IDE)
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include "BluetoothSerial.h"
3
4const int ENA = 25, IN1 = 27, IN2 = 33;   // sol motor / left motor
5const int ENB = 26, IN3 = 32, IN4 = 4;    // sağ motor / right motor
6const int SERVO_PIN = 13;
7const int TRIG = 18, ECHO = 19;           // ECHO 1 kΩ / 2 kΩ bölücüyle / ECHO via a 1 kΩ / 2 kΩ divider
8const int IR_LEFT = 23, IR_RIGHT = 22;    // LOW = engel / LOW = obstacle
9
10const int BACK_CM = 12, STOP_CM = 25, SLOW_CM = 60, GUARD_CM = 15, MAX_CM = 400;
11const int CRUISE = 200, SLOW = 130, TURN = 170;
12const int SERVO_CENTER = 90, SERVO_DIR = 1;
13const float MS_PER_DEG = 4.0;             // kalibre et / calibrate
14
15BluetoothSerial bt;
16bool autoMode = true;
17int manualLeft = 0, manualRight = 0;
18unsigned long lastCmd = 0, lastPing = 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  ledcWrite(ENA, abs(left));
26  ledcWrite(ENB, abs(right));
27}
28
29void servoWrite(int angle) {              // 0–180° → 544–2400 µs
30  angle = constrain(angle, 0, 180);
31  uint32_t us = map(angle, 0, 180, 544, 2400);
32  ledcWrite(SERVO_PIN, us * 16384 / 20000);   // 14 bit, 20 ms periyot / 14-bit, 20 ms period
33}
34
35int pingCm() {
36  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
37  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
38  digitalWrite(TRIG, LOW);
39  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
40  return t == 0 ? MAX_CM : (int)(t / 58);
41}
42
43int distanceCm() {                        // 3 ölçümün ortancası / median of 3 readings
44  int a = pingCm(); delay(60);
45  int b = pingCm(); delay(60);
46  int c = pingCm();
47  return max(min(a, b), min(max(a, b), c));
48}
49
50bool irBlocked(int pin) { return digitalRead(pin) == LOW; }
51
52void turnBy(int deg) {                    // + sola, - sağa / + left, - right
53  if (deg > 0) drive(-TURN, TURN);
54  else drive(TURN, -TURN);
55  delay((unsigned long)(abs(deg) * MS_PER_DEG));
56  drive(0, 0);
57  delay(100);
58}
59
60void avoid() {
61  drive(0, 0);
62  delay(120);
63  int front = distanceCm();
64  bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
65  if (front >= STOP_CM && !sides) return;            // yanlış alarm / false alarm
66  if (front < BACK_CM || sides) {                    // geri çekil / back off
67    drive(-TURN, -TURN);
68    delay(350);
69    drive(0, 0);
70  }
71  const int offsets[] = {70, 35, -35, -70};
72  int bestOffset = 0, bestCm = 0;
73  for (int off : offsets) {
74    servoWrite(SERVO_CENTER + SERVO_DIR * off);
75    delay(300);
76    int cm = distanceCm();
77    if (cm > bestCm) { bestCm = cm; bestOffset = off; }
78  }
79  servoWrite(SERVO_CENTER);
80  delay(200);
81  if (bestCm < STOP_CM + 10) {                       // çıkmaz / dead end
82    drive(-TURN, -TURN);
83    delay(400);
84    turnBy(180);
85  } else {
86    turnBy(bestOffset);
87  }
88}
89
90void setup() {
91  int outs[] = {IN1, IN2, IN3, IN4, TRIG};
92  for (int p : outs) pinMode(p, OUTPUT);
93  pinMode(ECHO, INPUT);
94  pinMode(IR_LEFT, INPUT_PULLUP);
95  pinMode(IR_RIGHT, INPUT_PULLUP);
96  ledcAttach(ENA, 1000, 8);               // motor PWM: 1 kHz, 8 bit
97  ledcAttach(ENB, 1000, 8);
98  ledcAttach(SERVO_PIN, 50, 14);          // servo: 50 Hz, 14 bit
99  drive(0, 0);
100  servoWrite(SERVO_CENTER);
101  bt.begin("BlueGrays-Robot");            // telefonda görünecek ad / name shown on the phone
102  delay(3000);
103}
104
105void loop() {
106  while (bt.available()) {
107    char c = bt.read();
108    lastCmd = millis();
109    if (c == 'A') autoMode = true;                                  // otomatik / auto
110    else if (c == 'M') {                                            // manuel / manual
111      autoMode = false;
112      manualLeft = manualRight = 0;
113      servoWrite(SERVO_CENTER);
114    }
115    else if (!autoMode) {
116      if (c == 'F')      { manualLeft = 200;  manualRight = 200; }  // ileri / forward
117      else if (c == 'B') { manualLeft = -200; manualRight = -200; } // geri / backward
118      else if (c == 'L') { manualLeft = -200; manualRight = 200; }  // sola / left
119      else if (c == 'R') { manualLeft = 200;  manualRight = -200; } // sağa / right
120      else if (c == 'S') { manualLeft = 0;    manualRight = 0; }    // dur / stop
121    }
122  }
123
124  if (millis() - lastPing < 60) return;               // 60 ms'de bir / every 60 ms
125  lastPing = millis();
126  int d = pingCm();
127
128  if (!autoMode) {
129    if (millis() - lastCmd > 600) manualLeft = manualRight = 0;     // komut yok: dur / no command: stop
130    bool forward = manualLeft > 0 && manualRight > 0;
131    if (forward && d < GUARD_CM) drive(0, 0);                       // çarpışma koruması / collision guard
132    else drive(manualLeft, manualRight);
133    return;
134  }
135
136  bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
137  if (d < STOP_CM || (l && r)) avoid();
138  else if (l) { drive(TURN, -TURN); delay(150); }
139  else if (r) { drive(-TURN, TURN); delay(150); }
140  else if (d < SLOW_CM) { int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); drive(s, s); }
141  else drive(CRUISE, CRUISE);
142}

Robô que desvia de obstáculos com Raspberry Pi Pico (MicroPython)

No Raspberry Pi Pico, a função time_pulse_us() do MicroPython funciona como o pulseIn() do Arduino; ela retorna um valor negativo quando nenhum eco chega, então tratamos isso como 400 cm, ou seja, espaço livre. Para o servo usamos PWM de 50 Hz e duty_ns(), que recebe a largura do pulso em nanossegundos. O servo no GP16 e os pinos de velocidade dos motores no GP15 e no GP10 ficam em fatias de PWM diferentes, então um não atrapalha a frequência do outro. Desta vez, as velocidades são dadas em porcentagem. O Pico não tem Bluetooth, então, se você quiser o modo manual, pode ligar um HC-05 na UART0 (GP0, GP1), como no guia do carrinho robô.

MicroPython
1from machine import Pin, PWM, time_pulse_us
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000)          # sol motor hızı / left motor speed
5enb = PWM(Pin(10)); enb.freq(1000)          # sağ motor hızı / right motor speed
6in1, in2 = Pin(14, Pin.OUT), Pin(13, Pin.OUT)
7in3, in4 = Pin(12, Pin.OUT), Pin(11, Pin.OUT)
8servo = PWM(Pin(16)); servo.freq(50)        # servo sinyali, 50 Hz / servo signal, 50 Hz
9trig = Pin(17, Pin.OUT, value=0)
10echo = Pin(18, Pin.IN)                      # 1 kΩ / 2 kΩ bölücü üzerinden / via a 1 kΩ / 2 kΩ divider
11ir_left = Pin(19, Pin.IN, Pin.PULL_UP)      # 0 = engel / 0 = obstacle
12ir_right = Pin(20, Pin.IN, Pin.PULL_UP)
13
14STOP_CM, BACK_CM = 25, 12
15CRUISE, TURN = 75, 65                       # hız yüzdesi / speed in percent
16MS_PER_DEG = 4                              # kalibre et / calibrate
17SERVO_CENTER = 90                           # öne bakmıyorsa düzelt / adjust if not facing ahead
18
19def drive(left, right):                     # -100 … 100 (%)
20    in1.value(left > 0); in2.value(left < 0)
21    in3.value(right > 0); in4.value(right < 0)
22    ena.duty_u16(int(min(abs(left), 100) * 655))
23    enb.duty_u16(int(min(abs(right), 100) * 655))
24
25def head(offset):                           # + sol, - sağ / + left, - right
26    angle = max(0, min(180, SERVO_CENTER + offset))
27    servo.duty_ns((544 + angle * 1856 // 180) * 1000)   # 0,544–2,4 ms darbe / 0.544–2.4 ms pulse
28
29def ping_cm():
30    trig.value(1); time.sleep_us(10); trig.value(0)     # 10 µs tetik / 10 µs trigger
31    t = time_pulse_us(echo, 1, 25000)
32    return 400 if t < 0 else t // 58                    # negatif = yankı yok / negative = no echo
33
34def distance_cm():                          # 3 ölçümün ortancası / median of 3 readings
35    r = []
36    for _ in range(3):
37        r.append(ping_cm())
38        time.sleep_ms(60)
39    return sorted(r)[1]
40
41def turn_by(deg):                           # + sola, - sağa / + left, - right
42    if deg > 0:
43        drive(-TURN, TURN)
44    else:
45        drive(TURN, -TURN)
46    time.sleep_ms(int(abs(deg) * MS_PER_DEG))
47    drive(0, 0)
48    time.sleep_ms(100)
49
50def avoid():
51    drive(0, 0)
52    time.sleep_ms(120)
53    front = distance_cm()
54    sides = not ir_left.value() or not ir_right.value()
55    if front >= STOP_CM and not sides:
56        return                              # yanlış alarm / false alarm
57    if front < BACK_CM or sides:            # geri çekil / back off
58        drive(-TURN, -TURN); time.sleep_ms(350); drive(0, 0)
59    best_off, best_cm = 0, 0
60    for off in (70, 35, -35, -70):          # tara / scan
61        head(off)
62        time.sleep_ms(300)
63        cm = distance_cm()
64        if cm > best_cm:
65            best_off, best_cm = off, cm
66    head(0)
67    time.sleep_ms(200)
68    if best_cm < STOP_CM + 10:              # çıkmaz / dead end
69        drive(-TURN, -TURN); time.sleep_ms(400)
70        turn_by(180)
71    else:
72        turn_by(best_off)
73
74drive(0, 0)
75head(0)
76time.sleep(3)                               # yere koyman için süre / time to put it down
77while True:
78    d = ping_cm()
79    left_hit, right_hit = not ir_left.value(), not ir_right.value()
80    if d < STOP_CM or (left_hit and right_hit):
81        avoid()
82    elif left_hit:
83        drive(TURN, -TURN); time.sleep_ms(150)   # sağa kaç / dodge right
84    elif right_hit:
85        drive(-TURN, TURN); time.sleep_ms(150)   # sola kaç / dodge left
86    else:
87        drive(CRUISE, CRUISE)
88    time.sleep_ms(60)                        # ölçümler arası / between readings

Ideias de projetos com um robô que desvia de obstáculos

  • Tela de radar: envie os ângulos e as distâncias da varredura pela porta serial e desenhe uma tela de radar verde no computador; detalhes no artigo projeto de radar com Arduino.
  • Linha mais obstáculo: faça o robô contornar uma caixa no caminho enquanto segue uma pista, como um robô seguidor de linha, e depois voltar à linha; é uma tarefa comum em competições.
  • Robô que segue a parede: mantenha o sensor virado 60 graus para a direita e ande a uma distância constante de 20 cm da parede; é o primeiro passo para resolver labirintos.
  • Robô com display: mostre a distância e o modo em um pequeno display OLED de 0,96 polegada; a depuração fica muito mais fácil.
  • Protótipo de robô aspirador: esboce um robô de limpeza que anda pela casa com escolhas aleatórias de direção, chaves de impacto e um sensor de queda.
  • Bengala inteligente: transforme a mesma lógica de medição de distância em um aviso por vibração para pessoas com deficiência visual; veja bengala inteligente com Arduino.
  • Projeto escolar ou de competição: apresente-o com uma pista de obstáculos, cronometragem e uma demonstração dos modos automático e manual; para ideias, dê uma olhada no artigo ideias de projetos Arduino para feira de ciências.

Erros comuns e como resolvê-los

ProblemaCausa provávelSolução
O robô bate na parede sem pararO sensor lê 0 (fio rompido, TRIG e ECHO trocados) e o código trata isso como espaço livreRode o sketch de teste, teste 10–60 cm com a mão, confira os pinos TRIG e ECHO
Ele raspa na parede ao se aproximar em ânguloO som é refletido pela superfície inclinada em outra direçãoAdicione sensores IR nos cantos dianteiros e reduza a velocidade
O Arduino reinicia quando para para fazer a varreduraA corrente do servo e dos motores derruba o barramento de 5 VCapacitor de 470 µF no servo, células 18650 totalmente carregadas; se precisar, alimente o servo por uma fonte de 5 V separada
O robô para e faz a varredura sem motivoUma única leitura ruim ou um eco do chãoSuba o sensor para 8–12 cm e incline-o levemente para cima; use a confirmação por mediana
O robô vira para o lado bloqueadoO servo está montado ao contrário, esquerda e direita estão trocadasDefina SERVO_DIR como -1
As curvas ficam curtas ou longas demaisMS_PER_DEG não está calibrado ou a bateria está fracaMeça com turnBy(360) e reajuste com a bateria cheia
Ele fica virando para a esquerda e para a direita em um cantoOs dois lados estão perto do limite e a decisão fica alternandoAumente o limite de beco sem saída (STOP_CM + 10) ou faça-o preferir a última direção de curva
A cabeça com o sensor tremeAlimentação fraca do servo ou interrupções da SoftwareSerialEncurte o cabo do servo, adicione um capacitor, evite enviar comandos Bluetooth com mais frequência que o necessário
O jeito mais rápido de achar a origem de um problema é deixar o robô na USB, rodá-lo com as rodas no ar e mostrar os valores de pingCm() no Monitor Serial. Se os valores fizerem sentido, o problema é mecânico ou está na alimentação. Para outros motivos pelos quais um Arduino fica reiniciando, veja o artigo Arduino reiniciando sozinho.

Por reunir leitura de sensores, filtragem, tomada de decisão e controle de motores em um único projeto, o robô que desvia de obstáculos é a melhor porta de entrada para a robótica. Primeiro observe o comportamento básico com o primeiro sketch, depois ajuste os limites ao seu cômodo com a versão avançada e, por fim, reforce seu robô com sensores IR e Bluetooth. No app BlueGrays você encontra, passo a passo e com esquemas do circuito e código, os projetos de robô que desvia de obstáculos, carrinho controlado por Bluetooth com L298N, medição de distância com HC-SR04 e sensor de estacionamento.

Perguntas frequentes

Do que você precisa para montar um robô que desvia de obstáculos?

Um robô básico que desvia de obstáculos precisa de um Arduino Uno, um sensor ultrassônico HC-SR04, um servo SG90, um driver de motor L298N ou L293D, um kit de chassi 2WD com dois motores CC com redução e duas células 18650. Você pode adicionar dois sensores de obstáculo IR para enxergar os cantos e um módulo Bluetooth HC-05 para dirigi-lo pelo celular.

Dá para montar um robô que desvia de obstáculos sem servo?

Sim. O sensor aponta para a frente; quando o robô vê um obstáculo, primeiro vira para um lado e mede de novo e, se o caminho continuar bloqueado, vira para o outro lado. Isso sai mais barato, mas o robô decide mais devagar, porque precisa girar o corpo inteiro a cada tentativa; com um servo, ele encontra a direção mais livre antes de virar.

Por que o HC-SR04 não vê alguns obstáculos?

Um sensor ultrassônico mede pelo eco. Superfícies macias como cortinas e sofás absorvem o som, paredes inclinadas o refletem em outra direção e objetos finos como pés de cadeira dão um eco muito fraco. O sensor também não enxerga obstáculos fora ou abaixo do seu cone de cerca de 15 graus; você pode cobrir essas falhas com sensores IR.

A que distância um robô que desvia de obstáculos deve parar?

25 cm é um bom valor inicial: a distância percorrida entre duas leituras, a distância de frenagem e a margem necessária para girar no lugar somam cerca de 20 cm, mais uma pequena folga de segurança. Para robôs mais rápidos e pisos escorregadios, aumente para 30–35 cm; com um obstáculo a menos de 12 cm, o robô deve recuar primeiro.

Qual é melhor para um robô, o L298N ou um shield L293D?

O L298N fornece 2 A por canal, então é mais robusto e permite escolher os pinos livremente. O shield L293D encaixa no Uno, por isso reduz a fiação e aciona quatro motores, mas sua corrente contínua é de 0,6 A por canal e ele ocupa muitos pinos. Para um robô 2WD pequeno com motores TT, qualquer um dos dois serve.

Dá para controlar um robô que desvia de obstáculos pelo celular?

Sim. Adicione um módulo Bluetooth HC-05 e envie letras de um app de controle remoto em um celular Android para alternar entre os modos automático e manual. Usar o sensor ultrassônico como proteção contra colisão no modo manual impede que você jogue o robô contra uma parede. Com um ESP32, você não precisa de um módulo separado, graças ao Bluetooth embutido.

Ver todos os projetos
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
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
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
Uso de servo motor
#35Iniciante
Premium

Uso de servo motor

O braço de um pequeno servo motor gira para três posições em sequência: o início, o meio e o fim do percurso, esperando dois segundos em cada parada. O Arduino informa ao motor em qual ângulo ficar, e

4.7(48)
67
Sensor de estacionamento com HC-SR04
#28Intermediário
Premium

Sensor de estacionamento com HC-SR04

Uma versão em miniatura do sensor de estacionamento de um carro: quanto mais perto um objeto chega, mais rápidos ficam os bipes. O sensor de distância mede a distância até o objeto à sua frente; se el

4.8(64)
85
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
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
Medidor ultrassônico de distância HC-SR04 com ESP32
#116Intermediário

Medidor ultrassônico de distância HC-SR04 com ESP32

O sensor ultrassônico HC-SR04 calcula a distância medindo o tempo que a onda sonora emitida leva para atingir um objeto e retornar (tempo de voo). Neste projeto, enviaremos um pulso de 10 microssegundos do ESP32 ao pino TRIG do sensor e leremos a duração do eco no pino ECHO com pulseIn(). Exibiremos a distância calculada em centímetros em um LCD I2C 16x2. Como o HC-SR04 opera a 5V, é preciso usar um divisor de tensão no pino ECHO; caso contrário, o GPIO de 3,3V do ESP32 será danificado.

4.9(28)
45