Guías · 20 min de lectura

Robot evasor de obstáculos con Arduino: HC-SR04 y servo

Robot evasor de obstáculos con Arduino: HC-SR04 sobre un servo, conexión del L298N o L293D, lógica parar-retroceder-girar, sensores IR y modo manual Bluetooth.

Un robot evasor de obstáculos es un coche robot autónomo que detecta una pared, una silla o una caja delante de él sin chocar y busca un camino nuevo. El montaje más habitual usa un Arduino Uno, un sensor ultrasónico HC-SR04, un servomotor SG90 que gira el sensor a izquierda y derecha, dos motorreductores de corriente continua y un driver de motores L298N (o L293D). El robot avanza en línea recta, se detiene al acercarse a un obstáculo, retrocede un poco, gira la cabeza hacia ambos lados para medir y sigue su camino hacia la dirección más despejada.

En esta guía convertimos el coche 2WD de nuestro artículo coche robot con Arduino en un robot autónomo manteniendo el mismo patillaje. Paso a paso veremos las tablas de conexión, cómo se calculan los umbrales de distancia, un código que crece en tres etapas, las superficies que el sensor no ve y cómo cubrir esos huecos con sensores infrarrojos y, por último, cómo pasar a modo manual por Bluetooth desde el móvil. Al final del artículo tienes también el código para manejar el mismo robot con un ESP32 o una Raspberry Pi Pico.

Sensor ultrasónico de distancia HC-SR04
HC-SR04: el transductor marcado con T emite un sonido de 40 kHz y el marcado con R escucha el eco. Es el ojo del robot; mide de 2 a 400 cm con un ángulo estrecho de unos 15 grados.

¿Cómo funciona un robot evasor de obstáculos?

Todo robot autónomo repite el mismo ciclo de tres pasos decenas de veces por segundo: percibir, decidir, actuar. La percepción la hace el HC-SR04: emite un sonido corto de 40 kHz que tu oído no puede oír y mide cuánto tarda en volver el eco; si divides ese tiempo en microsegundos entre 58, obtienes la distancia en centímetros. La decisión la toma el Arduino comparando la distancia medida con los umbrales que tú definas. De la acción se encargan el L298N y los motores. Como el sensor va montado sobre un servo, el robot puede "mirar" a izquierda y derecha sin girar el cuerpo, y eso le permite decidir los giros mucho mejor que un robot con el sensor fijo. Los detalles de cómo funciona el sensor están en el artículo Sensor de distancia HC-SR04.

PiezaFunciónNota
Arduino Uno (o Nano)Cerebro: mide, decide y da órdenes a los motoresTambién sirven un ESP32 o una Pico
HC-SR04Ojo: mide la distancia al obstáculo de delanteFunciona a 5 V, 2–400 cm
Servo SG90Cuello: gira el sensor a izquierda y derechaEl sensor se fija al brazo del servo con un soporte de plástico
L298N o shield L293DMúsculo: da dirección y velocidad a los motoresUn pin de Arduino no puede dar la corriente del motor
2 motorreductores TT y rueda locaTracción diferencialGira haciendo que las dos ruedas giren en sentidos opuestos
2 baterías 18650 (7,4 V)EnergíaUna pila de 9 V no basta
2 sensores IR de obstáculos (opcional)Detectan obstáculos en las esquinas y los ladosMódulos con LM393 como el FC-51
HC-05 (opcional)Control manual desde el móvilSolo funciona con Android
Arduino Uno
Arduino Uno
L298N
L298N
TT motor
TT motor
18650 Li-ion
18650 Li-ion

Alimentación: que el servo no reinicie el robot

Como el robot evasor de obstáculos suma un servo a los motores, el reparto de energía importa más que en un coche robot normal. Un SG90 consume unos 10 mA en reposo; en cuanto empieza a moverse sube a 100–250 mA y, si se fuerza, se acerca a medio amperio. Si ese pico coincide con la corriente de arranque de los motores, la línea de 5 V del Arduino cae por un instante y la placa se reinicia. La solución es sencilla: los motores se alimentan de la batería a través del L298N, el Arduino de la batería a través de VIN, y el servo, el HC-SR04, los sensores IR y el HC-05 comparten el pin 5V del Arduino, con un condensador de 470 µF junto a los cables de alimentación del servo. Incluso con el servo en movimiento, la carga total de la línea de 5 V se queda por debajo de medio amperio y, como dura poco, el regulador del Uno la aguanta. Para calcular la autonomía con tus propios valores, usa la herramienta Duración de la batería.

ParteSe alimenta deCorriente típica
2 motores TTBorne 12V del L298N ← 2 × 18650 (7,4 V)0,3–0,6 A en marcha, hasta 2 A al arrancar
Arduino UnoVIN ← positivo de la batería (después del interruptor)Unos 50 mA
Servo SG905V del Arduino + condensador de 470 µF~10 mA en reposo, 100–250 mA en movimiento
HC-SR045V del ArduinoUnos 15 mA
2 sensores IR5V del Arduino20–40 mA cada uno
HC-055V del Arduino30–40 mA buscando emparejamiento, unos 10 mA conectado
Une todos los GND entre sí (negativo de la batería, L298N, Arduino, servo, sensores); sin una masa común el sensor lee distancias absurdas y el servo tiembla. No alimentes el servo por el mismo cable dupont fino que los motores. Si usas un servo más potente como el MG90S o el MG996R, no lo alimentes desde el Arduino, sino desde un módulo reductor aparte ajustado a 5 V (por ejemplo, un LM2596).

Esquema de conexión: Arduino Uno, ESP32 y Pico

Los pines de los motores son los mismos que en la guía del coche robot, así que puedes pasar a este proyecto añadiendo solo la cabeza del sensor, sin desmontar el coche. Poner los pines de velocidad en el 5 y el 6 es intencionado: en el Uno la librería Servo usa el Timer1 y por eso desactiva el PWM de los pines 9 y 10; si las velocidades de los motores estuvieran ahí, el control de velocidad dejaría de funcionar en cuanto conectaras el servo. El HC-SR04 y los sensores IR van en A0–A3. Los pines analógicos del Uno también funcionan como pines digitales 14–19 cuando hace falta, y esta elección facilita usar el mismo código con un shield L293D, porque el shield no toca esos pines. Puedes ver la distribución de pines de tu placa en la herramienta Pinout.

ConexiónArduino UnoESP32Raspberry Pi Pico
L298N ENA (velocidad motor izquierdo)D5GPIO 25GP15
L298N IN1, IN2D7, D8GPIO 27, 33GP14, GP13
L298N ENB (velocidad motor derecho)D6GPIO 26GP10
L298N IN3, IN4D9, D4GPIO 32, 4GP12, GP11
Señal del servo (naranja)D3GPIO 13GP16
Servo + (rojo), − (marrón)5V, GND5V del L298N, GND5V del L298N, GND
HC-SR04 TRIGA0GPIO 18GP17
HC-SR04 ECHOA1 (directo)GPIO 19, con divisor de 1 kΩ / 2 kΩGP18, con divisor de 1 kΩ / 2 kΩ
HC-SR04 VCC5V5V del L298N5V del L298N
OUT del IR izquierdo, OUT del IR derechoA2, A3GPIO 23, 22GP19, GP20
VCC de los sensores IR5V3V33V3
HC-05 TXD, RXDD10, D11 (RXD con divisor)No hace falta (Bluetooth integrado)GP1, GP0 (UART0)
Alimentación de la placaVIN ← batería (después del interruptor)5V (VIN) ← 5V del L298NVSYS ← 5V del L298N
El pin ECHO del HC-SR04 da una señal de 5 V, mientras que los pines del ESP32 y la Pico trabajan a 3,3 V y no toleran 5 V. Monta un divisor de tensión así: ECHO → 1 kΩ → punto que va a la placa → 2 kΩ → GND, con lo que 5 V × 2 / 3 ≈ 3,3 V. TRIG no necesita divisor; una señal de 3,3 V basta para disparar el sensor. En el ESP32 y la Pico, la placa, el servo y el HC-SR04 comparten la misma salida de 5V del L298N, así que puedes acercarte al límite de unos 0,5 A de ese pequeño regulador; si ves reinicios, alimenta el servo desde una fuente de 5 V aparte.

Monta y prueba la cabeza del sensor

Micro servomotor SG90
SG90: un micro servo de 9 gramos que gira 60 grados en unos 0,1 segundos a 4,8 V. El soporte de plástico con el HC-SR04 se atornilla al brazo del servo.
  1. Conecta el servo sin el brazo y sube el sketch de prueba de abajo. Al final de cada barrido el código lleva el servo a 90 grados y espera 2 segundos; desconecta el cable USB durante esa pausa y el servo se queda donde está. Coloca el brazo en el eje de modo que el sensor mire justo al frente; así 90 grados significa "de frente".
  2. Fija el HC-SR04 al brazo con un soporte de plástico o con silicona caliente. Los cables del sensor deben ser lo bastante largos para no tensarse cuando gira el servo; que el cable se enganche en el chasis durante el barrido de 140 grados es una avería frecuente.
  3. Coloca el sensor a 8–12 cm del suelo y cerca del punto más adelantado del chasis. Si va muy bajo recibe ecos del suelo y de la textura de la alfombra; si va muy alto no ve los obstáculos bajos.
  4. Inclina el sensor 3–5 grados hacia arriba. Un sensor totalmente horizontal y bajo puede confundir un eco del suelo con un obstáculo, sobre todo en suelos rugosos.
  5. Abre el Monitor Serie a 9600 baudios. Si al poner la mano a la izquierda del sensor ves valores pequeños en las líneas de 125 y 160 grados, la izquierda corresponde a los ángulos grandes; si ocurre al revés, pon SERVO_DIR a -1 en el sketch avanzado e intercambia los valores de LEFT_ANGLE y RIGHT_ANGLE en el primer sketch.
Arduino (C++)
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 línea muestra un ángulo y la distancia en esa dirección. Si no hay nada a unos 4 metros delante del sensor, leerás 0 cm: pulseIn() devuelve 0 si no llega ningún eco en 25 ms. En el código principal trataremos el 0 como "camino libre", pero un sensor con un cable suelto o con TRIG y ECHO invertidos también suele leer 0. Por eso, antes de poner el robot en el suelo, coloca la mano a 10, 30 y 60 cm y comprueba que los valores cambian de verdad; un robot que se pone en marcha sin esta prueba se estrella contra la pared sin frenar.

El algoritmo de decisión: parar, retroceder, girar hacia la dirección más despejada

Dividir el comportamiento del robot en zonas según la distancia hace que el código sea sencillo y fiable. Sin obstáculos delante avanza a toda velocidad, frena a medida que se acerca un obstáculo y, al llegar al umbral de parada, se detiene y hace un barrido. Durante el barrido el sensor mira 70 y 35 grados a la izquierda y 35 y 70 grados a la derecha; en cada dirección se toman tres medidas y se usa la mediana. El robot gira hacia la dirección con la mayor distancia, exactamente ese ángulo. Si ninguna dirección está libre, por ejemplo porque se ha metido en una esquina, retrocede un poco y gira 180 grados.

Distancia medidaZonaQué hace el robot
Más de 60 cm (o sin eco)LibreAvanza a toda velocidad (PWM 200)
25–60 cmAproximaciónFrena en proporción a la distancia (de PWM 200 a 130)
12–25 cmObstáculoSe detiene, confirma la medida, barre a ambos lados y gira hacia la dirección más despejada
Menos de 12 cmDemasiado cercaPrimero retrocede 350 ms y después barre
Todas las direcciones a menos de 35 cmCallejón sin salidaRetrocede y gira 180 grados

Estos umbrales no son aleatorios. La distancia de parada es la suma de tres márgenes: el margen de reacción (lo que avanza entre dos medidas), el margen de frenado (lo que se desliza el robot después de cortar los motores) y el margen de giro (para que el morro no toque el obstáculo al girar sobre sí mismo). Un coche con motores TT a 6 V va a unos 0,5–0,7 m/s y recorre 3–4 cm en un intervalo de medida de 60 ms. El margen de frenado es de unos 5 cm y, para un chasis de 15 cm de ancho que gira sobre sí mismo, el margen de giro ronda los 10 cm. En total salen unos 18–20 cm; con unos centímetros más de margen de seguridad, STOP_CM = 25 es un buen punto de partida. Si tu robot es más rápido o el suelo resbala, sube el umbral a 30–35 cm. En pasillos estrechos, en cambio, un umbral demasiado grande hace que el robot se pare y barra todo el rato; haz el ajuste final en tu propio recorrido.

Código paso a paso 1: tu primer robot evasor de obstáculos

La primera versión es el esqueleto del algoritmo: avanzar, parar a 25 cm, retroceder, mirar a izquierda y derecha y girar hacia el lado más despejado. La función de los motores drive(izquierdo, derecho) es la misma que en la guía del coche robot y admite valores de -255 a 255; positivo es adelante y negativo, atrás. Después de subir el código, pon primero el coche sobre una caja con las ruedas en el aire y observa cómo se para y gira al acercar la mano al sensor; si todo va bien, deja el robot en el suelo.

Arduino (C++)
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 cumple, pero tiene algunos puntos débiles: una sola medida errónea detiene el robot sin motivo, siempre se acerca a los obstáculos a la misma velocidad, solo mira en dos direcciones y sus tiempos de giro no dependen del ángulo. Además, como espera con delay(), no puede reaccionar a nada durante el barrido. Los detalles de la lógica de espera están en el artículo millis() y delay(); la siguiente versión resuelve la mayoría de estos problemas.

Código paso a paso 2: filtro, frenado progresivo, IR y Bluetooth

La versión avanzada funciona con las mismas conexiones y no pasa nada si todavía no has puesto los sensores IR ni el HC-05. Como las entradas IR se configuran con INPUT_PULLUP, un sensor que no está conectado lee "sin obstáculo" (HIGH); sin HC-05 el robot simplemente arranca en modo automático. En la tabla de abajo tienes qué hace cada parte del código.

Arduino (C++)
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}
Función o ajusteQué hace
pingCm()Hace una medida; devuelve 400 cm (espacio libre) si no llega eco en 25 ms
distanceCm()Toma 3 medidas separadas 60 ms y devuelve la mediana; descarta un valor aislado que se dispara
autoStep()Mide cada 60 ms; según la distancia y los IR avanza, frena, esquiva o llama a avoid()
avoid()Se detiene, confirma la medida, retrocede si hace falta, mira en 4 direcciones y gira hacia la más despejada
turnBy(grados)Gira el robot sobre sí mismo el ángulo indicado; el tiempo se calcula con MS_PER_DEG
manualStep()Aplica la dirección enviada desde el móvil; al avanzar se para a 15 cm y se detiene si no llega ninguna orden en 600 ms
SERVO_CENTER, SERVO_DIREl ángulo en el que el sensor mira al frente y el sentido de la izquierda
STOP_CM, SLOW_CM, BACK_CMUmbrales de parada, frenado y retroceso

Para un robot evasor de obstáculos el filtro de mediana es mejor que la media: con tres medidas como 24, 26 y 400 cm la media sale 150 cm y engaña al robot, mientras que la mediana elige 26 cm. Si quieres profundizar, consulta el artículo filtrar datos de sensores. La separación entre modo automático y manual también es, en realidad, una pequeña máquina de estados; explicamos este enfoque en detalle en la guía máquina de estados en Arduino. Después de subir el código, ajusta el robot a tu habitación en este orden:

  1. Centro del servo: con el robot perpendicular a una pared lisa, si el sensor no apunta justo hacia ella, cambia SERVO_CENTER en pasos pequeños, por ejemplo a 85 o 95.
  2. Tiempo de giro: añade temporalmente la línea turnBy(360); al final de setup() y observa si el robot da una vuelta completa. Si gira 300 grados en lugar de 360, pon MS_PER_DEG en 4,0 × 360 / 300 = 4,8. Como este valor cambia con la batería y el suelo, ajústalo con la batería llena y sobre el suelo por el que va a circular.
  3. Umbral de parada: envía el robot hacia una pared y mide a qué distancia de ella queda el morro cuando se para. Si es menos de 10 cm, sube STOP_CM 5 cm.
  4. Velocidad lenta: si SLOW queda por debajo del umbral de arranque de los motores, el robot se queda parado en la zona de aproximación; en ese caso sube SLOW a 150.

Puntos ciegos del HC-SR04: superficies blandas, paredes inclinadas y obstáculos finos

Un sensor ultrasónico ve bien las superficies que devuelven el sonido, pero no todos los obstáculos lo devuelven hacia el sensor. El fabricante recomienda un objetivo de al menos 0,5 m² y lo más liso posible para obtener buenos resultados. En una habitación real, sin embargo, el robot se encuentra con cortinas, sofás, patas de sillas y esquinas. El sonido rebota con el mismo ángulo con el que llega, como una bola de billar: un sensor que mira de frente a una pared recibe un eco fuerte, pero si su dirección se desvía más de unos 30–40 grados de la perpendicular, el sonido sale rebotado hacia otro lado y nunca vuelve. Entonces pulseIn() devuelve 0 y el código cree que el camino está libre. Es la causa más común de que los robots se rocen contra las paredes.

Obstáculo o superficiePor qué no se veSolución
Cortinas, sofás, cojines, ropa, pelo de mascotaLas superficies blandas y porosas absorben el sonido y el eco vuelve muy débilAñade un sensor IR para corta distancia y conduce el robot despacio
Acercarse a una pared en diagonalEl sonido se refleja hacia otro lado y el eco no llega al sensorPon sensores IR inclinados hacia fuera en las esquinas delanteras y barre con ángulos más juntos
Patas de sillas y mesas, postes finosLa superficie que refleja es muy pequeña y el eco es débilBaja la velocidad, usa el filtro de mediana y añade IR en los lados
Obstáculos bajos bajo el sensor (cables, umbrales de puertas, zapatillas)Quedan por debajo del cono de sonidoAñade al parachoques delantero un sensor IR mirando hacia abajo o un microinterruptor
Bordes de mesa, huecos de escaleraEl sensor ultrasónico mira hacia delante y no ve un hueco en el sueloDetección de vacío con un TCRT5000 mirando al suelo
Objetos a menos de 2 cmZona muerta del sensor: el eco vuelve antes de que termine la emisión del sonidoAjusta el robot para que retroceda a 12 cm
Esquinas y pasillos estrechosEl sonido rebota entre dos paredes y vuelve tarde, así que la distancia parece mayorFiltro de mediana y medidas desde varios ángulos
Cubrir lo que un sensor no ve con otro sensor siempre es más eficaz que intentar que un único sensor sea perfecto. Pero lo primero es reducir la velocidad: un robot a 0,3 m/s no choca tan fuerte como uno a 0,7 m/s tras la misma medida errónea, y le queda tiempo para una segunda medida. Hemos reunido los puntos fuertes y débiles de distintos sensores de distancia en el artículo comparativa de sensores de distancia para Arduino.

Refuerzo con sensores IR

Los sensores ultrasónicos e infrarrojos (IR) compensan los puntos débiles del otro. Los sensores IR de obstáculos como el FC-51 constan de un LED infrarrojo y un receptor; cuando la luz reflejada es lo bastante fuerte, el comparador LM393 pone la salida en LOW y se enciende el LED de la placa. Su alcance se ajusta con un trimmer entre unos 2 y 30 cm, pero no mide distancias, solo dice "hay obstáculo" o "no hay". Si colocas dos sensores IR en las esquinas delanteras del robot, inclinados 30–45 grados hacia fuera, detectarás las patas de sillas y las paredes inclinadas que quedan fuera del cono estrecho del ultrasónico. En el código avanzado, el robot esquiva brevemente a la derecha cuando salta el IR izquierdo y a la izquierda cuando salta el derecho; si saltan los dos a la vez, se para y barre como si tuviera un obstáculo delante.

CaracterísticaHC-SR04 (ultrasónico)FC-51 (IR de obstáculos)TCRT5000 (IR reflectivo)
SalidaDistancia (cm)Sí / no (digital)Valor analógico y salida digital
Alcance2–400 cmUnos 2–30 cm (ajustable)Unos 1–15 mm
Ángulo de detecciónUnos 15°Unos 35°Muy estrecho
Punto débilSuperficies blandas, inclinadas y finasSuperficies negras y mates, luz solarCambios de altura, luz solar
Ve bienVidrio, objetos negros, superficies grandes y lisasTela, esquinas, obstáculos cercanosColor del suelo y bordes de mesa
Lugar en el robotOjo principal, movido por el servoEsquinas delanterasBajo el parachoques delantero, mirando al suelo
Sensor infrarrojo reflectivo TCRT5000
TCRT5000: montado mirando hacia abajo a 5–10 mm del suelo, detecta bordes de mesa y huecos de escalera; en cuanto desaparece el reflejo, el robot puede pararse y retroceder.

Para los robots que se mueven sobre una mesa, el obstáculo más peligroso es en realidad el vacío. Si colocas un módulo TCRT5000 mirando al suelo bajo el parachoques delantero, sabrás al instante cuándo desaparece el suelo. Conecta la salida digital del módulo a D2, que sigue libre en el Uno; comprueba primero en el Monitor Serie qué nivel toma la salida cuando no hay suelo y después lee ese pin al principio de autoStep() y, si el suelo ha desaparecido, detén los motores de inmediato y haz retroceder al robot. Esta comprobación debe ir antes de la medida de distancia; caerse siempre sale más caro que chocar.

Pasar a modo manual por Bluetooth

A veces querrás conducir el robot autónomo a mano: para llevarlo de una habitación a otra, ponerlo delante de un obstáculo y probar su reacción o simplemente para jugar. La conexión con el HC-05 del código avanzado lo permite. Cuando el móvil envía la letra A, el robot pasa a modo automático; cuando envía M, pasa a modo manual. En modo manual se usan las letras de control de la guía del coche robot (F, B, L, R, S), con una diferencia: si el robot ve un obstáculo a menos de 15 cm mientras avanza, se detiene aunque le hayas dado la orden. Esta protección anticolisión te permite aprovechar los sensores del robot autónomo también al conducir a mano. Los pasos para emparejar el HC-05 y los comandos AT están en el artículo Módulo Bluetooth HC-05.

LetraAcción¿En qué modo?
APasar a modo automáticoEn ambos modos
MPasar a modo manual y detener el robotEn ambos modos
F / BAdelante / atrásManual
L / RGirar sobre sí mismo a la izquierda / derechaManual
SPararManual
0–9Nivel de velocidad (PWM 100–240)Manual
  1. Conecta el pin TXD del HC-05 a D10, el pin RXD a D11 a través de un divisor de 1 kΩ / 2 kΩ, VCC a 5V y GND a GND.
  2. Empareja el HC-05 en los ajustes Bluetooth de tu móvil Android con el PIN 1234 (o 0000 si no funciona).
  3. Instala desde Play Store una app de mando para coches Bluetooth y conéctala al HC-05. Si mantienes el Arduino conectado por USB y abres el Monitor Serie a 9600 baudios, el código muestra cada letra que llega del móvil.
  4. La mayoría de las apps de mando tienen botones extra, como luces o claxon, y cada uno envía su propia letra. Mira esas letras en el Monitor Serie y ponlas en el código en lugar de 'A' y 'M'; si prefieres definir tú los botones, puedes hacer tu propio mando con la guía App Inventor con Arduino por Bluetooth.
  5. Desconecta el cable USB y acciona el interruptor; a los 3 segundos el robot arranca en modo automático. Pasa a modo manual y condúcelo desde el móvil.
En el Uno, SoftwareSerial retrasa las demás interrupciones durante 1 ms aproximadamente mientras recibe un byte; en ese tiempo los pulsos de la librería Servo pueden alargarse y la cabeza del sensor puede dar pequeños tirones. No altera el comportamiento del robot, pero si te molesta puedes pasarte al Bluetooth integrado del ESP32. El HC-05 y el HC-06 no funcionan con iPhone; para iPhone necesitas un módulo BLE o un ESP32 programado para BLE.

Montaje con un shield de motores L293D

Shield controlador de motores L293D
Shield de motores L293D: se coloca directamente sobre un Arduino Uno; cuatro motores de corriente continua van a los bornes M1–M4 y dos servos a los conectores de la esquina.

Si no te gusta el lío de cables, en lugar del L298N puedes usar un shield de motores L293D (el Adafruit Motor Shield V1 y sus clones) que se coloca directamente encima del Arduino Uno. El shield puede mover cuatro motores de corriente continua, tiene dos conectores para servos y los alimenta desde la línea de 5 V del Arduino. Los motores no van conectados directamente a los pines del Arduino, sino a través del registro de desplazamiento 74HC595 de la placa, así que se controlan con la Adafruit Motor Shield library (la de la V1, no la V2), es decir, AFMotor, que instalas desde el Gestor de librerías. El shield usa D3, D4, D5, D6, D7, D8, D11 y D12 para los motores y D9 y D10 para los servos; A0–A5, D2 y D13 quedan libres. Como el HC-SR04 y los sensores IR ya están en A0–A3, solo tienes que cambiar el código de los motores, el pin del servo y los pines del HC-05.

AjusteCon el L298NCon el shield L293D
Conexión de los motoresBornes OUT1–OUT4Bornes M1 (izquierdo) y M2 (derecho)
Código de los motoresdigitalWrite y analogWrite dentro de drive()AFMotor dentro de drive()
SERVO_PIN39 o 10, según el conector donde pongas el servo
HC-05D10, D11A4 (RX) y A5 (TX): SoftwareSerial bt(A4, A5)
Corriente por canal2 A continuos0,6 A continuos, 1,2 A de pico
Alimentación de los motoresBorne 12VBorne EXT_PWR; con el jumper PWR puesto, el Arduino también se alimenta de la misma fuente
Arduino (C++)
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}

El límite de 0,6 A por canal del L293D basta para la marcha normal de un robot 2WD con motores TT, pero cuando una rueda empuja contra un obstáculo y se bloquea, el motor puede consumir más de 1 A y el chip se calienta rápido. Por eso un algoritmo de barrido que detiene el robot antes de empujar contra un obstáculo también alarga la vida del shield. Los ajustes de jumpers del shield y un sketch para un coche de cuatro motores están en el artículo Shield de motores L293D, y el funcionamiento interno del L298N en la guía Control de motores DC con el L298N.

Robot evasor de obstáculos con ESP32

En el ESP32 hay dos diferencias importantes. La primera es que la librería Servo de Arduino no funciona en el ESP32, así que generamos la señal del servo directamente con LEDC, la unidad PWM de la placa: en una señal de 50 Hz, un pulso de 0,544–2,4 ms corresponde a 0–180 grados. Como los motores trabajan a 1 kHz y el servo a 50 Hz, primero se configuran los canales de los motores y después el del servo; así el servo usa su propio temporizador. La segunda es que el ESP32 lleva Bluetooth clásico integrado, de modo que no hace falta un HC-05; el robot aparece en el móvil como "BlueGrays-Robot" y las letras de dirección y de modo son las mismas que en la versión para Uno. El código es para la versión 3.x del core de Arduino para ESP32 (ledcAttach y ledcWrite trabajan con números de pin) y, como necesita Bluetooth clásico, no compila en el ESP32-S3 ni en el ESP32-C3.

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

Robot evasor de obstáculos con Raspberry Pi Pico (MicroPython)

En la Raspberry Pi Pico, la función time_pulse_us() de MicroPython funciona como pulseIn() en Arduino; si no llega eco devuelve un valor negativo, que tratamos como 400 cm, es decir, espacio libre. Para el servo se usan PWM a 50 Hz y duty_ns(), que recibe el ancho de pulso en nanosegundos. El servo en GP16 y los pines de velocidad de los motores en GP15 y GP10 están en slices de PWM distintos, así que no se alteran la frecuencia entre sí. Esta vez las velocidades se dan en porcentaje. La Pico no tiene Bluetooth; si quieres el modo manual, conecta un HC-05 a la UART0 (GP0, GP1) como en la guía del coche robot.

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

Ideas de proyectos con un robot evasor de obstáculos

  • Pantalla de radar: envía por el puerto serie los ángulos y distancias del barrido y dibuja una pantalla de radar verde en el ordenador; tienes los detalles en el artículo Proyecto de radar con Arduino.
  • Línea y obstáculo: haz que el robot, mientras recorre una pista como un robot seguidor de línea, rodee una caja que se le cruce y vuelva a la línea; es una prueba habitual en competiciones.
  • Robot que sigue paredes: deja el sensor girado 60 grados a la derecha y avanza a 20 cm constantes de la pared; es el primer paso para resolver laberintos.
  • Robot con pantalla: muestra la distancia y el modo en una pequeña pantalla OLED; depurar resulta mucho más fácil.
  • Prototipo de robot aspirador: diseña un robot de limpieza que recorra la casa con elección aleatoria de dirección, interruptores de choque y sensor de vacío.
  • Bastón inteligente: convierte la misma lógica de medir distancias en un aviso por vibración para personas con discapacidad visual; consulta Bastón inteligente con Arduino.
  • Proyecto escolar o de competición: preséntalo con un circuito de obstáculos, cronómetro y una demostración de los modos automático y manual; para más ideas, echa un vistazo al artículo ideas de proyectos de Arduino para ferias de ciencias.

Errores frecuentes y cómo solucionarlos

ProblemaCausa probableSolución
El robot choca contra la pared sin pararseEl sensor lee 0 (cable roto, TRIG y ECHO invertidos) y el código lo toma como espacio libreEjecuta el sketch de prueba, prueba con la mano entre 10 y 60 cm y revisa los pines TRIG y ECHO
Roza la pared al acercarse en diagonalEl sonido se refleja en la superficie inclinada hacia otro ladoAñade sensores IR en las esquinas delanteras y baja la velocidad
El Arduino se reinicia al pararse a barrerLa corriente del servo y los motores hunde la línea de 5 VCondensador de 470 µF en el servo y baterías 18650 cargadas; si hace falta, alimenta el servo con una fuente de 5 V aparte
El robot se para y barre sin motivoUna medida errónea aislada o un eco del sueloSube el sensor a 8–12 cm e inclínalo un poco hacia arriba; usa la confirmación por mediana
El robot gira hacia el lado bloqueadoEl servo está montado al revés y se han cruzado izquierda y derechaPon SERVO_DIR a -1
Los giros se quedan cortos o se pasanMS_PER_DEG no está calibrado o la batería está bajaMide con turnBy(360) y vuelve a ajustarlo con la batería llena
En una esquina no deja de girar a un lado y a otroLos dos lados están cerca del umbral y la decisión cambia sin pararAumenta el umbral de callejón sin salida (STOP_CM + 10) o haz que prefiera el último sentido de giro
La cabeza del sensor da tironesAlimentación del servo floja o interrupciones de SoftwareSerialAcorta el cable del servo, añade un condensador y no envíes órdenes Bluetooth más a menudo de lo necesario
La forma más rápida de encontrar el origen de un problema es dejar el robot conectado por USB, hacerlo funcionar con las ruedas en el aire y mostrar los valores de pingCm() en el Monitor Serie. Si los valores tienen sentido, el problema es mecánico o de alimentación. Para otras causas por las que un Arduino se reinicia una y otra vez, consulta el artículo Arduino se reinicia solo.

Como reúne en un solo proyecto la lectura de sensores, el filtrado, la toma de decisiones y el control de motores, el robot evasor de obstáculos es la mejor puerta de entrada a la robótica. Observa primero el comportamiento básico con el primer sketch, ajusta después los umbrales a tu habitación con la versión avanzada y, por último, refuerza tu robot con sensores IR y Bluetooth. En la app BlueGrays encontrarás paso a paso, con esquemas y código, los proyectos del robot evasor de obstáculos, el coche controlado por Bluetooth con L298N, la medición de distancia con HC-SR04 y el sensor de aparcamiento.

Preguntas frecuentes

¿Qué se necesita para hacer un robot evasor de obstáculos?

Para un robot evasor de obstáculos básico basta con un Arduino Uno, un sensor ultrasónico HC-SR04, un servo SG90, un driver de motores L298N o L293D, un kit de chasis 2WD con dos motorreductores y dos baterías 18650. Puedes añadir dos sensores IR de obstáculos para ver las esquinas y un módulo Bluetooth HC-05 para manejarlo desde el móvil.

¿Se puede hacer un robot evasor de obstáculos sin servo?

Sí. El sensor mira fijo hacia delante; cuando el robot ve un obstáculo, primero gira hacia un lado y vuelve a medir, y si el camino sigue bloqueado gira hacia el otro. Es más barato, pero el robot decide más despacio porque tiene que girar todo el cuerpo en cada intento; con un servo encuentra la dirección más despejada antes de girar.

¿Por qué el HC-SR04 no detecta algunos obstáculos?

Un sensor ultrasónico mide por el eco. Las superficies blandas como cortinas y sofás absorben el sonido, las paredes inclinadas lo desvían y los objetos finos, como las patas de una silla, dan un eco muy débil. Tampoco ve los obstáculos que quedan fuera o por debajo de su cono de unos 15 grados; esos huecos se cubren con sensores IR.

¿A qué distancia debe pararse un robot evasor de obstáculos?

25 cm es un buen valor inicial: lo que avanza entre dos medidas, la distancia de frenado y el margen para girar sobre sí mismo suman unos 20 cm, más un pequeño margen de seguridad. En robots más rápidos y suelos resbaladizos súbelo a 30–35 cm; con un obstáculo a menos de 12 cm conviene retroceder primero.

¿Qué es mejor para un robot, el L298N o un shield L293D?

El L298N da 2 A por canal, así que es más robusto y te deja elegir los pines libremente. El shield L293D se coloca sobre el Uno, reduce el cableado y puede mover cuatro motores, pero su corriente continua es de 0,6 A por canal y ocupa muchos pines. Para un robot 2WD pequeño con motores TT, cualquiera de los dos sirve.

¿Se puede controlar un robot evasor de obstáculos desde el móvil?

Sí. Añade un módulo Bluetooth HC-05 y envía letras desde una app de mando en un móvil Android para cambiar entre modo automático y manual. Usar el sensor ultrasónico como protección anticolisión en modo manual evita que estrelles el robot contra la pared. Con un ESP32 no necesitas un módulo aparte gracias a su Bluetooth integrado.

Ver todos los proyectos
HC-SR04 Medición de distancia (pantalla en serie)
#27Principiante

HC-SR04 Medición de distancia (pantalla en serie)

El sensor HC-SR04 funciona igual que un murciélago que se orienta en la oscuridad: emite un sonido que no puedes oír, escucha el eco y mide a qué distancia está el objeto que tiene delante. El Arduino

4.6(45)
68
Robot para evitar obstáculos
#81Avanzado
Premium

Robot para evitar obstáculos

¿Qué le parece construir un vehículo inteligente que encuentre su propio camino? El Robot Evitador de Obstáculos explora constantemente su entorno gracias al sensor ultrasónico que lleva delante. Cuan

5.0(38)
49
Coche controlado por Bluetooth con L298N
#80Avanzado
Premium

Coche controlado por Bluetooth con L298N

Convierte tu móvil en un mando y conduce tu propio coche robot. El Arduino recibe por un módulo Bluetooth las órdenes de una app de control remoto en el móvil y mueve las ruedas con un controlador de

5.0(38)
65
Uso del servomotor
#35Principiante
Premium

Uso del servomotor

El brazo de un pequeño servomotor gira por turnos a tres posiciones: al principio del todo, justo al centro y al final del todo, esperando dos segundos en cada parada. El Arduino le dice al motor en q

4.7(48)
67
Sensor de aparcamiento con HC-SR04
#28Intermedio
Premium

Sensor de aparcamiento con HC-SR04

Una versión en miniatura del sensor de aparcamiento de los coches: cuanto más se acerca algo, más rápidos son los pitidos. El sensor de distancia mide el objeto que tiene delante; si está a más de 20

4.8(64)
84
Lectura de reflexión (proximidad) con TCRT5000
#77Intermedio
Premium

Lectura de reflexión (proximidad) con TCRT5000

Con un pequeño sensor que emite luz invisible y mide cuánta rebota, detectas la superficie que tiene delante. Cuanto más se acerca un objeto o una superficie clara, más luz vuelve, y el Arduino escrib

4.6(33)
43
TCRT5000 Detección de superficie blanca y negra
#78Intermedio
Premium

TCRT5000 Detección de superficie blanca y negra

Con el pequeño sensor que hace de ojo en los robots sigue líneas, sabrás si el suelo es negro o blanco. El TCRT5000 lanza una luz invisible hacia el suelo y mira cuánta vuelve; sobre blanco se enciend

4.7(40)
49
ESP32 HC-SR04 Medidor de distancia por ultrasonidos
#116Intermedio

ESP32 HC-SR04 Medidor de distancia por ultrasonidos

El sensor ultrasónico HC-SR04 calcula la distancia midiendo el tiempo de vuelo (Time of Flight) de la onda sonora que emite al chocar contra un objeto y rebotar. En este proyecto, enviaremos un pulso de 10 microsegundos desde el ESP32 al pin TRIG del sensor y leeremos el tiempo de eco procedente del pin ECHO mediante `pulseIn()`. Mostraremos la distancia calculada en centímetros en una pantalla LCD I2C de 16x2. Dado que el HC-SR04 funciona a 5 V, es obligatorio utilizar un **divisor de tensión**

4.9(28)
44