← Zinan Yang · lab
MIT Biomimetics · Cheetah-Software

Cheetah Control Stack

controller_dt 0.002 s → 500 Hz use_wbc 1 · use_rc 1

A quadruped is a machine that is falling over almost all of the time. What stops it is not one clever algorithm but a stack of loops running at different speeds — a slow one that decides where to push, a fast one that works out which motors do the pushing, and a state machine that decides whether any of it should be running at all.

Gait · contact schedule
stance — foot loaded, exerts ground reaction force swing — foot in flight, tracking a foot trajectory now
01
The problem

You cannot push on air

The robot has twelve motors — three per leg — and a body with six degrees of freedom it does not directly control. The only way to move that body is to push against the ground, and the only feet that can push are the ones currently touching it. Every 2 ms the software has to answer one question: given which feet are down right now, what forces should they apply?

That single sentence is why the architecture looks the way it does. The set of feet on the ground changes several times a second, so the problem being solved changes several times a second too. In the animation above, watch the green feet — those are the only ones with any authority over the body at that instant.

Leg order matters. Throughout the codebase legs are indexed FR, FL, HR, HL — front-right, front-left, hind-right, hind-left. Get that order wrong in a contact array and the robot will confidently push with the wrong foot.
02
Architecture

Two clocks, one robot

Planning forces over the next third of a second is expensive — it is a quadratic program with dozens of variables. Converting those forces into motor torques is comparatively cheap. So the stack splits: the expensive planner runs slowly, the cheap converter runs fast, and the fast loop keeps using the planner's last answer until a fresh one arrives.

Loop rates · live
control ticks 0 MPC solves 0 ratio
500 HzWBC + joint PD
every 2 ms
~40 HzConvex MPC
every 13 ticks

Shown ~40× slower than real time. The fast train is the control loop; the amber tick is a fresh MPC solution landing.

Why not just run MPC at 500 Hz?

Because it would not finish. The QP over a 10-step horizon takes several milliseconds on the on-board computer. Attempting it every 2 ms would blow the deadline, and a missed deadline in a torque loop is a robot on the floor.

Why not run everything slowly?

Because leg dynamics are fast. At 40 Hz the joint controller would be 25 ms behind the leg it is meant to be steering — enough for a swing foot to stub the ground or a stance foot to slip.

03
The slow loop

Convex MPC plans forces, not footsteps

Model Predictive Control treats the body as a single rigid block — mass, inertia, and nothing else. Legs are massless force-applicators. That approximation is what makes the problem convex and therefore solvable in milliseconds.

It looks ahead ten steps, decides a ground reaction force for every foot that will be in contact at every step, then throws almost all of it away: only the first step's forces are actually used. Next cycle it re-solves from the new measured state. That is the receding horizon.

Receding horizon · 10 steps
applied — first step only predicted then discarded measured state
ElementWhat it is
Decision variablesGround reaction force f at each stance foot, at each of 10 horizon steps
ObjectiveTrack the commanded body trajectory; penalise large and jerky forces
ConstraintsFriction pyramid (no slipping), force limits, zero force on swing feet
SolverqpOASES by default — use_jcqp: 0 in the controller's user parameters
OutputFr_des[leg] — the desired reaction force handed to the fast loop
The friction cone is the interesting constraint. It is what stops the optimiser from producing a beautiful trajectory that requires a foot to push sideways harder than friction allows. A cone is not convex-friendly, so it is approximated as a four-sided pyramid — one of several places where the maths is bent to keep the QP fast.
04
The fast loop

WBC ranks its wishes

Whole-Body Control takes the MPC's desired forces and the full floating-base dynamics — all eighteen degrees of freedom, real leg masses included — and produces joint torques. But it is usually asked for more than it can deliver, so it obeys a strict priority order: satisfy the first task exactly, then satisfy the second only within whatever freedom the first left over, and so on down the list.

That "whatever freedom is left over" is null-space projection. A lower-priority task can never disturb a higher one. The order below is read straight from LocomotionCtrl::_ContactTaskUpdate in the Cheetah-Software source — tasks are pushed onto _task_list in exactly this sequence.

Task priority stack · click to toggle
DoF committed 0 / 18

18 degrees of freedom: 6 for the floating body, 12 for the joints.

Contacts are constraints, not tasks. A stance foot does not ask to stay put — it is required to. Those feet enter through _contact_list with the MPC's Fr_des attached; only swing feet become tasks that compete for leftover freedom.
05
Supervision

A state machine sits above all of it

Locomotion is only one mode. The robot also has to lie down safely, stand up, balance in place, recover after a fall, and — because it is a research platform — throw itself into a backflip. ControlFSM owns that, and the loop it runs each tick is worth memorising: safety pre-check → read RC mode → check transition → run state or run transition → safety post-check.

ControlFSM · select a state
operating mode NORMAL

Transitions are not instantaneous. When checkTransition() returns a different state, the FSM enters TRANSITIONING and calls transition() every tick until it reports done — only then does onExit() / onEnter() fire. That is what lets the robot lower itself gracefully instead of snapping between modes.

06
Safety

E-STOP means one thing: go limp

The safety checks bracket the control computation. Before it, safetyPreCheck() asks whether the body orientation is still recoverable — if the robot has tipped past the limit, the operating mode becomes ESTOP. After it, safetyPostCheck() sanity-checks what the controller just produced: are the desired foot positions physically reachable, are the feed-forward forces within limits.

Pre-check — is the robot safe to control?

checkSafeOrientation(), skipped when the mode is K_RECOVERY_STAND — because recovering from a fall necessarily starts from an unsafe orientation. Fail here and every state is abandoned for Passive.

Post-check — is the output sane?

checkPDesFoot() and checkForceFeedForward(), each opted into per state. These clamp rather than stop — a controller that asks for the impossible gets corrected, not killed.

Passive is not "off". It is the state where the joints are commanded to near-zero torque so the robot goes limp and folds under its own weight. On a legged machine that is the safe failure — a robot holding position with a fault is far more dangerous than one lying down.
07
Plumbing

LCM, and the bridge that makes simulation honest

There is no ROS here. Messaging is LCM — MIT's own publish/subscribe over UDP multicast, with no master process to start and no scheduler between a message and its handler. For a torque loop with a 2 ms budget, that absence is the point.

The other structural idea is the bridge. RobotRunner holds the controller and knows nothing about where its data comes from; HardwareBridge feeds it real SPI, EtherCAT and IMU traffic, SimulationBridge feeds it simulated equivalents. The same controller binary runs in both. A bug reproduced in simulation is genuinely the same bug.

LayerSource fileCarries
Messagingrt_interface_lcm.cppOperator commands, state estimates, logs
Motor busrt_spi.cpp · rt_ethercat.cppJoint commands out, encoder and torque in
Radiort_sbus.cpp · rt_rc_interface.cppRC transmitter → RC_mode → control mode
Inertialrt_vectornav.cpp · lord_imuOrientation and angular rate for the estimator
StartupJPosInitializer.cppEases joints to a known pose before control begins
cheater_mode: 0. With it off, the state estimator is doing real work — fusing IMU and leg kinematics. Set it to 1 and the controller is handed perfect ground-truth state from the simulator, which is useful for isolating whether a failure is control or estimation.
08
Further reading

Two papers carry the whole architecture

Start with Dynamic Locomotion in the MIT Cheetah 3 Through Convex Model-Predictive Control for section 03, then Highly Dynamic Quadruped Locomotion via Whole-Body Impulse Control and Model Predictive Control for section 04. The code itself is open source as mit-biomimetics/Cheetah-Software.