Skip to content

The parallel-linkage ankle

Stage 2 · Mechanics · ~30 min · Before this: 01 Degrees of freedom, 04 Actuators, 05 Leg loads and stairs · numbers from the repository as of 2026-10-29

Marvin Bot’s ankles have no motor at the ankle. Two motors sit up on the shin and push the foot through cranks and rods: together they tilt it forwards and back (pitch) and side to side (roll). This page shows why, how the two motors share the work, and where such a linkage runs out of strength.

Try swinging a hammer holding the head, then holding the end of the handle: the same mass is much harder to swing when it is far from your hand. A leg is a pendulum the robot must swing forward at every step, and the hip is the hand. The torque the hip needs to swing it grows with the leg’s moment of inertia about the hip, Σ m·d², where d is each part’s distance from the hip. Because the distance is squared, a kilogram at the ankle counts four times as much as a kilogram halfway down the leg. This is why legged robots move mass towards the body: the MIT Cheetah puts its knee motor at the hip and drives the knee through a linkage.[6] Weight low on the leg (distal mass) also adds to the impact at every foot strike.

For Marvin Bot’s two 0.90 kg ankle RS03 motors: placed at the ankle (0.64 m below the hip) they would add 2 × 0.90 × 0.64² = 0.74 kg·m² to the leg’s inertia about the hip. Mounted on the shin 80 and 180 mm above the ankle, they add 0.90 × (0.56² + 0.46²) = 0.47 kg·m², 36% less, and the foot and ankle stay slim.

The price: neither motor drives a joint by itself. A serial ankle (the kind page 01 described, one joint carried by the next) stacks a pitch motor and a roll motor, each driving one axis. A parallel ankle connects both motors to the foot through separate paths, so both motors share every load and their angles map onto pitch and roll through geometry. Picture two people lifting the back corners of a table: lift together and it tilts forwards (pitch); one up, one down and it tilts sideways (roll). That mapping is what this page is about.

Start here: press Marvin Bot today, then drag Ankle pitch and Ankle roll and watch the two cranks (the short arms on the motors) in the picture. Leave the geometry sliders for later.

How to read the pictures: the top drawing shows the left ankle from the side and from behind; blue is the lower motor (a) with its rod, orange the upper motor (b). In the lower pair of charts, the left map colours every pitch-and-roll pose by its worst “transmission” (how squarely the rods push: green good, orange fair, red poor; the white ring is the current pose), and the right chart shows which combinations of pitch and roll torque the two motors can make together at the current pose (green), against what the simulator assumes (violet box) and the demand you set (red dot).

Presets

Ankle pose

+ = toes up (dorsiflexion). Range ±40°.
Range ±20°. Left ankle; + rolls the sole's outer edge down.

Geometry

Length of the arm on each motor. Longer: more ankle speed, less ankle torque.
Where the rods grip the foot, measured back from the ankle. Further back: more pitch torque, less pitch speed.
How far apart sideways the rods grip. Wider: more roll torque, but a wider foot and shin.
Lower motor fixed 10 mm behind, 80 mm up; upper motor 180 mm up.

Motors and load

How much forwards-and-back turning force the ankle must make (a stair step needs about 24 N·m).
For rod forces and the torque map. 60 / 30 N·m is the repo's rod-sizing case.

Side view (left) and rear view (right) of the left ankle

–
Crank angles a / b
–
Transmission a / b (1 best, below 0.5 poor)
–
Pitch torque, rated / peak
–
Roll torque, rated / peak
–
Rod forces for the demand
–
Jacobian dθ/dq (rows a, b; explained below)
–

Whole range: worst transmission at each pose (left) and the torque the pair can make at this pose (right)

Worst pose: pitch rated / peak
–
Worst pose: roll rated / peak
–
Worst transmission (≥ 0.5)
–
Largest rod force
–
Ratios at neutral (pitch / roll)
–
Rod lengths; crank swing
–
motor a (low) and its rodmotor b (high) and its rodreal torque region (peak)real (rated)simulator's box (peak)demand

Each motor turns a crank, a short arm of radius r. A push rod of fixed length L joins the crank tip to a point on the foot, behind and beside the ankle’s pitch and roll axes, through ball joints (spherical rod ends) at both ends. The ankle itself is a passive cross (a universal joint). Motor, crank, rod, foot and shin form a closed kinematic chain: a loop, unlike the open chain of a serial arm. In the mechanism literature this is an RSU leg (revolute motor, spherical joint, universal joint) and the ankle is “2-RSU + U”.[1][2]

Inverse kinematics (from the foot pose to the crank angles) is the easy direction: each rod has a fixed length, so for a given foot angle there is only one crank angle (in the assembled arrangement) that lets the rod reach. It can be written as one formula.

Go deeper: the formula

For a foot pose (pitch qp, roll qr), rotate the rod’s foot point A0: A = Ry(−qp) Rx(qr) A0. The crank tip sits at M + r(cos θ, 0, sin θ), with M the motor centre. The rod must keep its length:

|M + r(cos θ, 0, sin θ) − A|² = L² ⇒ P cos θ + Q sin θ = K

With d = A − M: P = −2r·dx, Q = −2r·dz, K = L² − |d|² − r². Solution: θ = atan2(Q, P) ± arccos(K / √(P² + Q²)). Two answers (crank “elbow up” or “elbow down”); the linkage stays on the one it was assembled in. No answer when |K| > √(P² + Q²): the rod is too short or too long to reach. This is exactly what tools/ankle_linkage.py solves, and the playground runs the same equations in your browser.

The forward problem (from crank angles to foot pose) has no simple formula; it is solved numerically. That matters on the robot, because the encoders measure crank angles but the policy thinks in pitch and roll.

The Jacobian: speed and torque in one matrix

Section titled “The Jacobian: speed and torque in one matrix”

The Jacobian J is a small table of numbers (a 2 × 2 matrix) that says how fast each crank turns per unit of ankle motion. Read it as gear ratios: one row per motor, one column for pitch and one for roll. The same table, flipped (transposed, written Jᵀ), turns motor torques into ankle torques. The dots over symbols mean “rate of change” (speed):

[θ̇a, θ̇b]ᵀ = J · [q̇p, q̇r]ᵀ [τp, τr]ᵀ = Jᵀ · [τa, τb]ᵀ

The second equation follows from the first by conservation of energy (virtual work): the power the motors put in, τaθ̇a + τbθ̇b, equals the power at the ankle, τpq̇p + τrq̇r, for every motion.[4] At Marvin Bot’s neutral pose the playground (and the repo tool) finds

J ≈ [[1.02, −0.67], [0.96, +0.67]] ⇒ τp ≈ 1.02 τa + 0.96 τb, τr ≈ −0.67 τa + 0.67 τb

So pitch torque is about the sum of the two motor torques, and roll torque about 0.67 times their difference (angles measured about the same shin axis; the two motors face opposite ways, so in each motor’s own sign convention the roles swap). The numbers have a simple reading. The rod pushes the foot 60 mm behind the pitch axis, and the crank is 60 mm long, so for pitch the lever ratio is about 60/60 = 1. For roll the rod sits 40 mm beside the axis: 40/60 ≈ 0.67. Longer cranks give more speed and less torque; rods further out give more torque and less speed.

The consequence for a robot standing on one foot on a stair: it needs pitch and roll torque at the same time, and both come out of the same two motors. If motor a is already at its limit making pitch torque, any roll torque must come from motor b alone. The feasible set of (τp, τr) is a parallelogram (“diamond”), not a rectangle. The repo’s capacity figures (32 N·m pitch, 25 N·m roll continuous) are each pure pitch or pure roll at the worst pose; the playground’s torque map shows the combinations.

A crank can only push the rod along the rod. The useful part of the motor’s torque is the force along the rod times the crank’s lever, and that lever is largest when the rod is square to the crank. The transmission is the sine of the angle between crank and rod (1 when square, 0 when in line):

Frod = τmotor / (r · transmission)

When crank and rod line up, the transmission goes to zero: the motor can no longer move the foot in that direction, and a small foot load needs an enormous rod force. That is a singularity of the linkage (the Jacobian loses rank: the two motors can no longer move the foot in two independent ways), and near it the mechanism also loses stiffness and controllability.[1][3] A common rule of thumb in linkage design is to keep the transmission angle above 30° to 40°; Marvin Bot’s tool requires sin ≥ 0.5 (30°) everywhere in the range. The current geometry reaches 0.53 at the corner of the range (toes down 40°, rolled 20°).

How it got there (docs/decisions.md):

  • RS06 or RS03? With RS06 motors the first linkage gave 22 / 11 N·m continuous against about 22 / 14 needed for slow stairs and standing on one foot; the ankles went back to RS03 (2026-10-26).
  • Side by side to staggered. Two RS03 side by side made the shin about 132 mm wide. Staggering them on the shin’s centre line, one high and forward, one low and behind, gave a ~95 mm shin with the same torque, and kept the shin’s lower front within 40 mm of the ankle axis, which matters on stair nosings (2026-10-26).
  • Calf layout rejected. With the high motor behind, the search reached only 23 / 20 N·m, because the low motor’s rod becomes very short.
  • Rod ends. About 1.9 kN at the worst pose for a 60 / 30 N·m demand sizes the rod ends (M6 steel-ball rod ends or larger) and the push rods.

Today’s static stair check (sim/mujoco/check_step_up.py, 18 cm step, stance leg) asks 24.4 N·m pitch and 17.5 N·m roll of the ankle, both within the 32 / 25 N·m continuous available at the worst pose.

Closed loops are awkward for robot simulators. Most represent a robot as a tree of joints (each link has one parent), which is what URDF, the standard robot description file, can describe and what fast “reduced-coordinate” solvers (ones that track only the joint angles) exploit. A loop must be closed by an extra constraint: MuJoCo (a physics simulator, page 14) has equality constraints (connect, weld) that pin two points together[7]; Isaac Sim (PhysX) asks you to exclude one joint of the loop from the articulation, which the solver then treats with lower priority and which “tends to accumulate the most simulation error”.[8]

What this simplification costs:

  • Torque is coupled on the real robot but independent in simulation. The simulator’s limits form a box (pitch ±118.8 and roll ±80.4 N·m at the same time). The real limits form the diamond in the playground: full pitch leaves nothing for roll. A policy trained in the box could ask for combinations the hardware cannot make.
  • The limits change with pose on the real robot. At the worst pose the pair makes 96 N·m peak pitch and 74 roll, not 118.8 and 80.4. The simulator is optimistic there by about 24% in pitch and 9% in roll.
  • Speed, inertia and friction are mapped at neutral only. The two motors’ reflected inertia and friction appear in joint space through Jᵀ(·)J, which also varies with pose. The simulation uses one armature and friction value per joint (placeholders).
  • No rod compliance, backlash or rod-end friction in the simulation.

These are reasonable for a first policy, especially with the training rule that peaks stay under 70% of peak and with domain randomisation (training on many slightly different simulated robots so the policy does not depend on exact values). Two things follow for the hardware phase. The robot needs a small conversion layer between the policy and the motors: inverse kinematics (pitch and roll targets to crank angles, the playground’s equations), numeric forward kinematics (crank encoders to pitch and roll observations), and the joint-space gains mapped to motor space (Kmotor = J⁻ᵀ Kjoint J⁻¹, which couples the two motors). That layer is not in the repo yet (robot/ has only the RobStride driver). And a check worth adding: replay the trained policy’s ankle torques through the real Jacobian at each pose and count how often they leave the diamond.

Option For Against
Serial ankle (pitch motor, then roll motor at the ankle) simple kinematics; each motor one axis; no rods heavy foot end, higher leg inertia, wide ankle; roll motor carries little torque most of the time. Rejected 2026-10-25 (“heavier shin”)
Parallel 2-RSU, motors side by side symmetric; 31 / 26 N·m with 65 mm cranks and ±55 mm rods 132 mm wide shin, wider foot
Parallel 2-RSU, motors staggered (chosen) 95 mm shin, 32 / 25 N·m, slim lower front for stairs two different rod lengths (91 and 194 mm), asymmetric behaviour, worst transmission 0.53
Staggered with the high motor behind (calf) knee front clear 23 / 20 N·m: rejected
Linear actuators (SPU ankle: spherical joint, sliding “prismatic” actuator, universal joint) more uniform behaviour over the range needs ball screws; the RSU was judged better overall in an optimisation study[1]
Direct-driven joints with no linkages (as in Berkeley Humanoid Lite) simplest to build and model motor mass at the ankle[9]
  • Crank radius versus rod lever: a longer crank gives a faster ankle but less torque, and the crank swings through a bigger arc that must fit beside the shin.
  • How far beside the axis the rods attach: more roll torque, but a wider foot and shin that are more likely to clip a stair or the other leg.
  • Where the two motors sit on the shin: good transmission at the corners of the range against knee clearance (the calf layout freed the knee front but lost a third of the torque).
  • Motor size: RS06 motors would save 0.56 kg per ankle (lighter, easier-to-swing legs) but did not give enough torque for slow stairs and one-foot standing.
  • Whether to give the simulator the real coupled torque limit (or check the policy against it) before hardware: more work now, but fewer surprises when the policy first meets real ankles.
Your decision note

Built from your settings above. Paste it into an issue, a design discussion or an AI assistant session.

Where this leads. This page moved 1.8 kg of motors up each shin to make the legs easier to swing. Page 07 looks at the whole robot’s mass budget: where all 40 kg sit, and why every kilogram counts.

  1. G. Cervettini, R. Mauceri, A. Coppola, F. Bergonti, L. Fiorio, M. Maggiali, D. Pucci, “A Framework for Optimal Ankle Design of Humanoid Robots,” IEEE-RAS Humanoids 2025 (arXiv 2509.16469) — used for: RSU and SPU parallel ankles, closed-form inverse kinematics, Jacobian rank loss, RSU judged better overall.
  2. R. Kumar et al., “Kinematic analysis of a novel parallel 2SPRR+1U ankle mechanism in humanoid robot,” Advances in Robot Kinematics 2018 — used for: parallel ankle kinematics, workspace and singularity analysis.
  3. C. Zhou, N. Tsagarakis, “On the Comprehensive Kinematics Analysis of a Humanoid Parallel Ankle Mechanism,” J. Mechanisms and Robotics 10(5), 2018 — used for: forward kinematics needing a numeric solution; workspace and singularities of a 2-DOF parallel ankle.
  4. K. M. Lynch, F. C. Park, Modern Robotics, Cambridge University Press, 2017, ch. 5 and 7 — used for: velocity Jacobian, statics τ = JᵀF from virtual work, singularities, closed chains.
  5. Asimov 1 documentation, Joint Design and Actuation — used for: an open-source humanoid’s RSU ankle; its simulation models pitch and roll as direct joints mapped to two motors.
  6. P. M. Wensing et al., “Proprioceptive Actuator Design in the MIT Cheetah,” IEEE T-RO 33(3), 2017 — used for: keeping leg inertia low by placing motors near the body and driving distal joints through linkages.
  7. MuJoCo documentation, XML reference: equality constraints — used for: closing kinematic loops with connect and weld constraints.
  8. NVIDIA Isaac Sim documentation, Tutorial 10: Rig Closed-Loop Structures — used for: articulations must be trees; one loop joint excluded from the articulation accumulates the most error.
  9. Y. Chi et al., “Demonstrating Berkeley Humanoid Lite,” RSS 2025 (arXiv 2504.17249) — used for: an open humanoid whose joints are each driven by their own actuator with no linkages.