The problem in one paragraph
A cube standing on one edge is an inverted pendulum: gravity pulls it further over the harder it leans, and there is nothing outside the cube to push against. The only available lever is internal momentum — spin a flywheel one way and the body must rotate the other. The control problem is to measure the lean at a few hundred hertz and command a motor torque that both corrects the lean and stops the wheel winding up to a speed where it has no authority left. Corner balancing is three of these, coupled.
Signal flow
+----------+ tilt, rate +---------------+ torque +----------+
| IMU |---------------->| COORDINATOR |---------->| FOC NODE |
| SPI 500Hz| | fusion + K*x | setpoint | G431 |
+----------+ +-------+-------+ +----+-----+
| wheel speed | 3-phase
+------------------------+
+----v-----+
| BLDC + |
| FLYWHEEL |
+----------+ Controller state machine
BOOT --> CALIBRATE --> IDLE --> BALANCING --> FAULT
| gyro bias | wheel | |th|<20deg | |th|>20deg
| 10 s still | free | 500 Hz | or w > 0.9*w_nl
v v v v
store bias motor off tau = Kth*th motor disabled,
+ Ktd*thd + Kw*w dump ring buffer Part numbers and costs were gathered from vendor and datasheet pages on 17 Sep 2026. Verify at the cart — two independent checks returned different DigiKey prices for the ESC board on the same day.
| Part | MPN | Qty | Unit | Source | Note |
|---|---|---|---|---|---|
| Gimbal BLDC motor | CubeMars GL35 KV100 | 1–3 | $54.99 | cubemars.com | 0.15 N·m cont, 0.46 peak, 90 g — the only part verified to clear torque and speed |
| FOC driver | B-G431B-ESC1 | 1–3 | $29–54 | DigiKey / ST | STM32G431 + gate driver + current sense on one board |
| Budget driver | SimpleFOCMini (DRV8313) | 1 | $6.59 | Banggood | Voltage mode only — no current sensing, no true torque command |
| Encoder | AS5047P-TS-EK-AB | 1–3 | $4.93 | DigiKey | 14-bit SPI. AS5600 at ~$3 is the "get it running" part |
| IMU | ICM-42688-P | 1 | ~$15 | TDK / breakout | 2.8 mdps/√Hz, 32 kHz ODR. Avoid BNO055 — fusion capped at 100 Hz |
| Coordinator MCU | ESP32-S3-DevKitC-1 | 1 | ~$15 | Espressif | Hardware FPU. ESP32-C3 has none, and no MCPWM |
| Battery | 4S 1500 mAh LiPo | 1 | $19–28 | hobby | Capacity, not C-rating, is the constraint |
| Rim mass | M5×16 hex bolt + nut, A2 | 7–10/wheel | ~$0.15 | any | ~5.7 g each; count comes from the blueprint generator |
| Pivot | Hardened steel ball ∅7 mm | 1–2 | ~$1 | bearing supplier | Printed plastic creeps under point load |
| Bearing | 608-2RS | 1–3 | ~$1 | any | 8×22×7, only if the motor needs support |
| Nozzle | 0.6 mm hardened steel | 1 | ~$8 | printer vendor | Required for CF-filled filament; brass wears out in hours |
Totals. One-axis bench rig ≈ $58. Three-axis corner cube ≈ $419, of which ~$60 is an unsourced mechanical estimate — the least reliable line on the page.
Control tick — from a hardware timer, never from loop()
float tau = K_TH * (tilt - biasInt) + K_TD * tiltRate + K_W * w;
tau = constrain(tau, -TAU_MAX, TAU_MAX);
if (fabsf(tilt) > TILT_LIMIT || fabsf(w) > W_LIMIT) { motor.disable(); return; }
motor.move(tau); // TORQUE mode, not voltage Gains for the 120 mm baseline at 500 Hz, straight from the simulator: Kθ = 49.37, Kθ̇ = 5.35, Kω = 0.0268. The third is the one people omit; without it the wheel saturates and the cube drops after about four seconds.
Host environment
# requirements.txt - simulation + mesh side
numpy>=2.0
pillow>=10.0
# firmware toolchain
platformio>=6.1 # espressif32 platform
# NOTE: two espressif32 platforms resolve to core 2.0.17 vs 3.3.11;
# the LEDC and camera APIs differ between them. Pin it.
# Windows: export PYTHONIOENCODING=utf-8 before an upload, or esptool
# can hang mid-flash on a unicode progress glyph. The browser simulator and blueprint generator are dependency-free ES modules that also run under node, which is how they are unit-tested against a numpy reference — 44/44 on gains, poles, trajectories and the bisection solver.
Drawings are generated from your cube size rather than fixed — the flywheel is sized backwards from the momentum budget, so the bolt count is a physics result. Change the target lean and it moves.
Baseline output, 120 mm edge
| Item | Value | Derived from |
|---|---|---|
| Flywheel OD | 69.6 mm | 0.58 × edge, clipped by rail clearance |
| Bolt circle | 53.6 mm | OD − 2·bolt∅ − 6 |
| Rim bolts | 7 × M5×16 | required inertia ÷ inertia per bolt |
| Wheel inertia | 3.18 × 10⁻⁵ kg·m² | rim mass at bolt radius + disc |
| Pivot ball | ∅7 mm | 0.06 × edge |
Honest scope. These are dimensioned concept drawings — correct in proportion and in the derived numbers, with no tolerances or fits. .STEP, .STL and Gerber archives are not hosted yet; once storage is connected they can be attached as files on a post in this section.
Driver wiring
B-G431B-ESC1 MOTOR / SENSOR
PHASE U -------------------------- motor phase A
PHASE V -------------------------- motor phase B
PHASE W -------------------------- motor phase C
3V3 -------------------------- AS5047P VDD
GND -------------------------- AS5047P GND (star point at driver)
SPI SCK -------------------------- AS5047P CLK
SPI MISO -------------------------- AS5047P MISO
CS -------------------------- AS5047P CSn
UART TX -------------------------- coordinator RX (torque setpoints)
UART RX -------------------------- coordinator TX
If the motor locks or stutters at startup, swap any two phase wires. Slicer settings, structural parts
| Material | Nozzle | Hotend | Bed | Cooling | Walls |
|---|---|---|---|---|---|
| PA6-CF | 0.6 hardened | 290–300 °C | 80 °C | 0–20% | 5+ — dry the filament or it foams |
| PETG-CF | 0.6 hardened | 250–260 °C | 80 °C | 30–40% | 4 |
| PLA (jigs only) | 0.4–0.6 | 205–215 °C | 60 °C | 100% | 3 |
Walls, not infill. Frame stiffness comes almost entirely from perimeter count; raising infill instead adds mass, and mass costs recovery angle directly.
Everything below is simulated, against a model cross-checked in two languages. No hardware has been built, so there are no measured antenna, thermal or latency traces yet — those belong in posts in this section, with the raw data attached.
| Measurement | Value | Method |
|---|---|---|
| Open-loop unstable pole | 9.3106 rad/s | eigenvalue of the linearised plant |
| Fall time constant | 107 ms | 1/pole — sets the loop-rate floor |
| Peak torque, 8° catch | 0.209 N·m | nonlinear sim at 4 kHz |
| Peak wheel speed, 8° catch | 2740 rpm | same run |
| Max recoverable lean @ 0.18 N·m | 10.09° | bisection over full sim runs |
| Momentum dump excursion | 6.76° | wheel pre-spun to 2865 rpm, shed to 0 |
| Kω deleted → failure | t = 5.74 s | identical kicks; wheel winds to 4892 rpm |
Torque budget vs cube size
| Edge | Mass | Fall τ | Peak torque | Catches @ 0.18 N·m |
|---|---|---|---|---|
| 80 mm | 0.45 kg | 88 ms | 0.043 N·m | 18.1° |
| 100 mm | 0.65 kg | 98 ms | 0.078 N·m | 16.3° |
| 120 mm | 0.90 kg | 107 ms | 0.130 N·m | 14.5° |
| 150 mm | 1.45 kg | 120 ms | 0.262 N·m | 12.4° |
| 200 mm | 2.20 kg | 139 ms | 0.477 N·m | 8.7° |
2.25× the size costs 11× the torque. That is the strongest argument for building small.