
1. Lista de componentes y montaje
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.
| Componente | Cant. | Especificación / montaje |
|---|---|---|
| Controladora | 1 | ESP32-DO (NoLogo), dos motores integrados; confirmar límites de corriente |
| Chasis | 1 | PCB perforada 7 × 9 cm, refuerzos, separadores y tornillería |
| Tracción | 2 | 2 motorreductores N20 de 6 V con encoder de cuadratura A/B, ruedas de goma; ajustar RPM y corriente al driver |
| Detección frontal | 1 | VL53L0X ToF de la fotografía; requiere adaptar el controlador VL53L1X del código |
| Línea | 5 | Barra 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 |
| Color | 2 | AS7341 de la fotografía, izquierdo y derecho, orientados al suelo; adaptar controlador de color del código |
| Bus I²C | 1 | Multiplexor TCA9548A para ToF y dos sensores de color |
| Entradas | 1 | MCP23017 para cinco sensores digitales |
| Energía | 1 | Paquete 2S de 18650, BMS balanceado, cargador externo CC/CV 8,4 V y conector polarizado |
| Arranque | 1 | Pulsador START; interruptor, fusible y fijaciones |
Componentes reales seleccionados

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


Motor N20 con encoder A/B y neumático
Cantidad: 2
Presupuesto estimado: CLP 5.000–11.000 c/u
Buscar en AliExpress ↗
Barra NULLLAB de 5 sensores I²C
Cantidad: 1
Presupuesto estimado: CLP 3.000–9.000
Buscar en AliExpress ↗


Baterías 18650 2S + soporte
Cantidad: 2 celdas
Presupuesto estimado: CLP 10.000–22.000 conjunto
Buscar en AliExpress ↗
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



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.
| Origen / pines | Destino | Observación |
|---|---|---|
| ESP32-DO M1 | Motor izquierdo | Control interno GPIO2 / GPIO4 |
| ESP32-DO M2 | Motor derecho | Control interno GPIO13 / GPIO27 |
| GPIO16 / 25 | Encoder izquierdo A/B del N20 con encoder | Comprobar cableado 3,3 V |
| GPIO17 / 26 | Encoder derecho A/B del N20 con encoder | Comprobar cableado 3,3 V |
| GPIO21 / 22 | SDA / SCL bus I²C | Confirmar disponibilidad en revisión física |
| TCA canal 0 | VL53L1X frontal | SDA/SCL y alimentación del breakout |
| TCA canal 1 / 2 | TCS34725 I / D | Un sensor por canal: ambos 0x29 |
| I²C 0x20 | MCP23017 | GPA0–GPA4: cinco salidas DO de sensores de línea |
| GPIO5 | START hacia GND | INPUT_PULLUP; verificar libre y expuesto |
| Pack 2S / BMS | Fuente del robot | Salida protegida, fusible, interruptor; motores compatibles |
| Cargador externo | Puerto de carga | 8,4 V CC/CV, solo con robot apagado |
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.
// 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
- Integrar la barra NULLLAB de cinco sensores con biblioteca I²C oficial y sustituir las entradas discretas.
- Calibración individual sobre blanco y negro y control PID continuo para curvas rápidas.
- Reconocimiento fiable de marcadores de color después de registrar muestras de suelo reales.
- Evasión de obstáculos, rampas, intersecciones y detección de zonas sin línea. Hoy el carro solo se detiene.
- Control de batería 2S por celda, alarma de descarga y prueba de autonomía.
Comportamientos futuros en la pista
Secuencia de pruebas
- Probar motores con ruedas levantadas y comprobar sentido de giro.
- Verificar sensores de línea, color y distancia antes de poner el carro en la pista.
- Calibrar blanco, negro y la distancia de parada; ensayar el arranque con START.
- Probar con batería protegida, observar calentamiento y registrar resultados.