🤖 Level 4 – Mobile Robotics

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

PartHow many?Pin connection (R32)
D1 R321USB cable
Caterpillar chassis + motor driver1IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26
Ultrasonic HC‑SR041TRIG → 26, ECHO → 5 (example for sonar block)
PIR motion sensor1Signal → 23, VCC 5V, GND
Power1Battery 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.
On this page