Robótica • Ingeniería y programación

Carro de rescate / sigue líneas

ESP32-DO · PCB perforada · dos motores N20 con encoder · cinco detectores inferiores · dos sensores de color · ToF frontal · batería 2S

1. Lista de componentes y montaje

2N20 con encoder
5Detectores de línea
2S18650 con BMS

Esta propuesta es un seguidor de línea con detección de colores y obstáculos, adaptable a rescate. No equivale todavía a un robot que localiza y transporta víctimas; para eso faltan actuadores y reglas concretas.

ComponenteCant.Especificación / montaje
Controladora1ESP32-DO (NoLogo), dos motores integrados; confirmar límites de corriente
Chasis1PCB perforada 7 × 9 cm, refuerzos, separadores y tornillería
Tracción22 motorreductores N20 de 6 V con encoder de cuadratura A/B, ruedas de goma; ajustar RPM y corriente al driver
Detección frontal1VL53L0X ToF de la fotografía; requiere adaptar el controlador VL53L1X del código
Línea5Barra NULLLAB I²C de cinco sensores, según fotografía; requiere obtener mapa de registros/biblioteca. El código provisional usa cinco sensores discretos con MCP23017
Color2AS7341 de la fotografía, izquierdo y derecho, orientados al suelo; adaptar controlador de color del código
Bus I²C1Multiplexor TCA9548A para ToF y dos sensores de color
Entradas1MCP23017 para cinco sensores digitales
Energía1Paquete 2S de 18650, BMS balanceado, cargador externo CC/CV 8,4 V y conector polarizado
Arranque1Pulsador START; interruptor, fusible y fijaciones

Componentes reales seleccionados

Infografía con seis componentes del robot: sensor VL53L0X, barra de cinco infrarrojos, sensor AS7341, controladora ESP32-DO, PCB perforada 7 por 9 cm y motor N20 con encoder y rueda
Infografía general a partir de las fotografías de los componentes seleccionados. Más abajo encontrarás cada fotografía individual y su referencia de compra.
Correspondencia entre fotografías y código: el módulo láser mostrado es VL53L0X (el programa anterior usa una biblioteca para VL53L1X); el sensor de color mostrado es AS7341 (el programa anterior usa TCS34725); la barra NULLLAB de cinco sensores se comunica por I²C (el código anterior utiliza cinco salidas digitales a través de MCP23017). No son sustituciones intercambiables sin adaptar bibliotecas, lecturas y pines. El código siguiente se conserva como prototipo anterior, no como firmware listo para esos tres módulos específicos.

Catálogo visual de componentes y búsquedas en AliExpress

Precios referenciales: rangos orientativos en CLP para presupuestar, no son cotizaciones verificadas de AliExpress. Cada enlace abre una búsqueda en AliExpress (no un anuncio validado); consulta precio, envío, impuestos y especificación exacta antes de comprar. Las imágenes corresponden a referencias visuales del proyecto, no necesariamente al artículo exacto de una tienda.
Baterías 18650 2S + soporte

Baterías 18650 2S + soporte

Cantidad: 2 celdas

Presupuesto estimado: CLP 10.000–22.000 conjunto

Buscar en AliExpress ↗
Componente electrónico

Cargador Li-ion CC/CV 8,4 V

Cantidad: 1

Presupuesto estimado: CLP 5.000–12.000

Buscar en AliExpress ↗
Componente electrónico

Multiplexor TCA9548A I²C

Cantidad: 1

Presupuesto estimado: CLP 2.000–5.000

Buscar en AliExpress ↗
Componente electrónico

Ruedas para N20 y rueda loca

Cantidad: set

Presupuesto estimado: CLP 4.000–12.000

Buscar en AliExpress ↗

Presupuesto de referencia: CLP 80.000–170.000 aproximadamente para un montaje completo, sujeto a variante, calidad y lo que ya tengas. No sumar nuevamente piezas que ya posees.

Distribución sugerida

Cuatro vistas de la propuesta de carro seguidor de líneas con ESP32-DO, PCB perforada, motores N20 y batería 2S
Ilustración generada durante el desarrollo del proyecto: propuesta general de posición de placa, sensores y alimentación. Es una referencia visual, no un plano con medidas verificadas.
Motor N20 con rueda y soportes
Referencia del motor N20; el modelo a adquirir debe incluir encoder magnético Hall de dos canales A/B y seis conductores (dos de motor y cuatro de encoder).
Barra I2C de cinco detectores de línea
Referencia de la barra I²C de cinco sensores; su interfaz requiere adaptar el código, que por ahora usa cinco sensores digitales equivalentes.
VISTA SUPERIORVISTA INFERIOR ESP32-DO2 motores2S 18650BMS + cargadorToFfrontalAVANCE ↑ Barra de cinco detectoresColores I y DPack 2Sen posición protegidaEsquema ilustrativo · conexiones detalladas en tabla · comprobar cotas y rozamiento
Importante sobre la barra de cinco sensores fotografiada: el módulo NULLLAB del usuario se comunica por I²C. Su mapa de registros no se ha confirmado; por eso este código usa cinco salidas digitales de reflectancia leídas por MCP23017. Es un reemplazo funcional, no una afirmación sobre la interfaz interna del módulo fotografiado. Para integrar ese módulo concreto hay que obtener su biblioteca.
En el código antiguo, los dos sensores TCS34725 registran lecturas RGB para calibración; todavía no ejecutan clasificación de víctimas. En el código antiguo, el VL53L1X provoca una parada ante obstáculo, no navegación alrededor de él.

2. Diagrama de conexiones y alimentación

Las tablas indican conexiones propuestas; no sustituyen el esquema eléctrico oficial de cada revisión. Todos los módulos comparten GND y las señales de ESP32 deben permanecer a 3,3 V.

2 × 186502S · 8,4 V máx.BMS 2Scon balanceoFusible +interruptorReguladoressegún motoresMotores / ESP32y sensoresCargador 8,4 V CC/CVPuerto de carga polarizado conectado según terminales del BMS; no cargar durante el combate.
Origen / pinesDestinoObservación
ESP32-DO M1Motor izquierdoControl interno GPIO2 / GPIO4
ESP32-DO M2Motor derechoControl interno GPIO13 / GPIO27
GPIO16 / 25Encoder izquierdo A/B del N20 con encoderComprobar cableado 3,3 V
GPIO17 / 26Encoder derecho A/B del N20 con encoderComprobar cableado 3,3 V
GPIO21 / 22SDA / SCL bus I²CConfirmar disponibilidad en revisión física
TCA canal 0VL53L1X frontalSDA/SCL y alimentación del breakout
TCA canal 1 / 2TCS34725 I / DUn sensor por canal: ambos 0x29
I²C 0x20MCP23017GPA0–GPA4: cinco salidas DO de sensores de línea
GPIO5START hacia GNDINPUT_PULLUP; verificar libre y expuesto
Pack 2S / BMSFuente del robotSalida protegida, fusible, interruptor; motores compatibles
Cargador externoPuerto de carga8,4 V CC/CV, solo con robot apagado
Seguridad 2S: un BMS no es un cargador. Usa cargador Li-ion 2S CC/CV 8,4 V y terminación automática. Comprueba la corriente de bloqueo de los motores, límites de drivers y tensión del regulador. La carga se realiza apagado y bajo supervisión.

3. Código de funcionamiento (prototipo anterior, requiere adaptación)

Programa Arduino/C++ para Visual Studio Code con PlatformIO o Arduino IDE. Requiere las bibliotecas indicadas en los comentarios del archivo. Se entrega como base de desarrollo, sin validación física.

Descargar .ino
// RESCATE / SIGUELINEAS - ESP32-DO. Arduino-ESP32 core 3.x
// Requiere: Adafruit MCP23X17, Adafruit TCS34725, Adafruit BusIO,
// Pololu VL53L1X, Wire. 5 detectores digitales en MCP23017.
// Los sensores de color van en canales 1 y 2 del TCA9548A; ToF canal 0.
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <Adafruit_TCS34725.h>
#include <VL53L1X.h>

constexpr int SDA_PIN=21, SCL_PIN=22; // VERIFICAR acceso en la revisión ESP32-DO
constexpr uint8_t TCA=0x70;
constexpr int MOTOR_PINS[2][2]={{2,4},{13,27}};
constexpr int ENC_A[2]={16,17};
constexpr int ENC_B[2]={25,26};
constexpr bool MOTOR_INV[2]={false,true}; // ajustar sentido físicamente
constexpr int PWM_HZ=16000, PWM_BITS=8;
constexpr int LINE_WHITE_HIGH=1; // 1: DO HIGH sobre blanco; invertir si corresponde
constexpr int PIN_START=5; // START a GND; verificar pin expuesto/libre
constexpr int BASE_PWM=105, MAX_PWM=190;
// Encoders Hall A/B de motores N20: corrección proporcional básica de sincronía.
// PULSOS_POR_REV_EJE depende de reducción y encoder y debe calibrarse.
constexpr float K_SYNC=0.7f;
constexpr int MAX_SYNC_TRIM=18;
constexpr float KP=26.0f, KD=13.0f;
constexpr uint16_t OBSTACLE_MM=140;
Adafruit_MCP23X17 io;
VL53L1X tof;
Adafruit_TCS34725 colors[2]={
 Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_24MS,TCS34725_GAIN_4X),
 Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_24MS,TCS34725_GAIN_4X)};
volatile int32_t encCount[2]={0,0};
void IRAM_ATTR encoderL(){encCount[0]++;}
void IRAM_ATTR encoderR(){encCount[1]++;}
void selectBus(uint8_t channel){Wire.beginTransmission(TCA);Wire.write(1<<channel);Wire.endTransmission();}
void moveMotor(int idx,int value){
 value=constrain(value,-255,255); if(MOTOR_INV[idx]) value=-value;
 int a=MOTOR_PINS[idx][0],b=MOTOR_PINS[idx][1];
 if(value>=0){ledcWrite(a,value);ledcWrite(b,0);}
 else{ledcWrite(a,0);ledcWrite(b,-value);}
}
void drive(int left,int right){moveMotor(0,left);moveMotor(1,right);}
void stopDrive(){drive(0,0);}
void haltFault(const char *message){stopDrive();Serial.println(message);while(true){delay(500);}}

// 5 canales de reflectancia en MCP GPA0..4, orden izquierda a derecha
// Calibrar LINE_WHITE_HIGH: otros sensores entregan LOW sobre blanco.
int lineError(bool &detected){
 const int weights[5]={-2,-1,0,1,2};int sum=0,count=0;
 for(int i=0;i<5;i++){
   bool black=(io.digitalRead(i)!=LINE_WHITE_HIGH);
   if(black){sum+=weights[i];count++;}
 }
 detected=(count>0);
 return count?sum/count:0;
}
bool obstacle(){
 selectBus(0);
 uint16_t mm=tof.read();
 return !tof.timeoutOccurred() && mm>20 && mm<OBSTACLE_MM;
}
void inspectColors(){ // indicador diagnóstico; no interpreta víctimas sin calibración
 for(int n=0;n<2;n++){
  selectBus(n+1);uint16_t r,g,b,c;
  colors[n].getRawData(&r,&g,&b,&c);
  Serial.printf("Color %d: R%u G%u B%u C%u ",n,r,g,b,c);
 }
 Serial.println();
}
void setup(){
 Serial.begin(115200);
 pinMode(PIN_START,INPUT_PULLUP);
 for(int i=0;i<2;i++){
   ledcAttach(MOTOR_PINS[i][0],PWM_HZ,PWM_BITS);
   ledcAttach(MOTOR_PINS[i][1],PWM_HZ,PWM_BITS);
   pinMode(ENC_A[i],INPUT_PULLUP); pinMode(ENC_B[i],INPUT_PULLUP);
 }
 attachInterrupt(digitalPinToInterrupt(ENC_A[0]),encoderL,RISING);
 attachInterrupt(digitalPinToInterrupt(ENC_A[1]),encoderR,RISING);
 stopDrive();
 Wire.begin(SDA_PIN,SCL_PIN); Wire.setClock(100000);
 if(!io.begin_I2C(0x20,&Wire)) haltFault("MCP23017 no detectado");
 for(int i=0;i<5;i++)io.pinMode(i,INPUT_PULLUP);
 selectBus(0);tof.setTimeout(150);
 if(!tof.init()) haltFault("VL53L1X no detectado");
 tof.setDistanceMode(VL53L1X::Short);
 tof.setMeasurementTimingBudget(20000);tof.startContinuous(30);
 for(int i=0;i<2;i++){selectBus(i+1);if(!colors[i].begin())haltFault("Sensor de color no detectado");}
 Serial.println("Listo. Verificar blanco/negro; pulse START.");
 while(digitalRead(PIN_START)==HIGH){delay(10);}
 while(digitalRead(PIN_START)==LOW){delay(10);}
 delay(300); // SOLO antirrebote, configurar cuenta reglamentaria si corresponde
}
float lastError=0;int lastDir=1;unsigned long lastColor=0,lastTof=0;
unsigned long lastEncCheck=0;
int32_t lastEncLeft=0,lastEncRight=0;
int syncTrim=0;
void updateEncoderSync(unsigned long now){
  if(now-lastEncCheck<100)return;
  // Contadores de interrupción de dos N20 con encoder; comparar avance por ventana.
  noInterrupts();int32_t l=encCount[0],r=encCount[1];interrupts();
  int32_t dl=l-lastEncLeft,dr=r-lastEncRight;
  lastEncLeft=l;lastEncRight=r;lastEncCheck=now;
  syncTrim=constrain((int)(K_SYNC*(dl-dr)),-MAX_SYNC_TRIM,MAX_SYNC_TRIM);
  // Ajuste aproximado: no detecta patinaje ni sustituye calibración PID.
}
bool detectedObstacle=false;
void loop(){
 unsigned long now=millis();
 // ToF: lecturas a ~35 ms; bloqueo I2C acotado con timeout.
 if(now-lastTof>=40){lastTof=now;detectedObstacle=obstacle();}
 updateEncoderSync(now);
 if(detectedObstacle){stopDrive();delay(30);return;} // Parada; evasión es mejora futura
 bool seen=false;int error=lineError(seen);
 if(!seen){drive(-65*lastDir,65*lastDir);delay(8);return;}
 if(error!=0)lastDir=(error>0?1:-1);
 float u=KP*error+KD*(error-lastError); lastError=error;
 int left=constrain((int)(BASE_PWM+u),-MAX_PWM,MAX_PWM);
 int right=constrain((int)(BASE_PWM-u),-MAX_PWM,MAX_PWM);
 // Compensación de encoder solo cuando la línea está razonablemente centrada.
 if(abs(error)==0){left=constrain(left-syncTrim,0,MAX_PWM);right=constrain(right+syncTrim,0,MAX_PWM);}
 drive(left,right);
 if(now-lastColor>550){lastColor=now;inspectColors();}
 delay(8);
}

4. Posibles mejoras

El programa actual sigue la línea y se detiene si el sensor frontal ve un obstáculo. Primero conviene afinar ese seguimiento. Después, el mismo carro puede afrontar dos tareas nuevas en la pista.

Ajustes del seguidor

Comportamientos futuros en la pista

Resolver un laberinto. El carro podría recorrer un laberinto dibujado en la pista, de líneas o de paredes bajas. Con los cinco detectores, el sensor de distancia frontal y los encoders registraría cada cruce y el sentido tomado, para volver atrás y buscar la salida. El primer ensayo puede ser un laberinto corto de una sola línea; después, uno con paredes y callejones.
Detectar un objeto y llevarlo de un lugar a otro. El carro reconocería un objeto sobre la pista —por distancia y, si hace falta, por una marca de color en el suelo—, lo tomaría con una pinza o una bandeja y lo dejaría en otro punto marcado. Sirve como traslado entre dos puestos de la pista y, más adelante, como base del rescate: localizar una víctima y transportarla según el reglamento de la competencia.

Secuencia de pruebas

  1. Probar motores con ruedas levantadas y comprobar sentido de giro.
  2. Verificar sensores de línea, color y distancia antes de poner el carro en la pista.
  3. Calibrar blanco, negro y la distancia de parada; ensayar el arranque con START.
  4. Probar con batería protegida, observar calentamiento y registrar resultados.