Self-balancing ProtoBot

2026 · hardware
timeframe
2 days, August 2026 - 43 firmware versions, 100+ archived runs
hardware
ProtoBot (microbots.io), CodeCell C6 Drive
training
4 policy generations, 3 rebuilt simulators - PPO in MuJoCo, CPU only
result
stock holds 1-2 seconds; now 60+ seconds (session cap), both ways up
method
AI pair-work: I ran the experiments and measurements, the AI wrote most code and analysis

Overview

The ProtoBot is a small self-balancing robot kit. Out of the box it holds balance for a second or two. After a 2-day hacking session it holds 60+ seconds, both ways up. The approach: telemetry first, classical control up to its physical limit, then a small policy trained in simulation and distilled back into a linear formula.

The one-minute version

The loop

  • Release the robot, auto-archive the full telemetry of every run, let the AI read the traces and adjust the firmware, flash, release again. Dozens of cycles a day.
  • Training ran on the same machine: PPO in MuJoCo, CPU only, about 30 minutes per policy generation.
  • Builds with PlatformIO, flashed over USB in about 20 seconds.

Telemetry first

You can't debug a robot you can't see.

  • 50 Hz telemetry over BLE into a Web Bluetooth dashboard: live charts, a pose view, about 20 live-tunable parameters.
  • Every run archived with its exact settings, which kept the tuning honest.
  • One day was lost to a BLE dongle that shipped with its antenna unscrewed.

The classical ceiling

Careful tuning alone cannot fix the stock controller. The loop physics get in the way.

  • The sensor-to-motor delay is 20-25 ms, measured, and it caps every gain: past kp of about 8 the loop rings at 5 Hz no matter what.
  • The motors do not move below a breakaway duty of about 48. Pulse-density modulation, full-strength 10 ms pulses at varying density, keeps average torque linear down to zero.
  • There is no encoder, so wheel speed is modeled from the command history (first-order lag, fitted tau of 0.3 s). Without this model any small bias accelerates the robot to its 26 cm/s ceiling and it falls within a second.
  • The true balance point sits about 0.55 degrees off vertical from mass asymmetry, found by running two setpoints and interpolating the zero-drift point.
  • Classical control with all of the above: 15-18 second balance sessions.

The robot measures itself

Every guessed constant turned out wrong, so the firmware grew a 25-second self-test bench: breakaway staircases, loaded step trials, battery sag, and an open-loop fall test.

QuantityMeasuredNote
Sensor-to-motor delay20-25 msthe binding constraint on gains
Loaded breakaway duty48 ± 4 coldwanders 36-56 warm, asymmetric
Command-to-motion lag15-30 mselectrical rise + stiction
Thermal derating-20-25% torquewarm vs cold, two independent methods
Open-loop fall poleω ≈ 7-17 rad/swarm tires fall slower; rigid theory says 15.5
Balance point offset≈ -0.55°per-unit mass asymmetry
Top speed0.26-0.28 m/sa 2° standing error falls in ~0.75 s

A 1.2 KB policy

  • A 7-32-32-1 network trained with PPO, int8-quantized: 2.7 ms per inference on the FPU-less C6.
  • It balances 60+ seconds hands-free, recovers from driving over a cable, and works upside-down, a pose it saw in only 20% of training.
  • Two failed generations taught the honesty lesson: one trained on motors 3x too weak learned bang-bang violence and died in 2 seconds; one trained on rigid tires learned over-stiff reactions and wobbled at 4 Hz.
  • The fix that stuck is a calibration gate: training refuses to start unless the sim reproduces the measured torque curve and fall speed.

What the linear model does

A plain linear regression over the policy's good runs explains R² = 0.99 of its behavior near upright. The readout doubles as a review of the hand tuning:

TermNetwork policyHand-tuned PD
Error gain (duty/°)5.36 - rediscovered
Rate damping0.420.24 - hand tuning was 2x under-damped
Command memory+0.35 · u(k-1)missing - its own delay compensation
Wheel-speed feedpushes with motionopposed it
Distilled law, fitted on this unit: error 5.36, gyro damping 0.45, wheel speed 1.38, drift 0.23, command memory 0.35, on a 40-95 duty map.
  • Refitted without the accelerometer term (R² drops only 0.992 to 0.987), the law fits in about 20 stock-shaped lines of firmware.
  • Configuration ladder: stock holds 1-2 seconds, the snippet alone holds tens of seconds, and pulses plus the measured setpoint (or the neural policy) hold 60+.
  • [TODO: status of suggesting the snippet upstream to microbots]