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.
| Componente | Cant. | Especificación / montaje |
|---|---|---|
| Controladora | 1 | Maker-ESP32-Pro ESP32-WROOM-32E, 4 canales de motor + interfaces encoder |
| Tracción | 4 | N20 metálicos con encoder, escoger voltaje/corriente según fuente y driver |
| Ruedas | 4 | Goma/silicona de alta adherencia, 35–45 mm como prototipo |
| Distancia | 4 | VL53L1X en frontal, dos laterales y diagonal/trasera |
| Bordes | 4 | TCRT5000/QTR digitales; calibrar borde NEGRO |
| Expansión | 1+1 | TCA9548A + MCP23017 |
| Banderas | 2+2 | 2 servos MG90S + 2 aletas policarbonato y bisagras impresas PETG |
| Cuña | 1 | Aluminio de cantos redondeados y servo metálico de alto torque con bieleta y topes |
| Control servo | 1 | PCA9685 I²C + fuente regulada 5–6 V para servos |
| Batería | 1 | 2×18650 en serie, BMS 2S balanceado, cargador externo 8,4 V |
| Alimentación | 1 | Regulador 6 V alta corriente para placa/motores de 6 V; protección y fusible |
| Interfaz | 1 | Botón único START; tres selectores previos mediante MCP23017 |
Distribución sugerida



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

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 |
|---|---|---|
| M0: GPIO27 / 13 | Motor 0 | Encoder E0 A=18 B=19 |
| M1: GPIO4 / 2 | Motor 1 | Encoder E1 A=5 B=23 |
| M2: GPIO17 / 12 | Motor 2 | Encoder E2 A=35 B=36; solo entrada |
| M3: GPIO14 / 15 | Motor 3 | Encoder E3 A=34 B=39; solo entrada |
| GPIO21 / 22 | Bus I²C | Verificar conectores SDA/SCL |
| TCA9548A 0x70 | 4× VL53L1X | Sensores en canales 0,1,2,3 |
| MCP23017 0x20 | Bordes | GPA0 a GPA3, entradas pull-up |
| MCP23017 0x20 | Modos y START | GPA4 izquierda, GPA5 frontal, GPA6 derecha, GPA7 START |
| PCA9685 0x40 | Servos | Canal 0 bandera I, canal 1 bandera D, canal 2 cuña |
| Fuente 5–6 V | Servos | No alimentar desde 3V3 ESP32 |
| Batería 2S + BMS | Regulador 6 V / controladora | Separar 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.
// 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
- Validar mecánicamente las dos banderas con servos y topes; probar efecto sobre los sensores del rival.
- Usar microinterruptor de contacto protegido para activar la cuña y no estimar contacto solo por tiempo.
- Añadir lectura de corriente por motor para distinguir bloqueo de patinaje; preservar las protecciones eléctricas.
- Programar gestión de bordes también durante ataque, según interpretación del reglamento, para evitar autoexpulsión.
- Calibrar límites de tracción y empuje con dinamómetro; comparar cuatro motores N20 de distintas reducciones.
- Medición por celda 2S, monitoreo térmico y alertas de recarga, sin carga durante competencia.
- Pruebas con robot simulado/rival y registro de fallos de detección; respetar la autonomía total.
Secuencia de pruebas
- Probar motores con ruedas levantadas y comprobar sentido de giro.
- Verificar sensores y sus niveles eléctricos antes de acercarse al dohyo.
- Calibrar fondo, bordes y distancias reales; ensayar arranque reglamentario.
- Probar con batería protegida, observar calentamiento y registrar resultados.