Un dron que esquiva las boyas
Recorrido del nivel
Esa barra de arriba te dice en qué paso estás. Tocala para ver el índice completo y saltar a cualquier paso.
Conectar el sensor ultrasónico
Incorporamos un sensor de distancia ultrasónicoSensor de distancia ultrasónicoSensor que mide distancias emitiendo un pulso de sonido ultrasónico y midiendo el tiempo hasta que el eco regresa tras rebotar en un obstáculo. Tiene cuatro pines: VCC y GND para la alimentación, Trigger para disparar el pulso y Echo para detectar su llegada. que le permita al dron detectar obstáculos y cambiar el rumbo. El sensor emite un pulso de sonido y mide el tiempo que tarda en volver el eco tras rebotar en un objeto: ese tiempo es proporcional a la distancia. Tiene cuatro pines: dos de alimentación (VCC y GND) y dos de trabajo, Trigger (dispara el pulso) y Echo (detecta su llegada).
Empezamos con un solo sensor, el del frente, sumado al circuito de los dos motores del nivel inicial.
En el Arduino UNO conectamos el pin Trigger del sensor al pin 8 y el pin Echo al
pin 9 de la placa (las constantes PIN_TRIG_FRENTE y PIN_ECHO_FRENTE en el código),
con su alimentación de 5 V y GND.
En el ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. el armado es el mismo, pero usamos GPIO (los números
exactos están en las constantes PIN_TRIG_FRENTE y PIN_ECHO_FRENTE; en esta placa, los GPIO
19 y 21). Un cuidado propio del ESP32: el pin Echo del HC-SR04 entrega 5 V,
mientras que sus GPIO admiten como máximo 3,3 V. Por eso, entre el Echo y el GPIO hay que
interponer un divisor de tensión de dos resistencias (por ejemplo 1 kΩ y 2 kΩ), para no
dañar la placa. El Trigger no necesita divisor, y la alimentación del sensor sigue siendo de
5 V.
Conocer la medición del sensor
Antes de usar el sensor para decidir, conviene entender cómo mide. Para medir, el programa
dispara desde el pin Trigger un pulso ultrasónico y mide en el pin Echo cuánto tarda
en volver el rebote: como el sonido recorre ida y vuelta unos 58 microsegundos por cada
centímetro, dividiendo el tiempo por ese valor se obtiene la distancia en centímetros. Esa
cuenta vive en la función medirDistanciaCm() del código del paso 5, que recibe el par de
pines Trigger/Echo de un sensor y devuelve su distancia (o 0 si el eco no vuelve).
Si querés ver los valores con tus propios ojos antes de darle al sensor el control de los
motores, podés agregar de forma temporal una línea que imprima la distancia
(Serial.println(...)) y abrir el monitor serie del Arduino IDE 2.x (el ícono de la lupa
en el extremo superior derecho, o Herramientas → Monitor Serie). Vas a ver cómo el número
cambia al acercar y alejar un objeto frente al sensor. Es una buena forma de comprobar que el
sensor responde bien.
Descartar el «0»: los objetos fuera de rango
Hay un detalle importante de estos sensores: la mayoría devuelve una medición de 0 cuando el objeto está fuera de su alcance máximo. Es decir, si el sensor mide hasta 400 cm y la boya está a 401 cm, la lectura será 0, aunque la boya esté lejísimos.
Esto puede confundir, porque un 0 también podría significar que hay algo pegado al dron. Para resolverlo, el programa ignora el valor 0: solo tiene en cuenta una lectura si es mayor que 0. Así, el dron esquiva una boya cuando la distancia medida es menor que la distancia de seguridad (por ejemplo, 40 cm) y a la vez mayor que 0.
De un sensor a tres: ampliar la visión del dron
Un solo sensor registra solamente los objetos que tiene justo enfrente, lo que deja puntos ciegos a los costados. Por eso agregamos dos sensores más: uno a cada lado, para abarcar un rango más amplio. Ahora el dron tiene tres ojos: frente, izquierda y derecha.
Cada sensor tiene su propio par de pines Trigger/Echo. Esto es clave, y es donde el material original de 2019 tenía dos errores que en esta reedición están corregidos.
En el Arduino UNO, los tres sensores quedan así: el de la izquierda en los pines 2 y 3,
el del frente en los pines 8 y 9, y el de la derecha en los pines 10 y 11. En
el código son las constantes PIN_TRIG_IZQ/PIN_ECHO_IZQ, PIN_TRIG_FRENTE/PIN_ECHO_FRENTE
y PIN_TRIG_DER/PIN_ECHO_DER.
En el ESP32, cada sensor usa su propio par de GPIO: la izquierda en 5 y 18, el
frente en 19 y 21 y la derecha en 22 y 23, indicados en las constantes
PIN_TRIG_IZQ/PIN_ECHO_IZQ, PIN_TRIG_FRENTE/PIN_ECHO_FRENTE y
PIN_TRIG_DER/PIN_ECHO_DER. Recordá el divisor de tensión en cada uno de los tres pines
Echo (18, 21 y 23): los tres entregan 5 V y hay que bajarlos a 3,3 V antes de entrar al GPIO.
Programar el esquive de boyas
Ahora unimos todo en el programa de navegación. El dron avanza con los dos motores y, a
intervalos regulares (la constante INTERVALO_MEDICION_MS), mide sus tres sensores con la
función medirDistanciaCm(). Si cualquiera de los tres detecta una boya —una distancia
mayor que 0 y menor que el umbral UMBRAL_OBSTACULO_CM (40 cm), lo que decide la función
hayObstaculo()—, el dron inicia un giro de esquive: frena un motor para pivotar y sigue
girando durante un tiempo fijo (TIEMPO_GIRO_MS). Cuando ese tiempo termina, vuelve a
avanzar.
La regla de esquive es sencilla:
- Boya al frente → gira hacia el lado (izquierda o derecha) que tenga más espacio
libre (lo compara la función
espacioLibre(), que trata una lectura 0 como “muy lejos”). - Boya a la izquierda → gira a la derecha.
- Boya a la derecha → gira a la izquierda.
La clave es que la maniobra es no bloqueante: en lugar de un delay() que dejaría al dron
“ciego” mientras gira, el programa usa una máquina de estados con dos estados,
AVANZANDO y GIRANDO, apoyada en millis(). Cada vuelta de loop() es instantánea: si está
girando, revisa si ya se cumplió el tiempo del giro para volver a avanzar; si está avanzando,
mide los sensores y decide si esquivar.
Ver el código completo · dron-esquiva.ino
// Dron acuático — Nivel avanzado: esquiva de obstáculos (boyas).
// El dron avanza y usa TRES sensores ultrasónicos (izquierda, frente,
// derecha) para detectar boyas a menos de 40 cm y esquivarlas girando hacia
// el lado que tenga más espacio libre. Cuando el camino se despeja, retoma
// la marcha. Dos motores DC (vía ULN2003) con dirección diferencial.
//
// CÓMO ESQUIVA: mientras avanza, mide los tres sensores. Si alguno detecta
// una boya a menos de 40 cm, entra en un giro TEMPORIZADO no bloqueante hacia
// el lado despejado (frenando un motor) y luego vuelve a avanzar. El giro
// dura un tiempo fijo; se resuelve con millis() (máquina de estados), sin
// delay(), para no quedar "ciego" durante la maniobra.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL on/off — no invierte el giro del motor. El dron no
// tiene marcha atrás ni giro en el lugar: gira frenando un motor y dejando
// andar el otro. Giro en el lugar / marcha atrás requerirían un puente H
// (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// LECTURA 0 = FUERA DE RANGO: el HC-SR04 devuelve 0 cuando el eco no vuelve
// (nada dentro de su alcance, ~400 cm) o por timeout. 0 NO significa "boya
// pegada": significa "camino despejado". Por eso solo tratamos como
// obstáculo las lecturas mayores que 0 y menores que el umbral, y para
// comparar qué lado tiene más lugar, una lectura 0 cuenta como "muy lejos".
//
// SEGURIDAD (agua + electrónica): motores de baja tensión (5 V, 6 pilas AA)
// trabajando en la SUPERFICIE del agua, no sumergidos. Placa, ULN2003, pilas
// y cableado sobre la cubierta, por encima de la línea de flotación y
// protegidos de salpicaduras. No hay 220 V en el proyecto.
//
// Plataforma: Arduino UNO.
// Conexión: motor IZQUIERDO (IN ULN2003) → pin 5, motor DERECHO → pin 6.
// Sensor IZQUIERDA TRIG/ECHO → 2 / 3
// Sensor FRENTE TRIG/ECHO → 8 / 9
// Sensor DERECHA TRIG/ECHO → 10 / 11
//
// Basado en: Fragmentos de código de los pasos "Programar el movimiento" e
// "Incorporar/Programar los tres sensores" — Dron Acuático, Saberes
// Digitales (INET/EDUCAR 2019).
// DESVÍOS respecto del original (estaba muy buggeado):
// 1. BUG CORREGIDO: el original leía getDistance(2,3) para los TRES
// sensores (copy-paste) — el sensor 2 y el 3 nunca se leían de verdad.
// Acá cada sensor lee SU par de pines (2/3, 8/9, 10/11).
// 2. El texto del original asignaba el sensor 2 a los pines 6/7, pero el
// pin 6 es de un motor: chocaban. Se usa 8/9 (lo que el propio código
// del original ya cableaba).
// 3. Navegación reescrita: el original "esquivaba" con una coreografía de
// giros a ciegas dentro de setup() (se movía SIEMPRE igual sin importar
// qué sensor detectaba). Acá la maniobra depende de dónde está la boya
// y de qué lado hay más espacio.
// 4. Sin cruft de mBlock3 (_delay, _loop, angle_rad/deg); no bloqueante.
// --- Motores (entradas IN del ULN2003) ---
const int PIN_MOTOR_IZQ = 5;
const int PIN_MOTOR_DER = 6;
// El ULN2003 es on/off: HIGH = motor andando, LOW = motor frenado.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;
// --- Sensores ultrasónicos (cada uno con SU par TRIG/ECHO) ---
const int PIN_TRIG_IZQ = 2;
const int PIN_ECHO_IZQ = 3;
const int PIN_TRIG_FRENTE = 8;
const int PIN_ECHO_FRENTE = 9;
const int PIN_TRIG_DER = 10;
const int PIN_ECHO_DER = 11;
// Distancia a la que consideramos que una boya está "en el camino" (cm).
const float UMBRAL_OBSTACULO_CM = 40.0;
// Cuando una lectura es 0 (fuera de rango), la contamos como "muy lejos"
// para las comparaciones de qué lado está más despejado.
const float DISTANCIA_MUY_LEJOS_CM = 9999.0;
// El eco tarda 58 microsegundos por cm (ida y vuelta del sonido a ~343 m/s).
const float MICROSEGUNDOS_POR_CM = 58.0;
const unsigned long TIMEOUT_ECO_US = 30000;
// Ritmo de medición mientras avanza y duración del giro de esquive.
const unsigned long INTERVALO_MEDICION_MS = 100;
const unsigned long TIEMPO_GIRO_MS = 700;
// Máquina de estados no bloqueante.
enum Estado { AVANZANDO, GIRANDO };
Estado estado = AVANZANDO;
unsigned long finGiro = 0; // instante en que termina el giro actual
unsigned long ultimaMedicion = 0; // última vez que medimos los sensores
// Mide la distancia en cm de un sensor. Devuelve 0 si no llega el eco
// (fuera de rango o sensor desconectado): el llamador interpreta ese 0.
float medirDistanciaCm(int pinTrig, int pinEcho) {
digitalWrite(pinTrig, LOW);
delayMicroseconds(2);
digitalWrite(pinTrig, HIGH); // pulso de disparo de 10 microsegundos
delayMicroseconds(10);
digitalWrite(pinTrig, LOW);
return pulseIn(pinEcho, HIGH, TIMEOUT_ECO_US) / MICROSEGUNDOS_POR_CM;
}
// ¿Esta lectura corresponde a una boya en el camino? Solo si es mayor que 0
// (dentro de rango) y menor que el umbral. El 0 NO cuenta como obstáculo.
bool hayObstaculo(float distancia) {
return distancia > 0.0 && distancia < UMBRAL_OBSTACULO_CM;
}
// Espacio libre "efectivo" de un lado: una lectura 0 (fuera de rango) se
// trata como muy lejos, así el dron elige girar hacia el lado despejado.
float espacioLibre(float distancia) {
return distancia <= 0.0 ? DISTANCIA_MUY_LEJOS_CM : distancia;
}
void setMotores(int estadoIzq, int estadoDer) {
digitalWrite(PIN_MOTOR_IZQ, estadoIzq);
digitalWrite(PIN_MOTOR_DER, estadoDer);
}
// Inicia un giro temporizado (no bloqueante) hacia la derecha o la izquierda.
// Girar a la derecha = frenar el motor derecho; a la izquierda = frenar el
// izquierdo.
void iniciarGiro(bool haciaDerecha) {
if (haciaDerecha) {
setMotores(MOTOR_ANDANDO, MOTOR_FRENADO);
} else {
setMotores(MOTOR_FRENADO, MOTOR_ANDANDO);
}
finGiro = millis() + TIEMPO_GIRO_MS;
estado = GIRANDO;
}
void setup() {
pinMode(PIN_MOTOR_IZQ, OUTPUT);
pinMode(PIN_MOTOR_DER, OUTPUT);
pinMode(PIN_TRIG_IZQ, OUTPUT);
pinMode(PIN_ECHO_IZQ, INPUT);
pinMode(PIN_TRIG_FRENTE, OUTPUT);
pinMode(PIN_ECHO_FRENTE, INPUT);
pinMode(PIN_TRIG_DER, OUTPUT);
pinMode(PIN_ECHO_DER, INPUT);
// Arranca avanzando con los dos motores.
setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
estado = AVANZANDO;
}
void loop() {
unsigned long ahora = millis();
if (estado == GIRANDO) {
// Maniobra de esquive en curso: al terminar el tiempo, volver a avanzar.
if (ahora >= finGiro) {
setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
estado = AVANZANDO;
}
return;
}
// estado == AVANZANDO: medimos a intervalos y decidimos si esquivar.
if (ahora - ultimaMedicion < INTERVALO_MEDICION_MS) {
return;
}
ultimaMedicion = ahora;
float dIzq = medirDistanciaCm(PIN_TRIG_IZQ, PIN_ECHO_IZQ);
float dFrente = medirDistanciaCm(PIN_TRIG_FRENTE, PIN_ECHO_FRENTE);
float dDer = medirDistanciaCm(PIN_TRIG_DER, PIN_ECHO_DER);
if (hayObstaculo(dFrente)) {
// Boya al frente: girar hacia el lado con más espacio libre.
bool haciaDerecha = espacioLibre(dDer) >= espacioLibre(dIzq);
iniciarGiro(haciaDerecha);
} else if (hayObstaculo(dIzq)) {
// Boya a la izquierda: esquivar girando a la derecha.
iniciarGiro(true);
} else if (hayObstaculo(dDer)) {
// Boya a la derecha: esquivar girando a la izquierda.
iniciarGiro(false);
}
// Sin obstáculos: seguimos avanzando (los motores ya están andando).
} // Dron acuático — Nivel avanzado: esquiva de obstáculos (boyas).
// El dron avanza y usa TRES sensores ultrasónicos (izquierda, frente,
// derecha) para detectar boyas a menos de 40 cm y esquivarlas girando hacia
// el lado que tenga más espacio libre. Cuando el camino se despeja, retoma
// la marcha. Dos motores DC (vía ULN2003) con dirección diferencial.
//
// CÓMO ESQUIVA: mientras avanza, mide los tres sensores. Si alguno detecta
// una boya a menos de 40 cm, entra en un giro TEMPORIZADO no bloqueante hacia
// el lado despejado (frenando un motor) y luego vuelve a avanzar. El giro
// dura un tiempo fijo; se resuelve con millis() (máquina de estados), sin
// delay(), para no quedar "ciego" durante la maniobra.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL on/off — no invierte el giro del motor. El dron no
// tiene marcha atrás ni giro en el lugar: gira frenando un motor y dejando
// andar el otro. Giro en el lugar / marcha atrás requerirían un puente H
// (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// LECTURA 0 = FUERA DE RANGO: el HC-SR04 devuelve 0 cuando el eco no vuelve
// (nada dentro de su alcance, ~400 cm) o por timeout. 0 NO significa "boya
// pegada": significa "camino despejado". Por eso solo tratamos como
// obstáculo las lecturas mayores que 0 y menores que el umbral, y para
// comparar qué lado tiene más lugar, una lectura 0 cuenta como "muy lejos".
//
// SEGURIDAD (agua + electrónica): motores de baja tensión (5 V, 6 pilas AA)
// trabajando en la SUPERFICIE del agua, no sumergidos. Placa, ULN2003, pilas
// y cableado sobre la cubierta, por encima de la línea de flotación y
// protegidos de salpicaduras. No hay 220 V en el proyecto.
//
// NOTA ELÉCTRICA (ESP32, ¡importante!): el ESP32 trabaja a 3,3 V. El HC-SR04
// clásico se alimenta con 5 V y su pin ECHO devuelve el eco a 5 V: eso
// dañaría el GPIO. Es OBLIGATORIO un divisor de tensión en CADA ECHO
// (ECHO —1 kΩ— GPIO —2 kΩ— GND), que baja los 5 V a ~3,3 V. TRIG no necesita
// divisor: los 3,3 V del GPIO alcanzan para dispararlo. Alternativa sin
// divisores: el modelo HC-SR04P, alimentable a 3,3 V. Las salidas del ESP32
// sí alcanzan para las entradas IN del ULN2003; alimentá driver y motores
// desde las pilas (5 V), no desde 3V3, y uní las masas.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: motor IZQUIERDO (IN ULN2003) → GPIO 26, motor DERECHO → GPIO 27.
// Sensor IZQUIERDA TRIG/ECHO → GPIO 5 / 18 (ECHO con divisor)
// Sensor FRENTE TRIG/ECHO → GPIO 19 / 21 (ECHO con divisor)
// Sensor DERECHA TRIG/ECHO → GPIO 22 / 23 (ECHO con divisor)
//
// Basado en: Fragmentos de código de los pasos "Programar el movimiento" e
// "Incorporar/Programar los tres sensores" — Dron Acuático, Saberes
// Digitales (INET/EDUCAR 2019).
// DESVÍOS respecto del original (estaba muy buggeado):
// 1. BUG CORREGIDO: el original leía getDistance(2,3) para los TRES
// sensores (copy-paste) — el sensor 2 y el 3 nunca se leían de verdad.
// Acá cada sensor lee SU par de pines.
// 2. El texto del original asignaba el sensor 2 a los pines 6/7, que en el
// UNO chocan con un motor; en ESP32 se usan GPIO propios sin conflicto.
// 3. Navegación reescrita: el original "esquivaba" con una coreografía de
// giros a ciegas dentro de setup() (se movía SIEMPRE igual sin importar
// qué sensor detectaba). Acá la maniobra depende de dónde está la boya
// y de qué lado hay más espacio.
// 4. Sin cruft de mBlock3 (_delay, _loop, angle_rad/deg); no bloqueante.
// Se portó del UNO al ESP32 (GPIO seguros + divisor en cada ECHO).
// --- Motores (entradas IN del ULN2003) ---
const int PIN_MOTOR_IZQ = 26;
const int PIN_MOTOR_DER = 27;
// El ULN2003 es on/off: HIGH = motor andando, LOW = motor frenado.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;
// --- Sensores ultrasónicos (cada uno con SU par TRIG/ECHO) ---
const int PIN_TRIG_IZQ = 5;
const int PIN_ECHO_IZQ = 18; // con divisor de tensión 1 kΩ / 2 kΩ
const int PIN_TRIG_FRENTE = 19;
const int PIN_ECHO_FRENTE = 21; // con divisor de tensión 1 kΩ / 2 kΩ
const int PIN_TRIG_DER = 22;
const int PIN_ECHO_DER = 23; // con divisor de tensión 1 kΩ / 2 kΩ
// Distancia a la que consideramos que una boya está "en el camino" (cm).
const float UMBRAL_OBSTACULO_CM = 40.0;
// Cuando una lectura es 0 (fuera de rango), la contamos como "muy lejos"
// para las comparaciones de qué lado está más despejado.
const float DISTANCIA_MUY_LEJOS_CM = 9999.0;
// El eco tarda 58 microsegundos por cm (ida y vuelta del sonido a ~343 m/s).
const float MICROSEGUNDOS_POR_CM = 58.0;
const unsigned long TIMEOUT_ECO_US = 30000;
// Ritmo de medición mientras avanza y duración del giro de esquive.
const unsigned long INTERVALO_MEDICION_MS = 100;
const unsigned long TIEMPO_GIRO_MS = 700;
// Máquina de estados no bloqueante.
enum Estado { AVANZANDO, GIRANDO };
Estado estado = AVANZANDO;
unsigned long finGiro = 0; // instante en que termina el giro actual
unsigned long ultimaMedicion = 0; // última vez que medimos los sensores
// Mide la distancia en cm de un sensor. Devuelve 0 si no llega el eco
// (fuera de rango o sensor desconectado): el llamador interpreta ese 0.
float medirDistanciaCm(int pinTrig, int pinEcho) {
digitalWrite(pinTrig, LOW);
delayMicroseconds(2);
digitalWrite(pinTrig, HIGH); // pulso de disparo de 10 microsegundos
delayMicroseconds(10);
digitalWrite(pinTrig, LOW);
return pulseIn(pinEcho, HIGH, TIMEOUT_ECO_US) / MICROSEGUNDOS_POR_CM;
}
// ¿Esta lectura corresponde a una boya en el camino? Solo si es mayor que 0
// (dentro de rango) y menor que el umbral. El 0 NO cuenta como obstáculo.
bool hayObstaculo(float distancia) {
return distancia > 0.0 && distancia < UMBRAL_OBSTACULO_CM;
}
// Espacio libre "efectivo" de un lado: una lectura 0 (fuera de rango) se
// trata como muy lejos, así el dron elige girar hacia el lado despejado.
float espacioLibre(float distancia) {
return distancia <= 0.0 ? DISTANCIA_MUY_LEJOS_CM : distancia;
}
void setMotores(int estadoIzq, int estadoDer) {
digitalWrite(PIN_MOTOR_IZQ, estadoIzq);
digitalWrite(PIN_MOTOR_DER, estadoDer);
}
// Inicia un giro temporizado (no bloqueante) hacia la derecha o la izquierda.
// Girar a la derecha = frenar el motor derecho; a la izquierda = frenar el
// izquierdo.
void iniciarGiro(bool haciaDerecha) {
if (haciaDerecha) {
setMotores(MOTOR_ANDANDO, MOTOR_FRENADO);
} else {
setMotores(MOTOR_FRENADO, MOTOR_ANDANDO);
}
finGiro = millis() + TIEMPO_GIRO_MS;
estado = GIRANDO;
}
void setup() {
pinMode(PIN_MOTOR_IZQ, OUTPUT);
pinMode(PIN_MOTOR_DER, OUTPUT);
pinMode(PIN_TRIG_IZQ, OUTPUT);
pinMode(PIN_ECHO_IZQ, INPUT);
pinMode(PIN_TRIG_FRENTE, OUTPUT);
pinMode(PIN_ECHO_FRENTE, INPUT);
pinMode(PIN_TRIG_DER, OUTPUT);
pinMode(PIN_ECHO_DER, INPUT);
// Arranca avanzando con los dos motores.
setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
estado = AVANZANDO;
}
void loop() {
unsigned long ahora = millis();
if (estado == GIRANDO) {
// Maniobra de esquive en curso: al terminar el tiempo, volver a avanzar.
if (ahora >= finGiro) {
setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
estado = AVANZANDO;
}
return;
}
// estado == AVANZANDO: medimos a intervalos y decidimos si esquivar.
if (ahora - ultimaMedicion < INTERVALO_MEDICION_MS) {
return;
}
ultimaMedicion = ahora;
float dIzq = medirDistanciaCm(PIN_TRIG_IZQ, PIN_ECHO_IZQ);
float dFrente = medirDistanciaCm(PIN_TRIG_FRENTE, PIN_ECHO_FRENTE);
float dDer = medirDistanciaCm(PIN_TRIG_DER, PIN_ECHO_DER);
if (hayObstaculo(dFrente)) {
// Boya al frente: girar hacia el lado con más espacio libre.
bool haciaDerecha = espacioLibre(dDer) >= espacioLibre(dIzq);
iniciarGiro(haciaDerecha);
} else if (hayObstaculo(dIzq)) {
// Boya a la izquierda: esquivar girando a la derecha.
iniciarGiro(true);
} else if (hayObstaculo(dDer)) {
// Boya a la derecha: esquivar girando a la izquierda.
iniciarGiro(false);
}
// Sin obstáculos: seguimos avanzando (los motores ya están andando).
} Podés ver el comportamiento completo —el dron avanzando, detectando boyas y esquivándolas— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Para el aula
El original enseñaba este paso con bloques en mBlock3 y “esquivaba” con una coreografía fija
de giros escrita dentro de setup(): el dron se movía siempre igual sin importar cuál
sensor detectaba la boya, y encima quedaba bloqueado —a ciegas— durante cada maniobra. En esta
reedición la navegación se reescribió por completo: es reactiva (la maniobra depende de
dónde está la boya y de qué lado hay más espacio) y no bloqueante (máquina de estados con
millis(), sin delay()). Es un buen ejemplo para discutir con el grupo la diferencia entre
un programa que “espera parado” y uno que sigue atento mientras actúa, y por qué eso importa
cuando un robot se mueve en el mundo real. Además, ya con los dos errores de pines corregidos
(cada sensor lee su propio par y ninguno choca con un motor), es un buen caso para mostrar
cómo se depura una guía técnica heredada.
Verificá
En el Arduino IDE, verificá el programa (✓): si compila sin errores en rojo, está bien escrito. Con el sensor del frente, acercá un objeto: el dron (o el simulador) debe iniciar la maniobra de esquive. Probá también acercando algo por cada costado: cada lateral debe disparar el esquive por su lado. Si un lateral no reacciona, revisá que su sensor esté leyendo su propio par de pines y no el del frente.
Cierre del nivel
Al terminar este nivel, el dron de los científicos dejó de ser ciego: sus tres sensores ultrasónicos —cada uno leyendo su propio par de pines— vigilan el frente y los costados, descartan las lecturas de 0 de los objetos fuera de rango y disparan una maniobra de esquive cada vez que aparece una boya, para después retomar el barrido. En el camino corregiste dos errores reales del material original: los tres sensores que leían el mismo par de pines y el segundo sensor que chocaba con un motor. Con esto queda cerrado el recorrido del proyecto. Si el grupo quiere seguir, en la ficha están las extensiones sugeridas: trayectorias triangulares para optimizar el barrido, rutas alternativas para esquivar una misma boya, y un doble puente H que le permita al dron dar marcha atrás.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El dron tiene tres sensores ultrasónicos (uno al frente y dos laterales), cada uno leyendo su propio par de pines Trigger/Echo, con la electrónica protegida del agua.
- El dron mide la distancia a los obstáculos y descarta las lecturas de 0, que corresponden a objetos fuera de rango, no a boyas pegadas al casco.
- Cuando cualquiera de los tres sensores detecta una boya a menos de la distancia de seguridad, el dron la esquiva y luego retoma su trayectoria.