top of page

Solving Differential Drive Kinematics and Implementing It on a Custom Mobile Robot


If a two-wheel robot can't drive straight, it feels like a shopping cart with a mind of its own. The good news is that differential drive kinematics gives me a clean way to predict, command, and debug that motion with just a few measurements.

I only need the wheel radius (r), the wheelbase (L) (distance between wheel contact centers), encoder ticks per revolution, and a loop time (dt). From there, I solve two practical problems: forward kinematics (wheel motion to robot motion) and inverse kinematics (desired robot motion to wheel speeds). After that, it's testing and tuning, not endless math.

This post ends with a working implementation plan I can drop into an Arduino, a Raspberry Pi, or a ROS2 robot. It's only a few equations, then lots of real-world fixes.


Solve differential-drive kinematics step by step (without getting lost in math)

I like to think of a diff-drive robot as two hands pushing a box. If both hands push equally, the box goes straight. If one hand pushes harder, the box turns. That's basically the model.

I'm assuming a standard setup: two driven wheels on the same axle line, rolling without slipping. If you want a deeper derivation and the assumptions spelled out clearly, Northeastern's notes on kinematics for wheeled mobile robots are a solid reference.

Forward kinematics, turning left and right wheel speeds into v and omega

Let the wheel angular speeds be phi_dot_R and phi_dot_L (rad/s) in rad/s. Wheel linear speeds are:

  • v_R = r * phi_dot_R

  • v_L = r * phi_dot_L

From there, robot linear velocity (v) (forward) and angular velocity (\omega) (yaw rate) are:

  • v = (r/2) * (phi_dot_R + phi_dot_L)

  • omega = (r/L) * (phi_dot_R - phi_dot_L)

Plain language: the average of the wheels sets forward speed, and the difference sets turning rate.

Sanity checks I use (with (omega > 0) meaning counterclockwise, turning left):

  • Same wheel speeds (phi_dot_R = phi_dot_L): (omega = 0), it drives straight.

  • Equal and opposite (phi_dot_R = -phi_dot_L): (v = 0), it spins in place.

  • Right wheel faster (phi_dot_R > phi_dot_L): (omega > 0), it turns left.

Robot Diagram
Robot Diagram

Inverse kinematics, converting a target v and omega into wheel commands

Inverse kinematics is what I use to drive the robot on purpose. If I want a target (v) and (omega), the wheel angular speed targets become:

  • phi_dot_R = (2v + L*omega) / (2r)

  • phi_dot_L = (2v - L*omega) / (2r)

This maps nicely to joystick control or a planner output like ROS-style cmd_vel (linear.x and angular.z). I treat (v) as "how fast forward" and (omega) as "how fast turning."

Units matter, but the math doesn't change. I can work in rad/s, RPM, or ticks/s as long as I convert cleanly.

Here's the compact unit cheat sheet I keep nearby:

Quantity

Common unit

Quick conversion

Wheel angular speed

rad/s

RPM = rad/s * (60 / 2π)

Encoder speed

ticks/s

rad/s = ticks/s * (2π / ticks_per_revolution)

Wheel linear speed

m/s

v = r * phi_dot

Odometry updates, how I turn encoder ticks into x, y, and heading

Odometry is where the robot starts telling me what it actually did. Each loop:

  1. I read encoder tick deltas Delta_ticks_R , Delta_ticks_L.

  2. I convert them to wheel angle changes: [Delta_phi = 2π * (Delta_ticks / ticks_per_revolution)].

  3. I estimate wheel angular speeds: [phi_dot ≈ Delta_phi / dt].

  4. I compute (v) and (omega) using forward kinematics.

  5. I integrate pose:

[ x = x + v cos(theta) dt

y = y + v sin(theta) dt

theta = theta + omega * dt]

I keep (dt) small, usually 10 to 20 ms, and I make timing consistent. Drift is normal, especially on slick floors.

If my odometry is "wrong," I first assume wheel slip or bad (r) and (L), not bad trigonometry.

Implement it on a custom robot, from measurements to a stable control loop

Once the math is set, the real work begins. My typical hardware assumptions are simple: two driven wheels, a caster (optional), encoders on each motor or wheel, a motor driver, and a battery that can actually supply current.

If I want a classic reference on the Instantaneous Center of Curvature idea (ICC), Columbia's short notes are handy: Differential Drive Kinematics (PDF). I don't copy the derivation into my code, but the concept helps when debugging turns.

Measure r and L the right way, because small errors wreck my odometry

A small wheel radius error turns into a big distance error. So I measure "effective" values, not just what the CAD says.

What works for me:

  • Wheel radius (r): I do a roll test under load. I mark the tire, roll one full wheel turn, and measure distance. Then (r = d / (2π)).

  • Wheelbase (L): I measure center-to-center between wheel contact patches, not the motor mounts.

Then I validate with two calibration tests:

  • Straight-line test: command equal wheel speeds. If it curves, I adjust left/right scaling (or re-check wheel radii).

  • Spin test: command equal and opposite speeds for a full 360-degree spin. If the angle is off, (L) is usually the culprit.

The goal is boring motion. Boring means predictable, and predictable is gold.

A control loop that actually works, encoder reading, speed estimate, then PID per wheel

I run my motor loop at 50 to 200 Hz. Faster helps, but only if encoder reads are clean.

My loop structure stays the same across platforms:

  1. Read encoder ticks and compute measured wheel speed (ticks/s or rad/s).

  2. Convert the desired body command ((v,\omega)) into wheel targets with inverse kinematics.

  3. Run PID per wheel to hit those targets.

  4. Output PWM (or voltage command) to the motor driver.

I always tune left and right PIDs separately because motors never match perfectly. In addition, I smooth measured speed with a tiny moving average (3 to 5 samples). That knocks down tick noise without making the robot feel laggy.

A practical diff-drive control loop, from encoders to motors
A practical diff-drive control loop, from encoders to motors

Common gotchas on DIY diff-drive robots, and quick fixes I use

  • Wheel slip: I reduce acceleration, add grippier tires, or fuse an IMU for heading.

  • Motor deadband: I build a minimum PWM map so small commands still move.

  • Battery sag: I monitor voltage and scale max PWM as voltage drops.

  • Tick noise at low speed: I average speed over a few samples and avoid ultra-low setpoints.

  • Math overflow or truncation: I use floats for kinematics, even if PWM is integer.

Hook it into ROS2 or a simple teleop stack, so I can drive, log, and improve fast

Kinematics feels abstract until I can drive the robot, log data, and replay what happened. That's why I keep a clean interface: inputs are (v) and (omega), outputs are wheel targets, and feedback is odometry.

When I test, I log four signals first: commanded (v,omega), measured wheel speeds, and battery voltage. That combo exposes most problems within minutes.


If I use ROS2, where the diff-drive math lives (and what I need to configure)

In ROS2 (as of March 2026), I usually lean on ros2_control with a diff_drive_controller. It accepts cmd_vel style commands and can publish odometry from encoders. I configure wheel radius, wheel separation (L), encoder resolution, update rates, and frame IDs (often odom and base_link). I also set reasonable covariances early so downstream filters don't assume perfect sensors.


If I skip ROS2, a clean "v and omega" interface still keeps my code organized

Even without ROS2, I keep the same contract. One function converts ((v,omega)) to wheel targets (inverse kinematics). Another converts encoder deltas to ((x,y,theta)) (forward kinematics and integration). If I want smoother motion, I ramp (v) and (omega) to limit jerk.

When inverse kinematics feels "jumpy," I compare my sign and unit choices with real examples, like this discussion on smooth differential-drive inverse kinematics.

Conclusion

When I build a custom differential-drive robot, I follow the same loop every time: measure (r) and (L), read encoders, run forward kinematics for odometry, run inverse kinematics for wheel targets, then use per-wheel PID to track speed. After that, I calibrate using simple real tests and small adjustments.

Start simple and log everything. Tune one thing at a time, and trust the data. My favorite next step is a straight-line test and a 360-degree spin test, then I adjust (r) and (L) until reality matches the math.

Comments


bottom of page