Omni wheel and holonomic drive kinematics explained

How a holonomic robot with omni wheels moves in any direction: robot and world frames, the rotation matrix, and the X-drive forward and inverse kinematics that turn wheel speeds into motion and back, with live demos of a robot that slides, spins and drives to a point.

Guidefree, runs in your browser

An omni wheel has a ring of small rollers round its rim. It drives like a normal wheel in the direction it rolls, and the rollers let it slide freely sideways. Put four of them at the corners of a robot, each turned 45 degrees (an X-drive), and the robot can drive forwards, sideways and diagonally and spin, all at the same time and in any mix. A drive that can do that is called holonomic. Omni and mecanum wheels are common on school competition robots, on the small robots in robot football leagues, and on some factory and warehouse vehicles that have to slide sideways into tight spaces. Kinematics is the maths that links the wheels to the motion: forward kinematics turns wheel speeds into how the robot moves, and inverse kinematics turns the motion you want into wheel speeds. On this page a small robot drives on a mat, and each demo below is a real program you can change and run.

The robot on this page, the BugBot, is holonomic but has no wheels. Four small vibration motors shake its feet, each foot pushes in a slightly different direction, and mixing them slides it any way it is told. Its drive() command takes the three motions directly and does that mixing itself. So the demos do the X-drive sums for four omni wheels and show the wheel speeds they give, and the BugBot drives the motion those wheels would make.

In the overhead view of each demo, the blue line is where the robot has been, and anything red is what the program wanted: a target, a line to follow, or the path its model predicts. Each demo says what its chart shows.

What holonomic means

A robot on a floor has three degrees of freedom: where it is across the floor (x), where it is along it (y), and which way it faces (its heading). A drive is holonomic when it can change all three independently, at any moment, whichever way it is facing.

A car cannot. Its wheels roll forwards and backwards but will not slide sideways, so it has no direct control of sideways motion. It can still reach any spot facing any way, as parallel parking proves, but only by a series of forward and backward moves. A limit on which way you can move at each instant, which does not limit where you can end up, is called a non-holonomic constraint. A robot with a driven wheel on each side and a caster (a differential drive) is non-holonomic in the same way: it can spin on the spot, but it cannot slide sideways.

The BugBot's drive(fwd, lat, rot) has one number for each degree of freedom: forward, sideways (positive to the right) and rotation (positive clockwise), each from −100 to 100 percent of that axis's top speed. The robot keeps doing it until the next command. forward(), backward(), left() and right() with a distance= move along one axis and stop, turn_left() and turn_right() with an angle= turn and stop, and stop() stops everything.

Here the robot slides right into the green zone without turning. The chart shows its sideways and forward speeds in its own frame, in cm/s, and its heading in degrees.

Sideways at 70: the robot slides right at 11 to 12 cm/s, its forward speed stays within 0.5 cm/s of zero, and its heading drifts 2.5 degrees in 6 seconds.
The program
from bugbot import *
connect()

# change these and press Run
FWD = 0      # forward, -100 to 100
LAT = 70     # sideways (+ right), -100 to 100
ROT = 0      # rotation (+ clockwise), -100 to 100

START = (60, 100)     # where the robot starts on the mat
trail = []
drive(FWD, LAT, ROT)
for tick in range(60):             # 6 seconds
    x, y = position()
    trail.append((START[0] + x, START[1] + y))
    draw("robot", trail, "blue", "line")
    right_speed, fwd_speed = velocity()
    plot("right cm/s", right_speed)
    plot("forward cm/s", fwd_speed)
    plot("heading", (heading() + 180) % 360 - 180)
    wait(0.1)
stop()
wait(0.6)
print("ended at", position(), "facing", round(heading(), 1))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The robot faces the way it started the whole way across and ends 68 cm to the right, 1 cm up the mat. The heading does creep, 2.5 degrees anticlockwise over the run, because no drive is perfectly balanced. A program that has to hold its heading corrects it with the gyro.

Try FWD = 70 with LAT = 0: the robot drives forwards at 12.5 to 13 cm/s, faster than it went sideways, because this drive's sideways axis is built slower than its forward one. Try both at 70: it goes diagonally, but at about 40 degrees to its forward direction instead of 45, for the same reason. Add ROT = 20 to a forward drive and the path curves, because the robot turns while it moves.

Two frames and the rotation matrix

Every motion can be described in two ways. The robot frame (or body frame) is fixed to the robot: x to its right, y straight ahead. The world frame is fixed to the mat: x to the right, y up the mat. Wheel speeds and drive() commands are in the robot frame, because the wheels are fixed to the robot. Targets, walls and paths are in the world frame. Mixing the two up is the commonest bug in mobile robot code.

The conventions on this page are the BugBot's:

  • lengths in cm and speeds in cm/s;
  • heading in degrees, 0 facing up the mat (world +y), clockwise positive, so 90 faces world +x;
  • the robot's vx is to its right and vy straight ahead, and a turn rate ω (omega) is clockwise positive.

A robot at heading h, moving at (vx, vy) in its own frame, is moving on the mat at:

wx =  vx × cos h + vy × sin h
wy = −vx × sin h + vy × cos h

That is the rotation matrix. To go the other way, from the world into the robot's frame, rotate by −h:

vx = wx × cos h − wy × sin h
vy = wx × sin h + wy × cos h

Check it on cases you can picture. At h = 0 the robot faces up the mat and the two frames agree: wx = vx and wy = vy. At h = 90 it faces world +x, so driving straight ahead (vx = 0, vy = 10) gives wx = 10 and wy = 0: 10 cm/s across the mat. A rotation never changes a vector's length, which gives another check: hypot(vx, vy) is the same in both frames.

Many maths books measure angles anticlockwise, and then the minus sign moves to the other corner of each matrix. Either works, as long as the whole program uses the same one. In Python, remember math.radians(): sin and cos work in radians, and heading() is in degrees.

Omni wheels and the X layout

An omni wheel can only push along the direction it rolls. Whatever the robot does at right angles to that, the rollers let it slide. So the speed each wheel has to turn at is the part of the robot's motion, at that wheel, that points along its rolling direction.

On an X-drive the four wheels sit at the corners of a square, each the same distance R from the centre, and each turned so that it rolls round the centre, at 45 degrees to the robot's forward direction. Seen from above, with the front at the top, the rolling directions make a diamond:

              front
     FL  /             \  FR

     BL  \             /  BR
              back

Each wheel's speed counts as positive when it rolls the robot forwards. Driving straight ahead, all four roll forwards together. Each is at 45 degrees to the motion, so each only sees cos 45° = 1/√2 of it.

Inverse kinematics: from motion to wheel speeds

The speed of any point on the robot is the robot's own velocity plus a part from turning. For a clockwise turn at ω radians a second, a point at (px, py) from the centre moves an extra (ω × py, −ω × px): a point on the front moves right, a point on the left side moves forwards. Take the part of that along each wheel's rolling direction (a dot product) and the four wheel speeds come out as:

FL = (vy + vx) / √2 + R × ω
FR = (vy − vx) / √2 − R × ω
BL = (vy − vx) / √2 + R × ω
BR = (vy + vx) / √2 − R × ω

Here vx and vy are in cm/s, ω is in radians a second (degrees × π / 180) and R is in cm, so the wheel speeds come out in cm/s at the rim. Divide by the wheel's own radius to get the motor's speed in radians a second. As a matrix it is 4 rows by 3 columns, one row per wheel:

[FL]   [ 1/√2   1/√2    R ]
[FR] = [−1/√2   1/√2   −R ]   [vx]
[BL]   [−1/√2   1/√2    R ] × [vy]
[BR]   [ 1/√2   1/√2   −R ]   [ω ]

Some cases to check it against, for an X-drive with its wheels 8 cm from the centre:

Motion wanted FL FR BL BR
forwards at 10 cm/s 7.1 7.1 7.1 7.1
right at 10 cm/s 7.1 −7.1 −7.1 7.1
forwards and right, diagonally, at 10 cm/s 10 0 0 10
clockwise at 60 degrees a second 8.4 −8.4 8.4 −8.4

Spinning clockwise, the left wheels roll forwards and the right wheels backwards, as on a tank. Sliding right, the wheels on one diagonal roll forwards and the other two backwards. Going diagonally, one pair of wheels does all the work and the other pair just rolls along on its rollers.

That diagonal row shows something odd about the X-drive. With every wheel at its top speed s, driving forwards gives vy = √2 × s, about 1.41 times as fast as the wheels turn. Diagonally only two wheels drive, and the top speed is s. An X-drive is fastest forwards, backwards and sideways, and slowest along its diagonals. The extra speed is paid for in force: each wheel pushes at 45 degrees to the motion, so only 1/√2 of each push goes the way the robot is going.

If the sums ask any wheel for more than its top speed, scale all four down by the same factor so that the fastest one is at its top speed. The robot then goes more slowly, in the right direction. Clipping only the wheel that is over the limit changes the direction of travel as well as the speed.

On the BugBot the second step is simpler. Its drive() does the mixing, so its inverse kinematics only has to scale each axis by its top speed: about 20 cm/s forwards, 15 cm/s sideways and 120 degrees a second of turn at 100 percent.

fwd = 100 × vy / 20
lat = 100 × vx / 15
rot = 100 × ω / 120        (ω in degrees a second)

Those three lines stand in for the matrix in every demo on this page. The pattern is the same for every holonomic robot: rotate the motion you want into the robot's frame, then map it onto whatever the robot's motors are.

Forward kinematics: from wheel speeds to motion

Forward kinematics runs the other way: given four wheel speeds, how does the robot move? Add and subtract the four equations and the terms cancel in pairs:

vx = (FL − FR − BL + BR) × √2 / 4
vy = (FL + FR + BL + BR) × √2 / 4
ω  = (FL − FR + BL − BR) / (4 × R)

Four wheels and three motions leave one combination over: (FL + FR) − (BL + BR), which the inverse kinematics always makes zero. Four wheel speeds that break it cannot all be obeyed at once. The wheels fight, some of them slip, and the motion comes out close to the best fit, which is what the three lines above give. A program can use the leftover as a measure of how much the wheels disagree.

To predict where the robot goes, rotate the body motion into the world with the heading and add it up in small steps of time. In the demo you set four wheel speeds. The program works out the body motion with the three lines above, sends that motion to the BugBot, and runs the model alongside it, a tenth of a second at a time. The red line is where the model says the robot will be, and the chart is the gap between the model and the robot, in cm.

FL and BR at 12 cm/s, FR and BL stopped: the model says 8.5 cm/s right and 8.5 cm/s forwards, a 45 degree diagonal, and after 4 seconds the robot is 4.4 cm from where the model says.
The program
from bugbot import *
import math
connect()

# four wheel speeds for an X-drive, cm/s (+ rolls
# the robot forwards). Change them and press Run.
FL, FR = 12, 0       # front left, front right
BL, BR = 0, 12       # back left, back right
R = 8                # cm from the centre to each wheel

# forward kinematics: wheel speeds to body motion
k = math.sqrt(2) / 4
vx = k * (FL - FR - BL + BR)             # cm/s, right
vy = k * (FL + FR + BL + BR)             # cm/s, forward
w = (FL - FR + BL - BR) / (4 * R)        # rad/s, clockwise
spin = math.degrees(w)                   # deg/s
print("body motion:", round(vx, 1), "cm/s right,",
      round(vy, 1), "cm/s forward,", round(spin), "deg/s")
print("wheels disagree by", (FL + FR) - (BL + BR), "cm/s")

# the BugBot has no wheels: send it the same body motion
V_MAX, V_LAT, W_MAX = 20.0, 15.0, 120.0
drive(100 * vy / V_MAX, 100 * vx / V_LAT, 100 * spin / W_MAX)

START = (50, 20)
gx, gy, gh = START[0], START[1], 0.0     # where the model says
trail, model = [], [START]
for tick in range(40):                   # 4 seconds
    x, y = position()
    trail.append((START[0] + x, START[1] + y))
    draw("robot", trail, "blue", "line")
    draw("model", model, "red", "line")
    plot("gap cm", math.dist(trail[-1], (gx, gy)))
    # the model: body to world, then add up
    a = math.radians(gh)
    gx += (vx * math.cos(a) + vy * math.sin(a)) * 0.1
    gy += (-vx * math.sin(a) + vy * math.cos(a)) * 0.1
    gh += spin * 0.1
    model.append((gx, gy))
    wait(0.1)
stop()
wait(0.6)
print("model says", (round(gx - START[0], 1), round(gy - START[1], 1)),
      "and the robot is at", position())
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The gap opens by 2.5 cm in the first half second, because the drive takes about a quarter of a second to get up to speed and the model assumes it is there at once. After that it grows slowly. This robot goes a little faster sideways and a little slower forwards than the 15 and 20 cm/s in the model, so it runs about 4 degrees to the right of the red line. A model is never exact, and knowing how far out it is belongs with it.

Try all four wheels at 10: the model says 14.1 cm/s straight ahead, √2 times the wheel speed, and after 4 seconds the robot is 8.1 cm behind it, because at 71 percent this robot's forward axis is slower than the model thinks. Try FL and BL at 5 with FR and BR at −5: it spins on the spot at 36 degrees a second, and the model and the robot stay within a quarter of a centimetre, because neither moves. Try FL at 12 with the other three at 0: the wheels disagree by 12 cm/s, and the best fit is a slow diagonal with a turn of 21 degrees a second, which makes a curve.

Driving in the world while spinning

Most jobs are set in the world frame: go up the mat, go to that point, follow that line. So a holonomic robot's program usually does two steps every time round its loop:

  1. Rotate the velocity it wants on the mat into the robot's frame, using the heading now.
  2. Turn that into wheel speeds, or on the BugBot into the three drive() numbers.

Competition robots call this field-centric (or field-oriented) drive: the driver pushes the stick forwards and the robot goes up the field, whichever way it is facing. Because step 1 is done again every tick, the robot can spin while it travels in a straight line, which a car or a differential drive cannot do.

In this demo the robot drives straight up the mat at 12 cm/s and spins clockwise at 72 degrees a second, a full turn every 5 seconds. The red line is the path it should follow. The chart shows the four wheel speeds, in cm/s, that an X-drive would need to make the same motion.

Up the mat at 12 cm/s, spinning at 72 degrees a second: the robot stays within 2.1 cm of the red line, and each X-drive wheel speed rises and falls once per turn.
The program
from bugbot import *
import math
connect()

# change these numbers and press Run
WX, WY = 0, 12       # the velocity wanted on the mat, cm/s
SPIN = 72            # the turn wanted, deg/s (clockwise)
LAG = 0.25           # s the drive takes to respond
R = 8                # an X-drive's wheels, cm from the centre

V_MAX, V_LAT, W_MAX = 20.0, 15.0, 120.0
START = (50, 20)
draw("wanted", [START, (START[0] + WX * 5, START[1] + WY * 5)],
     "red", "line")
trail, worst = [], 0
for tick in range(50):                   # 5 seconds
    # 1. world to body, with the heading NOW, aimed
    #    ahead by the turn the lag will add
    h = math.radians(heading() + SPIN * LAG)
    vx = WX * math.cos(h) - WY * math.sin(h)     # right
    vy = WX * math.sin(h) + WY * math.cos(h)     # forward
    # 2. body to the BugBot's own three axes
    drive(100 * vy / V_MAX, 100 * vx / V_LAT, 100 * SPIN / W_MAX)

    # the four wheel speeds an X-drive would need, cm/s
    w = math.radians(SPIN)
    plot("FL", (vy + vx) / math.sqrt(2) + R * w)
    plot("FR", (vy - vx) / math.sqrt(2) - R * w)
    plot("BL", (vy - vx) / math.sqrt(2) + R * w)
    plot("BR", (vy + vx) / math.sqrt(2) - R * w)

    x, y = position()
    trail.append((START[0] + x, START[1] + y))
    draw("robot", trail, "blue", "line")
    # how far off the red line, cm
    off = abs(WY * x - WX * y) / math.hypot(WX, WY)
    worst = max(worst, off)
    wait(0.1)
stop()
wait(0.6)
print("ended at", position(), "facing", round(heading()))
print("worst distance off the line:", round(worst, 1), "cm")
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

Look at the wheel speeds. Each one is a wave, because as the robot turns, the direction "up the mat" sweeps round it, and each wheel's share of that motion goes up and down. FL and BR rise and fall together, and FR and BL do the same a quarter of a turn later. On top of the wave, the left wheels carry an extra R × ω, 10 cm/s, for the spin, and the right wheels 10 cm/s less. So FL and BL swing between about −2 and 22 cm/s, and FR and BR between −22 and 2. No wheel runs at a steady speed, and the path is still a straight line.

LAG is there because the drive takes about a quarter of a second to follow a new command, and in a quarter of a second the robot turns another 18 degrees (72 × 0.25). A command worked out from the heading now lands on a robot that has already turned further, so the program aims ahead by that much. Set LAG = 0 and the robot slants off to the right, 17.3 cm from the line at worst, and ends 18.9 cm to the right of where it started. Wheeled robots have the same problem, from the motors' response and from how often the loop runs, and the same fix.

The mistake to look out for is reading the heading once, before the loop. Change heading() in the loop to 0, the heading at the start, and the command never changes while the robot spins under it. The robot drives round in a small circle, up to 16 cm to the right of the line, and after 5 seconds it is back within 3 cm of where it started.

Going to a point

With world-frame driving in place, going to a point is a short loop: find the vector to the target on the mat, choose a speed that shrinks as the robot gets close, point the velocity along the vector, and put it through the inverse kinematics. A holonomic robot can do its turning at the same time, separately. Here the robot starts facing 200 degrees, nearly backwards, and has to reach a point 120 cm away while turning to face up the mat. The red dot is the target. The chart shows the gap to the target in cm and the heading error in degrees.

The robot goes straight to a point 120 cm away and comes to rest 3.1 cm from it after 11.2 s, turning 160 degrees on the way to finish facing 0, never more than 3.3 cm off the straight line.
The program
from bugbot import *
import math
connect()

# change these numbers and press Run
TX, TY = 80.0, 90.0    # the target, cm from the start
FACE = 0               # the heading to finish at, degrees
TOP = 14.0             # top speed, cm/s
FLOOR = 4.0            # never ask for less than this, cm/s
LAG = 0.25             # s the drive takes to respond

V_MAX, V_LAT, W_MAX = 20.0, 15.0, 120.0
START = (60, 60)
draw("target", [(START[0] + TX, START[1] + TY)], "red", size=4)
trail, off = [], 0
for tick in range(300):
    x, y = position()
    trail.append((START[0] + x, START[1] + y))
    draw("robot", trail, "blue", "line")
    dx, dy = TX - x, TY - y
    gap = math.hypot(dx, dy)
    turn = (FACE - heading() + 180) % 360 - 180
    plot("gap cm", gap)
    plot("heading error", turn)
    # how far off the straight line from the start, cm
    off = max(off, abs(TX * y - TY * x) / math.hypot(TX, TY))
    if gap < 4:
        break
    # the world velocity: at the target, P on the gap
    speed = min(TOP, max(FLOOR, 0.5 * gap))
    wx, wy = speed * dx / gap, speed * dy / gap
    # the spin: P on the heading error, deg/s
    spin = max(-60, min(60, 2 * turn))
    # inverse kinematics, with the heading now
    a = math.radians(heading() + spin * LAG)
    vx = wx * math.cos(a) - wy * math.sin(a)
    vy = wx * math.sin(a) + wy * math.cos(a)
    drive(100 * vy / V_MAX, 100 * vx / V_LAT, 100 * spin / W_MAX)
    wait(0.1)
stop()
wait(0.6)
x, y = position()
print("stopped", round(math.hypot(TX - x, TY - y), 1),
      "cm from the target after", clock(), "s")
print("facing", round(heading()) % 360)
print("worst distance off the straight line:", round(off, 1), "cm")
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The chart shows the two errors coming down separately. The heading error falls from 160 degrees to zero in the first 3 seconds. The gap falls in a straight line at the top speed of 14 cm/s, then curves off once 0.5 × gap drops below 14, about 28 cm out. Both are proportional controllers, the P of a PID controller. The path stays straight while the robot turns under it, because the direction of travel is worked out on the mat and rotated into the robot's frame every tick, with the same aim-ahead as the last demo. Set LAG = 0 and the path bows 8.2 cm off the line. Take the turning out (spin = 0) and it is 2.9 cm off at worst, and arrives still facing 196.

FLOOR is there because of the drive. Below about 15 percent the BugBot does not move at all, and wheeled robots have the same kind of dead band from friction. Set FLOOR = 0 and the robot stops 6.4 cm from the target, where 0.5 × gap asks for too little to move it, and sits there until the loop runs out at 30 seconds. The same happens to the heading: try FACE = 90 and it finishes facing 84, because 6 degrees of error asks for 12 degrees a second, 10 percent, which the drive ignores.

The dividing by gap matters too. dx / gap and dy / gap make a vector of length 1 that only holds the direction, and multiplying by speed sets how fast. Leave the division out and the speed depends on how far away the target is as well.

Omni wheels, mecanum wheels and differential drive

Differential drive Mecanum X-drive (omni)
Wheels one driven wheel each side, plus casters four, facing forwards, rollers at 45 degrees four omni wheels at the corners, turned 45 degrees
Moves sideways no yes yes
Top speed forwards, for wheel speed s s s 1.41 × s
Inverse kinematics 2 equations 4 × 3 matrix 4 × 3 matrix

A differential drive robot has one driven wheel on each side, a distance W apart. With the left wheel at speed left and the right at right, it moves forwards at (left + right) / 2 and turns clockwise at (left − right) / W radians a second. The inverse is left = v + ω × W / 2 and right = v − ω × W / 2. There is no sideways term at all: ask it for a sideways velocity and the equations have no answer. It has to turn to face the way it wants to go, which is why path followers for it, such as pure pursuit, steer along arcs.

A mecanum wheel has its rollers at 45 degrees to the wheel, so its push comes out at an angle even though the wheel faces forwards. Four of them, mounted the usual way round, give the same pattern of signs as the X-drive, without the √2:

FL = vy + vx + (a + b) × ω
FR = vy − vx − (a + b) × ω
BL = vy − vx + (a + b) × ω
BR = vy + vx − (a + b) × ω

Here a is half the distance between the left and right wheels and b is half the distance between the front and back wheels. This is the y + x + rx formula found in many competition robots' gamepad code. A mecanum robot can be the shape of a car and pushes hard forwards. In practice it usually slides sideways more slowly than it drives forwards, because the rollers slip.

An X-drive moves the same in every one of its four main directions, and every wheel pushes round the centre when it spins. The price is a square footprint and less push, since each wheel drives at 45 degrees to the motion. Three omni wheels at 120 degrees apart also make a holonomic drive, with a 3 by 3 matrix in place of the 4 by 3.

The BugBot is another kind of holonomic drive again: vibrating feet instead of wheels, with a sideways axis that is slower than its forward one. The same two steps drive all of them. Rotate the motion you want into the robot's frame, then map it onto the motors.

Questions

What is a holonomic drive?

A drive that can control all three of a robot's degrees of freedom on the floor independently: across, along and heading. It can move in any direction while facing any way, and change direction without turning first. Omni wheel X-drives, mecanum drives and three-wheel omni drives are holonomic. Cars and two-wheeled robots, which have to turn before they can go sideways, are not.

What is the difference between holonomic and non-holonomic robots?

A non-holonomic robot has a limit on which way it can move at each instant, usually "no sliding sideways", even though it can still reach any position in the end. A car or a two-wheeled differential drive robot is non-holonomic: to move sideways it has to turn, drive and turn back, or shuffle as in parallel parking. A holonomic robot has no such limit, so it can follow any path and face any way along it, which makes planning and control simpler.

How do omni wheels work?

An omni wheel has small rollers round its rim, with their axles at right angles to the wheel's own axle. Driven by its motor, it pushes along the direction it rolls, like a normal wheel. Pushed from the side, the rollers turn and it slides freely. With several omni wheels at different angles, each motor controls a push in one direction, and the rollers let the robot move in whatever direction the pushes add up to.

What is an X-drive?

Four omni wheels at the corners of a square robot, each turned 45 degrees so that it rolls round the centre. Its wheel speeds are FL = (vy + vx) / √2 + R × ω, FR = (vy − vx) / √2 − R × ω, BL = (vy − vx) / √2 + R × ω and BR = (vy + vx) / √2 − R × ω. It drives forwards about 1.41 times as fast as its wheels turn, and diagonally at the wheel speed.

What are the inverse kinematics of a four-wheel omni robot?

With vx to the right, vy forwards, ω clockwise in radians a second and the wheels R from the centre: FL = (vy + vx) / √2 + R × ω, FR = (vy − vx) / √2 − R × ω, BL = (vy − vx) / √2 + R × ω, BR = (vy + vx) / √2 − R × ω. If the motion you want is in the world frame, first rotate it into the robot's frame with the heading h: vx = wx × cos h − wy × sin h and vy = wx × sin h + wy × cos h, with the heading clockwise from straight up the mat.

What are the forward kinematics of an omni wheel robot?

For an X-drive: vx = (FL − FR − BL + BR) × √2 / 4, vy = (FL + FR + BL + BR) × √2 / 4 and ω = (FL − FR + BL − BR) / (4 × R). Four wheels for three motions means the matrix is not square, so these give the best fit when the wheels disagree. To get the robot's position, rotate the body motion into the world with the heading and add it up over time, which is odometry, or dead reckoning.

Why is an X-drive faster forwards than diagonally?

Going forwards, all four wheels push, each at 45 degrees to the motion, and the sideways parts of their pushes cancel. Each wheel only has to turn at 1/√2 of the robot's speed, so with all four at top speed the robot goes √2, about 1.41, times as fast as the wheels. Going diagonally, two wheels roll along the motion and the other two freewheel on their rollers, so the robot goes at the wheel speed. The extra forward speed is paid for with less pushing force.

What is the difference between omni wheels and mecanum wheels?

An omni wheel's rollers are at right angles to the wheel, so omni wheels have to be mounted at angles to each other to push in different directions. A mecanum wheel's rollers are at 45 degrees, so all four wheels can face forwards like a car's and still make the robot slide sideways. Both give a holonomic drive with a 4 by 3 kinematics matrix and the same pattern of plus and minus signs.

What is field-centric drive?

Driving in the world frame instead of the robot's. The driver or the program says which way to go on the field or mat, and every time round the loop the robot rotates that into its own frame with its heading, usually from a gyro, before it works out the wheel speeds. Forwards on the stick means up the field whichever way the robot faces. The third demo on this page is field-centric: it goes straight up the mat while it spins.

Why does my holonomic robot curve when it drives and turns at the same time?

The drive takes time to respond, so each command arrives when the robot has already turned further than the heading it was worked out from, and the path slants to one side. Aim ahead by the turn rate times the delay. On this page, 72 degrees a second times a quarter of a second is 18 degrees, and aiming ahead by that cut the worst error from 17.3 cm to 2.1 cm. If the path curls round in circles instead, the program is reading the heading once rather than every time round the loop.

Why does my robot drift when it strafes?

No drive is perfectly balanced. Motors differ in strength, wheels or feet grip differently and the weight is not quite in the middle, so a sideways command also makes a little rotation and some forward motion. The BugBot on this page turned 2.5 degrees while it slid 68 cm sideways. The fix is feedback: read the heading from a gyro and add a small rotation term that holds it, and correct the path from the robot's position.

Is holonomic drive kinematics on the A level specification?

Not by name. None of the A level Computer Science specifications include robot kinematics. The maths it uses is A level maths: trigonometry and vectors from A level Mathematics, and matrices for rotations from A level Further Mathematics. It makes a good A level Computer Science programming project: a world-to-robot rotation, an inverse kinematics function and a go-to-point loop, as in the demos on this page.

Learn it step by step

These lessons build the same ideas one at a time, each with tasks the simulator marks.

  1. U2.1 Two frames Kinematics and frames, University
  2. U2.2 The rotation matrix Kinematics and frames, University
  3. U2.3 A drive that goes sideways Kinematics and frames, University
  4. U2.4 Forward kinematics Kinematics and frames, University
  5. U2.5 Inverse kinematics Kinematics and frames, University
  6. U2.6 Go to a point Kinematics and frames, University
Open the lessons