Objetivo y caso de uso
Qué construirás: Construirás un prototipo de Vehículo Terrestre No Tripulado (UGV) guiado por pista que utiliza sensores de reflectancia infrarroja para navegar autónomamente por una ruta de alto contraste. El sistema aprovecha el control de lazo cerrado para ajustar continuamente la cinemática de tracción diferencial en tiempo real.
Por qué es importante / Casos de uso
- Transporte automatizado en almacenes: Los AGV del mundo real utilizan el seguimiento óptico de líneas para el enrutamiento predecible de materiales con una sobrecarga de computación casi nula.
- Entregas en hospitales y ensamblaje: Los robots navegan por pasillos predefinidos de manera confiable sin requerir algoritmos SLAM complejos, ahorrando >90% de utilización de CPU/GPU.
- Robótica educativa: Proporciona una plataforma determinista y altamente observable para dominar los sistemas de control de lazo cerrado y el sondeo de sensores.
Resultado esperado
- Seguimiento continuo de la ruta: El UGV seguirá suavemente una línea oscura sobre una superficie clara, ajustando dinámicamente las velocidades de las ruedas izquierda y derecha con una latencia del lazo de control inferior a 10ms.
- Mecanismo de seguridad automático: El robot detendrá automáticamente toda la salida de los motores en 50ms si se pierde la pista por completo, evitando escenarios de descontrol.
Audiencia: Desarrolladores de sistemas embebidos e ingenieros en robótica; Nivel: Intermedio
Arquitectura/flujo: Matriz de sensores IR → Microcontrolador (sondeo ADC y lazo de control PID a 100Hz) → Controlador de motores → Motores de CC de tracción diferencial
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, 3 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 la configuración, verifique la distribución de pines de su revisión exacta de la placa ULX3S, mantenga las señales de E/S de la FPGA a 3.3 V, nunca conecte 5 V directamente a los pines de E/S, desconecte la alimentación antes de cambiar el cableado y use fuentes externas adecuadas para cargas, motores o servos, compartiendo la tierra 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
- Una Raspberry Pi 4 Modelo B con Raspberry Pi OS Bookworm (64-bit).
- Python 3.11 instalado (
python3 --version). - Comprensión básica de la ejecución en línea de comandos y SSH.
- Familiaridad con el cableado de componentes electrónicos usando cables puente (jumper).
- La biblioteca de Python
gpiozeroinstalada (pip install gpiozero).
Materiales
- Microcontrolador: Raspberry Pi 4 Modelo B.
- Controlador de motores: Placa de conexión del controlador de motor dual TB6612FNG (más eficiente que el antiguo L298N).
- Sensor: Matriz de sensores de línea TCRT5000 (típicamente de 3 a 5 canales; este tutorial utiliza una configuración de 3 canales para Izquierda, Centro y Derecha).
- Chasis: Chasis UGV 2WD estándar (incluye dos motores de engranajes de CC, ruedas y una rueda loca/giratoria).
- Fuente de alimentación 1 (Lógica): Batería externa (power bank) USB-C de 5V para alimentar la Raspberry Pi.
- Fuente de alimentación 2 (Motores): Paquete de baterías de 6V-9V (por ejemplo, 4 celdas AA o 2 celdas 18650) para alimentar los motores de CC.
- Accesorios: Protoboard, cables puente hembra-hembra y macho-hembra, cinta eléctrica negra (para la pista) y una cartulina blanca grande o un piso de color claro.
Configuración/Conexión
La configuración del hardware aísla el voltaje lógico (3.3V/5V) del voltaje del motor (6V-9V) para proteger la Raspberry Pi. El controlador TB6612FNG requiere señales PWM para el control de velocidad y señales digitales para la dirección. Los sensores TCRT5000 emiten señales digitales altas/bajas basadas en un umbral de comparador incorporado (generalmente ajustable a través de un potenciómetro en la placa del sensor).
Raspberry Pi a Controlador de Motores TB6612FNG
| Pin TB6612FNG | Pin Raspberry Pi | Función |
|---|---|---|
| VCC | 3.3V (Pin 1) | Voltaje lógico para el CI del controlador |
| VMOT | Batería del Motor (+) | Fuente de alimentación para los motores de CC (NO conectar a la Pi) |
| GND | GND (Pin 6) y Batería (-) | Tierra común (La Pi y la batería deben compartir GND) |
| PWMA | GPIO 17 (Pin 11) | Control de velocidad para el Motor Izquierdo |
| AIN1 | GPIO 27 (Pin 13) | Control de dirección 1 para el Motor Izquierdo |
| AIN2 | GPIO 22 (Pin 15) | Control de dirección 2 para el Motor Izquierdo |
| PWMB | GPIO 18 (Pin 12) | Control de velocidad para el Motor Derecho |
| BIN1 | GPIO 23 (Pin 16) | Control de dirección 1 para el Motor Derecho |
| BIN2 | GPIO 24 (Pin 18) | Control de dirección 2 para el Motor Derecho |
| STBY | 3.3V (Pin 17) | Pin de espera (en alto para habilitar el controlador) |
| 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 |
Raspberry Pi a Matriz de Sensores TCRT5000
Nota: La mayoría de las matrices TCRT5000 emiten un LOW (0) digital cuando se refleja en una superficie clara y un HIGH (1) digital cuando absorbe luz en una línea oscura. Verifique la lógica de su sensor específico.
| Pin TCRT5000 | Pin Raspberry Pi | Función |
|---|---|---|
| VCC | 3.3V (Pin 1) | Alimentación para emisores IR y comparadores |
| GND | GND (Pin 9) | Tierra |
| OUT1 (Izquierda) | GPIO 5 (Pin 29) | Salida digital del sensor izquierdo |
| OUT2 (Centro) | GPIO 6 (Pin 31) | Salida digital del sensor central |
| OUT3 (Derecha) | GPIO 13 (Pin 33) | Salida digital del sensor derecho |
Código validado
El software está dividido en dos archivos. El primer archivo (ugv_hardware.py) actúa como una Capa de Abstracción de Hardware (HAL). Maneja las interacciones físicas de los GPIO y proporciona clases simuladas (mock) para pruebas en seco (dry-run). El segundo archivo (ugv_line_follower.py) contiene la lógica de control.
1. Capa de Abstracción de Hardware
Crea un archivo llamado ugv_hardware.py.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
"""
ugv_hardware.py
Hardware Abstraction Layer for 2WD UGV with TB6612FNG and TCRT5000.
Includes mock classes for off-hardware dry-run validation.
"""
import time
try:
from gpiozero import PWMOutputDevice, DigitalOutputDevice, DigitalInputDevice
GPIO_AVAILABLE = True
except ImportError:
GPIO_AVAILABLE = False
class MockMotor:
"""Mock motor class for dry-run validation."""
def __init__(self, name):
self.name = name
self.current_speed = 0.0
def drive(self, speed):
# Constrain speed between -1.0 and 1.0
self.current_speed = max(min(speed, 1.0), -1.0)
direction = "FORWARD" if self.current_speed > 0 else "REVERSE" if self.current_speed < 0 else "STOPPED"
print(f"[MOCK] {self.name} Motor -> Speed: {abs(self.current_speed):.2f} | Dir: {direction}")
class MockSensorArray:
"""Mock sensor array that cycles through predefined track states."""
def __init__(self):
# (Left, Center, Right) - 1 means line detected, 0 means no line
self.test_sequence = [
(0, 1, 0), # Centered
(1, 1, 0), # Drifting slightly right (left sensor hits line)
(1, 0, 0), # Drifting hard right
(0, 1, 0), # Centered again
(0, 0, 1), # Drifting hard left
(0, 0, 0), # Line lost
]
self.step = 0
def read_sensors(self):
state = self.test_sequence[self.step % len(self.test_sequence)]
self.step += 1
print(f"[MOCK] Sensors read (L, C, R): {state}")
return state
class RealMotor:
"""Real implementation for TB6612FNG motor control using gpiozero."""
# ... continúa para miembros en el código completo validado ..."""
ugv_hardware.py
Hardware Abstraction Layer for 2WD UGV with TB6612FNG and TCRT5000.
Includes mock classes for off-hardware dry-run validation.
"""
import time
try:
from gpiozero import PWMOutputDevice, DigitalOutputDevice, DigitalInputDevice
GPIO_AVAILABLE = True
except ImportError:
GPIO_AVAILABLE = False
class MockMotor:
"""Mock motor class for dry-run validation."""
def __init__(self, name):
self.name = name
self.current_speed = 0.0
def drive(self, speed):
# Constrain speed between -1.0 and 1.0
self.current_speed = max(min(speed, 1.0), -1.0)
direction = "FORWARD" if self.current_speed > 0 else "REVERSE" if self.current_speed < 0 else "STOPPED"
print(f"[MOCK] {self.name} Motor -> Speed: {abs(self.current_speed):.2f} | Dir: {direction}")
class MockSensorArray:
"""Mock sensor array that cycles through predefined track states."""
def __init__(self):
# (Left, Center, Right) - 1 means line detected, 0 means no line
self.test_sequence = [
(0, 1, 0), # Centered
(1, 1, 0), # Drifting slightly right (left sensor hits line)
(1, 0, 0), # Drifting hard right
(0, 1, 0), # Centered again
(0, 0, 1), # Drifting hard left
(0, 0, 0), # Line lost
]
self.step = 0
def read_sensors(self):
state = self.test_sequence[self.step % len(self.test_sequence)]
self.step += 1
print(f"[MOCK] Sensors read (L, C, R): {state}")
return state
class RealMotor:
"""Real implementation for TB6612FNG motor control using gpiozero."""
def __init__(self, pwm_pin, in1_pin, in2_pin):
self.pwm = PWMOutputDevice(pwm_pin)
self.in1 = DigitalOutputDevice(in1_pin)
self.in2 = DigitalOutputDevice(in2_pin)
def drive(self, speed):
speed = max(min(speed, 1.0), -1.0)
if speed > 0:
self.in1.on()
self.in2.off()
self.pwm.value = speed
elif speed < 0:
self.in1.off()
self.in2.on()
self.pwm.value = abs(speed)
else:
self.in1.off()
self.in2.off()
self.pwm.value = 0.0
class RealSensorArray:
"""Real implementation for TCRT5000 array using gpiozero."""
def __init__(self, left_pin, center_pin, right_pin):
self.left = DigitalInputDevice(left_pin)
self.center = DigitalInputDevice(center_pin)
self.right = DigitalInputDevice(right_pin)
def read_sensors(self):
# Assuming sensor outputs 1 when dark line is detected
return (self.left.value, self.center.value, self.right.value)
def get_hardware(dry_run=False):
"""Factory function to return hardware interfaces based on execution mode."""
if dry_run or not GPIO_AVAILABLE:
if not dry_run:
print("Warning: gpiozero not found. Defaulting to dry-run mode.")
left_motor = MockMotor("Left")
right_motor = MockMotor("Right")
sensors = MockSensorArray()
return left_motor, right_motor, sensors
# Hardware pin mapping based on setup table
left_motor = RealMotor(pwm_pin=17, in1_pin=27, in2_pin=22)
right_motor = RealMotor(pwm_pin=18, in1_pin=23, in2_pin=24)
sensors = RealSensorArray(left_pin=5, center_pin=6, right_pin=13)
return left_motor, right_motor, sensors
2. Lógica de control del seguidor de línea
Crea un archivo llamado ugv_line_follower.py.
Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.
"""
ugv_line_follower.py
Main control loop for the UGV line following prototype.
Reads sensor states and applies differential steering.
"""
import argparse
import time
import sys
from ugv_hardware import get_hardware
# Configuration parameters
BASE_SPEED = 0.5 # Normal forward speed (0.0 to 1.0)
TURN_SPEED = 0.6 # Speed of the outer wheel during a turn
REDUCE_SPEED = 0.2 # Speed of the inner wheel during a turn
LOOP_DELAY = 0.1 # Delay between control loops in seconds
def follow_line(left_motor, right_motor, sensors, max_iterations=None):
"""
Main control loop. Evaluates sensor states to determine motor speeds.
"""
print("--- Starting UGV Line Follower ---")
print("Press Ctrl+C to stop.")
iterations = 0
try:
while True:
if max_iterations and iterations >= max_iterations:
print("Max iterations reached. Stopping.")
break
left_val, center_val, right_val = sensors.read_sensors()
# State 1: Centered on the line
if center_val == 1 and left_val == 0 and right_val == 0:
left_motor.drive(BASE_SPEED)
right_motor.drive(BASE_SPEED)
# State 2: Drifting Right (Left sensor sees line)
elif left_val == 1 and right_val == 0:
# Turn Left: Right motor fast, Left motor slow
left_motor.drive(REDUCE_SPEED)
right_motor.drive(TURN_SPEED)
# State 3: Drifting Left (Right sensor sees line)
elif right_val == 1 and left_val == 0:
# Turn Right: Left motor fast, Right motor slow
left_motor.drive(TURN_SPEED)
# ... continúa para miembros en el código completo validado ..."""
ugv_line_follower.py
Main control loop for the UGV line following prototype.
Reads sensor states and applies differential steering.
"""
import argparse
import time
import sys
from ugv_hardware import get_hardware
# Configuration parameters
BASE_SPEED = 0.5 # Normal forward speed (0.0 to 1.0)
TURN_SPEED = 0.6 # Speed of the outer wheel during a turn
REDUCE_SPEED = 0.2 # Speed of the inner wheel during a turn
LOOP_DELAY = 0.1 # Delay between control loops in seconds
def follow_line(left_motor, right_motor, sensors, max_iterations=None):
"""
Main control loop. Evaluates sensor states to determine motor speeds.
"""
print("--- Starting UGV Line Follower ---")
print("Press Ctrl+C to stop.")
iterations = 0
try:
while True:
if max_iterations and iterations >= max_iterations:
print("Max iterations reached. Stopping.")
break
left_val, center_val, right_val = sensors.read_sensors()
# State 1: Centered on the line
if center_val == 1 and left_val == 0 and right_val == 0:
left_motor.drive(BASE_SPEED)
right_motor.drive(BASE_SPEED)
# State 2: Drifting Right (Left sensor sees line)
elif left_val == 1 and right_val == 0:
# Turn Left: Right motor fast, Left motor slow
left_motor.drive(REDUCE_SPEED)
right_motor.drive(TURN_SPEED)
# State 3: Drifting Left (Right sensor sees line)
elif right_val == 1 and left_val == 0:
# Turn Right: Left motor fast, Right motor slow
left_motor.drive(TURN_SPEED)
right_motor.drive(REDUCE_SPEED)
# State 4: Intersection or perpendicular line (All sensors see line)
elif left_val == 1 and center_val == 1 and right_val == 1:
print("Intersection detected. Stopping for safety.")
left_motor.drive(0.0)
right_motor.drive(0.0)
break
# State 5: Line completely lost (No sensors see line)
elif left_val == 0 and center_val == 0 and right_val == 0:
print("Line lost. Halting.")
left_motor.drive(0.0)
right_motor.drive(0.0)
break
else:
# Catch-all for ambiguous states (e.g., 1, 0, 1) - maintain base speed cautiously
left_motor.drive(BASE_SPEED * 0.5)
right_motor.drive(BASE_SPEED * 0.5)
iterations += 1
time.sleep(LOOP_DELAY)
except KeyboardInterrupt:
print("\nManual interrupt received.")
finally:
# Failsafe: Ensure motors are stopped on exit
print("Shutting down motors.")
left_motor.drive(0.0)
right_motor.drive(0.0)
def main():
parser = argparse.ArgumentParser(description="UGV Line Follower Control")
parser.add_argument("--dry-run", action="store_true", help="Run without hardware using mock interfaces")
parser.add_argument("--self-test", action="store_true", help="Run a short automated test sequence and exit")
args = parser.parse_args()
# Initialize hardware or mocks
left_motor, right_motor, sensors = get_hardware(dry_run=args.dry_run)
# Determine execution length
max_iters = 6 if args.self-test else None
# Execute control loop
follow_line(left_motor, right_motor, sensors, max_iterations=max_iters)
if __name__ == "__main__":
main()
Comandos de compilación/flasheo/ejecución
Usa los siguientes comandos para validar y ejecutar tu código.
| Comando | Propósito |
|---|---|
pip install gpiozero | Instala la biblioteca de control de hardware requerida. |
python3 ugv_line_follower.py --dry-run --self-test | Ejecuta la secuencia de validación simulada localmente sin mover el chasis. |
python3 ugv_line_follower.py | Ejecuta el bucle real de seguimiento de línea en el hardware físico. |
Flujo de trabajo:
1. Conéctate a tu Raspberry Pi a través de SSH o abre un terminal local.
2. Crea los dos archivos de Python (ugv_hardware.py y ugv_line_follower.py) en el mismo directorio.
3. Ejecuta primero el comando de prueba en seco (dry-run) para verificar tu entorno de Python y el flujo lógico.
4. Eleva el chasis del UGV para que las ruedas no toquen el suelo (por ejemplo, colócalo sobre un bloque).
5. Ejecuta el comando de ejecución en vivo (python3 ugv_line_follower.py).
6. Pasa manualmente un trozo de cinta negra debajo de los sensores para verificar que las ruedas responden correctamente.
7. Coloca el UGV en tu pista de prueba y ejecuta el comando en vivo de nuevo.
Validación paso a paso
Usa estos puntos de control para asegurarte de que tu prototipo funciona correctamente.
- Validación de software en seco (Dry-Run)
- Acción: Ejecuta
python3 ugv_line_follower.py --dry-run --self-test. - Observación esperada: La consola imprime una secuencia de lecturas de sensores simuladas
(0, 1, 0),(1, 1, 0), etc., seguida de las velocidades de los motores correspondientes (por ejemplo, Motor Izquierdo -> Velocidad: 0.50, Motor Derecho -> Velocidad: 0.50). - Condición de aprobación: El script completa la secuencia de 6 pasos y sale limpiamente, imprimiendo «Shutting down motors.»
- Comprobación de calibración de sensores
- Acción: Enciende la Pi y coloca el UGV sobre una superficie blanca.
- Observación esperada: Los LED indicadores en la parte posterior de los módulos TCRT5000 deberían encenderse, indicando reflexión.
- Condición de aprobación: Al mover un trozo de cinta negra debajo de un sensor, su LED indicador se apaga (o cambia su estado dependiendo del módulo). Ajusta el potenciómetro del módulo si es necesario.
- Prueba de banco (Ruedas elevadas)
- Acción: Apoya el UGV
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.




