RobotForge
Published·~13 min

Differential drive: the robot you build first

Two wheels, two motor commands, endless variations. The kinematics, the command conversion, the slip problem, and the 50 lines of code that control any diff-drive robot.

by RobotForge
#mobile-robots#kinematics#beginner

Almost every first robot is differential drive. Two independently controlled wheels on opposite sides, a caster or ball for balance, and you're off. The math fits on an index card. The edge cases fit on the side of your robot as it drives itself off a desk.

The setup

  • Wheel base (center-to-center distance between wheels): L
  • Wheel radius: r
  • Left wheel angular velocity: ω_L (rad/s)
  • Right wheel angular velocity: ω_R

The robot's motion in its own body frame:

v = r · (ω_R + ω_L) / 2       # forward speed (m/s)
ω = r · (ω_R - ω_L) / L       # angular velocity (rad/s)

If both wheels spin equally, the robot drives straight. If one spins faster, it turns. If they spin in opposite directions at equal rates, it spins in place. That's it. That's the whole kinematic model.

The inverse: command velocity, get wheel speeds

Real code usually goes the other way — you receive a Twist message commanding (v, ω) and need to compute wheel speeds:

ω_R = (v + ω · L / 2) / r
ω_L = (v - ω · L / 2) / r

Five lines of code turn geometry_msgs/Twist into motor commands. This is literally what most ROS 2 diff-drive bases do under the hood.

Pose integration

If you want to know where the robot is (odometry), integrate velocity over time:

x += v · cos(θ) · dt
y += v · sin(θ) · dt
θ += ω · dt

This is dead reckoning. No sensors needed beyond wheel encoders. Accuracy: good for seconds, terrible for minutes. The reasons are below.

Python implementation

class DiffDrive:
    def __init__(self, wheel_radius, wheel_base):
        self.r = wheel_radius
        self.L = wheel_base
        self.x, self.y, self.theta = 0.0, 0.0, 0.0

    def twist_to_wheels(self, v, omega):
        wR = (v + omega * self.L / 2) / self.r
        wL = (v - omega * self.L / 2) / self.r
        return wL, wR

    def wheels_to_twist(self, wL, wR):
        v = self.r * (wR + wL) / 2
        omega = self.r * (wR - wL) / self.L
        return v, omega

    def update_pose(self, v, omega, dt):
        self.x += v * math.cos(self.theta) * dt
        self.y += v * math.sin(self.theta) * dt
        self.theta += omega * dt

Fifty lines including imports and a main loop. This controls every two-wheel robot at the kinematic level.

The slip problem

The kinematic model assumes the wheels roll without slipping. Real wheels do both of these things:

  • Longitudinal slip: the commanded wheel velocity is higher than the actual ground velocity. Happens under heavy acceleration, on slick surfaces, or with worn tires.
  • Lateral slip: the robot slides sideways during turns. Worse with wide robots and high angular velocities.

Consequences:

  • Commanded motion ≠ actual motion.
  • Pose integration drifts. After 30 meters of driving, your odometry may be off by a meter.

Mitigations:

  • IMU fusion: use gyro readings for θ instead of integrating ω from wheels.
  • Visual odometry or SLAM: correct the drift with external references.
  • Slower turns: lateral slip scales with ω². Slow down in corners.
  • Calibration: measure real wheel radius and wheel base on your actual robot; they're not what the datasheet says.

What a "real" diff-drive robot adds

  • Closed-loop velocity control. A PID on each wheel — encoder feedback, not open-loop voltage commands.
  • Acceleration limits. Jumping from 0 to max speed is a great way to strip a gearbox. Ramp.
  • Stall detection. If the encoder reports zero motion but the motor is commanded, stop before you burn the motor.
  • Emergency stop. A big red button that cuts motor power electrically, not via firmware.

The constraint diff-drive can't escape

Diff-drive robots are non-holonomic — they can't move sideways. You can only translate along the direction you're facing. Parallel parking is multi-step: turn, drive, turn back.

This constrains motion planning: an RRT for a diff-drive robot has to respect it, producing smooth curved paths instead of arbitrary straight-line motions. The alternative (mecanum wheels, omnidirectional wheels) buys you sideways motion at the cost of mechanical complexity and traction.

Building your first one

Minimum BOM for a working diff-drive robot:

  • 2× geared DC motors with encoders (e.g., Pololu metal-gearmotors) — $20–40 each
  • 1× motor driver (L298N for cheap, TB6612FNG for better efficiency) — $5–15
  • 1× MCU (ESP32, Raspberry Pi Pico) — $5–15
  • 1× battery + regulator
  • 1× caster wheel
  • A chassis — 3D-printed, laser-cut, or cardboard for v0

Total: $80–150. Weekend build. You now have every mental model you need to reason about it — and a clear path to adding IMU fusion, laser-based localization, and SLAM as the project grows.

Next

Ackermann steering (cars) and omnidirectional drive (mecanum wheels) are the two-wheel siblings. Different kinematics, same control patterns.

Comments

    Sign in to post a comment.