[Haller]
Mobile base
§Base

Chassis geometry and weight distribution

First-principles sizing of the three-wheel base — where the arms, the battery, the caster and the camera mast go so that a fully extended arm can't tip the robot over.

This is a paper design, not a build log. Nothing on this page has been cut, printed or bolted. It fixes the numbers that the rest of the mechanical work depends on — longitudinal length, caster position, battery position, mast height — so that the first chassis is sized against arithmetic rather than against a guess. Every figure is either traced to a datasheet or flagged as an assumption.

The base is a triangle seen from above: two driven wheels on the motor axis line at the front, one swivel caster behind. The two SO-101 arms sit on a line parallel to the motor axis, and they reach forward — out past the front wheels, over the only tipping edge that matters. The whole design problem is that one sentence.

Frame and sign conventions

Origin on the ground at the midpoint of the drive-wheel axle.

  • x — forward, perpendicular to the motor axis. Positive is the direction the arms reach.
  • y — left, along the motor axis.
  • z — up.

So the forward tipping edge is the line x = 0, and the whole of the weight-distribution problem is "keep the centre of mass at negative x".

Haller base — top viewmotor axis = tipping edge (x = 0)MF5010 + ⌀100 wheelcastermastHilti B 22-1951.33 kgLR476 mm max reach476 mm max reach266 mm arm separation340 mm track180 arm line200 battery300 mast360 caster (L)forward →

What is already fixed

These are not design choices left to make — they are already in the repo or in a datasheet, and the rest of the geometry has to live with them.

QuantityValueWhere it comes from
Wheel track340 mmmotor_params.yaml + URDF. Changing it breaks odometry.
Wheel radius50 mm (⌀100)Same.
Drive motors2× MF5010, 0.26 N·m rated / 0.40 N·m peak (10T), 137 g, ⌀49 mmMF5010_Specs.pdf. Direct drive, no gearbox.
Motor bearing rated load153 NSame. The wheel cantilevers off the rotor, so this is a real limit.
Arm separation266 mmXLeRobot arm_base_joint at y = ±0.133 m in xlerobot.urdf.
Shoulder torque1.863 N·m stallSTS3215 C001, 19 kg·cm at 7.4 V. One servo per joint — no doubling.
BatteryHilti B 22-195 Nuron, 1.33 kgpower_system.md.

The sim currently places the two arms at ±200 mm (sim/builder.py, "so arms don't overlap"). That is an arbitrary number, not the XLeRobot one. If the real base is built at 266 mm, the sim offsets should move to ±133 mm or every policy trained in sim inherits a 34 mm per-side geometry error.

Mass budget

Only the parts that matter for balance. Everything else — LiDAR, converters, wiring, fuses, hub — is deliberately excluded, and gets a reserve line instead.

ItemQtyUnitTotalSource
SO-101 arm, moving links2609 g1218 gSum of <inertial> masses in so_arm100.xml
SO-101 base link (servo + printed shell)2~150 g300 g55 g STS3215 + estimated shell — assumption
MF5010 motor2137 g274 gDatasheet
Drive wheel, ⌀100 metal hub + neoprene2~300 g600 gVendor listings — weigh these
Swivel caster1200 g200 gGiven
Hilti B 22-195 Nuron11330 g1330 gpower_system.md
Jetson Orin Nano dev kit1176 g176 gNVIDIA
RealSense D4551103 g103 gIntel community measurement (Intel publishes no figure)
Mast + pedestal + arm plate1~600 g600 gPrinted — assumption
Chassis plate + motor mounts1~1500 g1500 gDesign allowance — assumption
Dry total6.30 kg
Payload, 2 × 500 g1.00 kgDesign case
Loaded total7.30 kg

Four of those eleven lines are assumptions, and they are 3.0 kg of the 6.3 — so treat the absolute numbers as provisional and the ratios as the real output. hardware_inventory.md has recorded "total robot mass is not measured" as an open item for months; this table is the first estimate to close against.

What the arms can actually lift

This sets the disturbance the base has to survive, so it comes first.

The shoulder (Pitch) is a single STS3215 at 1.863 N·m stall. Holding the arm out horizontally already costs most of that: at full extension the unloaded gravity torque at the shoulder is 0.956 N·m, just over half the budget, before anything is in the gripper.

Solving τ_shoulder(m_payload) ≤ 1.863 N·m across the workspace, using the MJCF link masses and centres of mass:

Reach from arm mountPayload at stallRealisable?
150 mm2643 g✗ torque says yes, the gripper and wrist say no
200 mm1510 g✗
250 mm1020 g~
300 mm731 g✓
350 mm546 g✓
400 mm403 g✓
450 mm290 g✓
476 mm (max reach)231 g✓

Max reach is 476 mm from the arm mount to the grip point, at 120 mm above the mount plane. At 50% of stall — a sane continuous rating — the payload at full extension is zero: the arm can just barely hold itself up.

The useful result: the forward moment an arm can apply is capped, and the cap barely moves with the payload assumption. Because the shoulder axis is parallel to the tipping edge, the arm's moment about that edge is τ_shoulder + g · x_mount · (m_arm + m_payload), and τ_shoulder can never exceed stall. Searching every pose:

  • payload capped at 250 g → worst moment about the arm mount is 2.50 N·m
  • payload capped at 500 g → worst moment is 2.69 N·m

Doubling the assumed payload moves the design load by 7%, because a heavier payload forces a shorter reach. The base can be sized against a torque, not against a workspace — which means this analysis survives changing the gripper.

Tipping: the only equation that matters

The support polygon is the triangle with vertices at the two wheel contacts (0, ±170) and the caster contact (−L, 0).

For forward tip-over the edge is the front axle, and the caster's position is irrelevant — only the centre of mass matters:

tips forward  ⟺  x_cm > 0

Write d = −x_cm, the distance the CoM sits behind the axle. Then the static load split follows from one moment balance about the front contact line:

N_caster = W · d / L        N_drive = W · (1 − d / L)

That is the whole trade in two symbols. d buys tip-over safety; d/L costs traction. Push the CoM back to stop the arms tipping the robot and you unload the drive wheels; the only way to have both is to make L — the longitudinal length — longer.

Two more disturbances eat into d, and both scale with CoM height:

braking:   d_used = a · z_cm / g          slope θ:  d_used = z_cm · tan θ

This is where the arm mount height bites, and it is why the mast is not free.

Haller base — side view, lever armstipping edgeworkspace seen by the D455D455 · 57° downgrip, 476 mm out≤ 231 g hereCoM 7.30 kgd = 104 mmarm plate 450 mmcamera 950 mmdeck 100 mmz_cm 325 mm

Where the arms have to go

Run the CoM sum for the worst arm pose while sliding the arm mount line along x (battery and caster fixed):

Arm line vs axledCaster loadTips at
+50 mm (ahead)−3 mm—already over
0 (on the axle)19 mm6%2.9°
−100 mm63 mm21%9.7°
−180 mm104 mm29%17.7°
−250 mm129 mm43%19.3°

Mounting the arms over the wheel axle — the intuitive choice — leaves 19 mm of margin and a robot that tips at 2.9°. Mounting them ahead of the axle tips it standing still. The arms have to sit ~180 mm behind the motor axis, reaching forward over the drive wheels rather than out beyond a cantilever.

Moving the battery cannot substitute for this. To reach d = 90 mm with the arms on the axle, the 1.33 kg pack would have to sit 600 mm behind the axle — well outside any sane footprint. The battery is a 1.33 kg trim adjustment on a 7.3 kg robot; the arm position is the actual control.

The design point

ParameterValueWhy
Wheel track340 mmfixed
Arm separation266 mmXLeRobot
Arm mount line180 mm behind the axletipping — see above
Arm plate height450 mmheight trade — see below
Caster360 mm behind the axle (L)caster load ≤ 30% loaded
Battery centre200 mm behind the axle, on the decktrims d, keeps z_cm low
Mast300 mm behind the axlebehind the arms, over the caster
Camera950 mm above ground, 57° downD455 min-Z — see below
Deck460 × 300 mm at 100 mmclears wheels, matches caster height
Overall448 × 365 mm, ~1.0 m tall

Which gives:

StowedArms extended + 2 × 500 g
Mass6.30 kg7.30 kg
CoM behind axle (d)167 mm104 mm
CoM height278 mm325 mm
Caster load46.3%28.9%
Tips forward at31.3°17.7°
Tips sideways at16.3°18.6°
Tips under braking at5.97 m/s²3.14 m/s²

Forward and sideways tip angles land within 1° of each other in the loaded case (17.7° vs 18.6°), which is the sign that L is correctly matched to the track — neither axis is the weak one.

The margin budget

104 mm of static margin sounds generous. It is almost entirely spoken for:

Stability margin budgetbraking at motor peak · 72.6 mm5° downslope · 28.4 mmtotal static margin d = 104 mm2.9 mm spare

Braking at the motors' peak thrust costs 72.6 mm; a 5° downslope costs another 28.4 mm. Doing both at once leaves 3 mm. The robot is stable, but it is stable by design, not by accident — which means two rules follow:

  • Cap deceleration in software, or accept that a hard stop on a wheelchair ramp is the tip-over case. The limit falls from 3.14 m/s² on the flat to 2.27 m/s² on a 5° downslope — below the motors' 2.19 m/s² peak by only 4%.
  • Any mass added high up spends this budget. Every 10 mm of z_cm costs about 3 mm of margin.

Arm mount height is stability-limited

d doesn't change with arm height, but z_cm does, and the braking term scales with it:

Arm plate heightz_cmTips atBraking limitvs motor peak 2.19 m/s²
350 mm281 mm20.7°3.70 m/s²1.69×
450 mm325 mm17.7°3.14 m/s²1.43×
550 mm369 mm16.0°2.82 m/s²1.29×
650 mm413 mm14.4°2.52 m/s²1.15×
750 mm458 mm13.1°2.28 m/s²1.04×

The arms cannot go to table height on this chassis. At a 750 mm arm plate — roughly where XLeRobot puts its arms, on a 760 mm cart top — the robot tips within 4% of the deceleration its own motors can produce. Working at 450 mm means Haller works on low surfaces, its own bench, or the floor. Raising it to bench height needs one of: low ballast at the back, a longer L, a wider track, or a hard software cap on deceleration. That is a real constraint, and it should be decided before anything is printed.

Traction and the caster load

Direct drive means thrust comes straight from motor torque with no gearbox multiplication — F = 2τ/r:

TorqueThrustAccel at 7.3 kgGrade
Rated0.26 N·m10.4 N1.42 m/s²8.4°
Peak (10T)0.40 N·m16.0 N2.19 m/s²12.9°
Peak (35T)0.53 N·m21.2 N2.90 m/s²17.2°

Available traction is μ · N_drive:

Surface (μ)Loaded (50.9 N on drives)Stowed (33.2 N on drives)
0.4 — polished concrete20.4 N ✓13.3 N — slips at peak
0.525.5 N ✓16.6 N ✓
0.6 — typical rubber on floor30.5 N ✓19.9 N ✓

The awkward case is stowed: folding the arms back moves the CoM to 167 mm behind the axle and hands 46% of the weight to the caster. On slick floors the wheels break traction before the motors stall. That is a benign failure — slip, not fall — and current-limiting fixes it, but it is worth knowing that the robot has less grip when it is not carrying anything.

Per-wheel load peaks at ~25 N against the MF5010's 153 N bearing rating, so the cantilevered wheel mount is not a concern.

Caster swivel shrinks the support triangle. A swivel caster's contact point trails its pivot. Right after a direction reversal the contact swings through 2 × trail before it settles — with 40 mm of trail, L is momentarily 280 mm rather than 360 mm, pushing the caster load from 29% to 37% and the sideways tip angle from 18.6° down to about 15°. Size L against L − 2 × trail, and prefer a short-trail caster.

Where the camera mast goes

The D455 sits behind the arms so its mass helps the balance instead of hurting it, and so it looks over the arms rather than through them. The mast at 300 mm behind the axle puts it 120 mm behind the arm line.

The constraint that sets its height is not field of view — it is minimum depth range. The D455's wide 95 mm baseline buys it 20 m of range at the cost of a min-Z around 0.52 m; anything nearer returns no depth. The nearest point of the workspace is only 150 mm forward of the mast horizontally, so:

√(0.15² + Δz²) ≥ 0.52   →   Δz ≥ 0.50 m

The camera must sit 500 mm above the arm plane — 950 mm above ground — purely so the near edge of the workspace is outside its blind zone. Field of view is not close to binding: the workspace spans 32.6° vertically against the D455's 58°, and 300 mm of half-width against the 745 mm it sees at the far edge. Depression angles run 73° (near) to 41° (far), so the mount wants ~57° down.

For comparison, XLeRobot puts its head camera 438 mm above its arm plane. Same ballpark, arrived at independently.

Ride height and caster sizing

The deck sits at 100 mm, which is set by the caster, not by the wheels: a 3-inch swivel caster with a top plate is ~100 mm from floor to mounting face. The drive axle has to be at 50 mm — the wheel radius — so the motors hang 50 mm below the deck on drop brackets. Get this wrong and the robot rocks on two wheels.

The BOM already has 4× 3-inch swivel casters on hand for a layout that needs one, so there is nothing to buy here.

What to measure before cutting anything

  1. Weigh the two drive wheels. 300 g each is a vendor listing, and 600 g is 10% of the dry mass.
  2. Weigh an assembled SO-101. The 760 g figure is 609 g of MJCF inertials plus a 150 g guess for the base link.
  3. Measure the XLeRobot plate's actual arm spacing and confirm the 266 mm from the URDF matches the physical part.
  4. Measure the caster's trail — it sets how much of L is real.
  5. Confirm the MF5010 winding (10T vs 35T). It changes peak thrust by a third, which changes the braking term, which is 70% of the stability budget. This is already flagged unrecorded in the BOM.
  6. Decide the arm height before printing the pedestal. 450 mm and 750 mm are different robots.

Then fix the sim: move the arm offsets from ±200 mm to ±133 mm, and add the base so that policies see the same geometry they will be deployed onto.

On this page