Differential-drive kinematics: from wheel rates to pose

Convert left and right wheel rates into robot speed, turning rate, and an exact constant-rate pose update. Explore straight travel, arcs, spins, and reverse motion, then check the limits of wheel odometry.

By 14 min read

What you will learn

  • Convert signed wheel rotation rates into body forward speed and yaw rate.
  • Calculate wheel rates for a requested linear and angular velocity.
  • Predict straight, circular, spinning, and reverse motion from one model.
  • Integrate a constant body velocity without a division-by-zero case at straight motion.
  • Explain why an exact model update still leaves physical odometry error.

Before you start

A robot drives its left wheel at 2 radians per second and its right wheel at 4. Where will its center be four seconds later?

Differential-drive kinematics answers that question using wheel size, wheel separation, and the starting pose. The calculation has two parts: convert wheel rotation into body velocity, then integrate that velocity as the heading changes.

Model a robot with two driven wheels

A differential-drive robot steers by controlling two wheels independently. The wheels share an axle line, and each wheel rolls forward or backward along the direction it faces. A caster can support the chassis without adding a steering command.

Use an ideal model with equal wheel radii, a rigid chassis, and a flat floor. Assume the wheels roll without longitudinal slip and do not slide sideways. This lesson predicts geometry from wheel motion; it does not calculate motor torque or acceleration.

The sideways restriction links differential drive to the unicycle model in Modern Robotics. The robot has a planar pose with three coordinates, but its instantaneous body velocity has only two independent controls: forward speed and yaw rate.

Set the axes and measure the geometry

Track the midpoint between the wheel centers. Its pose is (x, y, θ): two world coordinates in meters and a heading in radians. World x points right in the plot, world y points up, and positive θ turns counterclockwise from world x.

The body x axis points forward along the current heading; body y points left. At θ = 0, forward agrees with world x. Review coordinate frames if a velocity that changes direction in the world feels different from a constant forward command.

SymbolMeaningUnit
rRadius of each driven wheelm
bFull distance between left and right wheel centersm
φ̇L, φ̇RSigned left and right wheel rotation ratesrad/s
vForward velocity of the axle midpoint in the body framem/s
ωCounterclockwise yaw raterad/s

Track width b is the whole axle separation, so each wheel sits b/2 from the midpoint. Wheel radius r converts rotation into rolling distance. The turning radius introduced below is a third quantity with a different job.

Positive wheel rotation means forward rolling for each wheel. A hardware encoder may use the opposite sign on one side, so map its readings into this convention before applying the equations.

Convert wheel rates to body motion

Rolling through one radian advances a wheel by its radius. The left and right forward rim speeds are therefore uL = rφ̇L and uR = rφ̇R.

The midpoint takes the average forward speed. Their difference, divided by the full track width, sets the yaw rate:

v = r(φ̇L + φ̇R) / 2
ω = r(φ̇R − φ̇L) / b

For the sign check, imagine a forward left turn. The right wheel follows the outside of the turn and travels faster, so φ̇R − φ̇L is positive. These are the forward velocity equations derived in Dellaert and Hutchinson's differential-drive motion model.

Rotate the forward velocity into world coordinates:

ẋ = v cos θ
ẏ = v sin θ
θ̇ = ω

The body has zero sideways velocity. Its world y coordinate can still change because the robot's forward direction turns with θ; nonholonomic constraints develops that distinction.

Calculate the wheel rates for a command

A controller often requests v and ω. At the left wheel, rotation subtracts bω/2 from the midpoint's forward speed; at the right wheel, rotation adds bω/2. Divide each result by r to recover wheel rates:

φ̇L = (v − bω/2) / r
φ̇R = (v + bω/2) / r

For v = 0.4 m/s, ω = −0.6 rad/s, r = 0.1 m, and b = 0.5 m, the left wheel needs (0.4 + 0.15)/0.1 = 5.5 rad/s. The right needs (0.4 − 0.15)/0.1 = 2.5 rad/s. The faster left wheel produces the requested clockwise turn.

Check both results against the wheels' speed limits. Independently clipping one wheel changes both v and ω. WPILib's differential-drive kinematics documentation uses the same body-to-wheel relationship with wheel linear speeds; divide by radius when you need angular rates.

Recognize straight motion, arcs, and spins

These examples all use r = 0.1 m and b = 0.5 m. Wheel rates stay constant, and the robot starts facing world x.

Left rateRight ratevωCenter motion
3 rad/s3 rad/s0.3 m/s0 rad/sStraight forward
2 rad/s4 rad/s0.3 m/s0.4 rad/sForward left arc
−2 rad/s2 rad/s0 m/s0.8 rad/sSpin in place
−3 rad/s−3 rad/s−0.3 m/s0 rad/sStraight reverse

When ω is nonzero, the signed turning radius of the midpoint is R = v/ω. The instantaneous rotation center lies R meters along the robot's body y axis: positive R lies to its left. A spin has R = 0; straight motion has no finite rotation center.

The magnitude |R| describes the center's circle, while r describes each wheel. For the forward arc above, R = 0.3/0.4 = 0.75 m even though each wheel has radius 0.1 m. Unlike a forward-only Dubins vehicle, this ideal differential drive can spin at zero center speed.

Integrate a constant command exactly

Holding v and ω constant does not hold the world velocity constant during a turn. The heading evolves as θ(t) = θ₀ + ωt, so x and y integrate a rotating forward direction.

For nonzero ω, direct integration gives the circular-arc update:

θ₁ = θ₀ + ωt
x₁ = x₀ + (v/ω)(sin θ₁ − sin θ₀)
y₁ = y₀ + (v/ω)(cos θ₀ − cos θ₁)

Near ω = 0, those differences lose numerical precision, and division by zero prevents using the expression for a straight line. A trigonometric identity gives a form that covers the straight limit:

a = ωt/2,   sinc(a) = sin(a)/a,   sinc(0) = 1
d = vt sinc(a)
x₁ = x₀ + d cos(θ₀ + a)
y₁ = y₀ + d sin(θ₀ + a)
θ₁ = θ₀ + 2a

Here sinc is unnormalized: its argument is in radians, with no extra π. The midpoint angle alone is insufficient; the sinc factor accounts for the arc's displacement. At ω = 0, this reduces continuously to a straight translation of vt along θ₀.

The formula is exact for the ideal model with constant rates; floating-point arithmetic still rounds the calculation. Keep θ unwrapped when you want to count turns. The same update can be expressed through homogeneous transformations and the constant-twist integration used in Modern Robotics' odometry lesson.

Work through a four-second left turn

Start at (0, 0, 0) with r = 0.1 m, b = 0.5 m, φ̇L = 2 rad/s, and φ̇R = 4 rad/s. Hold those rates for four seconds.

  1. Wheel rim speeds are 0.2 and 0.4 m/s.
  2. Forward speed is (0.2 + 0.4)/2 = 0.3 m/s.
  3. Yaw rate is (0.4 − 0.2)/0.5 = 0.4 rad/s.
  4. The final heading is 0.4 × 4 = 1.6 rad, and R is 0.75 m.
  5. The final center is x = 0.75 sin(1.6) = 0.749680 m and y = 0.75(1 − cos(1.6)) = 0.771900 m, rounded to six decimals.

The center travels 0.3 × 4 = 1.2 m along its arc. The straight distance between the two endpoint positions is only 1.076034 m. During a full circle, that endpoint distance would return to zero while travel remained positive.

A single forward Euler update using only the starting heading would predict (1.2, 0). It misses the changing direction during these four seconds. The exact constant-rate update follows the whole turn without requiring a smaller integration step.

Change the wheels and inspect the pose

The initial experiment reproduces the calculation above. The amber path traces the axle midpoint, and the final arrow shows heading. Each change recomputes a motion from (0, 0) with the selected initial heading; it does not append to the previous path.

Two wheels, one body motion

Predict the pose from constant wheel rates

Start the axle midpoint at (0, 0). Set both wheel rates, the wheel radius and the full distance between wheels, then follow the ideal rolling motion.

2.0 rad/s
4.0 rad/s
0.10 m
0.50 m
4.0 s

Positive wheel rates roll forward. A faster right wheel produces positive yaw, which turns the heading counterclockwise. Rates stay constant for the selected duration; each change starts a fresh motion at the origin.

Axle midpoint in world coordinates (m)

Arc: final center (0.749680, 0.771900) metersThe world x axis points right and y points up, with equal scales. A blue origin and dashed arrow mark the initial pose. The amber curve follows the axle midpoint. A black ring and solid arrow mark the final center and heading. A gray dashed segment shows the straight start-to-end displacement. Arrow sizes are schematic.-1.5-1.5001.51.5xy
The chart rescales both axes together. Blue dashed arrow: initial heading. Black solid arrow: final heading. The path shows the center, with no robot footprint or obstacles. A spin changes the arrow while its center stays at the origin.
Command type
Arc
Left rim speed (m/s)
0.200000
Right rim speed (m/s)
0.400000
Body forward speed (m/s)
0.300000
Yaw rate (rad/s)
0.400000
Final x (m)
0.749680
Final y (m)
0.771900
Final heading (rad)
1.600000
Center travel (m)
1.200000
Start-to-end distance (m)
1.076034
Signed turn radius (m)
0.750000

After 4.0 s, the axle midpoint is at (0.749680, 0.771900) m with heading 1.600000 rad. Center travel is 1.200000 m.

The heading is unwrapped, so it can pass 2π radians. Center travel uses |v| × time; the dashed displacement joins only the endpoints. Signed turn radius v/ω locates the rotation center along the robot's left axis. These predictions assume equal wheel radii and rolling without slip.

Choose Spin in place and compare the heading with center travel. After four seconds, θ is 3.2 rad while the center stays at (0, 0). Choose Reverse straight to reach x = −1.2 m with heading still zero: reversing changes the signed speed without rotating the body.

Reset the experiment and choose an initial heading of 90°. The final position becomes (−0.771900, 0.749680) m while the speeds and travel stay the same. This rotates the trajectory into a different world direction.

With the default wheel rates, doubling the track width would halve yaw rate while preserving forward speed. Doubling both wheel radii doubles v and ω, so it preserves R while advancing farther around the same circle in the same time. Use the radius control to check that second prediction.

Separate a motion prediction from measured odometry

This lab predicts motion from prescribed wheel rates. Wheel odometry starts from measured encoder increments and estimates pose over successive intervals. With equal radii, an interval's signed center travel is Δs = r(ΔφL + ΔφR)/2, and its heading change is Δθ = r(ΔφR − ΔφL)/b.

Treating those increments as one constant body motion gives d = Δs sinc(Δθ/2) and the same midpoint-angle update. Total encoder increments do not reveal every possible ordering of changing rates within an interval. Use shorter measurement intervals when a single constant motion poorly represents what happened.

An exact integration formula cannot correct wheel slip, an incorrect wheel radius, or a mismeasured track width. Each pose update carries previous error forward. Modern Robotics describes combining odometry with observations from external sensors to constrain that accumulated error.

For a concrete implementation example, WPILib's differential-drive odometry combines wheel distances with a gyro heading and still warns of drift. The wheel odometry lesson works through encoder intervals and shows how calibration error and lost wheel travel affect the estimate. A motion model also needs separate collision checks and actuator limits before it becomes an executable robot plan. If you train a model to correct odometry, compare it against this kinematic baseline and ground-truth poses across the conditions it must handle.

Reproduce the motion in Python

This example uses only Python's standard library. It implements the same constant-rate update, with a short Taylor expansion for sinc near zero. Inputs use meters, seconds, and radians; the wheel geometry must be positive and duration nonnegative.

from math import cos, sin


def forward(left, right, radius=0.1, track=0.5):
    return radius * (left + right) / 2, radius * (right - left) / track


def inverse(v, omega, radius=0.1, track=0.5):
    return (v - track * omega / 2) / radius, (v + track * omega / 2) / radius


def integrate(pose, v, omega, duration):
    x, y, theta = pose
    half_turn = omega * duration / 2
    if abs(half_turn) < 1e-4:
        z = half_turn * half_turn
        sinc = 1 - z / 6 + z * z / 120 - z * z * z / 5040
    else:
        sinc = sin(half_turn) / half_turn
    chord = v * duration * sinc
    midpoint = theta + half_turn
    return (
        x + chord * cos(midpoint),
        y + chord * sin(midpoint),
        theta + 2 * half_turn,
    )


for name, left, right in [
    ("arc", 2, 4),
    ("straight", 3, 3),
    ("spin", -2, 2),
    ("reverse", -3, -3),
]:
    v, omega = forward(left, right)
    x, y, theta = integrate((0.0, 0.0, 0.0), v, omega, 4)
    print(f"{name}: pose=({x:.6f}, {y:.6f}, {theta:.6f})")

left, right = inverse(0.4, -0.6)
print(f"inverse: left={left:.6f}, right={right:.6f} rad/s")
x, y, theta = integrate((0.0, 0.0, 0.0), 0.5, 1e-12, 2)
print(f"tiny turn: x={x:.6f}, y={y:.3e}, theta={theta:.3e}")

Expected output:

arc: pose=(0.749680, 0.771900, 1.600000)
straight: pose=(1.200000, 0.000000, 0.000000)
spin: pose=(0.000000, 0.000000, 3.200000)
reverse: pose=(-1.200000, 0.000000, 0.000000)
inverse: left=5.500000, right=2.500000 rad/s
tiny turn: x=1.000000, y=1.000e-12, theta=2.000e-12

The tiny-turn result keeps its small positive sideways displacement in world coordinates. A rule that replaces every small nonzero ω with zero would erase that motion. The sinc expression gives a continuous result without such a cutoff.

Try it yourself

Exercise 1: request a quarter-circle motion. A robot has r = 0.1 m and b = 0.4 m. Request v = 0.2 m/s and ω = 0.5 rad/s from (0, 0, 0) for π seconds. Calculate both wheel rates, the final pose, and the center's travel distance.

Show the quarter-circle solution

The wheel rates are (0.2 − 0.4 × 0.5/2)/0.1 = 1 rad/s on the left and (0.2 + 0.4 × 0.5/2)/0.1 = 3 rad/s on the right. The heading change is π/2 rad, and the turning radius is 0.2/0.5 = 0.4 m.

The final pose is (0.4, 0.4, π/2). Center travel is 0.2π = 0.628319 m, while the endpoint distance is √(0.4² + 0.4²) = 0.565685 m. Set duration = 3.141592653589793 in the Python example to verify the pose.

Exercise 2: reverse a turn. Run the default arc for four seconds. From that final pose, run both wheel rates with the opposite sign, −2 and −4 rad/s, for four more seconds. Predict the final pose and the total center travel across both intervals; explain why changing only v's sign would give a different result.

Show the reversed-motion solution

Negating both wheel rates gives v = −0.3 m/s and ω = −0.4 rad/s. The second interval retraces the first and returns to (0, 0, 0) within floating-point rounding. Total center travel is 1.2 + 1.2 = 2.4 m, even though the net displacement is zero.

Negating only v while keeping ω positive continues increasing the heading, so it cannot undo the first interval. Check the true reversal with first = integrate((0, 0, 0), 0.3, 0.4, 4) followed by integrate(first, -0.3, -0.4, 4).

Sources and further study

Continue with nonholonomic constraints to test which instantaneous motions rolling wheels allow and how several allowed maneuvers can produce a sideways displacement.