Project 4.7: "Autonomous Caterpillar Robot"
What you’ll learn
- Goal 1: Navigate and avoid obstacles using the official ultrasonic block.
- Goal 2: Track motion with a PIR sensor to “look” toward movement.
- Goal 3: Run an automatic patrol mode with calm, periodic scanning.
- Goal 4: Use a simple power‑saving loop by pacing actions and reducing checks.
- Goal 5: Make autonomous decisions (forward, turn, stop) based on sensor conditions.
Blocks glossary
- sonar.Sonar(TRIG, ECHO).checkdist(): Lectura de distancia en cm para evitar obstáculos.
- machine.Pin(pin, machine.Pin.IN).value(): Lectura digital del PIR (0/1).
- machine.Pin(pin, machine.Pin.OUT).value(v): Control de pines para motores del caterpillar (L298N u otro).
- print(…): Mensajes por Serial para entender cada decisión.
- time.sleep_ms(ms): Pausas cortas para cadencia y ahorro de energía.
- control:
while True:,if:y pequeñas funciones para organizar acciones.
What you need
| Part | How many? | Pin connection (R32) |
|---|---|---|
| D1 R32 | 1 | USB cable |
| Caterpillar chassis + motor driver | 1 | IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26 |
| Ultrasonic HC‑SR04 | 1 | TRIG → 26, ECHO → 5 (example for sonar block) |
| PIR motion sensor | 1 | Signal → 23, VCC 5V, GND |
| Power | 1 | Battery pack for motors, USB for R32 (GND shared) |
- Comparte GND entre el R32, el driver de motores, el PIR y el ultrasonido.
- Mantén cables cortos y el sensor ultrasónico al frente, PIR con vista abierta.
Before you start
- Conecta motores al driver y al R32, el ultrasonido al frente (TRIG/ECHO), y el PIR a un lateral o arriba.
- Abre el monitor serial para verificar mensajes y decisiones.
- Bloque de prueba:
print("Ready!") # Confirmar que el monitor serial funciona
Microprojects 1–5
Microproject 4.7.1 – Obstacle‑avoiding navigation
import machine # Bloque: importar pines para motores
import sonar # Bloque: importar ultrasonido oficial
import time # Bloque: importar tiempo para pausas
# Motores del caterpillar (ejemplo L298N)
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1 motor izquierdo
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2 motor izquierdo
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3 motor derecho
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4 motor derecho
print("[Nav] Motors ready") # Bloque: confirmar pines
# Ultrasonido al frente
us = sonar.Sonar(26, 5) # Bloque: TRIG=26, ECHO=5
print("[Nav] Sonar ready TRIG=26 ECHO=5") # Bloque: confirmar sonar
SAFE_CM = 25 # Bloque: distancia segura en cm
def forward(): # Bloque: función avanzar
in1.value(1); in2.value(0) # Bloque: motor izquierdo adelante
in3.value(1); in4.value(0) # Bloque: motor derecho adelante
print("[Nav] FORWARD") # Bloque: log acción
def stop(): # Bloque: función detener
in1.value(0); in2.value(0) # Bloque: motor izquierdo OFF
in3.value(0); in4.value(0) # Bloque: motor derecho OFF
print("[Nav] STOP") # Bloque: log acción
def right(): # Bloque: función girar derecha
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(0); in4.value(1) # Bloque: derecho atrás
print("[Nav] RIGHT") # Bloque: log acción
d = us.checkdist() # Bloque: leer distancia en cm
print("[Nav] Distance:", d) # Bloque: mostrar distancia
if (d is None) or (d <= 0): # Bloque: lectura inválida
stop() # Bloque: detener por seguridad
elif d < SAFE_CM: # Bloque: obstáculo cercano
right() # Bloque: girar para evitar
time.sleep_ms(600) # Bloque: girar un momento
stop() # Bloque: detener para reevaluar
else: # Bloque: camino libre
forward() # Bloque: avanzar
time.sleep_ms(600) # Bloque: avanzar un momento
stop() # Bloque: detener tras avance
Reflection: Con decisión simple, el robot evita obstáculos con lecturas limpias.
Challenge: Cambia SAFE_CM a 20/35 y observa cómo cambia el comportamiento.
Microproject 4.7.2 – Motion search (track motion)
import machine # Bloque: importar pines
import time # Bloque: importar tiempo
pir = machine.Pin(23, machine.Pin.IN) # Bloque: PIR en pin 23 como entrada
print("[Motion] PIR ready on 23") # Bloque: confirmar PIR
# Simples motores (reutiliza idea de 4.7.1 si ya conectados)
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4
print("[Motion] Motors ready") # Bloque: confirmar motores
def stop(): # Bloque: función detener
in1.value(0); in2.value(0) # Bloque: izquierdo OFF
in3.value(0); in4.value(0) # Bloque: derecho OFF
print("[Motion] STOP") # Bloque: log
def turn_to_motion(): # Bloque: función giro atento
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(0); in4.value(1) # Bloque: derecho atrás
print("[Motion] TURN-to-PIR") # Bloque: log giro
time.sleep_ms(500) # Bloque: giro breve
stop() # Bloque: detener
if pir.value() == 1: # Bloque: si detecta movimiento
turn_to_motion() # Bloque: orientarse hacia movimiento
else: # Bloque: si no hay movimiento
stop() # Bloque: quieto y atento
Reflection: El robot “mira” hacia donde detecta movimiento con el PIR.
Challenge: Si hay movimiento, además imprime “TRACK” y gira 200 ms extra.
Microproject 4.7.3 – Automatic patrol mode
import machine # Bloque: importar pines
import sonar # Bloque: importar ultrasonido
import time # Bloque: importar tiempo
# Motores
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4
print("[Patrol] Motors ready") # Bloque: confirmar motores
# Sonar
us = sonar.Sonar(26, 5) # Bloque: TRIG=26, ECHO=5
print("[Patrol] Sonar ready") # Bloque: confirmar sonar
SAFE_CM = 25 # Bloque: umbral seguro
def forward(): # Bloque: avanzar
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(1); in4.value(0) # Bloque: derecho adelante
print("[Patrol] FORWARD") # Bloque: log
def right(): # Bloque: girar derecha
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(0); in4.value(1) # Bloque: derecho atrás
print("[Patrol] RIGHT") # Bloque: log
def stop(): # Bloque: detener
in1.value(0); in2.value(0) # Bloque: izquierdo OFF
in3.value(0); in4.value(0) # Bloque: derecho OFF
print("[Patrol] STOP") # Bloque: log
# Patrulla: avanzar, checar, girar si cerca, repetir calmado
forward() # Bloque: iniciar avance breve
time.sleep_ms(700) # Bloque: avanzar un poco
stop() # Bloque: parar para medir
d = us.checkdist() # Bloque: leer distancia
print("[Patrol] d=", d) # Bloque: log distancia
if (d is None) or (d <= 0): # Bloque: lectura inválida
stop() # Bloque: mantener detenido
elif d < SAFE_CM: # Bloque: obstáculo cerca
right() # Bloque: girar
time.sleep_ms(600) # Bloque: giro breve
stop() # Bloque: reevaluar
else: # Bloque: camino libre
forward() # Bloque: avanzar más
time.sleep_ms(700) # Bloque: avanzar breve
stop() # Bloque: parar para nueva ronda
Reflection: La patrulla avanza, mide y decide con un ritmo tranquilo y claro.
Challenge: Inserta una pausa de 300 ms entre rondas para que se vea “humano”.
Microproject 4.7.4 – Power saving mode
import machine # Bloque: importar pines
import time # Bloque: importar tiempo
# Motores (para poder apagarlos cuando no se usan)
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4
print("[Power] Motors ready") # Bloque: confirmar motores
def sleep_stop(ms): # Bloque: función ahorro simple
in1.value(0); in2.value(0) # Bloque: OFF motor izquierdo
in3.value(0); in4.value(0) # Bloque: OFF motor derecho
print("[Power] Idle for", ms, "ms") # Bloque: log de reposo
time.sleep_ms(ms) # Bloque: pausa para ahorrar energía
sleep_stop(800) # Bloque: reposo corto inicial
sleep_stop(1200) # Bloque: reposo un poco más largo
Reflection: Cortas consumo deteniendo motores y aumentando pausas cuando es seguro.
Challenge: Aplica reposo de 2000 ms cuando no hay cambio por 3 ciclos seguidos.
Microproject 4.7.5 – Autonomous decision making
import machine # Bloque: importar pines
import sonar # Bloque: importar ultrasonido
import time # Bloque: importar tiempo
# Motores
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4
print("[Auto] Motors ready") # Bloque: confirmar motores
# Sonar
us = sonar.Sonar(26, 5) # Bloque: TRIG/ECHO
print("[Auto] Sonar ready") # Bloque: confirmar sonar
SAFE_CM = 25 # Bloque: umbral seguro
def forward(): # Bloque: avanzar
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(1); in4.value(0) # Bloque: derecho adelante
print("[Auto] FORWARD") # Bloque: log
def right(): # Bloque: girar derecha
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(0); in4.value(1) # Bloque: derecho atrás
print("[Auto] RIGHT") # Bloque: log
def stop(): # Bloque: detener
in1.value(0); in2.value(0) # Bloque: izquierdo OFF
in3.value(0); in4.value(0) # Bloque: derecho OFF
print("[Auto] STOP") # Bloque: log
d = us.checkdist() # Bloque: leer distancia
print("[Auto] d=", d) # Bloque: log lectura
if (d is None) or (d <= 0): # Bloque: si inválido
stop() # Bloque: detener seguro
elif d < SAFE_CM: # Bloque: si cerca
right() # Bloque: esquivar
time.sleep_ms(700) # Bloque: girar un momento
stop() # Bloque: reevaluar
else: # Bloque: si libre
forward() # Bloque: avanzar
time.sleep_ms(800) # Bloque: avanzar breve
stop() # Bloque: evaluar siguiente decisión
Reflection: El robot decide por sí mismo con reglas claras y comprensibles.
Challenge: Añade un segundo umbral (por ejemplo 40 cm) para “avanza más tiempo” si está muy despejado.
Main project
Autonomous caterpillar robot with obstacle avoidance, motion tracking, patrol loop, power saving, and simple decisions
- Obstacle avoidance: Sonar decide giro o avance seguro.
- Motion tracking: PIR orienta con un pequeño giro hacia el movimiento.
- Patrol: Avances cortos + mediciones periódicas.
- Power saving: Pausas de reposo con motores apagados.
- Decisions: Reglas simples basadas en distancia y estado del PIR.
import machine # Bloque: importar pines y hardware
import sonar # Bloque: importar ultrasonido oficial
import time # Bloque: importar tiempo
# Motores caterpillar
in1 = machine.Pin(21, machine.Pin.OUT) # Bloque: IN1 motor izquierdo
in2 = machine.Pin(13, machine.Pin.OUT) # Bloque: IN2 motor izquierdo
in3 = machine.Pin(27, machine.Pin.OUT) # Bloque: IN3 motor derecho
in4 = machine.Pin(26, machine.Pin.OUT) # Bloque: IN4 motor derecho
print("[Main] Motors ready") # Bloque: confirmar motores
# Sensores
us = sonar.Sonar(26, 5) # Bloque: ultrasonido (TRIG=26, ECHO=5)
pir = machine.Pin(23, machine.Pin.IN) # Bloque: PIR entrada digital
print("[Main] Sensors ready") # Bloque: confirmar sensores
# Parámetros
SAFE_CM = 25 # Bloque: umbral para evitar
REST_MS = 300 # Bloque: reposo corto entre acciones
def forward(): # Bloque: avanzar
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(1); in4.value(0) # Bloque: derecho adelante
print("[Main] FORWARD") # Bloque: log
def right(): # Bloque: girar derecha
in1.value(1); in2.value(0) # Bloque: izquierdo adelante
in3.value(0); in4.value(1) # Bloque: derecho atrás
print("[Main] RIGHT") # Bloque: log
def stop(): # Bloque: detener
in1.value(0); in2.value(0) # Bloque: OFF izquierdo
in3.value(0); in4.value(0) # Bloque: OFF derecho
print("[Main] STOP") # Bloque: log
while True: # Bloque: bucle autónomo
d = us.checkdist() # Bloque: leer distancia cm
print("[Main] d=", d) # Bloque: log distancia
if (d is None) or (d <= 0): # Bloque: inválido
stop() # Bloque: detener
time.sleep_ms(REST_MS) # Bloque: reposo corto
continue # Bloque: siguiente ciclo
if d < SAFE_CM: # Bloque: obstáculo cerca
right() # Bloque: girar para evitar
time.sleep_ms(700) # Bloque: giro breve
stop() # Bloque: detener
else: # Bloque: camino libre
# Si PIR detecta movimiento, orienta un poco hacia “interés”
if pir.value() == 1: # Bloque: movimiento detectado
right() # Bloque: giro breve de atención
time.sleep_ms(300) # Bloque: giro corto
stop() # Bloque: detener para avanzar limpio
forward() # Bloque: avanzar
time.sleep_ms(700) # Bloque: avanzar breve
stop() # Bloque: detener para medir otra vez
time.sleep_ms(REST_MS) # Bloque: reposo corto para ahorrar energía
External explanation
Usamos solo bloques oficiales: sonar para distancia, PIR como entrada digital y pins para motores. Cada decisión se explica en Serial y se ejecuta con pausas cortas para mantener un ritmo “humano”. El robot patrulla, evita, se orienta hacia movimiento y descansa sin estructuras complejas.
Story time
Tu caterpillar no solo avanza: observa, decide y se guarda energía. Si ve algo cerca, esquiva; si siente movimiento, se asoma. Y cuando el camino está limpio, recorre el territorio con paciencia.
Debugging (2)
Debugging 4.7.A – Cycles in navigation
# Si el robot entra en bucles de girar-avanzar sin progreso:
print("[Debug] Insert extra rest and longer turn") # Bloque: consejo en serial
# Aumenta giro a 900 ms y añade un descanso de 500 ms entre vueltas
# right(); time.sleep_ms(900); stop(); time.sleep_ms(500)
Debugging 4.7.B – Erratic behavior
# Si las lecturas del sonar son inestables:
print("[Debug] Slow down cadence and recheck") # Bloque: consejo en serial
# Añade una pausa de 300–500 ms antes de usar d = us.checkdist() de nuevo
# time.sleep_ms(400); d = us.checkdist()
Final checklist
- Motores responden a forward/right/stop con claridad.
- Sonar entrega distancia y el robot evita obstáculos.
- PIR orienta el robot hacia movimiento detectado.
- Patrulla con pausas y decisiones calmadas.
- Cadencia añade ahorro de energía básico.
Extras
- Student tip: Ajusta SAFE_CM y REST_MS para distintos espacios (estrecho vs abierto).
- Instructor tip: Pide a los equipos que definan reglas extra (doble umbral: muy cerca vs lejos).
- Glossary:
- Sonar: Lectura de distancia (HC‑SR04) con bloque oficial.
- PIR: Detección de movimiento como entrada digital (0/1).
- Cadence: Ritmo de acciones y pausas para estabilidad.
- Mini tips:
- Mantén el sonar al frente y estable.
- Evita cables sueltos que afecten lecturas.
- Usa mensajes en Serial para “ver” las decisiones en tiempo real.