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.
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.
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.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.
Shown ~40× slower than real time. The fast train is the control loop; the amber tick is a fresh MPC solution landing.
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.
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.
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.
| Element | What it is |
|---|---|
| Decision variables | Ground reaction force f at each stance foot, at each of 10 horizon steps |
| Objective | Track the commanded body trajectory; penalise large and jerky forces |
| Constraints | Friction pyramid (no slipping), force limits, zero force on swing feet |
| Solver | qpOASES by default — use_jcqp: 0 in the controller's user parameters |
| Output | Fr_des[leg] — the desired reaction force handed to the fast loop |
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.
18 degrees of freedom: 6 for the floating body, 12 for the joints.
_contact_list with the MPC's Fr_des attached; only swing feet become tasks that compete for leftover freedom.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.
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.
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.
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.
checkPDesFoot() and checkForceFeedForward(), each opted into per state. These clamp rather than stop — a controller that asks for the impossible gets corrected, not killed.
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.
| Layer | Source file | Carries |
|---|---|---|
| Messaging | rt_interface_lcm.cpp | Operator commands, state estimates, logs |
| Motor bus | rt_spi.cpp · rt_ethercat.cpp | Joint commands out, encoder and torque in |
| Radio | rt_sbus.cpp · rt_rc_interface.cpp | RC transmitter → RC_mode → control mode |
| Inertial | rt_vectornav.cpp · lord_imu | Orientation and angular rate for the estimator |
| Startup | JPosInitializer.cpp | Eases joints to a known pose before control begins |
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.