The Micro Wheeled-Leg Robot is best understood as three interlocking layers: a mechanical structure that defines range of motion and rigidity; a hardware stack of four custom PCBs that handle power, motor drive, angle sensing, IMU data, and servo multiplexing; and a firmware stack running entirely on the ESP32 that closes the balance loop, steers the robot, moves the legs, and streams a control interface to any nearby browser. Understanding how these layers interact is the fastest path to modifying, tuning, or extending the platform.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/MuShibo/Micro-Wheeled_leg-Robot/llms.txt
Use this file to discover all available pages before exploring further.
Mechanical Subsystem
The robot’s chassis combines three materials to balance stiffness, weight, and machinability. A steel body base (BodyBase-Steel.stp) provides a low center of gravity and mounting rigidity. Aluminum 6061 motor mounts (MotorBase-Aluminum6061.stp) hold the brushless wheel motors at the correct geometry. Carbon fiber panels (defined in Panel.dwg / Panel.pdf) enclose the electronics bay while minimizing added mass.
The leg assemblies are built from nine 3D-printed parts:
| Part | Qty | Role |
|---|---|---|
Thigh | 1 | Left thigh link connecting servo horn to lower leg |
Thigh_MIR | 1 | Right-side mirror of the thigh link |
ThighCover | 1 | Cosmetic cover for the left thigh |
ThighCover_MIR | 1 | Cosmetic cover for the right thigh |
Calf | 1 | Left lower leg link terminating at the wheel axle |
Calf_MIR | 1 | Right-side mirror of the calf link |
ColumnCover | 2 | Pair of column shrouds for the servo mast |
Hub | 2 | Wheel hubs that mate the BLDC motor shaft to the tyre |
OriginalRobotModel.stp in the 1.RobotModel directory and can be opened in any STEP-compatible CAD tool for measurement or modification.
Electronics Subsystem
Four custom PCBs, all designed in LCEDA with full schematic and layout source files provided, make up the complete electronics stack.| PCB | Purpose | Key ICs |
|---|---|---|
| Controller PCB | Main brain: runs ESP32, drives both BLDC motors, monitors battery voltage via ADC pin 35, exposes USB and UART for programming and serial tuning | ESP32-WROOM-32, L6234PD013TR ×2 |
| Encoder PCB | Reads absolute wheel shaft angle over I²C for each motor; two identical boards, one per side | AS5600 ×2 |
| IMU PCB | Provides pitch, roll, and yaw angle and rate data; shares the right-encoder I²C bus at 400 kHz | MPU6050 module |
| Servo Debug PCB | Merges the two STS3032 half-duplex serial lines into one UART signal through time-division multiplexing so the ESP32 can address both servos on a single Serial2 port | SN74LVC1G125DBV, SN74LVC1G126DBV |
Firmware Modules
All firmware lives in the3.Software/wl_pro_robot/ directory and is compiled with the Arduino IDE targeting the ESP32 board package.
| File | Responsibility |
|---|---|
wl_pro_robot.ino | Entry point: instantiates all objects, runs setup() for peripheral initialization, and drives the main loop() calling every subsystem in sequence |
robot.cpp / robot.h | Defines the Wrobot struct (command state: height, roll, linear, angular, dir, joyx, joyy, go) and the RobotProtocol class with parseBasic() for JSON decoding and spinOnce() for periodic protocol updates |
wifi.cpp / wifi.h | Provides WiFi_SetAP() (AP mode, robot becomes its own hotspot) and set_sta_wifi() (STA mode, robot joins an existing network) |
Servo_STS3032.cpp / Servo_STS3032.h | SMS_STS driver for FEETECH STS3032 bus servos over Serial2 at 1 Mbaud; exposes SyncWritePosEx() for simultaneous position, speed, and acceleration commands to multiple servo IDs |
basic_web.h | Embeds the complete single-page HTML/JavaScript remote control interface as a C string stored in ESP32 flash; served by the HTTP server on port 80 |
Control Architecture
The balance and motion control pipeline is a cascade of feedback loops, each producing a reference or correction for the layer below it. The MPU6050 provides raw pitch angle and pitch rate (Y-axis), roll angle (X-axis), and yaw angle and yaw rate (Z-axis) after automatic gyro offset calibration duringsetup().
The LQR pitch balance loop (lqr_balance_loop()) combines four proportional terms — pitch angle error (pid_angle), pitch rate (pid_gyro), wheel displacement error (pid_distance), and wheel speed error (pid_speed) — into a single scalar torque demand LQR_u. When the robot is near equilibrium and the joystick is neutral, an integrating stage (pid_lqr_u) compensates low-torque motor nonlinearity, and pid_zeropoint trims the balance angle setpoint to track the true center of gravity.
The PID yaw steering loop (yaw_loop()) accumulates heading error from getAngleZ() and adds a joystick-commanded heading rate. Its output YAW_output is added differentially: motor1.target = -0.5 × (LQR_u + YAW_output) and motor2.target = -0.5 × (LQR_u - YAW_output).
The leg height loop (leg_loop()) maps the WebSocket height field to absolute servo positions using the formula 2048 ± 12 ± 8.4 × (height − 32), then adds a roll-axis correction from pid_roll_angle acting on low-pass-filtered getAngleX() data to keep the chassis laterally level.
SimpleFOC closes the innermost loop: motor.loopFOC() runs the field-oriented current control, and motor.move() applies the torque target computed above.
The balance control gain (pid_speed.P) is automatically reduced as the robot extends its legs, compensating for the higher moment of inertia at greater ride heights.
Communication Stack
The ESP32 simultaneously runs two servers in theweb_loop() call each iteration:
WebServeron port 80 — serves the embedded HTML/JS page frombasic_web.hvia a singleGET /route, accessible at192.168.1.11in AP mode.WebSocketsServeron port 81 — receives real-time JSON frames from the browser joystick interface.
mode field. When mode is "basic", RobotProtocol::parseBasic() extracts the joystick axes (joy_y, joy_x), direction, height, and stable flag from the StaticJsonDocument<300> and writes them into the global Wrobot wrobot struct. The control loops read from this struct on every iteration, so the latency from browser input to motor response is bounded by one main-loop cycle.
Browser sends JSON
The user moves the joystick or taps a button in the browser. The JavaScript client encodes the action as a JSON object (e.g.,
{"mode":"basic","joy_y":50,"joy_x":0,"height":38,"stable":1}) and transmits it over WebSocket to port 81.WebSocket receives and parses
The
webSocketEventCallback() fires on WStype_TEXT, deserializes the payload into a StaticJsonDocument<300>, checks the mode field, and calls rp.parseBasic(doc) to decode command fields.Wrobot struct updated
RobotProtocol::parseBasic() writes decoded values into the global wrobot struct fields: joyy (from JSON joy_y), joyx (from JSON joy_x), dir, height, and go (from JSON stable). spinOnce() handles buffer refresh logic to detect stale commands.Control loops compute torque
On the next main-loop iteration,
lqr_balance_loop() reads wrobot.joyy to set the wheel speed target, yaw_loop() reads wrobot.joyx to accumulate heading demand, and leg_loop() reads wrobot.height to set servo positions. All loops produce their outputs before motor.move() is called.SimpleFOC drives motors and servos move legs
The final torque targets are written to
motor1.target and motor2.target and applied by motor1.loopFOC() / motor1.move(). Simultaneously, sms_sts.SyncWritePosEx() sends updated position commands to both STS3032 servos over Serial2, completing the full sense → compute → actuate cycle.