Skip to main content
Identifies the four friction-model parameters for one joint via a bidirectional velocity sweep. Gravity is computed centrally from the URDF (see Gravity compensation), so this command only fits the friction half-difference and a constant offset. Also available from the diagnostics dashboard (axol serve). Friction model: τ = Fc·tanh(0.1·k·v) + Fv·v + Fo Sweep safety is built in: shoulder_2 sweeps only its outboard side, capped at 0 (starting at rest is fine — travel past it swings into the base); wrist_2 gets the elbow raised to midpoint, which clears the gripper from the base through its full range; and shoulder_3 / wrist_1 sweeps hold shoulder_2 10° outboard so the arm swings clear of the chest cameras. The sweep poses are shared with tune.gravity, which additionally needs the test joint gravity-loaded — so shoulder_3 sweeps with shoulder_1 raised to humerus-horizontal (+90° on the left arm, −90° on the right — the shoulder_1 frame is mirrored, and +90° is the right arm’s hard stop) and the elbow bent (capped ±90°), wrist_1 with the elbow bent and wrist_2 at 85° (capped ±90°), and wrist_2 adds wrist_1 at 90° to the elbow raise. The friction fit is pose-independent (the fwd/bwd half-difference cancels gravity), so sharing the loaded poses costs it nothing. Friction is motor-specific — the shared defaults in config.py were measured on one reference robot. Run this per joint, per arm, on every robot you build, and pass --save to persist the fit to that machine’s calibration file (~/.almond/calibration.json). AxolConfig loads the file on construction, so saved values apply to every operation on the robot without a code change; explicit config overrides still win over them.
Every calibration tool (tune.factory, tune.friction, tune.gravity, tune.breakaway) holds and moves the arm on impedance only, at fixed calibration gains (kp/kd 250/3.5 on the shoulders, 180/5 shoulder_3, 130/5 elbow, 180/1.7 wrist_1, 130/2.25 and 130/2 the Damiao wrists) with gravity fed forward — never the motors’ own position loops (0xA4), and never the production gains, which rely on the realtime core’s host damping. Every joint is held before anything moves.
Why the slow profile. The core’s Coulomb term is capped at k = 100, so it applies only 17% of fc at 1°/s and 48% at 3°/s; whatever friction the joint really has at those speeds must come from the Stribeck term, whose tanh(v/0.02) is sharp. The slow profile measures exactly those speeds and fits both terms together, so the runtime cancels the friction the joint has where slow motion runs. On the jelly robot’s right shoulder_1 the hand-tuned setting applied ~1.10 Nm at 3°/s under 5 Nm of load against 0.93 Nm measured; the fit applies 0.93.