Mobile Robot Kinematics and Odometry
Differential, Ackermann and mecanum drives, odometry from encoders, how it drifts, calibration and fusing a gyroscope.
Drive types
| Drive | How it steers | Holonomic? | Strengths | Limits |
|---|---|---|---|---|
| Differential (two driven wheels + caster) | Speed difference between the two wheels | No | Simple, spins on the spot, cheap | Caster drag; drifts straight-line if wheels differ |
| Skid steer / tracked | Same as differential, with slip | No | Rugged, rough terrain, good traction | Large slip: odometry is poor |
| Ackermann (car-like) | Steered front wheels, driven rear | No | Stable at speed, outdoors | Cannot turn on the spot; needs a turning radius |
| Mecanum | Four angled-roller wheels give sideways motion | Yes | Moves in any direction, spins freely | Slips on rollers, needs flat floors, odometry drifts |
| Omni (three or four omni wheels) | Free-rolling rollers at right angles | Yes | Fast changes of direction | Low traction; rollers wear |
| Swerve | Each wheel steered and driven independently | Yes | Best manoeuvrability and traction | Complex and costly |
A holonomic base can move in any direction without first turning; a non-holonomic one (differential, Ackermann) cannot move sideways, so path planners must respect that (the plans are curves with a minimum radius, or turn-then-drive).
Differential drive
A differential-drive robot has two driven wheels on one axle, each with its own motor, and a caster for balance. Steering comes from the wheel speed difference. With wheel speeds vl and vr (m/s) and a track width L (distance between the wheels):
v = (vr + vl) ÷ 2 ω = (vr − vl) ÷ L R = v ÷ ω
vr = v + ω L ÷ 2 vl = v − ω L ÷ 2 (the inverse: wheel speeds from a velocity command)
Worked example: speeds and turning
- Wheel speed from rpm. A 100 mm wheel at 120 rpm turns 2 revolutions per second: v = π × 0.1 × 2 = 0.628 m/s.
- Turning. With L = 0.2 m, vl = 0.4 and vr = 0.8 m/s: v = 0.60 m/s, ω = (0.8 − 0.4) ÷ 0.2 = 2.00 rad/s (115° per second), and the turning radius R = 0.60 ÷ 2.00 = 0.30 m. The left wheel runs on radius R − L/2 = 0.20 m and the right on 0.40 m: speeds 0.40 and 0.80 m/s ✓.
- From a command to wheel speeds. /cmd_vel asks for v = 0.5 m/s and ω = 1.0 rad/s: vl = 0.5 − 1.0 × 0.1 = 0.40 m/s and vr = 0.60 m/s; on 100 mm wheels that is 76 and 115 rpm.
- Spin on the spot. If vr = −vl, then v = 0 and the robot turns about its own center with ω = 2 vr ÷ L: wheels at ±0.30 m/s give 3.0 rad/s (172°/s).
def wheel_speeds(v, w, track): # command -> wheel speeds (m/s)
return v - w * track / 2, v + w * track / 2
def body_velocity(vl, vr, track): # wheel speeds -> (v, w)
return (vr + vl) / 2, (vr - vl) / track
wheel_speeds(0.5, 1.0, 0.2) # (0.40, 0.60)
Ackermann, mecanum and others
Worked example: an Ackermann robot
- A car-like robot with a wheelbase W = 0.30 m and a track of 0.20 m steers by an angle δ at the front: the turning radius (of the rear axle center) is R = W ÷ tan δ. With a maximum steering angle of 30°, Rmin = 0.30 ÷ tan 30° = 0.52 m: it cannot turn tighter, so a planner must use curves at least that wide (and a 3-point turn to reverse).
- Inner and outer front wheels turn by different angles so that they roll without slip about one center: inner δi = atan(W ÷ (R − T/2)) = 35.6°, outer δo = atan(W ÷ (R + T/2)) = 25.8°. A steering linkage with Ackermann geometry does this; a single servo steering both wheels equally scrubs the tires.
- Speed: v is the speed of the rear axle center; ω = v ÷ R = v tan δ ÷ W.
Worked example: a mecanum base
Four mecanum wheels (radius 0.05 m) at ±0.15 m and ±0.12 m from the center can move in any direction. For a body velocity (vx, vy, ω) the wheel angular speeds (front-left, front-right, rear-left, rear-right) are ωi = (vx ∓ vy ∓ (lx + ly) ω) ÷ r. For vx = 0.4 m/s forward, vy = 0.2 m/s sideways and ω = 0.5 rad/s, with lx + ly = 0.27 m: front-left 1.3, front-right 14.7, rear-left 9.3, rear-right 6.7 rad/s. Pure sideways motion (vy only) drives the diagonal pairs in opposite directions; the rollers slip, so odometry is poor and good floors matter.
Wheel encoders
Encoders on the wheel or motor shaft count rotation (see Sensors and Field Devices and Motion Control and Actuators). A quadrature encoder of 360 pulses per revolution gives 4 × 360 = 1 440 counts per turn. On a 100 mm wheel (circumference 314.16 mm) one count is π × 100 ÷ 1 440 = 0.2182 mm.
Worked example: count rates
At 1.0 m/s the counts arrive at 1.0 ÷ 0.000218 m = 4 584 counts/s; a 50 Hz loop sees about 92 counts per cycle, so the speed resolution is one count per cycle = 0.011 m/s: coarse at low speed (a loop-rate-limited estimate is noisy; estimate speed from the time between counts, or use a higher-resolution encoder or a faster loop). If the encoder is on the motor before a 20:1 gearbox, the counts per wheel turn are 20 times larger.
- Count in hardware (a timer in encoder mode, or an interrupt on a microcontroller) so that no counts are lost at high speed (see Embedded Systems).
- Use differences of the counter, with the wrap-around handled by unsigned arithmetic.
- Speed from counts: over a fixed interval (good at high speed) or from the interval between counts (good at low speed); filter lightly (see Filters).
Odometry: dead reckoning
Odometry estimates the robot’s pose by adding up wheel movement. Each cycle, with Δsl and Δsr the distances the wheels rolled:
Δs = (Δsr + Δsl) ÷ 2 Δθ = (Δsr − Δsl) ÷ L x += Δs cos(θ + Δθ/2) y += Δs sin(θ + Δθ/2) θ += Δθ
Worked example: three odometry updates
Track width L = 0.2 m, starting at the origin facing +x. Wheel distances Δsl / Δsr (m) for three cycles:
| Cycle | Δsl / Δsr | Δs | Δθ (rad) | Position (x, y) | Heading |
|---|---|---|---|---|---|
| 1 | 0.10 / 0.10 | 0.100 | 0.000 | (0.100, 0.000) | 0.0° |
| 2 | 0.08 / 0.12 | 0.100 | 0.200 | (0.200, 0.010) | 11.5° |
| 3 | 0.12 / 0.08 | 0.100 | -0.200 | (0.299, 0.020) | 0.0° |
The first cycle drives straight (equal distances, Δθ = 0); the second turns left (the right wheel travels further), the third turns back. Note the midpoint angle θ + Δθ/2 in the update: it is a much better approximation than using the old heading.
import math
class Odometry:
def __init__(self, track):
self.track, self.x, self.y, self.th = track, 0.0, 0.0, 0.0
def update(self, dsl, dsr): # distances rolled since the last update (m)
ds = (dsr + dsl) / 2
dth = (dsr - dsl) / self.track
self.x += ds * math.cos(self.th + dth / 2)
self.y += ds * math.sin(self.th + dth / 2)
self.th = (self.th + dth + math.pi) % (2 * math.pi) - math.pi # keep within -pi..pi
return self.x, self.y, self.th
How odometry drifts
Worked example: a 0.5 % error
- The robot is commanded to drive a 2 m square (8 m of travel). If the left wheel diameter is 0.5 % larger than the software assumes, the left wheel rolls 0.5 % further on every meter. The heading error grows as Δθ = ε s ÷ L: over one 2 m side, 0.005 × 2.0 ÷ 0.2 = 0.050 rad (2.9°), and the sideways error at the end of the side is about ε s² ÷ (2 L) = 0.05 m.
- After the whole square, the simulation above ends 0.27 m from the start with that one wheel error, and 0.09 m with a 1 % error in the track width (every turn is 1 % too large or small). With a 0.2 m track the robot is very sensitive, because the heading changes by ε ÷ L per meter.
- The errors do not average out: a systematic error (wheel size, track width) grows in proportion to the distance; a random one (slip, floor) as its square root; and heading errors matter most, because they turn into position errors in proportion to the distance traveled afterwards.
- Rule of thumb: without correction, wheel odometry is good to about 1 to 5 % of distance traveled on a floor, and a few degrees per turn.
| Error source | Effect | Correct by |
|---|---|---|
| Wheel diameter error (unequal wheels) | Curved path on a straight command | Calibrate each wheel diameter |
| Track width error | Turns too large or small | Calibrate by spinning a known number of turns |
| Wheel slip and skidding | Counts do not match travel | Gentle accelerations; a gyroscope; vision or lidar |
| Floor irregularities | Random errors | Absolute corrections from a map |
| Encoder resolution and sampling | Quantization noise | Higher resolution; update fast |
| Wheel compression and load | Effective diameter changes with weight and tire pressure | Calibrate loaded |
| Time delays | Pose is for a moment earlier | Time-stamp every update at the encoder reading |
Calibrating the wheels
Worked example: two quick tests
- Straight-line test: command 2.000 m forward and measure with a tape: the robot actually moved 1.972 m. The odometry is high by 1.014: multiply the wheel diameter by 0.9860 (a 1.40 % error). Repeat in both directions and average.
- Rotation test: command 10 full turns on the spot and mark the heading; it actually turned only 9.72 turns. The effective track is wider than assumed: Lnew = L × commanded ÷ measured = 0.20 × 10 ÷ 9.72 = 0.2058 m (2.9 % larger).
- Bidirectional square test (UMBmark): drive a square clockwise and counter-clockwise; the two end-point errors separate the wheel-diameter and track-width errors, so both can be corrected.
- Do it loaded, on the real floor, at the speed you use, and repeat after changing the tires or the payload.
Fusing a gyroscope and other sensors
Heading is the quantity odometry gets worst, and a gyroscope measures it directly. A MEMS gyroscope has no slip, but a small constant bias that integrates into drift. The two have opposite weaknesses, so combine them.
Worked example: gyro drift and a complementary filter
- A cheap gyroscope with a bias of 0.1 °/s drifts by 0.1 × 60 = 6° in a minute if integrated alone: useless over a long run, excellent over a few seconds.
- A complementary filter trusts the gyro in the short term and the wheel (or compass) heading in the long term: θ ← α (θ + ω Δt) + (1 − α) θwheel. With α = 0.98 and Δt = 0.01 s the crossover time constant is α Δt ÷ (1 − α) = 0.5 s: faster changes come from the gyro, slower ones are pulled to the wheel heading.
- The Kalman filter does this optimally and also estimates the bias (see Localization, Mapping and Navigation); in ROS the
robot_localizationpackage fuses wheel odometry, an IMU and other sensors into theodomframe. - Calibrate the gyroscope bias at start-up by averaging its output while the robot is still.
- An absolute reference (a map and lidar scan matching, visual landmarks, GNSS outdoors) removes drift entirely: odometry then fills the gaps between corrections (see Localization, Mapping and Navigation).
Sizing motors and wheels
Worked example: torque and power for a 10 kg robot
- The robot (10 kg) must accelerate at 0.5 m/s² while climbing a 10° slope, with a rolling resistance coefficient of 0.02. The traction force is F = m (a + g sin θ + g Crr cos θ) = 10 × (0.5 + 1.70 + 0.19) = 24.0 N.
- With two driven wheels of radius 0.05 m each wheel supplies 12.0 N: torque at the wheel = 12.0 × 0.05 = 0.60 N·m.
- Through a 20:1 gearbox at 80 % efficiency the motor torque is 0.60 ÷ (20 × 0.8) = 37 mN·m at 3 820 rpm for 1.0 m/s; the mechanical power is F v = 24.0 × 1.0 = 24 W (add the drivetrain loss and a margin of 1.5 to 2 for the peak).
- Choose the motor for continuous torque at the operating speed and peak torque for acceleration, and the supply voltage so that the back-EMF does not limit the top speed (see Motion Control and Actuators).
- Larger wheels roll over obstacles and reduce the torque needed for a given speed (torque = F r), but raise the speed per rpm and lose resolution.
- Do not forget the caster or skid: a poorly chosen caster adds drag and a heading disturbance when reversing.
- Check the slope and the surface in the specification, not only the flat floor.
Velocity commands and acceleration limits
A velocity command from a planner or a person can jump: send it as given and the motors slam, the wheels slip and the odometry breaks. Put a limit on the acceleration (and on the jerk if the load is delicate) between the command and the motor controllers; nav2_velocity_smoother and the diff_drive_controller parameters do it.
Worked example: an acceleration limit
With a limit of 0.8 m/s², reaching 1.0 m/s takes 1.0 ÷ 0.8 = 1.25 s and covers ½ a t² = 0.63 m. A stop from 1 m/s needs at least the same distance, which sets the clearances the planner must keep (see Robot Software Architecture).
- Limit by what the wheels can grip: the maximum acceleration is μ g, so a floor with a friction coefficient of 0.5 limits it to about 4.9 m/s² (and a wet floor much less): far above most robots’ motor limit, but not above the grip of a heavy robot with a high center of mass, which tips.
- Command timeouts: if no new command arrives for a few periods, ramp the speed down to zero.
- Limit angular acceleration separately, and the speed in turns (the outer wheel runs faster).
Related
- Robotics Tools: the overview of this guide.
- Motor Control, PID and Trajectories: the wheel speed loops that follow these commands.
- Localization, Mapping and Navigation: Kalman filters and SLAM that correct odometry.
- Frames, Transforms and Arm Kinematics: frames, transforms and arms.
- Sensors and Field Devices and Motion Control and Actuators: encoders, gyroscopes, motors and drives.
FAQ
Why does my robot drive in a curve?
The wheels are not the same size, a motor is slower, or one wheel’s encoder scale is wrong. Calibrate each wheel’s diameter with a straight-line test and use closed-loop speed control on each wheel.
How accurate is wheel odometry?
On a good floor, about 1 to 5 % of the distance, and a few degrees per turn; much worse with slip. Use it for short-term motion and correct it with a gyroscope and a map.
Differential drive or mecanum?
Differential drive for simplicity, spinning in place and good odometry; mecanum when you must move sideways in tight spaces on a flat, smooth floor and can accept poor odometry and slip.
Where should the encoders go?
On the wheel shaft for the best odometry (no gear backlash in the measurement), or on the motor shaft for higher resolution and a faster speed loop; account for the gear ratio and backlash.
References
- Siegwart, R., Nourbakhsh, I. and Scaramuzza, D., Introduction to Autonomous Mobile Robots (kinematics and odometry).
- Borenstein, J. and Feng, L., “Measurement and correction of systematic odometry errors in mobile robots”, IEEE Transactions on Robotics and Automation, 1996 (UMBmark).
- ROS 2
diff_drive_controller,robot_localizationand Nav2 velocity-smoother documentation.
Last reviewed: October 2026. All values are computed in the page builder: measure your own robot.
