Community project

Mecanum Balancing Inventory Robot

ESP32
Photo of Mecanum Balancing Inventory Robot
Generated with AI

thiago

Published October 1, 2026

This guide builds a self-balancing inventory robot on Mecanum wheels, powered by a 48 V lithium-ion battery pack and driven by a sensored BLDC motor through a high-performance FOC controller. The robot uses a 6-axis IMU to detect tilt and maintain balance, with an ESP32 managing the sensor feedback, motor commands, and safety interlocks.

The assembly covers the complete high-voltage power path from battery through fused disconnect, precharge circuit, and main contactor, then the low-voltage control wiring for the IMU, motor controller, and enable switches. Readers will receive a full wiring diagram, parts list with specifications, firmware source code, and step-by-step assembly instructions that culminate in safe commissioning procedures.

Wiring diagram

Wiring diagram for Mecanum Balancing Inventory Robot

Gather all the parts

QtyComponent
1

13S5P 21700 lithium-ion traction battery pack, 21 Ah

13S5P, 21 Ah, 48.1 V nominal / 54.6 V full

A 48 V-class rechargeable battery pack that stores roughly 1 kWh for the drive system.

1

13S lithium-ion BMS, 250 A continuous

13S / 250 A continuous, with temperature probes

A battery safety board that monitors all 13 cell groups and disconnects the pack during unsafe voltage, current, or temperature conditions.

1

250 A DC traction fuse and insulated holder

250 A DC, rated at least 80 VDC

A serviceable fuse that opens if the high-voltage drive wiring develops a severe short circuit.

1

60 VDC 300 A continuous main contactor with auxiliary contact

48 V coil, 300 A continuous contacts

A high-current electrically controlled switch that connects battery power to the motor controller only after precharge and safety checks.

1

48 V precharge resistor module with momentary bypass switch

100 Ω, 50 W pulse-rated resistor with guarded momentary switch

A resistor and switch assembly that slowly charges the controller capacitors before the main contactor closes, preventing a damaging inrush spark.

1

Twist-release emergency-stop switch, normally closed

Two NC contacts, 60 VDC-rated

A large red safety switch that opens the contactor-coil circuit when pressed so drive power is removed even if software fails.

1

Isolated 48 V contactor-coil MOSFET driver

3.3 V logic input, 60 V / 3 A coil output

A protected low-side switch that lets the 3.3 V controller request the contactor while keeping high-voltage coil wiring away from the ESP32.

1

Isolated 60 V-to-5 V DC-DC converter, 3 A

36–72 V input, 5 V 3 A isolated output

A regulated isolated converter that powers the ESP32 and sensors from the switched traction battery bus.

1

VESC 75/300-class sensored FOC motor controller

12–67.2 V input; configure ≤200 A battery current and ≤300 A motor current

A high-current controller that drives the balancing wheel's brushless motor with smooth torque control and reports its own electrical faults.

1

Sensored 48 V BLDC traction motor with reduction drive

48 V, ≥3 kW peak, Hall-sensored; final torque/rpm selection required from wheel geometry

A brushless traction motor sized with a reduction drive to provide the fast bidirectional torque a balancing wheel requires.

1

DFRobot SEN0142 Fermion MPU-6050 6 DOF Sensor Breakout

DFRobot SEN0142 MPU-6050 breakout with 3-5 V board input, I2C interface, onboard I2C pull-ups, and i2cdevlib Arduino example coverage.

1

Keyed arm-enable switch

Maintained key switch, logic-level interface

A key-operated switch that lets a person arm the balancing controller only during supervised testing.

Assemble it in 7 steps

1. Build the protected battery enclosure

Have a qualified battery-pack builder assemble battery_13s5p as a 13-series, 5-parallel 21700 pack with welded interconnects, insulation, a rigid enclosure, and strain relief. Connect its B+ and B− leads and the complete cell-group sense harness to bms_13s_250a exactly in the BMS maker’s specified order.

  • A 13S pack is 54.6 V when fully charged; label it as hazardous DC.
  • Keep the cell sense connector unplugged until the BMS instructions say it is time to connect it.
  • A reversed or misordered BMS cell-sense lead can destroy the BMS and can start a battery fire.
  • Do not assemble or test a high-energy lithium pack on a wooden bench or near flammable materials.

2. Install the high-current protection path

Bolt battery_13s5p positive to main_fuse IN using properly crimped, insulated high-current cable. Bolt main_fuse OUT to main_contactor BAT+, and main_contactor LOAD+ to vesc_75_300 B+. Bolt the protected battery negative path through bms_13s_250a P− to vesc_75_300 B−. Mount main_fuse, main_contactor, and vesc_75_300 on non-flammable, vibration-resistant supports with covered terminals.

  • Use cable, lugs, torque values, and terminal covers rated for the expected fault current and at least 80 VDC.
  • Keep positive and negative traction cables close together to reduce electrical noise.
  • Never work on this path while the pack is connected; 54.6 V at this pack’s available current can melt tools and cause severe burns.
  • A fuse is not a switch: disconnect the battery before changing any high-current wiring.

3. Add precharge and the emergency stop

Wire precharge_module from main_fuse OUT to vesc_75_300 B+ so its guarded momentary switch can fill the controller’s capacitors before the contactor closes. Wire estop_nc in series between main_fuse OUT and main_contactor COIL+. Wire main_contactor COIL− to contactor_driver LOAD−, then wire contactor_driver HV_GND to bms_13s_250a P−.

  • Put the emergency-stop button where a standing operator can hit it without reaching across the wheel.
  • Use the precharge switch only briefly before enabling the contactor; it is not a continuous-power switch.
  • Bypassing precharge can damage the controller, contactor, or battery connectors through an inrush spark.
  • The emergency stop must physically remove contactor-coil power even if the ESP32 freezes.

4. Wire the low-voltage controls

Connect dc_dc_48v_5v VIN+ to main_fuse OUT and VIN− to bms_13s_250a P−. Connect its 5 V output to the ESP32 VIN and its output ground to ESP32 GND. Connect contactor_driver IN to ESP32 GPIO25 and LOGIC_GND to ESP32 GND. Connect arm_enable_switch OUT to GPIO32 and its other side to GND so the code’s internal pull-up reads LOW when the key is enabled.

  • Keep the small logic wires physically separate from the motor-phase cables.
  • Mount the keyed enable switch where it cannot be bumped on.
  • Do not feed the ESP32 directly from the traction pack; it will be destroyed.
  • Make sure the DC-DC converter polarity is correct before plugging in the ESP32.

5. Mount and wire the balance sensor

Rigidly mount mpu6050_1 near the robot’s center structure with its board axes recorded. Wire VIN to 3V3 (power), GND to GND (ground), SDA to GPIO21 (data), SCL to GPIO22 (clock), and INT to GPIO27 (signal). Do not mount it on a flexible camera or arm bracket.

  • Mark the robot’s forward direction on the IMU mount; the later balance controller needs that exact orientation.
  • Use short twisted I2C wires if the controller enclosure is electrically noisy.
  • A loose or misoriented IMU produces incorrect tilt data and can make a balancing robot accelerate in the wrong direction.

6. Connect the motor controller and wheel motor

With the battery disconnected, connect vesc_75_300 U, V, and W to wheel_motor U, V, and W. Connect the Hall supply, Hall ground, and Hall A/B/C wires pin-for-pin between wheel_motor and vesc_75_300. Connect VESC UART TX to ESP32 GPIO16 (data), UART RX to GPIO17 (data), and UART GND to ESP32 GND (ground).

  • Route the Hall and UART wires away from the three thick motor-phase wires.
  • Use a mechanically guarded test stand that leaves the wheel off the floor for all controller setup.
  • Motor phase wires can move or heat suddenly if the controller is misconfigured; keep hands, hair, tools, and loose clothing away from the wheel.
  • Do not test balancing with the 5-foot structure free-standing; use a tethered rig or rigid overhead restraint.

7. Commission without a rider or free balancing

Start with the emergency stop released, the key off, and the wheel restrained. Press the precharge switch briefly, then turn the key on only after the ESP32 serial status says the IMU is online and the robot is within the allowed tilt angle. Verify that pressing the emergency stop opens the contactor immediately. Keep the supplied firmware unchanged: it only supervises contactor permission and does not command motor torque.

  • Have a second adult operate the emergency stop during early testing.
  • Configure motor-current and battery-current limits in the VESC conservatively before any restrained spin test.
  • This submitted project is not a balancing controller and must not be used to attempt a free-standing balance test.
  • A 40 lb, 5-foot robot can fall with serious force; use a test enclosure, tether, and an operator safety plan before adding torque control.

Review all connections

1. Connections between "battery_13s5p" and "ESP32"

Functionbattery_13s5pESP32
powerB+ → 13S lithium-ion BMS, 250 A continuous B+EXT
groundB- → 13S lithium-ion BMS, 250 A continuous B-EXT

2. Connections between "bms_13s_250a" and "ESP32"

Functionbms_13s_250aESP32
groundP- → VESC 75/300-class sensored FOC motor controller B-EXT
dataCELL HARNESS → 13S5P 21700 lithium-ion traction battery pack, 21 Ah CELL HARNESSEXT

3. Connections between "main_fuse" and "ESP32"

Functionmain_fuseESP32
powerIN → 13S5P 21700 lithium-ion traction battery pack, 21 Ah B+EXT
powerOUT → 60 VDC 300 A continuous main contactor with auxiliary contact BAT+EXT

4. Connections between "precharge_module" and "ESP32"

Functionprecharge_moduleESP32
powerIN → 250 A DC traction fuse and insulated holder OUTEXT
powerOUT → VESC 75/300-class sensored FOC motor controller B+EXT

5. Connections between "main_contactor" and "ESP32"

Functionmain_contactorESP32
powerLOAD+ → VESC 75/300-class sensored FOC motor controller B+EXT
powerCOIL+ → Twist-release emergency-stop switch, normally closed NC2EXT

6. Connections between "estop_nc" and "ESP32"

Functionestop_ncESP32
digitalNC1 → 250 A DC traction fuse and insulated holder OUTEXT

7. Connections between "contactor_driver" and "ESP32"

Functioncontactor_driverESP32
groundLOAD- → 60 VDC 300 A continuous main contactor with auxiliary contact COIL-EXT
groundHV_GND → 13S lithium-ion BMS, 250 A continuous P-EXT
digitalINGPIO 25
groundLOGIC_GNDGND

8. Connections between "dc_dc_48v_5v" and "ESP32"

Functiondc_dc_48v_5vESP32
powerVIN+ → 250 A DC traction fuse and insulated holder OUTEXT
groundVIN- → 13S lithium-ion BMS, 250 A continuous P-EXT
powerVOUT+VIN
groundVOUT-GND

9. Connections between "vesc_75_300" and "ESP32"

Functionvesc_75_300ESP32
powerU → Sensored 48 V BLDC traction motor with reduction drive UEXT
powerV → Sensored 48 V BLDC traction motor with reduction drive VEXT
powerW → Sensored 48 V BLDC traction motor with reduction drive WEXT
powerHALL_5V → Sensored 48 V BLDC traction motor with reduction drive HALL_5VEXT
groundHALL_GND → Sensored 48 V BLDC traction motor with reduction drive HALL_GNDEXT
uartUART_TXGPIO 16
uartUART_RXGPIO 17
groundUART_GNDGND

10. Connections between "wheel_motor" and "ESP32"

Functionwheel_motorESP32
digitalHALL_A → VESC 75/300-class sensored FOC motor controller HALL_AEXT
digitalHALL_B → VESC 75/300-class sensored FOC motor controller HALL_BEXT
digitalHALL_C → VESC 75/300-class sensored FOC motor controller HALL_CEXT

11. Connections between "mpu6050_1" and "ESP32"

Functionmpu6050_1ESP32
powerVIN3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22
digitalINTGPIO 27

12. Connections between "arm_enable_switch" and "ESP32"

Functionarm_enable_switchESP32
digitalOUTGPIO 32
groundGNDGND

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <MPU6050.h>

constexpr int CONTACTOR_ENABLE_PIN = 25;
constexpr int KEY_ENABLE_PIN = 32;
constexpr int IMU_INT_PIN = 27;
constexpr int VESC_RX_PIN = 16;
constexpr int VESC_TX_PIN = 17;
constexpr int I2C_SDA_PIN = 21;
constexpr int I2C_SCL_PIN = 22;

constexpr float MAX_ARM_ANGLE_DEG = 10.0f;
constexpr uint32_t STABLE_TIME_MS = 2000;
constexpr uint32_t STATUS_INTERVAL_MS = 500;

MPU6050 imu;
HardwareSerial vescSerial(2);

bool imuReady = false;
bool contactorClosed = false;
uint32_t stableSinceMs = 0;
uint32_t lastStatusMs = 0;

float readPitchDegrees() {
  int16_t ax, ay, az, gx, gy, gz;
  imu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  const float axf = static_cast<float>(ax);
  const float ayf = static_cast<float>(ay);
  const float azf = static_cast<float>(az);
  return atan2f(ayf, sqrtf(axf * axf + azf * azf)) * 180.0f / PI;
}

void setContactor(bool close) {
  digitalWrite(CONTACTOR_ENABLE_PIN, close ? HIGH : LOW);
  contactorClosed = close;
}

void setup() {
  pinMode(CONTACTOR_ENABLE_PIN, OUTPUT);
  setContactor(false);
  pinMode(KEY_ENABLE_PIN, INPUT_PULLUP);
  pinMode(IMU_INT_PIN, INPUT);

  Serial.begin(115200);
  vescSerial.begin(115200, SERIAL_8N1, VESC_RX_PIN, VESC_TX_PIN);
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(400000);

  imu.initialize();
  imuReady = imu.testConnection();
  Serial.println(imuReady ? "IMU online; traction remains disabled." : "IMU not found; traction locked out.");
}

void loop() {
  const uint32_t now = millis();
  const bool keyEnabled = digitalRead(KEY_ENABLE_PIN) == LOW;
  bool upright = false;
  float pitchDeg = 0.0f;

  if (imuReady) {
    pitchDeg = readPitchDegrees();
    upright = fabsf(pitchDeg) <= MAX_ARM_ANGLE_DEG;
  }

  if (keyEnabled && upright) {
    if (stableSinceMs == 0) {
      stableSinceMs = now;
    }
    if (now - stableSinceMs >= STABLE_TIME_MS) {
      setContactor(true);
    }
  } else {
    stableSinceMs = 0;
    setContactor(false);
  }

  // This project is an electrical and safety architecture only. Do not add
  // VESC torque commands here until the restrained balance controller has
  // been designed and independently tested.
  if (now - lastStatusMs >= STATUS_INTERVAL_MS) {
    lastStatusMs = now;
    Serial.printf("pitch=%.1f deg, key=%s, contactor=%s\n", pitchDeg,
                  keyEnabled ? "on" : "off", contactorClosed ? "closed" : "open");
  }

  delay(10);
}

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