PROJECTTINKER
[ 02 ]build manual

Build it, stage by stage

Nine stages, each one a working thing on its own with a pass/fail test you can actually run. Pick a difficulty path, tick tasks off as you go, and use the animated figures where the mechanism is hard to picture. Nothing here is a summary — the tests are quantitative and the numbers come from the simulator.

Pick how far you're going [ easiest first ]

These are not three different projects; they are the same project stopped at three points. Every path starts at stage 1. Choosing one re-labels the stages below and dims what you don't need.

Do not start at the corner cube. Both of the best-documented projects in this field — ETH's Cubli and the HTL Hollabrunn diploma build — built a single-axis prototype first and only then scaled to three. Most of the abandoned hobby repos did not. The stage-1 rig costs about $58 and teaches you 90% of what the full cube needs.

01

Bench pendulum

1 weekend · difficulty ●○○○○ · every path starts here

Objective. A rig that can fall over in exactly one plane, with a motor and flywheel at the top. No cube. No CAD. You are building a test bench, not a product, and it should look like it.

Why a flywheel can hold anything up

Get this in your bones before cutting anything. There is no outside force available — the cube cannot push on the air. All it can do is move momentum around inside itself. Spin the wheel one way and the body must rotate the other way, because the total has to stay put. The figure below has gravity switched off so the bookkeeping is visible: the blue bar is the sum, and it never moves.

Conservation of angular momentum, gravity off. Torque pushes body and wheel in opposite directions; the total is pinned at zero. With gravity on, this is the only lever you have.

Parts

  • Gimbal BLDC motor, driver, magnetic encoder, IMU, microcontroller, bench supply or LiPo — see the stacks page for four costed options.
  • One 608 bearing (skateboard bearing, 8 mm bore) and an 8 mm shaft or bolt.
  • An arm: 150–200 mm of aluminium extrusion, plywood, or a printed beam. Stiff beats light.
  • A base with real mass — 2 kg minimum, or a G-clamp to the bench.

Steps

Pass/fail test
  1. Hold the arm at 10° and release. It should fall smoothly and accelerate. On a 200 mm arm expect roughly a quarter to half a second to reach the stop.
  2. Flick the flywheel by hand. It should coast for five seconds or more. If it stops dead in two, the bearing is preloaded or the wheel is rubbing, and that friction eats a torque budget you have not measured yet.
  3. Waggle the arm sideways. Any perceptible side play means a loose bearing fit — fix it now, because it becomes phantom dynamics later.
Three ways to do the flywheel
easiest

Printed disc + bolts. Print a disc with a ring of holes and put M5 bolts and nuts through them. Inertia becomes trivially adjustable — add or remove bolts. This is what the blueprint generator dimensions for you.

middle

Printed hub + steel ring. A ring of steel bar or a stack of large washers bonded into a printed carrier. More inertia for the same diameter, harder to adjust once glued.

hardest

Machined aluminium with a steel rim. Best inertia-to-mass, and it can be balanced properly on a mandrel. Only worth it once you know the design works.

02

Tilt estimate, motor unplugged

1 evening · difficulty ●○○○○ · do not skip

Objective. An angle you trust, at your real loop rate, with the motor physically disconnected. Most "my cube oscillates" problems are stage-2 problems that were never tested.

You have two sensors and both lie, in different directions. The accelerometer tells you which way gravity is — true on average, buried in noise, and corrupted by any real acceleration. The gyroscope tells you how fast you are rotating — smooth and clean, with a small bias that integrates into a steadily growing error. Blend them: trust the gyro over short times, let the accelerometer slowly pull the estimate back.

A complementary filter running live. White is the raw accelerometer estimate; steel blue is the integrated gyro walking away from truth; faint grey is the real angle; bright blue is the fused output. One line of code, and it is enough — none of the hobby builds researched used a Kalman filter in production.
// once per control tick, dt seconds apart
float accAngle = atan2f(ax, ay) * 57.2958f;      // degrees, check YOUR axes
tilt = (1.0f - ALPHA) * (tilt + gyroZ * dt) + ALPHA * accAngle;
// ALPHA around 0.02 at 500 Hz. Smaller = trust the gyro more.

Steps

Pass/fail test
  1. Print a 10° wedge, or use a phone level. Rest the arm on it and hold for 60 seconds. The estimate must read 10.0° ±0.3° the whole minute. If it creeps, the bias subtraction is wrong or ALPHA is too small.
  2. Tap the rig sharply. The estimate should wobble and settle inside about 100 ms, not ring for a second.
  3. Check loop jitter, not just rate: log actual microseconds between ticks for 10 s. Spread should be a few percent. A loop that averages 500 Hz but occasionally stalls 8 ms is worse than a steady 200 Hz one.
03

Torque control, arm clamped

1–2 evenings · difficulty ●●○○○ · the real gate

Objective. Prove that when you ask for 0.05 N·m you get 0.05 N·m. The controller's output is a torque; if your driver only does voltage or speed, every stage after this is built on sand.

Check your driver honestly here. The most-recommended cheap board in this space, the SimpleFOCMini (DRV8313), is voltage-mode only — SimpleFOC's own driver comparison lists it as having no current sensing. It will spin a gimbal motor beautifully and cannot accept a true torque command. For real torque control you need in-line or low-side current sensing: the SimpleFOC Shield v2, a DRV8302 module, or the B‑G431B‑ESC1.

The measurement that settles it

Do not trust the library's word for it. Clamp the arm so it cannot move, command a step of torque, and record the wheel's speed from the encoder. The wheel's angular acceleration times its inertia is the delivered torque:

tau_delivered = J_wheel × (dω/dt)

Command 25%, 50% and 100% of your intended maximum and plot delivered against commanded. You want a straight line through the origin. A flat spot near zero is motor deadzone — identify it now and compensate, or the controller spends its life fighting an invisible nonlinearity. A curve that flattens at the top means you hit a current limit earlier than you thought.

Why the top end sags

Available torque falls as the wheel speeds up, because back-EMF eats the voltage headroom. This is not a detail; it is the reason cubes fall over. Watch the operating point walk out along the envelope during a real catch:

The shaded region is what the motor can actually deliver. The dot is the live operating point during a recovery from 7°. When it reaches the edge, the controller is asking for torque that does not exist, and only different hardware changes that.
Pass/fail test

Delivered torque within ±15% of commanded across the range, in both directions, with deadzone under about 5% of maximum. If torque does not reverse cleanly through zero, stop — a balancing wheel lives at zero crossings, and a driver that stumbles there will never hold the cube up.

04

Measure, then simulate

2 hours · difficulty ●○○○○ · free, and it saves weekends

Objective. Four numbers measured off your actual rig, put into the simulator, giving you three gains and — more importantly — a verdict on whether your motor is big enough, before you tune anything.

Measuring the four numbers

NumberHow to get it
Total mass MKitchen scales. Everything that moves — arm, motor, wheel, any electronics mounted on the arm.
CoM distance lBalance the whole arm horizontally on a knife edge (a ruler works). The balance point is the centre of mass; measure from the pivot.
Pendulum inertia JpLet it swing hanging down (stable, not inverted) through a small angle and time 20 swings for period T. Then Jp = M·g·l·T²/4π².
Flywheel inertia JwBifilar pendulum: hang the wheel level on two threads a distance d apart and L long, twist a few degrees, time 20 torsional oscillations. Then Jw = m·g·d²·T²/(16π²L).

The bifilar trick is worth ten minutes. Flywheel inertia is the number the whole design hinges on, and a printed wheel with bolts in it is nothing like the uniform disc a CAD mass-properties dialog assumes.

Pass/fail test

Max recoverable lean above with real noise and latency included. Below 3°, stop and fix hardware — bigger motor, heavier rim, or smaller cube. No amount of tuning rescues a torque budget that is not there. For scale, ETH reported roughly 7° on the original Cubli's 1‑DOF prototype, torque-limited because they ran gearless.

05

Close the loop

1 weekend · difficulty ●●●○○ · it will oscillate first

Objective. It balances. Three gains, applied in a specific order, with safety cutoffs written before the control law.

The failure that kills most first builds

Leave out the wheel-speed term and your cube balances beautifully for four seconds and then drops. The wheel winds up one way, reaches no-load speed, has no torque left, and that is that. With the term in, the controller deliberately leans a fraction to bleed the wheel back toward zero — trading a little tilt for momentum headroom. Both cubes below are identical and get identical kicks; the left has that one gain deleted.

Left: Kω deleted. Right: Kω = 0.0268. Same plant, same disturbances. Simulated over 20 s, the left winds to 4892 rpm and falls at t = 5.7 s; the right peaks at 1593 rpm and returns to zero.

Order of tuning

  1. Safety first. Tilt past 20°, wheel speed past 90% of no-load, or a missed control tick — cut the motor. Write this before the control law, because you need it in the first thirty seconds.
  2. Kθ alone, raised until the rig fights back and then oscillates. Note where oscillation starts.
  3. Kθ̇ raised until that oscillation damps. Too much and it goes sluggish and noise-sensitive.
  4. Kω last, and never zero. Start at the simulator's value; raise it until the wheel reliably returns toward zero within a few seconds of a disturbance.

Firmware skeleton

Structure matters more than the exact API — fixed-rate task, filter, state feedback, saturate, safety. The motor calls are whatever your driver library uses.

// ---- gains straight from the simulator ----
const float K_TH = 49.37f;     // tilt        (N.m per rad)
const float K_TD = 5.35f;      // tilt rate   (N.m per rad/s)
const float K_W  = 0.0268f;    // wheel speed (N.m per rad/s)
const float ALPHA = 0.02f;     // complementary filter
const float DT = 1.0f / 500.0f;
const float TILT_LIMIT = 20.0f * DEG2RAD;
const float W_LIMIT = 0.90f * W_NOLOAD;
const float TAU_MAX = 0.18f;

float tilt = 0, tiltRate = 0, biasInt = 0;

void controlTick() {              // from a hardware timer, NOT loop()
  imu.read();
  tiltRate = imu.gyroZ - gyroBias;
  float accAngle = atan2f(imu.ax, imu.ay);
  tilt = (1.0f - ALPHA) * (tilt + tiltRate * DT) + ALPHA * accAngle;

  float w = motor.shaftVelocity();          // rad/s, relative to the body

  // ETH's trick: absorb constant sensor bias into a filter state instead
  // of letting it park the wheel at a non-zero speed forever.
  biasInt += 0.02f * tilt * DT;
  float tiltCorrected = tilt - biasInt;

  if (fabsf(tilt) > TILT_LIMIT || fabsf(w) > W_LIMIT) { motor.disable(); return; }

  float tau = K_TH * tiltCorrected + K_TD * tiltRate + K_W * w;
  tau = constrain(tau, -TAU_MAX, TAU_MAX);
  motor.move(tau);                          // TORQUE mode, not voltage

  logRing[logIdx++ & LOG_MASK] = { tilt, w, tau };  // dump later, never print here
}

The biasInt term is worth understanding rather than copying. A constant offset in your tilt estimate — a millimetre of IMU misalignment — looks to the controller like a permanent lean, so it holds permanent torque, so the wheel accelerates forever. ETH's published trace shows an uncorrected 0.058 rad offset parking the wheel at 37 rad/s indefinitely. The low-pass integrator soaks that into a filter state instead.

Pass/fail test
  1. Balances unaided for 60 seconds.
  2. Wheel speed stays bounded over that minute — wandering around zero, not climbing.
  3. Recovers from a finger flick, and the wheel returns toward zero within a few seconds afterwards.

Bench-pendulum path finishes here. Everything below is optional.

06

Mechanical refinement

1 weekend · difficulty ●●○○○ · before you scale up

Objective. Remove the mechanical problems that would otherwise be multiplied by three. The big one is flywheel balance, and it has a quantitative test most people skip.

Balancing the flywheel, with a number

An unbalanced wheel shakes the frame and your IMU reads that shake as tilt. You do not need a balancing machine — you already have an accelerometer bolted to the thing:

  1. Clamp the rig. Spin the wheel to a steady 1500 rpm under control.
  2. Log accelerometer magnitude for 5 seconds; record peak-to-peak.
  3. Stop, mark the wheel at 12 o'clock, add a small mass (tape, a nut) at one position, repeat.
  4. Work around the wheel until you find the position that reduces vibration, then refine the amount.

Target: vibration at 1500 rpm small compared to the tilt signal you are trying to measure. In practice, under about 0.05 g peak-to-peak and the control loop stops caring.

Pass/fail test

Vibration at 1500 rpm under 0.05 g peak-to-peak, no visible rim wobble, and the stage-5 balance test still passes on the same gains.

07

Cube on an edge

1–2 weekends · difficulty ●●●○○ · it starts to look real

Objective. The same controller, now inside a cube standing on one edge. The plant has changed, so you measure it again — a real cube with motors bolted to its faces is nothing like the uniform solid a simulator assumes.

Dimensioned drawings for frame, flywheel, motor mount and pivot are generated from your cube size on the blueprints page — including how many bolts to put in the wheel to reach the inertia the physics actually asks for.

Geometry worth knowing before you cut

Balanced on an edge, the centre of mass sits a/√2 above the pivot — for a 120 mm cube, 84.9 mm. That is your lever arm, and required torque scales with it and with mass, which is why doubling cube size costs far more than double the motor.

Pass/fail test

Stands on one edge 60 seconds unaided, recovers from a light push, wheel speed bounded. Edge-balancer path finishes here.

08

Three axes

3–6 weekends · difficulty ●●●●● · the integration is the hard part

Objective. Corner balancing. Three wheels, spin axes along the three cube edges meeting at the balancing corner, each wheel plane ideally passing through the centre of mass.

The hard part is electronic, not mathematical. There is no cheap microcontroller that cleanly runs three current-sensed FOC loops at once — each wants its own set of high-resolution PWM channels plus phase-current ADC sampling synchronised to the PWM edge, and sub-$30 MCUs generally expose one or two complete sets. The architecture that works is three driver boards each closing its own current loop, reporting to one coordinator that runs IMU fusion and the balance law and sends torque setpoints over UART or CAN. No single library glues that together; that firmware is yours to write.

Centre of mass becomes a first-class problem

For corner balancing the CoM must sit on the body diagonal, and you will not hit that by luck. Both ETH and the HTL Hollabrunn team independently flag that at an unstable equilibrium, small CoM offsets create outsized tipping moments. Find it empirically: suspend the cube from one corner on a thread and mark the vertical line on the body; repeat from a second corner. The lines intersect at the CoM. Add trim mass until that point lands on the diagonal.

Pass/fail test

Stands on a corner 60 seconds, started by hand near equilibrium. Worth knowing: in the ECC 2013 paper the full 3D Cubli also had to be started near equilibrium by hand — the autonomous jump-up was demonstrated on the 1‑DOF prototype.

09

Jump-up

open-ended · difficulty ●●●●● · optional showmanship

Objective. Get from lying flat to balancing on a corner without being placed there. Spin a wheel up, brake it hard, and the momentum transfer flips the cube.

The two hops. Flat to edge is a 45° rotation; edge to corner is 35.3°. Each is a single violent momentum transfer rather than a controlled manoeuvre — spin up, brake, then catch whatever you land in.

ETH used an RC servo slamming a metal barrier into a bolt head on the wheel — a mechanical brake, not an electrical one. Shorting the motor phases is the cheap electrical version; it transfers momentum more slowly, so you need a bigger wheel or a faster spin for the same impulse. Their first design used a solenoid and was dropped for being 39 g heavier.

Pass/fail test

Flat to edge, unassisted, then caught and held. Corner-to-corner jump-up is a stretch goal that ETH themselves only published on the 1‑DOF rig.

!!

Failure modes, attributed

learn on someone else's time

What went wrongDocumented byThe lesson
Sensor update rate too slow for the loop; abandoned before ever balancingcroomjm/CubliVerify sensor throughput at your target rate before writing the controller
Magnetic encoder "super sensitive and unreliable" beside the spinning wheelDami Kim, Hackaday.ioMagnet gap tolerance is tight — validate in situ, not on the bench
Motor overheated and died mid public demoDami Kim, Hackaday.ioBalancing is continuous-torque duty; size for thermal, not just peak
PWM routed to an unsupported controller pin, found after PCB fabWillem PenningsBreadboard the driver interface before committing a board
Thread taps snapped off inside stainless motor mountsHTL HollabrunnPlan tap depth and hole geometry for the actual fastener material
Linearisation breaks down past ~25°, causing steady-state errormecatronica-UTC RWIPYour LQR is only valid near upright — which is fine, your torque budget is too
Motor deadzone below a PWM thresholdmecatronica-UTC RWIPIdentify and compensate it in stage 3
Constant sensor bias parks the wheel at non-zero speed foreverETH Cubli, both papersAdd the bias integrator — Kω alone does not fix this

One pattern visible only in aggregate: most public "Cubli" repositories are short-lived student projects with a handful of commits and no documented successful balance. The ones that worked published their full parameter set or their actual gains. Treat an undocumented build as unverified, however good the video looks.