Robótica • Ingeniería y programación

Carro sumo 4×4 · ESP32 WROOM-32E

Maker-ESP32-Pro · cuatro motores con encoder · cuatro sensores ToF · cuatro sensores de borde · dos banderas con servos · cuña elevadora · 2S

1. Lista de componentes y montaje

800 gPeso máximo
25×25×20cm iniciales
4×4Tracción con encoders

Las reglas de peso, altura, arranque y permisos mecánicos provienen de las bases Concepción 2026 aportadas para la categoría Sumo. Consulta siempre la revisión del torneo.

ComponenteCant.Especificación / montaje
Controladora1Maker-ESP32-Pro ESP32-WROOM-32E, 4 canales de motor + interfaces encoder
Tracción4N20 metálicos con encoder, escoger voltaje/corriente según fuente y driver
Ruedas4Goma/silicona de alta adherencia, 35–45 mm como prototipo
Distancia4VL53L1X en frontal, dos laterales y diagonal/trasera
Bordes4TCRT5000/QTR digitales; calibrar borde NEGRO
Expansión1+1TCA9548A + MCP23017
Banderas2+22 servos MG90S + 2 aletas policarbonato y bisagras impresas PETG
Cuña1Aluminio de cantos redondeados y servo metálico de alto torque con bieleta y topes
Control servo1PCA9685 I²C + fuente regulada 5–6 V para servos
Batería12×18650 en serie, BMS 2S balanceado, cargador externo 8,4 V
Alimentación1Regulador 6 V alta corriente para placa/motores de 6 V; protección y fusible
Interfaz1Botón único START; tres selectores previos mediante MCP23017

Distribución sugerida

Robot sumo cuatro ruedas con aletas desplegables y cuña elevadora articulada
Ilustración conceptual generada: mecanismos de dos banderas accionadas por servos y cuña elevadora; no es plano CAD ni prueba de compatibilidad mecánica.
Vistas de carro sumo y ubicación de motores y sensores
Referencia visual de la distribución de motores y sensores. Revisar geometría y masas antes del montaje 4×4.
Placa Maker ESP32 Pro con controlador de cuatro motores
Fotografía de referencia de la controladora Maker-ESP32-Pro utilizada para el diseño.
ALETAS PLEGADAS / INICIOALETAS ABIERTAS / COMBATE MakerESP32-Pro4 encoders2S 18650MakerESP32-Pro4 encoders2S 18650Todo debe caber en 25×25×20 cm inicialmenteApertura sujeta a autorización de jueces
Reglas Concepción 2026: el robot debe caber inicialmente en 25 × 25 × 20 cm y pesar como máximo 800 g. El dohyo es blanco con borde negro; al soltar el botón único START comienza el movimiento. Las aletas desplegables y la cuña elevadora necesitan evaluación de jueces si pueden considerarse invasivas o de sujeción. Las bases permiten empujar o levantar, pero prohíben enganchar y atrapar.
Mapa de motores: verifique la correspondencia real entre M0–M3 y las posiciones FL/FR/RL/RR. El código cuenta pulsos de un canal por encoder (A) y usa una asignación preliminar de lados. GPIO34/35 requieren niveles y pull-ups externos apropiados cuando correspondan. Los pines de control y encoder comparten puertos específicos según la documentación Maker.

2. Diagrama de conexiones y alimentación

Esquema visual de la batería con BMS y distribución de energía 2S
Esquema visual de referencia para carga y energía 2S. Verificar con la ficha del BMS los terminales correctos; no debe utilizarse como circuito de cableado literal.

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
M0: GPIO27 / 13Motor 0Encoder E0 A=18 B=19
M1: GPIO4 / 2Motor 1Encoder E1 A=5 B=23
M2: GPIO17 / 12Motor 2Encoder E2 A=35 B=36; solo entrada
M3: GPIO14 / 15Motor 3Encoder E3 A=34 B=39; solo entrada
GPIO21 / 22Bus I²CVerificar conectores SDA/SCL
TCA9548A 0x704× VL53L1XSensores en canales 0,1,2,3
MCP23017 0x20BordesGPA0 a GPA3, entradas pull-up
MCP23017 0x20Modos y STARTGPA4 izquierda, GPA5 frontal, GPA6 derecha, GPA7 START
PCA9685 0x40ServosCanal 0 bandera I, canal 1 bandera D, canal 2 cuña
Fuente 5–6 VServosNo alimentar desde 3V3 ESP32
Batería 2S + BMSRegulador 6 V / controladoraSeparar carga de servos; fusible e interruptor
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

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
// SUMO 4x4 - Maker ESP32 Pro WROOM32E, Arduino-ESP32 core >=3.0
// Librerías: Adafruit MCP23X17, Adafruit PWM Servo Driver, Pololu VL53L1X
// I2C: TCA9548A (4 ToF), MCP23017 (bordes y botones), PCA9685 (3 servos).
// Sensores de borde: SUPERFICIE BLANCA + BORDE NEGRO (bases Concepción 2026).
// Un único botón START: arrancar AL SOLTAR, sin cuenta regresiva inventada.
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <Adafruit_PWMServoDriver.h>
#include <VL53L1X.h>
constexpr int SDA_PIN=21,SCL_PIN=22; // Verificar GPIO en placa/revisión
constexpr uint8_t TCA=0x70;
constexpr int MPIN[4][2]={{27,13},{4,2},{17,12},{14,15}};
constexpr int E_A[4]={18,5,35,34};
constexpr bool INVERT[4]={false,true,false,true}; // calibrar sentido de giro
// 0 y 2 ruedas izquierda; 1 y 3 ruedas derecha (AJUSTAR conectores físicos)
constexpr int SIDE[4]={0,1,0,1};
constexpr int PWM_HZ=16000,PWM_BITS=8;
constexpr int BLACK_LEVEL=LOW; // TCRT5000 digital: calibrar sobre dohyo real
constexpr int EDGE[4]={0,1,2,3}; // MCP GPA0...3 (FL,FR,RL,RR)
constexpr int SEL_LEFT=4,SEL_FRONT=5,SEL_RIGHT=6,START=7; // MCP GPA4...7, activos LOW
constexpr int SERVO_LEFT=0,SERVO_RIGHT=1,SERVO_WEDGE=2;
constexpr int CLOSED_L=260,OPEN_L=430,CLOSED_R=430,OPEN_R=260;
constexpr int WEDGE_LOW=280,WEDGE_HIGH=385; // ticks PCA9685, CALIBRAR
constexpr int FRONT_MM=560,SIDE_MM=500;
constexpr int PUSH_PWM=215,TURN_PWM=155,SEARCH_PWM=95;
constexpr unsigned long STALL_WINDOW=320,PULSE_ON=130,PULSE_OFF=85;
Adafruit_MCP23X17 io;
Adafruit_PWMServoDriver servos=Adafruit_PWMServoDriver(0x40);
VL53L1X tof[4];
volatile uint32_t ticks[4]={0,0,0,0};
void IRAM_ATTR isr0(){ticks[0]++;}
void IRAM_ATTR isr1(){ticks[1]++;}
void IRAM_ATTR isr2(){ticks[2]++;}
void IRAM_ATTR isr3(){ticks[3]++;}
void mux(uint8_t channel){Wire.beginTransmission(TCA);Wire.write(1<<channel);Wire.endTransmission();}
void motor(int n,int pwm){
 pwm=constrain(pwm,-255,255);if(INVERT[n])pwm=-pwm;
 int a=MPIN[n][0],b=MPIN[n][1];
 if(pwm>=0){ledcWrite(a,pwm);ledcWrite(b,0);}
 else{ledcWrite(a,0);ledcWrite(b,-pwm);}
}
void drive(int left,int right){for(int i=0;i<4;i++)motor(i,SIDE[i]==0?left:right);}
void stopAll(){drive(0,0);}
void fail(const char *s){stopAll();Serial.println(s);while(1)delay(500);}
uint16_t dmm[4]={65535,65535,65535,65535}; // FRONT, LEFT, RIGHT, REAR/diagonal
bool edgeNow(){for(int i=0;i<4;i++)if(io.digitalRead(EDGE[i])==BLACK_LEVEL)return true;return false;}
void readTof(int n){mux(n);uint16_t r=tof[n].read();if(!tof[n].timeoutOccurred()&&r>=30&&r<=1200)dmm[n]=r;else dmm[n]=65535;}
void servoPose(bool open){servos.setPWM(SERVO_LEFT,0,open?OPEN_L:CLOSED_L);servos.setPWM(SERVO_RIGHT,0,open?OPEN_R:CLOSED_R);}
void wedge(bool lift){servos.setPWM(SERVO_WEDGE,0,lift?WEDGE_HIGH:WEDGE_LOW);}
int mode=0; // -1 izquierda, 0 frontal, +1 derecha
void selectMode(){
 if(io.digitalRead(SEL_LEFT)==LOW)mode=-1;
 if(io.digitalRead(SEL_FRONT)==LOW)mode=0;
 if(io.digitalRead(SEL_RIGHT)==LOW)mode=1;
}
void setup(){
 Serial.begin(115200);
 for(int i=0;i<4;i++){
  ledcAttach(MPIN[i][0],PWM_HZ,PWM_BITS);
  ledcAttach(MPIN[i][1],PWM_HZ,PWM_BITS);
  // GPIO34,35 SOLO entrada; encoder debe incorporar pullup externo si corresponde
  pinMode(E_A[i],INPUT);
 }
 attachInterrupt(digitalPinToInterrupt(E_A[0]),isr0,RISING);
 attachInterrupt(digitalPinToInterrupt(E_A[1]),isr1,RISING);
 attachInterrupt(digitalPinToInterrupt(E_A[2]),isr2,RISING);
 attachInterrupt(digitalPinToInterrupt(E_A[3]),isr3,RISING);
 stopAll(); Wire.begin(SDA_PIN,SCL_PIN);Wire.setClock(100000);
 if(!io.begin_I2C(0x20,&Wire))fail("MCP23017 no responde");
 for(int i=0;i<8;i++)io.pinMode(i,INPUT_PULLUP);
 servos.begin();servos.setPWMFreq(50);
 servoPose(false);wedge(false);
 for(int i=0;i<4;i++){
   mux(i);tof[i].setTimeout(130);
   if(!tof[i].init())fail("Sensor ToF sin respuesta");
   tof[i].setDistanceMode(VL53L1X::Short);
   tof[i].setMeasurementTimingBudget(20000);
   tof[i].startContinuous(25);
 }
 Serial.println("Calibre: borde NEGRO, ruedas y servos. Un solo START.");
 // Preselección sin mover motores: una selección por vez.
 while(io.digitalRead(START)==HIGH){selectMode();delay(20);}
 // El reglamento: se suelta botón START a la orden arbitral
 while(io.digitalRead(START)==LOW){delay(5);}
 delay(25);servoPose(true); // aletas desplegadas tras START
}
unsigned long lastRead=0,escUntil=0,stallSince=0,pulseSince=0,contactSince=0;
uint32_t lastTicks[4]={0,0,0,0};
int pulseStep=0,escapePhase=0;
void loop(){
 unsigned long now=millis();
 if(now-lastRead>=90){lastRead=now;for(int i=0;i<4;i++)readTof(i);}
 bool closeFront=(dmm[0]<FRONT_MM);
 // Borde prevalece en búsqueda/orientación; atención: prueba de seguridad real
 if(!closeFront && edgeNow()){
   wedge(false);drive(-140,-140);delay(260);drive(155,-155);delay(240);stopAll();return;
 }
 if(!closeFront){
   wedge(false);contactSince=0;stallSince=0;pulseStep=0;
   if(dmm[1]<SIDE_MM)drive(-TURN_PWM,TURN_PWM);
   else if(dmm[2]<SIDE_MM)drive(TURN_PWM,-TURN_PWM);
   else if(mode<0)drive(-SEARCH_PWM,SEARCH_PWM);
   else if(mode>0)drive(SEARCH_PWM,-SEARCH_PWM);
   else drive(SEARCH_PWM,SEARCH_PWM);
   return;
 }
 // Ataque frontal: encoders indican giro de ruedas, no desplazamiento real.
 if(contactSince==0)contactSince=now;
 if(now-contactSince>330)wedge(true); // elevar solo tras contacto PROBABLE, no confirmado
 if(stallSince==0){stallSince=now;for(int i=0;i<4;i++)lastTicks[i]=ticks[i];}
 if(now-stallSince>=STALL_WINDOW){
   uint32_t sum=0;
   for(int i=0;i<4;i++){sum+=ticks[i]-lastTicks[i];lastTicks[i]=ticks[i];}
   stallSince=now;
   if(sum<=4 && pulseStep==0){pulseStep=1;pulseSince=now;}
 }
 if(pulseStep){
   unsigned long elapsed=now-pulseSince;
   unsigned long cycle=PULSE_ON+PULSE_OFF;
   if(elapsed>=4*cycle){pulseStep=0;drive(PUSH_PWM,PUSH_PWM);}
   else if(elapsed%cycle<PULSE_ON)drive(PUSH_PWM,PUSH_PWM);
   else drive(0,0);
 }else drive(PUSH_PWM,PUSH_PWM);
 // Si el rival se pierde, reiniciar detección de contacto en el siguiente ciclo.
 if(!closeFront)contactSince=0;
 delay(5);
}

4. Posibles mejoras

Secuencia de pruebas

  1. Probar motores con ruedas levantadas y comprobar sentido de giro.
  2. Verificar sensores y sus niveles eléctricos antes de acercarse al dohyo.
  3. Calibrar fondo, bordes y distancias reales; ensayar arranque reglamentario.
  4. Probar con batería protegida, observar calentamiento y registrar resultados.