Objetivo y caso de uso
Lo que construirás: Un prototipo de vehículo guiado automáticamente (AGV) de tracción en 2 ruedas (2WD) que avanza continuamente y ejecuta una parada de emergencia de baja latencia cuando se detecta un obstáculo a menos de 15 centímetros.
Por qué es importante / Casos de uso
- Logística de almacenes: Esencial para carros automatizados que requieren una prevención de colisiones robusta para proteger el inventario y las estanterías estáticas.
- Abstracción de hardware: Desacopla la lógica de navegación de alto nivel de la manipulación GPIO de bajo nivel, permitiendo probar el código en computadoras portátiles estándar antes de la implementación física.
- Integración de sensores: Demuestra la interconexión segura de sensores analógicos/digitales de 5V (HC-SR04) con microprocesadores de 3.3V utilizando divisores de voltaje.
- Control de motores: Aplica conceptos de puente H para el control de velocidad PWM y la gestión del estado de espera (standby) utilizando el controlador TB6612FNG.
Resultado esperado
- Una arquitectura de software en Python 3.11 que utiliza el patrón Strategy para alternar sin problemas entre interfaces de hardware físicas y simuladas (mock).
- Sondeo continuo de distancia ultrasónica ejecutándose a aproximadamente 10Hz.
- Desactivación inmediata del motor (parada) activada en el momento en que la distancia medida cae por debajo del umbral de 15.0 cm.
Audiencia: Desarrolladores de Python e ingenieros en robótica; Nivel: Intermedio
Arquitectura/flujo: Capa de abstracción de hardware en Python sondeando un sensor HC-SR04 a 10Hz, evaluando umbrales de distancia y controlando un controlador de motor TB6612FNG a través de PWM GPIO con anulaciones de parada automáticas.
Nota educativa de validación
Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.
Evidencia de validación publicada
- Resultado automático: PASS.
- Estructura parseada: 3 apartados, 2 tablas y 2 bloques de código detectados antes de publicar.
- Código comprobado: 2 Python/py_compile.
- Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
- Hallazgos del informe: sin hallazgos bloqueantes.
Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.
Nota educativa de seguridad
Este proyecto es un prototipo educativo, no un producto certificado. Antes de encender el montaje, verifica el esquema de pines de la revisión exacta de tu placa ULX3S, mantén las señales de E/S de la FPGA a 3.3 V, nunca conectes 5 V directamente a los pines de E/S, desconecta la alimentación antes de cambiar el cableado y utiliza fuentes de alimentación externas adecuadas para cargas, motores o servos, compartiendo la tierra (ground) solo cuando el cableado lo requiera.
Diagrama de bloques conceptual
Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.
Arquitectura funcional
Flujo conceptual de control: entrada de botones, selección de modo, temporización PWM y movimiento del servo.
Ruta de validación
La validación automática comprueba sintaxis, simulación/lint y compatibilidad con la toolchain ULX3S/ECP5.
Requisitos previos
- Software: Una computadora o portátil para pruebas en vacío (dry-run) (Windows, macOS o Linux) con Python 3.11 instalado. Para la implementación física, una Raspberry Pi ejecutando Raspberry Pi OS Bookworm (64 bits) con Python 3.11 y la biblioteca
gpiozeroinstalada. - Habilidades: Familiaridad básica con operaciones de línea de comandos en Linux, programación fundamental en Python (clases, herencia, manejo de excepciones) y uso básico de protoboard (comprensión de tierra común y divisores de voltaje).
Materiales
Para completar este caso práctico, debes usar EXACTAMENTE esta configuración de modelos de dispositivos:
* Microcomputadora: Raspberry Pi 4 Model B (cualquier variante de RAM).
* Controlador de motores: Placa breakout de controlador de motor dual TB6612FNG.
* Sensor de distancia: Sensor ultrasónico HC-SR04.
* Plataforma del robot: Chasis UGV 2WD (incluye dos motores de engranajes de CC y ruedas).
* Fuente de alimentación: Un banco de energía (power bank) USB-C de 5V para la Raspberry Pi, y un paquete de baterías separado (ej. 4x AA que proporcionan 6V) para los motores de CC.
* Componentes pasivos: Una resistencia de 1kΩ y una resistencia de 2kΩ (requeridas para el divisor de voltaje del HC-SR04).
* Cableado: Protoboard y cables puente (jumper) variados macho-hembra y macho-macho.
Configuración/Conexión
El cableado adecuado es fundamental para evitar daños a la Raspberry Pi. Los pines GPIO de la Raspberry Pi operan con lógica de 3.3V. El HC-SR04 requiere 5V para funcionar y emite una señal de 5V en su pin ECHO. Conectar un pin ECHO de 5V directamente a un pin GPIO de 3.3V dañará la Raspberry Pi. Debemos usar un divisor de voltaje (resistencias de 1kΩ y 2kΩ) para reducir la señal de 5V a aproximadamente 3.3V.
Además, los motores deben ser alimentados por un paquete de baterías externo, no por los pines de 5V o 3.3V de la Raspberry Pi. Los motores de CC consumen una corriente significativa y crean picos de voltaje que pueden causar que la Raspberry Pi sufra caídas de tensión (brown-out) o se reinicie.
Cableado del controlador de motor dual TB6612FNG
| Pin TB6612FNG | Conexión / GPIO de Raspberry Pi | Propósito |
|---|---|---|
| VCC | Pi 3.3V (Pin 1) | Alimentación lógica para el CI del controlador de motor. |
| VMOT | Positivo del paquete de baterías (ej. 6V) | Alimentación de alta corriente para los motores de CC. |
| GND | Pi GND (Pin 6) + GND de batería | Referencia de tierra común. Crucial. |
| PWMA | Pi GPIO 12 (Pin 32) | Señal PWM para la velocidad del Motor A (Izquierdo). |
| AIN1 | Pi GPIO 5 (Pin 29) | Control de dirección 1 para el Motor A. |
| AIN2 | Pi GPIO 6 (Pin 31) | Control de dirección 2 para el Motor A. |
| STBY | Pi GPIO 17 (Pin 11) | Pin de espera (standby). Debe estar en ALTO (HIGH) para habilitar los motores. |
| PWMB | Pi GPIO 13 (Pin 33) | Señal PWM para la velocidad del Motor B (Derecho). |
| BIN1 | Pi GPIO 16 (Pin 36) | Control de dirección 1 para el Motor B. |
| BIN2 | Pi GPIO 26 (Pin 37) | Control de dirección 2 para el Motor B. |
| AO1 / AO2 | Terminales del motor izquierdo | Salida de potencia al motor de CC izquierdo. |
| BO1 / BO2 | Terminales del motor derecho | Salida de potencia al motor de CC derecho. |
Cableado del sensor ultrasónico HC-SR04
| Pin HC-SR04 | Conexión | Propósito |
|---|---|---|
| VCC | Pi 5V (Pin 2) | Fuente de alimentación para el sensor ultrasónico. |
| GND | Pi GND (Pin 39) | Referencia de tierra común. |
| TRIG | Pi GPIO 23 (Pin 16) | Recibe el pulso de activación (trigger) de 3.3V desde la Pi. |
| ECHO | Divisor de voltaje -> Pi GPIO 24 (Pin 18) | Emite 5V. El divisor lo reduce a 3.3V para la Pi. |
Construcción del divisor de voltaje para ECHO:
1. Conecta el pin ECHO del HC-SR04 a un extremo de una resistencia de 1kΩ.
2. Conecta el otro extremo de la resistencia de 1kΩ al GPIO 24 de la Raspberry Pi.
3. Desde el GPIO 24 de la Raspberry Pi, conecta una resistencia de 2kΩ a tierra (GND).
Esto forma un circuito donde V_out = V_in * (2k / (1k + 2k)) = 5V * (2/3) = 3.33V.
Método de validación y evidencia esperada
Para validar las afirmaciones de rendimiento (sondeo a 10Hz y umbral de parada absoluto de 15.0 cm):
1. Verificación en vacío (Dry-Run): Ejecuta la aplicación principal con la bandera --dry-run. Observa la salida de la consola. Deberías ver 10 lecturas de distancia por segundo (10Hz). La distancia simulada disminuirá en 2 cm con cada tick. Exactamente cuando la lectura de distancia caiga por debajo de 15.0 cm, la consola debe mostrar OBSTACLE DETECTED! Executing emergency halt. y salir inmediatamente.
2. Verificación física: Coloca el robot terminado sobre una superficie plana frente a una pared exactamente a 30 cm de distancia. Ejecuta el script físico. El robot debe avanzar y detenerse. Usa una cinta métrica para medir la distancia entre la parte delantera del sensor HC-SR04 y la pared. La evidencia esperada es una distancia de reposo de aproximadamente 14.0 cm a 14.9 cm (teniendo en cuenta la inercia física después de la activación a los 15.0 cm).
Código de la capa de abstracción de hardware
Guarda el siguiente código como ugv_hardware.py. Este archivo maneja la manipulación directa de GPIO o la salida simulada (mock) dependiendo de cómo sea invocado por el script principal.
"""
ugv_hardware.py
Hardware Abstraction Layer for the UGV.
Provides physical and mock implementations for the TB6612FNG and HC-SR04.
"""
try:
from gpiozero import PWMOutputDevice, DigitalOutputDevice, DistanceSensor
HAS_GPIO = True
except ImportError:
HAS_GPIO = False
class BaseMotorDriver:
"""Abstract base class for a dual motor driver."""
def forward(self, speed_percent: float) -> None:
pass
def stop(self) -> None:
pass
class BaseUltrasonic:
"""Abstract base class for an ultrasonic distance sensor."""
def get_distance_cm(self) -> float:
return 0.0
class MockMotorDriver(BaseMotorDriver):
"""Simulated motor driver for dry-run testing."""
def forward(self, speed_percent: float) -> None:
print(f"[MOCK MOTOR] Moving FORWARD at {speed_percent}% speed.")
def stop(self) -> None:
print("[MOCK MOTOR] HALTED.")
class MockUltrasonic(BaseUltrasonic):
"""Simulated ultrasonic sensor that gradually approaches an obstacle."""
def __init__(self) -> None:
self.tick_count = 0
self.starting_distance = 35.0 # Start at 35 cm
def get_distance_cm(self) -> float:
self.tick_count += 1
# Simulate moving 2 cm closer each tick
current_distance = self.starting_distance - (self.tick_count * 2.0)
if current_distance < 5.0:
current_distance = 5.0
return current_distance
class PhysicalMotorDriver(BaseMotorDriver):
"""Physical motor driver using gpiozero for TB6612FNG."""
def __init__(self) -> None:
if not HAS_GPIO:
raise RuntimeError("gpiozero library not found. Cannot use physical hardware.")
# Left motor
self.pwma = PWMOutputDevice(12)
self.ain1 = DigitalOutputDevice(5)
self.ain2 = DigitalOutputDevice(6)
# Right motor
self.pwmb = PWMOutputDevice(13)
self.bin1 = DigitalOutputDevice(16)
self.bin2 = DigitalOutputDevice(26)
# Standby
self.stby = DigitalOutputDevice(17)
self.stby.on() # Enable driver
def forward(self, speed_percent: float) -> None:
speed = max(0.0, min(1.0, speed_percent / 100.0))
self.ain1.on()
self.ain2.off()
self.pwma.value = speed
self.bin1.on()
self.bin2.off()
self.pwmb.value = speed
def stop(self) -> None:
self.pwma.value = 0.0
self.pwmb.value = 0.0
self.ain1.off()
self.ain2.off()
self.bin1.off()
self.bin2.off()
class PhysicalUltrasonic(BaseUltrasonic):
"""Physical ultrasonic sensor using gpiozero for HC-SR04."""
def __init__(self) -> None:
if not HAS_GPIO:
raise RuntimeError("gpiozero library not found. Cannot use physical hardware.")
# max_distance=2.0 meters provides enough range for a 15cm threshold
self.sensor = DistanceSensor(echo=24, trigger=23, max_distance=2.0)
def get_distance_cm(self) -> float:
# DistanceSensor returns distance in meters, convert to cm
return self.sensor.distance * 100.0
Código de la lógica de control principal
Guarda el siguiente código como ugv_main.py en el mismo directorio. Este script contiene la lógica de sondeo a 10Hz y el umbral para evitar obstáculos.
"""
ugv_main.py
Main control logic for the UGV obstacle avoidance.
"""
import time
import argparse
import sys
from ugv_hardware import (
MockMotorDriver, MockUltrasonic,
PhysicalMotorDriver, PhysicalUltrasonic, HAS_GPIO
)
def main() -> None:
parser = argparse.ArgumentParser(description="UGV Obstacle Avoidance Control")
parser.add_argument("--dry-run", action="store_true", help="Run with mock hardware")
args = parser.parse_args()
if args.dry_run:
print("Initializing MOCK hardware...")
motor = MockMotorDriver()
sensor = MockUltrasonic()
else:
if not HAS_GPIO:
print("Error: gpiozero not installed. Run with --dry-run or install gpiozero.")
sys.exit(1)
print("Initializing PHYSICAL hardware...")
motor = PhysicalMotorDriver()
sensor = PhysicalUltrasonic()
threshold_cm = 15.0
polling_rate_hz = 10.0
sleep_interval = 1.0 / polling_rate_hz
print("Starting UGV autonomous navigation...")
try:
while True:
distance = sensor.get_distance_cm()
print(f"Distance: {distance:.1f} cm")
if distance < threshold_cm:
print("OBSTACLE DETECTED! Executing emergency halt.")
motor.stop()
if args.dry_run:
# Break out of loop for automated dry-run validation
print("Dry-run test complete.")
break
else:
motor.forward(50.0) # Drive forward at 50% speed
time.sleep(sleep_interval)
except KeyboardInterrupt:
print("\nManual override triggered. Shutting down.")
finally:
motor.stop()
print("UGV safely halted.")
if __name__ == "__main__":
main()
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.




