devol.dev

Differential drive and its relatives

Two wheels on a common axle, one motor each, and a caster carrying the weight at the back. It is the cheapest drivetrain that can go anywhere on a floor, and almost every mobile robot that is not a car is one, or is pretending to be.

The whole of it is two numbers. A forward speed vv and a yaw rate ω\omega, and every model on this page differs only in what hardware produces that pair and which pairs it is allowed to produce.

Two wheels, two motors

Drive it

Click the floor, then drive. W and S set the forward speed, A and D the yaw rate. The readouts are the two wheel speeds and the chassis motion they add up to. Grid squares are one metre.

Left0.00
Right0.00
v m/s0.00
ω rad/s0.00
Click the floor to take the controls.

Hold A on its own. Both wheels turn, in opposite directions, and the robot spins about a point exactly between them without going anywhere. Nothing else on this page can do that.

The two equations

Put the left wheel at y=+b/2y = +b/2 and the right at y=b/2y = -b/2, with bb the track width. Both roll without slipping, so each wheel’s contact speed is the chassis speed at that point:

vL=vωb2vR=v+ωb2v_L = v - \omega \frac{b}{2} \qquad v_R = v + \omega \frac{b}{2}

Two equations, two unknowns, and a square matrix that is never singular while b0b \ne 0. Invert it and you have the forward kinematics:

v=vR+vL2ω=vRvLbv = \frac{v_R + v_L}{2} \qquad \omega = \frac{v_R - v_L}{b}

That is the entire model. Compare it with the four-wheel mecanum case, which needs a 4x3 matrix and a pseudoinverse because it has four motors chasing three degrees of freedom. Here there are two of each. Nothing is redundant, nothing is lost, and the wheel speeds and the chassis motion are the same information written two ways.

The sum and the difference are worth reading as such. The sum drives, the difference turns, and the difference is divided by the track width, so a narrow robot is twitchy in yaw for reasons that have nothing to do with the control loop.

Where the turn is happening

At any instant the chassis is rotating about one point, and R=v/ωR = v/\omega says where it is: on the axle line, RR to the left. It is called the instantaneous centre of curvature, and every point on the robot is circling it.

Instantaneous centre of curvature

One point, and everything circles it

The crosshair is the centre of the turn and the dashed circle is the path being driven. A and D work like a steering wheel here: they wind the turn on and leave it there. Hold W, then tap A, and watch the centre come in from off the floor.

Left0.00
Right0.00
v m/s0.00
ω rad/s0.00
Click the floor to take the controls.

Three cases and no others. Turning: the centre sits at a finite RR and the robot drives a circle. Straight: ω=0\omega = 0, RR is infinite, the centre has gone off to the horizon. Spinning on the spot: v=0v = 0, R=0R = 0, the centre is on the robot itself.

Press A alone and the crosshair lands between the wheels. Add W and it slides out along the axle, receding as the turn opens up, and off the floor entirely as the robot straightens. Curvature 1/R=ω/v1/R = \omega/v is the honest coordinate here, because it passes smoothly through the straight line while RR blows up.

What it cannot do

Set v=0v = 0 and ω=0\omega = 0 at once and the robot is still. There is no third input, and no combination of the two wheels moves the axle sideways. The velocity of the axle centre is always parallel to the heading:

x˙sinθy˙cosθ=0\dot{x}\sin\theta - \dot{y}\cos\theta = 0

This is a constraint on velocities that cannot be integrated into a constraint on positions, which is what nonholonomic means, and the distinction is the whole reason parallel parking exists. The robot can reach every pose in the plane. It has three coordinates of freedom and only two of control, so it cannot go to most of them directly.

A mecanum chassis buys the missing degree of freedom outright, and pays about 30% of its thrust and most of its odometry accuracy for it. A differential drive keeps the thrust and does a three-point turn.

When one wheel wants more than it has

The equations will happily ask for 1.7 m/s from a motor that tops out at 1.2. Something has to give, and which thing you give up is a choice.

Scale both wheels by the same factor. Curvature is the ratio ω/v\omega/v, so a common factor cancels out of it: the robot drives the same circle, more slowly. Clip only the wheel that is over the limit and that ratio changes, and the robot drives an arc nobody asked for. It is a good bug, in the sense of being hard: it appears only at speed, only in turns, and looks exactly like a gain that wants lowering.

Hold W and A together in the first tool. One wheel pegs, both get scaled, and the radius in the readout does not move.

Integrating it

Constant vv and constant ω\omega over a timestep is a circular arc, and there is a closed form for it, so there is no reason to approximate:

x=x+R(sin(θ+ωdt)sinθ)y=yR(cos(θ+ωdt)cosθ)θ=θ+ωdt\begin{aligned} x' &= x + R\left(\sin(\theta + \omega\,dt) - \sin\theta\right) \\ y' &= y - R\left(\cos(\theta + \omega\,dt) - \cos\theta\right) \\ \theta' &= \theta + \omega\,dt \end{aligned}

with the straight line as the ω0\omega \to 0 limit, which has to be special cased because RR is infinite there and the expression is 0×0 \times \infty.

The line everybody writes first is x+=vcosθdtx \mathrel{+}= v\cos\theta\,dt. That walks the tangent at the start of each step instead of the arc, so the path is a polygon drawn around the outside of the true circle. The heading stays right and the position creeps outward, which is why the error presents as a scale factor rather than as drift, and why it is usually blamed on the wheel diameter.

Odometry is the same algebra with distances instead of speeds. Both wheels are driven, both are meant to roll cleanly, and there is no unsensed direction for the robot to escape through. It is the best-behaved dead reckoning in mobile robotics, which is worth saying because the next section takes it away.

Skid steer

Four wheels, rigidly mounted, no steering. Left pair together, right pair together, drive it like a tank.

Four rigid wheels

Skid steer

Same two equations, same commands, four wheels instead of two. Turn, and compare the yaw rate the wheel speeds promise with the one the chassis delivers. Switch back to differential and drive the same input again.

Left0.00
Right0.00
v m/s0.00
ω rad/s0.00
Click the floor to take the controls.

The two equations still apply. The problem is the word “rolling”. A wheel at the front of the chassis and a wheel at the back cannot both roll cleanly through the same turn, because the turn wants them pointed at different angles and they are bolted straight. So they scrub, dragging sideways across the floor, and the yaw that results is less than the wheel speed difference predicts.

The standard treatment folds all of it into one multiplier on the track width:

ω=vRvLλb\omega = \frac{v_R - v_L}{\lambda b}

A skid steer is a differential drive measured with the wrong ruler. λ\lambda is typically 1.2 to 1.7, and the important word is calibrated: it is not derived from the wheelbase, it is measured by driving the robot in circles. It depends on the surface, the tyres, the weight distribution and where the load sits, so a value tuned on carpet is wrong on concrete and a robot with a full hopper is not the robot that was calibrated empty.

Drive the tool in circles and switch between the two models. Same keys, same wheel speeds, different circle.

What this costs is odometry. The heading is now wrong by whatever λ\lambda is wrong by, and heading error is the expensive kind: it rotates every displacement that comes after it, so the position error grows with distance travelled rather than staying put. Nine seconds of gentle arc on a chassis whose real λ\lambda is 1.35, modelled as 1.0, is over half a metre out.

What it buys is straightforward. No steering linkage, no castor, four driven wheels on the ground, and it will climb over things that would beach a two-wheeler. Skid steer is what you build when the floor is not a floor.

Ackermann, and cars

Now steer the wheels instead.

Collapse each axle to one wheel on the centre line and you have the bicycle model. The rear wheel points along the body, the front wheel points at the steering angle δ\delta, and both roll cleanly, so their axes cross at the centre of the turn. That right triangle gives everything:

R=Ltanδω=vtanδLR = \frac{L}{\tan\delta} \qquad \omega = \frac{v\tan\delta}{L}

where LL is the wheelbase, front axle to rear.

Steered front axle

Ackermann

A and D now turn the steering wheel, and it stays where you leave it. Steer to full lock and then press W. Then release W and hold A: the wheels turn and nothing else does.

Left0.00
Right0.00
v m/s0.00
ω rad/s0.00
Click the floor to take the controls.

Look at the vv in the yaw rate. A differential drive sets ω\omega directly and spins on the spot for free. A car multiplies its steering by its speed, so at v=0v = 0 the yaw rate is zero however far the wheel is cranked. Hold A without W and watch the front wheels swing over while the car sits there.

That single term is the entire practical difference between the two drivetrains. It is why a car needs a three-point turn and a differential drive does not, why every parking manoeuvre is a sequence rather than a motion, and why car-like planners are a separate field with their own literature.

The other consequence is a floor under the turn radius. Full lock is Rmin=L/tanδmaxR_{min} = L/\tan\delta_{max}, about 0.48 m on this chassis, and no amount of patience gets below it. A differential drive’s minimum radius is zero.

Why the front wheels disagree

On a real four-wheeled car the two front wheels are not parallel. The inner one is on a tighter circle, so it must be turned further, and a linkage that turns them equally drags both tyres sideways through every corner.

Ackermann’s geometry is the condition that both front axes and the rear axle meet at one point:

cotδoutercotδinner=tL\cot\delta_{outer} - \cot\delta_{inner} = \frac{t}{L}

with tt the track width. Steer to full lock in the tool and the two front wheels are visibly at different angles. The bicycle model’s δ\delta is the angle at the centre line, in between the two.

Putting something behind it

A trailer has no motors and no steering. It is towed, its wheels refuse to go sideways, and that constraint alone decides its entire path.

Towed, not driven

The trailer hitch model

A and D wind a turn on and leave it there, so a radius can be held long enough for the trailer to settle behind it. Straighten up, then reverse, and try to keep it straight. The hitch slider moves the pivot from directly over the drive axle out to a tow ball 32 cm behind.

Left0.00
Right0.00
v m/s0.00
ω rad/s0.00
Click the floor to take the controls.

Let the hitch point sit a distance hh behind the tractor’s axle, and the trailer’s axle a distance LL behind the hitch. Call the hitch angle γ\gamma, the tractor’s heading minus the trailer’s.

The hitch point is rigidly attached to the tractor, so its velocity is the tractor’s plus the yaw rate crossed with the offset. Project that onto the trailer’s own axes, and the no-slip constraint does the rest:

v1=v0cosγ+hω0sinγω1=v0sinγhω0cosγL\begin{aligned} v_1 &= v_0\cos\gamma + h\,\omega_0\sin\gamma \\ \omega_1 &= \frac{v_0\sin\gamma - h\,\omega_0\cos\gamma}{L} \end{aligned}

No forces, no masses, no tyre model, no hitch stiffness. A trailer’s path is pure geometry, and the coupling runs one way only: the tractor decides, the trailer follows, and nothing the trailer does comes back up the drawbar.

Set h=0h = 0 and the pivot is directly over the drive axle. Everything simplifies to γ˙=ω0v0sinγ/L\dot\gamma = \omega_0 - v_0\sin\gamma / L, which is the classical n-trailer system that most of the control literature is written about. It is also, on a real vehicle, an awkward thing to build, which is why a tow ball hangs off the back and hh is not zero.

The angle it settles at

Hold a constant turn and the trailer stops swinging when its yaw rate matches the tractor’s. Setting ω1=ω0\omega_1 = \omega_0 and dividing out the yaw rate leaves a condition on geometry alone:

Rsinγhcosγ=LR\sin\gamma - h\cos\gamma = L

Drive a wide circle in the tool and watch the hitch readout converge. It gets there on its own, with nothing steering it.

It cuts the corner

Once settled, the trailer circles the same centre as the tractor, at a shorter radius. The hitch point is R2+h2\sqrt{R^2 + h^2} from the centre, the trailer sits a rigid LL from the hitch, and it is tangent to its own circle, so the three form a right triangle:

Rtrailer=R2+h2L2R_{trailer} = \sqrt{R^2 + h^2 - L^2}

The difference is offtracking, and it is what makes a lorry swing out into the opposite lane before turning into a side street. It is nearly nothing on a motorway sweep and enormous at a junction, because it grows as the radius shrinks. Both circles are drawn in the tool. Turn tighter and watch them separate.

Watch the offtrack readout as you enter the turn, though, not just after it. It goes negative first. The trailer is still pointed down the old heading when the tractor starts curving away, so for a moment it is running outside the new circle rather than inside it, and the rust ring sits outside the blue one until the hitch angle catches up.

Jackknife is arithmetic

That square root goes imaginary when L>R2+h2L > \sqrt{R^2 + h^2}, and so does the equilibrium angle. There is no angle at which the trailer can hold station behind a turn that tight. It has to keep folding.

This is worth being precise about, because it is usually explained as a loss of traction. It is not. The trailer here has infinite grip by construction and it still jackknifes, because the geometry has no solution. A vehicle cannot turn tighter than the length of what it is towing, and no amount of care with the throttle changes that.

Hold W and A in the tool. The command is a 0.5 m radius and the trailer is 0.55 m long, so the hitch angle winds up and never stops. Then drag the hitch slider out to 0.32 m and try again: R2+h2\sqrt{R^2 + h^2} clears LL and the fold turns back into an equilibrium. Moving the pivot backwards buys turn radius.

Why reversing is hard

Differentiate the hitch angle rate with respect to the hitch angle, on a straight run:

γ˙γ=v0cosγL\frac{\partial\dot\gamma}{\partial\gamma} = -\frac{v_0\cos\gamma}{L}

Driving forward, v0>0v_0 > 0 and that is negative. The trailer straightens itself out, and you have felt this: a trailer pulled forward lines up behind the car with no input at all.

Reverse and v0v_0 flips sign with nothing else changing. The same geometry that was pulling the trailer straight is now pushing it away from straight, and the rate goes as v0/L|v_0|/L, so a short trailer runs away faster than a long one. That is why reversing a caravan is easier than reversing a small box trailer, which is the opposite of everyone’s first guess.

Nothing about the mechanism changes between the two cases. There is no extra force in reverse and no lost grip. It is one unstable eigenvalue, and the driver is the only feedback in the loop.

Try it. Hold S and try to keep the trailer straight with taps of A and D. The correction goes the wrong way round, and it gets worse the longer you wait, because the error is growing exponentially while you decide.

The tow ball swings the wrong way first

Set the hitch slider to zero, drive straight, then turn left. The trailer swings left with you.

Now put the hitch back out at 0.32 m and do it again. For the first moment the trailer swings right. Read it off the equation: at γ=0\gamma = 0 the yaw rate is hω0/L-h\,\omega_0/L, which has the opposite sign to the turn.

The hitch point is behind the axle, so when the tractor yaws left, the hitch itself sweeps right, and it drags the trailer with it before the forward motion can take over. Anything that has to go the wrong way before it goes the right way is difficult to control, and off-axle hitching is exactly that. It is why the tidy theory is written for h=0h = 0 and why the vehicles are not.

Add a second trailer in the tool and the effect compounds down the chain, which is the short version of why articulated lorries have one trailer.

The same offset, read backwards

A point held off the axle was a nuisance above. It is also the most useful trick a differential drive has.

Stop tracking the axle and track a point PP held a distance dd ahead of it. PP moves at the axle’s velocity plus the yaw rate crossed with the offset:

[P˙xP˙y]=[cosθdsinθsinθdcosθ][vω]\begin{bmatrix} \dot{P}_x \\ \dot{P}_y \end{bmatrix} = \begin{bmatrix} \cos\theta & -d\sin\theta \\ \sin\theta & d\cos\theta \end{bmatrix} \begin{bmatrix} v \\ \omega \end{bmatrix}

The determinant is dd. So for any d0d \ne 0 the matrix inverts, and PP can be given any velocity in the plane you like:

v=uxcosθ+uysinθω=uxsinθ+uycosθdv = u_x\cos\theta + u_y\sin\theta \qquad \omega = \frac{-u_x\sin\theta + u_y\cos\theta}{d}

The robot still cannot strafe. PP does not care. Point it at a goal with a plain proportional law and it walks there in a straight line, with the chassis underneath sorting out its own heading, and no arcs or plans anywhere.

There is a price, and it is the heading. Nobody is controlling it any more: it settles to whatever direction keeps PP on course. And dd trades one problem for another. Small dd tracks tightly and spins violently, because ω\omega is divided by it. Large dd is smooth and cuts every corner by about dd.

It is the trailer hitch with the sign of the problem reversed. There, a point rigidly held off the axle dragged something unhelpful behind it. Here, the same offset is the only reason a chassis with two degrees of freedom can be commanded like one with three.

The code

Differential drive, the whole of it:

struct Body { double v = 0.0, omega = 0.0; };       // forward speed, yaw rate
struct Wheels { double left = 0.0, right = 0.0; };  // contact-patch speed, not rpm

Wheels wheelSpeeds(const Body& body, double trackWidth) {
    const double half = trackWidth / 2.0;
    return { body.v - body.omega * half, body.v + body.omega * half };
}

// Scale both wheels together, but only if one of them is out of range.
// Curvature is omega/v, so a common factor cancels out of it: the robot
// drives the same circle, more slowly. Clipping only the wheel over the
// limit changes that ratio, and the robot drives an arc nobody asked for.
Wheels clampToLimit(const Wheels& wheels, double maxSpeed) {
    const double peak = std::max(std::abs(wheels.left), std::abs(wheels.right));
    if (peak <= maxSpeed) return wheels;
    const double scale = maxSpeed / peak;
    return { wheels.left * scale, wheels.right * scale };
}

Odometry, from two encoder deltas, with the arc rather than the chord:

struct Pose { double x = 0.0, y = 0.0, heading = 0.0; };

// Distances in place of speeds, the same algebra as the forward kinematics.
// lambda is 1 for a true differential drive; skid steer measures the same
// equation with the wrong ruler and calibrates lambda above 1.
Pose integrateEncoders(const Pose& pose, double dLeft, double dRight,
                        double trackWidth, double lambda = 1.0) {
    const double ds = (dRight + dLeft) / 2.0;
    const double dTheta = (dRight - dLeft) / (trackWidth * lambda);

    if (std::abs(dTheta) < 1e-9) {
        return { pose.x + ds * std::cos(pose.heading),
                 pose.y + ds * std::sin(pose.heading),
                 pose.heading };
    }

    const double r = ds / dTheta;
    return { pose.x + r * (std::sin(pose.heading + dTheta) - std::sin(pose.heading)),
             pose.y - r * (std::cos(pose.heading + dTheta) - std::cos(pose.heading)),
             pose.heading + dTheta };
}

And the handle, which is four lines and replaces a pure pursuit controller for most indoor work:

// Drive the point d ahead of the axle straight at the goal.
Body trackGoal(const Pose& pose, double d, double kp, double goalX, double goalY) {
    const double px = pose.x + d * std::cos(pose.heading);
    const double py = pose.y + d * std::sin(pose.heading);
    const double ux = kp * (goalX - px);
    const double uy = kp * (goalY - py);

    return { ux * std::cos(pose.heading) + uy * std::sin(pose.heading),
             (-ux * std::sin(pose.heading) + uy * std::cos(pose.heading)) / d };
}

Choosing between them

Differential drive. Two motors, zero turning radius, the cleanest odometry of the four, and a caster that will catch on every cable and lip in the building. Indoors, on a flat floor, it is almost always right.

Skid steer. Four driven wheels and nothing to catch, so it goes over terrain the others cannot. Pays for it in odometry, in tyre wear, and in a scrub factor that has to be recalibrated whenever the surface or the payload changes. Yaw is the axis it lies to you about.

Ackermann. Efficient at speed, no scrub, and the only one of the four that scales up to something road-legal. Cannot turn in place, has a floor under its radius, and everything about planning for it is harder as a direct result.

Anything towed. Free load capacity, no extra motors, and geometry that turns hostile the moment you select reverse. If a robot has to back a trailer into anything, put the sensing on the trailer and the effort into the controller, because the plant is open-loop unstable and it will not improve.

The pattern across all four is that the mechanism you pick decides which set of (v,ω)(v, \omega) pairs you can reach, and everything downstream, planning, control and odometry alike, is a consequence of that set. Work out the set first.