Community project

Bluetooth Soccer Robot

Photo of Bluetooth Soccer Robot
Generated with AI

julian rodriguez

Published November 11, 2026

This Bluetooth soccer robot is a fast, agile platform controlled wirelessly via Xbox Series X controller. Built around an ESP32 microcontroller, dual N20 micro metal gear motors, and a DRV8833 motor driver, the robot delivers responsive arcade-style gameplay with real-time battery monitoring and safety features including emergency stop and low-battery cutoff.

This guide provides a complete wiring diagram, full parts list with sourcing notes, pre-configured firmware with Xbox controller support, and step-by-step assembly instructions covering battery preparation, power distribution, motor control setup, and safe first-run testing. Builders will learn how to integrate Bluetooth control, implement dual-motor differential steering, and manage Li-ion power safely with BMS protection and external charging.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

DRV8833 Dual DC Motor Driver

Dual H-bridge DC/stepper motor driver IC or carrier module. VM motor supply range is 2.7V-10.8V; logic inputs support 1.8V, 3.3V, and 5V controllers. TI rates the PWP/RTY package options at 1.5A RMS and 2A peak per H-bridge at VM=5V and 25C; carrier-board current depends on board layout and cooling. Controlled with AIN1/AIN2 and BIN1/BIN2 inputs, which may be driven as digital or PWM signals. nSLEEP must be high for the bare IC, while some carrier boards pull it high by default. nFAULT is an open-drain fault output.

1

DRV8833 Dual DC Motor Driver

Dual H-bridge DC/stepper motor driver IC or carrier module. VM motor supply range is 2.7V-10.8V; logic inputs support 1.8V, 3.3V, and 5V controllers. TI rates the PWP/RTY package options at 1.5A RMS and 2A peak per H-bridge at VM=5V and 25C; carrier-board current depends on board layout and cooling. Controlled with AIN1/AIN2 and BIN1/BIN2 inputs, which may be driven as digital or PWM signals. nSLEEP must be high for the bare IC, while some carrier boards pull it high by default. nFAULT is an open-drain fault output.

1

N20-6V-300RPM Micro Metal Gear Motor

N20 form-factor brushed DC micro metal gear motor rated 6V with about 300 RPM no-load speed. Two bare motor terminals connect directly to a motor driver output channel such as DRV8833 AOUT1/AOUT2 or BOUT1/BOUT2. Direction and speed are controlled by the motor driver. No firmware library is required for the motor itself.

1

N20-6V-300RPM Micro Metal Gear Motor

N20 form-factor brushed DC micro metal gear motor rated 6V with about 300 RPM no-load speed. Two bare motor terminals connect directly to a motor driver output channel such as DRV8833 AOUT1/AOUT2 or BOUT1/BOUT2. Direction and speed are controlled by the motor driver. No firmware library is required for the motor itself.

1

N20-6V-300RPM Micro Metal Gear Motor

N20 form-factor brushed DC micro metal gear motor rated 6V with about 300 RPM no-load speed. Two bare motor terminals connect directly to a motor driver output channel such as DRV8833 AOUT1/AOUT2 or BOUT1/BOUT2. Direction and speed are controlled by the motor driver. No firmware library is required for the motor itself.

1

N20-6V-300RPM Micro Metal Gear Motor

N20 form-factor brushed DC micro metal gear motor rated 6V with about 300 RPM no-load speed. Two bare motor terminals connect directly to a motor driver output channel such as DRV8833 AOUT1/AOUT2 or BOUT1/BOUT2. Direction and speed are controlled by the motor driver. No firmware library is required for the motor itself.

1

18650 Li-ion Cell

2500mAh, matched 3.7V cells; >=5A continuous

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

18650 Li-ion Cell

2500mAh, matched 3.7V cells; >=5A continuous

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

2S 18650 holder with accessible midpoint

Series holder with midpoint tap for BMS. Two cells are separate entries. Use a holder rated >=5A, not a thin spring-contact low-current holder.

1

2S 10A common-port BMS

HX-2S-JH20 style, marked B-/BM/B+/P+/P-. Charge and discharge through P terminals. Do not assume balancing is included.

1

XT30 mating connector pair

Wire by molded + and - marks, insulated female contacts on battery side.

1

5A DC inline fuse and holder

Install close to BMS positive output, before charger and load branches. Rating <= wire/holder rating.

1

Rocker Switch

>=10A at 12V DC

Classic compact panel-mount rocker switch for on/off power control in enclosures.

1

MP1584 Buck Converter

Adjusted to 5.0V before connection

Adjustable buck (step-down) DC-DC converter, 4.5-28 V in -> 0.8-20 V out, ~3 A. Configured to 5 V to step a 9 V / 12 V supply or battery pack down to the board's 5 V rail.

1

Pololu D36V50F6 6V regulator

6V fixed motor supply; other pins EN/PG/VRP left disconnected, enabled by default. Input minimum 6.5V; rail may sag near empty. Source https://www.pololu.com/product/4092

1

5.5 x 2.1mm insulated DC charging jack

Center positive. Identify contacts with continuity meter; unused switch contact if present remains isolated.

1

External 8.4V 1A CC/CV 2S Li-ion charger

Certified enclosed charger, center-positive 5.5x2.1mm plug. Not an ordinary 8.4V power adapter. No exposed mains wiring.

1

Electrolytic Capacitor

100uF 16V electrolytic

Radial aluminium electrolytic capacitor bought by value and voltage rating, such as 10 µF, 100 µF or 1000 µF 10 V. It is polarised: the + lead goes to the higher voltage and the - lead, marked by the stripe on the sleeve, to the lower voltage or ground. It is mostly used for bulk filtering across a power rail, with a voltage rating comfortably above the rail.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Electrolytic Capacitor

100uF 16V electrolytic

Radial aluminium electrolytic capacitor bought by value and voltage rating, such as 10 µF, 100 µF or 1000 µF 10 V. It is polarised: the + lead goes to the higher voltage and the - lead, marked by the stripe on the sleeve, to the lower voltage or ground. It is mostly used for bulk filtering across a power rail, with a voltage rating comfortably above the rail.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Ceramic Capacitor

100nF >=25V ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

100k ohm 1% divider resistor

100k ohm 1%

Passive upper resistor of battery voltage divider, with analog sense junction at P2.

1

Resistor

33k ohm 1%

Through-hole resistor (current-limiting in series with an LED)

1

Ceramic Capacitor

100nF ceramic

Through-hole ceramic capacitor bought by value, such as 22 pF, 10 nF or 100 nF (0.1 µF). It is non-polarised, so either lead can go to either net. Its most common job is decoupling: a 100 nF capacitor between an IC's supply pin and ground, placed as close to the chip as possible.

1

Resistor

1k ohm

Through-hole resistor (current-limiting in series with an LED)

Assemble it in 7 steps

1. Prepare the battery safely

Use two matched genuine 18650 cells with equal charge. The holder must expose its series midpoint. Wire holder BAT- to BMS B-, MID to BM, and BAT+ to B+, following the BMS maker's connection order. Never connect raw cell negative to circuit ground; use protected P- only.

  • A shorted lithium battery can cause fire. Do not solder directly to cells. A BMS is not a charger and may not balance cells.

2. Add protected power and charging

Connect BMS P+ through the 5A fuse to XT30 positive and the charging jack center. Connect P- to XT30 negative and jack sleeve. XT30 positive feeds the main switch; negative is circuit ground. Insulate every exposed contact. Use the external 8.4V CC/CV charger only with the main switch OFF.

  • Charging remains live with the main switch off. Never charge unattended or while driving. Do not use an ordinary power adapter.

3. Set the two power rails

With ESP32 and drivers disconnected, feed switched positive into both regulator inputs and protected ground into both grounds. Adjust the MP1584 output to 5.0V using a meter; verify the fixed motor regulator outputs 6.0V. Then connect 5V to ESP32 VIN and 6V to both driver VM pins.

  • Never connect 8.4V directly to these 6V motors. Disconnect battery power before connecting USB to avoid backfeeding the ESP32 power rail.

4. Fit driver and motor noise capacitors

At each driver connect its 100uF capacitor positive to VM and negative to GND, plus its 100nF ceramic across VM/GND. Solder one 100nF ceramic directly across each motor's terminals, with short leads.

  • Reversed electrolytic capacitors can rupture. Keep motor current wiring off solderless breadboards.

5. Connect control and battery sensing

Left driver AIN1/AIN2 go to GPIO16/17 and BIN1/BIN2 to GPIO18/19. Right AIN1/AIN2 go to GPIO21/22 and BIN1/BIN2 to GPIO25/26. Join both nSLEEP pins to GPIO23 and the 1k resistor to ground. Connect switched battery positive through 100k to GPIO34, then 33k and 100nF from GPIO34 to ground. Join all circuit grounds to protected P-.

  • At a full 8.4V pack the divider should measure about 2.08V at GPIO34.

6. Connect each independent motor

Left AOUT1/2 feed front-left M+/M-; left BOUT1/2 feed rear-left. Right AOUT1/2 feed front-right; right BOUT1/2 feed rear-right. Keep wires away from moving wheels and insulate connections.

  • Verify each motor's 6V stall current is within the actual driver's continuous rating; 10A BMS does not mean drivers can handle 10A. Avoid prolonged stalls.

7. First test with wheels clear

Secure the robot with all wheels off the surface. Follow the firmware test instructions to test FL, RL, FR and RR individually at low speed. Switch off before changing any wiring. Check driver temperature after each short test. Keep the main switch within reach.

  • A software brake is not a physical power disconnect. Test controller loss before putting the robot on the floor.

Review all connections

1. Connections between "cell1" and "ESP32"

Functioncell1ESP32
groundGND → 2S 18650 holder with accessible midpoint BAT-EXT
power+V → 2S 18650 holder with accessible midpoint MIDEXT

2. Connections between "cell2" and "ESP32"

Functioncell2ESP32
groundGND → 2S 18650 holder with accessible midpoint MIDEXT
power+V → 2S 18650 holder with accessible midpoint BAT+EXT

3. Connections between "holder" and "ESP32"

FunctionholderESP32
groundBAT- → 2S 10A common-port BMS B-EXT
powerMID → 2S 10A common-port BMS BMEXT
powerBAT+ → 2S 10A common-port BMS B+EXT

4. Connections between "bms" and "ESP32"

FunctionbmsESP32
powerP+ → 5A DC inline fuse and holder INEXT
groundP- → XT30 mating connector pair -EXT
groundP- → 5.5 x 2.1mm insulated DC charging jack SLEEVEEXT

5. Connections between "fuse" and "ESP32"

FunctionfuseESP32
powerOUT → XT30 mating connector pair +EXT
powerOUT → 5.5 x 2.1mm insulated DC charging jack CENTEREXT

6. Connections between "xt30" and "ESP32"

Functionxt30ESP32
power+ → Rocker Switch T1EXT
ground-GND

7. Connections between "charger" and "ESP32"

FunctionchargerESP32
power+ → 5.5 x 2.1mm insulated DC charging jack CENTEREXT
ground- → 5.5 x 2.1mm insulated DC charging jack SLEEVEEXT

8. Connections between "switch" and "ESP32"

FunctionswitchESP32
powerT2 → MP1584 Buck Converter VINEXT
powerT2 → Pololu D36V50F6 6V regulator VINEXT
powerT2 → 100k ohm 1% divider resistor P1EXT

9. Connections between "logicbuck" and "ESP32"

FunctionlogicbuckESP32
groundGNDGND
powerVOUTVIN

10. Connections between "motorbuck" and "ESP32"

FunctionmotorbuckESP32
groundGNDGND
powerVOUT → DRV8833 Dual DC Motor Driver VMEXT
powerVOUT → DRV8833 Dual DC Motor Driver VMEXT

11. Connections between "left" and "ESP32"

FunctionleftESP32
groundGNDGND
digitalnSLEEPGPIO 23
pwmAIN1GPIO 16
pwmAIN2GPIO 17
pwmBIN1GPIO 18
pwmBIN2GPIO 19
dataAOUT1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
dataAOUT2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT
dataBOUT1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
dataBOUT2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

12. Connections between "flnoise" and "ESP32"

FunctionflnoiseESP32
digital1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
digital2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

13. Connections between "rlnoise" and "ESP32"

FunctionrlnoiseESP32
digital1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
digital2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

14. Connections between "leftbulk" and "ESP32"

FunctionleftbulkESP32
power+ → DRV8833 Dual DC Motor Driver VMEXT
ground-GND

15. Connections between "leftbypass" and "ESP32"

FunctionleftbypassESP32
digital1 → DRV8833 Dual DC Motor Driver VMEXT
ground2GND

16. Connections between "right" and "ESP32"

FunctionrightESP32
groundGNDGND
digitalnSLEEP → DRV8833 Dual DC Motor Driver nSLEEPEXT
pwmAIN1GPIO 21
pwmAIN2GPIO 22
pwmBIN1GPIO 25
pwmBIN2GPIO 26
dataAOUT1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
dataAOUT2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT
dataBOUT1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
dataBOUT2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

17. Connections between "frnoise" and "ESP32"

FunctionfrnoiseESP32
digital1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
digital2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

18. Connections between "rrnoise" and "ESP32"

FunctionrrnoiseESP32
digital1 → N20-6V-300RPM Micro Metal Gear Motor M+EXT
digital2 → N20-6V-300RPM Micro Metal Gear Motor M-EXT

19. Connections between "rightbulk" and "ESP32"

FunctionrightbulkESP32
power+ → DRV8833 Dual DC Motor Driver VMEXT
ground-GND

20. Connections between "rightbypass" and "ESP32"

FunctionrightbypassESP32
digital1 → DRV8833 Dual DC Motor Driver VMEXT
ground2GND

21. Connections between "sleepdown" and "ESP32"

FunctionsleepdownESP32
digitalP1 → DRV8833 Dual DC Motor Driver nSLEEPEXT
groundP2GND

22. Connections between "rtop" and "ESP32"

FunctionrtopESP32
analogP2GPIO 34

23. Connections between "rbottom" and "ESP32"

FunctionrbottomESP32
digitalP1 → 100k ohm 1% divider resistor P2EXT
groundP2GND

24. Connections between "adcfilter" and "ESP32"

FunctionadcfilterESP32
digital1 → 100k ohm 1% divider resistor P2EXT
ground2GND

Deploy the firmware

#include <Arduino.h>
#include <XboxSeriesXControllerESP32_asukiaaa.hpp>
#include <math.h>

// Wiring confirmed: shared sleep GPIO23; divider sense GPIO34 (ADC1).
// -1 = normal drive; 0=FL, 1=RL, 2=FR, 3=RR for raised-wheel tests.
constexpr int TEST_MOTOR = -1;
constexpr bool INVERT[4] = {false, false, true, true};
constexpr uint8_t PINS[4][2]={{16,17},{18,19},{21,22},{25,26}};
constexpr uint8_t SLEEP_PIN=23, BATTERY_PIN=34;
constexpr float BATTERY_CAL=1.0f; // Adjust against a voltmeter if needed.
XboxSeriesXControllerESP32_asukiaaa::Core xbox;
struct Input { bool ready=false,b=false,start=false; uint16_t y=32768,x=32768,lt=0,rt=0; uint32_t received=0; };
Input sharedInput;
portMUX_TYPE mux=portMUX_INITIALIZER_UNLOCKED;
volatile bool rumbleRequested=false;
float battery=0,forward=0,turn=0,leftSpeed=0,rightSpeed=0;
bool critical=false,estop=false,armed=false;
uint32_t batteryAt=0,debugAt=0,warningAt=0;

void rumble() {
  XboxSeriesXHIDReportBuilder_asukiaaa::ReportBase r;
  r.v.select.center=true; r.v.select.left=false;
  r.v.select.right=false; r.v.select.shake=false;
  r.v.power.center=35; r.v.timeActive=15;
  r.v.timeSilent=0; r.v.countRepeat=0;
  xbox.writeHIDReport(r);
}
// Bluetooth operations may block during pairing; motor safety runs independently.
void controllerTask(void*) {
  xbox.begin(); bool wasReady=false;
  for (;;) {
    xbox.onLoop();
    Input v;
    v.ready=xbox.isConnected() && !xbox.isWaitingForFirstNotification();
    v.received=xbox.getReceiveNotificationAt();
    if(v.ready) {
      v.y=xbox.xboxNotif.joyLVert; v.x=xbox.xboxNotif.joyRHori;
      v.lt=xbox.xboxNotif.trigLT; v.rt=xbox.xboxNotif.trigRT;
      v.b=xbox.xboxNotif.btnB; v.start=xbox.xboxNotif.btnStart;
    }
    portENTER_CRITICAL(&mux); sharedInput=v; portEXIT_CRITICAL(&mux);
    if(v.ready && (!wasReady || rumbleRequested)) {rumbleRequested=false; rumble();}
    wasReady=v.ready;
    vTaskDelay(pdMS_TO_TICKS(5));
  }
}
void initMotors() {
  pinMode(SLEEP_PIN,OUTPUT); digitalWrite(SLEEP_PIN,LOW);
  for(int m=0;m<4;m++) for(int p=0;p<2;p++) {
    int c=m*2+p; ledcAttachChannel(PINS[m][p],20000,8,c); ledcWriteChannel(c,0);
  }
  digitalWrite(SLEEP_PIN,HIGH); delay(2);
}
void motor(int m,float speed) {
  speed=constrain(speed,-1.0f,1.0f); if(INVERT[m]) speed=-speed;
  int duty=lroundf(fabsf(speed)*255);
  // IN1=IN2=HIGH is active braking. PWM uses slow-decay brake intervals.
  if(speed>0) {ledcWriteChannel(2*m,255); ledcWriteChannel(2*m+1,255-duty);}
  else if(speed<0) {ledcWriteChannel(2*m,255-duty); ledcWriteChannel(2*m+1,255);}
  else {ledcWriteChannel(2*m,255); ledcWriteChannel(2*m+1,255);}
}
void stopMotors() {for(int i=0;i<4;i++) motor(i,0); leftSpeed=rightSpeed=0;}
float stick(uint16_t raw) {
  float v=(float(raw)-32767.5f)/32767.5f;
  if(fabsf(v)<0.10f) return 0;
  return copysignf((fabsf(v)-0.10f)/0.90f,v);
}
void monitorBattery() {
  uint32_t now=millis();
  if(batteryAt && now-batteryAt<1000) return;
  batteryAt=now; uint32_t sum=0;
  for(int i=0;i<16;i++) sum+=analogReadMilliVolts(BATTERY_PIN);
  battery=(sum/16.0f)*0.001f*(133.0f/33.0f)*BATTERY_CAL;
  if(battery<6.2f) critical=true; // latched until power cycle
  if(battery<6.6f && (warningAt==0 || now-warningAt>=10000)) {
    rumbleRequested=true; warningAt=now;
  }
}
void mixDrive(const Input& v) {
  forward=-stick(v.y); turn=stick(v.x);
  float limit=v.lt>100 ? 0.35f : (v.rt>100 ? 1.0f : 0.70f);
  if(battery<6.6f) limit=fminf(limit,0.50f);
  leftSpeed=forward+turn; rightSpeed=forward-turn;
  float scale=fmaxf(1.0f,fmaxf(fabsf(leftSpeed),fabsf(rightSpeed)));
  leftSpeed=leftSpeed/scale*limit; rightSpeed=rightSpeed/scale*limit;
  if(TEST_MOTOR>=0) {
    leftSpeed=rightSpeed=forward*fminf(limit,0.25f);
    for(int i=0;i<4;i++) motor(i,i==TEST_MOTOR ? leftSpeed : 0);
  } else {
    motor(0,leftSpeed); motor(1,leftSpeed); motor(2,rightSpeed); motor(3,rightSpeed);
  }
}
void handleController() {
  Input v; portENTER_CRITICAL(&mux); v=sharedInput; portEXIT_CRITICAL(&mux);
  bool fresh=v.ready && v.received && uint32_t(millis()-v.received)<300;
  if(!fresh) {armed=false; forward=turn=0; stopMotors(); return;}
  forward=-stick(v.y); turn=stick(v.x);
  bool neutral=forward==0 && turn==0 && v.lt<100 && v.rt<100;
  if(v.b) {estop=true; armed=false;}
  if(estop && !v.b && neutral && v.start) estop=false;
  if(!estop && neutral) armed=true;
  if(critical || estop || !armed) {stopMotors(); return;}
  mixDrive(v);
}
void setup() {
  Serial.begin(115200); initMotors(); stopMotors();
  analogReadResolution(12); analogSetPinAttenuation(BATTERY_PIN,ADC_11db);
  monitorBattery();
  xTaskCreatePinnedToCore(controllerTask,"Xbox",8192,nullptr,1,nullptr,0);
  Serial.println("Pair Xbox BLE controller. Center sticks to arm. B stops; neutral + Start resets B stop.");
}
void loop() {
  monitorBattery(); handleController();
  if(millis()-debugAt>=250) {
    debugAt=millis();
    Serial.printf("Y=%.2f X=%.2f L=%.2f R=%.2f battery=%.2fV armed=%d Bstop=%d cutoff=%d test=%d\n",
      forward,turn,leftSpeed,rightSpeed,battery,armed,estop,critical,TEST_MOTOR);
  }
  delay(5);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik