← All lessons
Kinematics · chassis speeds · swerve · WPILib

Four wheels,
one motion.

Before you can control a mechanism — an arm, a drivetrain — you need a translator between two languages: "the business end should do this" and "each joint should do this." That translator is called kinematics — and swerve drive is where it gets beautiful.

chassis motion (vx, vy) rotation (ω) wheels & modules the robot
Part 1 / The dictionary problem

Two languages, one mechanism

You think in terms of the business end: "put the claw on the game piece", "drive forward at 2 m/s while spinning". Your motors only understand joint commands: this joint at this angle, this wheel at this speed. A kinematic model is the dictionary between the two languages — pure geometry, no forces, no motors, no physics of pushing. (How hard to push is the business of feedback and feedforward. Kinematics only decides where things should point and how far they should move.)

Roboticists call the business end the end effector — the hand of an arm, the chassis of a drivetrain: the one thing you actually care about placing. The dictionary reads in both directions, and the two directions have completely different personalities. Forward kinematics goes joints → end effector: take the change each joint made and stack them, one on top of the next, until the overall result falls out. It always returns exactly one answer. Inverse kinematics goes the other way: it cares only about where the end effector should be, and works backwards to joint positions that would put it there — of which there may be several, or none. It doesn't hand you the answer; it hands you possibilities:

Joints & wheels what you command End effector the hand, the chassis forward — stack every change inverse — find the possibilities
The whole lesson in one picture. Forward kinematics adds up what the joints did — always exactly one answer. Inverse kinematics proposes joint positions for a desired end effector — 0, 1, or many possibilities. Everything from here on is filling in the two arrows — first on an arm, then on a swerve drive.
Part 2 / Forward kinematics

Stack the changes, find the hand

Start with the friendliest mechanism that has joints: a two-joint arm. A shoulder carries an upper arm of length L1; an elbow at its end carries a forearm of length L2. Where does the hand end up? Walk the chain and stack each joint's change: the shoulder swings everything downstream of it, so take one step of length L1 in the shoulder's direction. The elbow adds its own bend on top of whatever the shoulder already did, so take a second step of length L2 in the combined direction. Tip to tail — the hand is wherever the stack ends:

base 1 · the shoulder's change: walk L1 at θ1 2 · the elbow's change, stacked on top: walk L2 at θ1 + θ2 = the hand: every change, added up reach: the circle the stack can touch
Forward kinematics is vector addition. Each joint contributes one step; the steps stack tip to tail; the end effector is the total. The recipe scales: a 20-joint snake arm is the same walk, 20 steps long — and however many joints, the stack lands in exactly one place.
forward x = L1·cos(θ1) + L2·cos(θ1 + θ2)
y = L1·sin(θ1) + L2·sin(θ1 + θ2)
x, y — where the hand (end effector) ends up, measured from the shoulder baseθ1 — the shoulder's angleθ2 — the elbow's angle, measured relative to the upper arm — each joint only knows its own local bendL1, L2 — the two link lengths (m)θ1 + θ2 — the stacking, written down: the forearm's direction is the shoulder's change plus the elbow'sFirst term: the shoulder's step. Second term: the elbow's step, aimed by both joints because it rides downstream of both. Feed in any (θ1, θ2) and exactly one (x, y) falls out — forward kinematics never shrugs.
Widget 00 — The stacking benchjoints in, hand out
Hand x
—
Hand y
—
Answers
always 1

Experiments: (1) θ2 = 0 — the arm is one straight stick and the hand sits at exactly L1 + L2 = 1.00 m from the base; check it against the readouts. (2) Hold θ2 and sweep θ1 — the whole stack swings together, because everything downstream of a joint inherits its change. (3) θ1 = 90°, θ2 = −90° — shoulder straight up, elbow folded flat: the readouts should land at x = 0.45 (that's L2) and y = 0.55 (that's L1). The equation, verified by hand.

Part 3 / Inverse kinematics

The hand is the boss; the joints negotiate

Now flip the question, because this is the direction robot code usually needs: the game piece is at (x, y) — what should the joints do? Inverse kinematics doesn't care how the chain gets there. It cares only about the end effector, and works backwards from it, computing possibilities for the joint positions. Plural, on purpose: most reachable points can be touched by two different elbow shapes — elbow-up and elbow-down — a point at exactly full stretch by one, and a point beyond the arm's reach by none:

the target — all IK cares about …reached two different ways possibility 1 — elbow down possibility 2 — elbow up out here: zero possibilities
Inverse kinematics returns a menu, not an answer. One target, two joint stories that both check out under forward kinematics. The math can't choose between them — your code does, using joint limits, obstacles, or least travel from where the arm is now.
inverse cos(θ2) = (x² + y² − L1² − L2²) / (2·L1·L2)
θ2 = ±acos(…)   θ1 = atan2(y, x) − atan2(L2·sin θ2, L1 + L2·cos θ2)
x, y — the target: where you want the hand; the only input IK looks atcos(θ2) — the elbow bend the target's distance demands (this line is the law of cosines in disguise)± — the headline character: two signs, two elbow shapes, two possibilitiesatan2(…) — aims the shoulder so the chosen elbow shape lands on the targetThe failure mode is honest, too: if the first line demands |cos θ2| > 1, no elbow angle exists — the target is outside the arm's reach (or inside the hole a folded arm can't touch) and IK returns zero possibilities. Exactly ±1 → the two possibilities merge into one.
Widget 01 — The possibility finderhand in, joints out
Possibilities
—
Elbow-down θ1, θ2
—
Elbow-up θ1, θ2
—

Experiments: (1) Default target — two arms, one solid, one ghost, both touching the same point: a two-item menu. (2) Slide to x = 1.00, y = 0 — full stretch: the two possibilities collapse into one straight arm. (3) Push past the rim (x = 1.00, y = 0.60) — zero possibilities; the readout says so and forward kinematics can't be argued with. (4) Creep toward the base (x = 0.05, y = 0.05) — unreachable again, from the inside: a two-link arm can't fold shorter than L1 − L2 = 0.10 m.

Part 4 / From arms to drivetrains

A drivetrain is an arm wearing wheels

Now swap mechanisms and keep the ideas. For a drivetrain, the end effector is the whole chassis, and the "joints" are the wheels. One honest difference: an arm's dictionary is written in positions, a drivetrain's in velocities — wheels roll continuously, so the natural question isn't "where is the chassis" but "how is it moving right now". WPILib packs that "one motion" into a ChassisSpeeds — three numbers, always in the robot's own frame:

chassis (vx, vy, ω)
vx — forward speed, in m/s; positive is toward the robot's frontvy — sideways speed, in m/s; positive is toward the robot's left (WPILib's convention)ω — spin rate ("omega"), in rad/s; positive is counter-clockwise viewed from aboveA drivetrain that can command all three independently — slide any direction while facing anywhere — is called holonomic. Swerve is holonomic; a tank drive is not: its wheels can't roll sideways, so vy is stuck at zero.

What does holonomic buy you? Watch two robots get the same order — "the game piece is directly to your left, go get it, keep facing downfield" — and see it in the sim:

Widget 02 — The sideways testholonomic vs tank

The swerve robot just… goes. Its wheels pivot left and roll, heading untouched. The tank robot has to translate the order into the only language it speaks — turn, drive, turn back — and pays for it in time and in losing sight of the field. Both robots run their real kinematic equations; nothing here is scripted motion.

Part 5 / Warm-up: two wheels

The smallest dictionary that works

Start with the drivetrain you can read at a glance: a differential drive (tank drive) — one wheel speed per side. Both wheels forward: drive. One faster: arc. Opposite: spin. The whole dictionary is two lines of arithmetic:

forward v = (vR + vL) / 2   ω = (vR − vL) / W
inverse vL = v − ω·W/2   vR = v + ω·W/2
vL, vR — left and right wheel speeds (m/s)v — the chassis forward speed: just the average of the two sidesω — the spin rate: the difference between the sides, divided by how far apart they areW — the track width: the distance between left and right wheels (m)Read the forward line out loud and it's common sense: "the robot goes as fast as its wheels agree, and turns as hard as they disagree." Every drivetrain dictionary is this idea with more geometry.
Widget 03 — Two sliders, one robotdifferential drive
Plot — chassis v & ω vs time

The two chassis numbers, computed live from the wheel sliders by the forward-kinematics lines above. Move one slider and watch both curves respond — every wheel change is simultaneously a speed change and a turn change. That coupling is exactly what swerve gets rid of.

Chassis v
—
Chassis ω
—
Turn radius
—

Experiments: (1) Equal speeds — ω reads exactly 0, the trace is a straight line. (2) vL = −vR — v reads 0 and the robot pirouettes in place. (3) vR = 2·vL — a clean arc; check that the turn-radius readout equals v/ω. (4) Now try to make it drive sideways. You can't — no combination of two forward-rolling wheels produces vy. That's non-holonomic in one sentence.

Part 6 / The rigid-body trick

One equation to rule every wheel

Now swerve. Four modules, each free to point anywhere — how do you find each one's marching orders? Here's the trick that makes it easy: a robot chassis is a rigid body, and every point on a rigid body moves as the sum of two ingredients. Ingredient one: the translation — every point shares it identically. Ingredient two: the spin — every point orbits the center, moving perpendicular to its arm, faster the farther out it sits (stand on the edge of a merry-go-round and you know this in your stomach).

rigid body vi = (vx − ω·riy , vy + ω·rix)
vi — the velocity that module i's corner of the chassis is actually moving with (a 2D vector, robot frame)vx, vy — the chassis translation, shared by every module identicallyω — the chassis spin raterix, riy — module i's position on the chassis, measured from the robot's center (m)The ω terms are the spin ingredient: swapped coordinates with one minus sign is the recipe for "perpendicular to the arm, proportional to ω and to distance." Physics classes write it as a cross product, ω × r. Same thing.

See both ingredients add, tip-to-tail, at all four corners at once:

Widget 04 — The vector kitchentranslation + spin
FL module speed
—
FL module angle
—

Experiments: (1) ω = 0 — four identical violet arrows: pure translation is the same everywhere. (2) vx = vy = 0 — the orange arrows form a pinwheel: pure spin, each one perpendicular to its corner's arm. (3) Mix them — now every corner's green resultant is different, because each corner adds the same violet arrow to a different orange one. Those four green arrows are the marching orders — the arm's tip-to-tail stack from Widget 00, played four times at once.

Part 7 / Swerve, driving direction

Inverse kinematics: from stick to modules

The green arrows are the answer — each module just has to become its arrow. A swerve module can do exactly two things: point somewhere and roll at some speed. So convert each arrow to polar form, and you have a module state:

module state speedi = √(vix² + viy²)   anglei = atan2(viy, vix)
desaturate if max(speed) > vmax: scale all speeds by vmax / max(speed)
speedi — how fast module i's wheel should roll (m/s): the length of its green arrowanglei — where module i should point: the direction of its green arrow (atan2 is arctangent that gets the quadrant right)vmax — the fastest a wheel can physically rollDesaturation is the honesty step. Ask for full speed and full spin and the math happily orders a corner wheel to exceed vmax — which it can't do, so the motion would warp. Scaling every wheel by the same factor keeps the motion's shape and gives up only its size.

That's the entire swerve driving pipeline: rigid-body equation → polar form → desaturate. Fly it yourself — and try the field-relative toggle, which is the one extra trick every real swerve uses (it rotates your stick command by the gyro heading, so "away from you" stays "away from you" no matter where the robot faces):

Widget 05 — The command mixerinverse kinematics, live
Plot — the four module speeds vs time

All four wheel speeds, with the dashed line at vmax. Drive gently and they move independently; pile on translation plus spin and watch the whole family get scaled down together the moment the fastest one hits the ceiling — desaturation happening live.

Fastest module
—
Desat scale
—
Heading θ
—

Experiments: (1) Pure vx, then add ω — the robot corkscrews, and the modules point four different ways at once. (2) Crank vx = 2 and ω = 3 — the desat readout drops below ×1.00: you asked for more than the wheels have. (3) Spin for a moment, stop, then command pure vx with field-relative off — the robot drives off toward wherever its nose points. Turn field-relative on and repeat: same stick, and the trace marches straight down the field regardless of heading. That's the gyro doing the translating. One reassurance while you corkscrew: with vy = 0 and spin on, the path curling sideways is correct — "forward" rotates with the robot, so constant vx plus ω traces a circle by definition. The version of this that is a bug shows up in Part 8.

Who makes the module obey? Kinematics hands each module a setpoint — an angle and a speed. Actually reaching that setpoint is a control problem: real modules run a PID controller on the steering angle and feedforward plus feedback on the wheel speed. The dictionary writes the orders; the controllers carry them out.
Part 8 / The fine print

Skew: when spinning bends your line

One more thing before the sensing direction, and it's a famous swerve gotcha. Your code doesn't command the robot continuously — it runs a loop, recomputing module states every T seconds (20 ms in WPILib) and holding them fixed in between. But the modules are bolted to a chassis that keeps rotating during those milliseconds, so a command that meant "translate that way" when the loop ran is pointing somewhere slightly different by the loop's end. On average, the executed translation is smeared off-target by half the rotation per loop:

skew executed direction is off by ≈ ω·T/2, every loop
fix speeds = ChassisSpeeds.discretize(speeds, T);
ω — how fast the chassis is spinning (rad/s)T — the control loop period (s): how long each command is held before being recomputedω·T/2 — the average heading change during one loop: the angle the held translation gets smeared bydiscretize — WPILib's pre-correction: it replaces your command with the one whose rotate-while-translate result, after one whole loop, lands exactly where the original command intendedThe smear compounds: command "straight downfield while spinning" and the sideways error accumulates every single loop — a curved path out of a straight order. The exact correction inside discretize is the pose exponential (twist) — derived in full in the Controls Engineering in FRC textbook. Feel the size of it: ω = 2 rad/s with T = 100 ms is a 5.7° smear, about 10 cm sideways per meter driven.
Widget 06 — The skew labraw vs discretized

Both robots get the identical order — hold your dashed line at 1.4 m/s while spinning at ω, recomputed every T. The top robot sends the raw command; the bottom robot runs it through discretize first.

Raw: off the line
—
Discretized: off by
—
Skew ω·T/2
—

Experiments: (1) Defaults (ω = 2, T = 100 ms) — the raw robot peels off its lane about 10 cm per meter while the discretized one nails it; the skew readout says 5.7°. (2) Drop T to 20 ms — a real robot loop. The raw drift shrinks to ~2 cm per meter: smaller, not gone. This is exactly why an un-discretized swerve subtly bows its auto paths whenever it rotates mid-drive. (3) ω = 0 — no spin, no skew, both robots perfect: skew is strictly a rotate-while-translating disease. (4) ω = 3 with T = 150 ms — cartoon-grade skew, so you can watch the mechanism: each held command starts correct and rots as the chassis turns under it.

Part 9 / Swerve, sensing direction

Forward kinematics: the wheels vote

Now run the dictionary backwards. Each module's encoders report what it actually did — a measured speed and angle, i.e. a measured green arrow. From four arrows, reconstruct the chassis motion — the drivetrain's version of stacking every joint's change into one overall end-effector result. But notice the bookkeeping: four modules × 2 numbers = 8 measurements, describing a motion with only 3 unknowns (vx, vy, ω). The system is overdetermined — so the wheels vote, and the mismatch between their stories is physically real: it's wheels fighting each other, called scrub:

forward vx = mean(vix)   vy = mean(viy)
spin ω = Σ(rix·viy − riy·vix) / Σ(rix² + riy²)
vix, viy — module i's measured velocity vector (from its encoder speed and angle)mean(·) — average over the four modules: translation is what the wheels agree onω — recovered spin: each wheel's "turning contribution" (its velocity crossed with its arm), averaged with lever-arm weightingThis is the least-squares answer — the single (vx, vy, ω) that best explains all eight numbers at once, for a symmetric module layout. Whatever it can't explain is the scrub.
Widget 07 — The scrub detectorforward kinematics

The chassis is commanded to corkscrew (vx = 1.2, ω = 1.2), but the front-left module's steering is misaligned by the slider amount. Forward kinematics takes the four measured arrows and votes; the scrub meter shows how much of the wheels' story it couldn't explain.

Voted vx
—
Voted ω
—
Scrub
—

Experiments: (1) 0° — the vote reproduces the command exactly and scrub reads zero: consistent wheels tell one story. (2) 20° — the voted vx and ω shift and the scrub meter wakes up; on carpet this is tire wear, wasted current, and a robot that crabs when told to go straight. (3) 45° — one wheel now fights three; the vote stays surprisingly sane, because least squares lets the majority outvote the liar. This same vote, run on distances instead of speeds, is how the robot tracks its position — the whole next lesson.

Part 10 / WPILib & recap

The dictionary, shipped as a class

Everything on this page is four lines of WPILib. You describe the geometry once — where each module sits — and the library does both directions of the dictionary plus desaturation:

kinematics = new SwerveDriveKinematics(m_flLoc, m_frLoc, m_blLoc, m_brLoc);
states = kinematics.toSwerveModuleStates(chassisSpeeds);
SwerveDriveKinematics.desaturateWheelSpeeds(states, kMaxSpeed);
measured = kinematics.toChassisSpeeds(state1, state2, state3, state4);
SwerveDriveKinematics — the dictionary object; you hand it each module's position as a Translation2d (+x forward, +y left, meters from robot center)toSwerveModuleStates — inverse kinematics: ChassisSpeeds in, one SwerveModuleState (speed + angle) per module out, in constructor orderdesaturateWheelSpeeds — the honesty step from Part 4, applied in placetoChassisSpeeds — forward kinematics: the wheels voteThree extras worth knowing: ChassisSpeeds.fromFieldRelativeSpeeds(vx, vy, ω, gyroAngle) is the field-relative toggle from Widget 05; ChassisSpeeds.discretize(speeds, 0.02) is the skew fix from Part 8, called right before toSwerveModuleStates; and toSwerveModuleStates accepts an optional center of rotation — pass a module's own location and the robot pirouettes around that corner. Full docs: Intro and Chassis Speeds and Swerve Drive Kinematics; for the full matrix derivation, see the Controls Engineering in FRC textbook.

Six ideas to walk away with

1. Kinematics is a dictionary, not a controller. Pure geometry between "what the joints do" and "what the end effector does" — it never decides how hard to push.

2. Forward stacks; inverse proposes. Forward kinematics adds every joint's change tip to tail into one certain answer for the end effector. Inverse kinematics starts from the end effector and returns possibilities — two elbows, one, or none — and your code picks among them.

3. Chassis speeds are the drivetrain's end effector. (vx, vy, ω), robot-relative, written in velocities instead of positions. Commanding all three independently is what "holonomic" means.

4. One equation runs all of swerve. Every module's job is vi = translation + spin × arm; convert each arrow to speed-and-angle, then desaturate so no wheel is ordered past its limit.

5. Real loops are discrete — mind the skew. Rotating while translating smears each held command by ω·T/2, bowing straight lines. ChassisSpeeds.discretize pre-bends the command so every loop lands where you meant.

6. Forward kinematics is a vote. Eight measurements, three unknowns: least squares stacks what the wheels report into the best single story, and the leftover disagreement is real, physical scrub.

kinematics — the geometry dictionary between joint motion and end-effector motion
end effector — the business end being placed: the hand, the chassis
forward kinematics — stack every joint's change → one end-effector answer (the sensing direction)
inverse kinematics — end-effector target → joint possibilities (the driving direction)
chassis speeds — (vx, vy, ω): one robot motion, robot-relative
holonomic — able to command vx, vy, ω independently, like swerve
module state — one swerve module's orders: a speed and an angle
desaturation — scaling all wheels down together when one exceeds vmax
field-relative — stick commands rotated by the gyro so "away" stays "away"
skew — the sideways drift from rotating while translating on a discrete loop
discretize — WPILib's pre-correction that makes each held command land as intended
scrub — wheels fighting each other: the motion the vote can't explain
Pocket glossary — the whole page, one line each. Further reading: WPILib kinematics docs and the Controls Engineering in FRC textbook.