Gyroscope vs accelerometer
What a gyroscope and an accelerometer each measure, why adding a turn rate up drifts while an absolute reading only jitters, and what a complementary filter buys you. Four demos run on a live robot: change A, press Run and watch the drift come back.
A gyroscope measures how fast something is turning. An accelerometer measures how hard it is being pushed, including the steady pull of gravity. They are different sensors answering different questions, and they sit next to each other on one chip in every phone, drone, games controller and robot. That chip is an IMU, an inertial measurement unit. Neither sensor can say which way a robot is facing on its own: the gyroscope's answer slides away over a minute or two, and the accelerometer's answer is buried in noise. Used together they do the job, which is why they are almost never sold apart. On this page a small robot stands still on a 2 metre mat while both are read, and each demo below is a real program you can change and run.
What each one measures
A gyroscope reports a rate: degrees per second of turn. It says nothing about which way you are pointing. To get an angle you have to add the rate up over time, and that is where the trouble starts.
An accelerometer reports an acceleration in each of three directions, in metres per second squared or, on this robot, centimetres per second squared. Gravity counts. A sensor lying flat and completely still reads about 9.8 m/s² straight down and nothing sideways, so the direction of that reading tells you which way is down. Tip it over and the reading tips with it. That is an absolute measurement: it is made afresh every time, against something outside the robot that never moves.
The BugBot carries a BNO055 on its odometry board. imu() hands back four numbers: the heading the chip itself believes, the turn rate from the gyroscope, and the two accelerations the accelerometer feels along the robot's own right and forward axes.
Two readings from a robot that is not moving
The robot sits still for 20 seconds. Both sensors should read zero. Neither does.
The program
from bugbot import *
connect()
# the robot never moves, so both readings should be zero
rates, sides = [], []
for tick in range(200): # 20 seconds
fused, rate, right, ahead = imu()
plot("turn rate deg/s", rate)
plot("sideways cm/s/s", right)
rates.append(rate)
sides.append(right)
wait(0.1)
stop()
n = len(rates)
print("turn rate: average", round(sum(rates) / n, 2), "deg/s, biggest", round(max(rates), 1))
print("sideways: average", round(sum(sides) / n, 2), "cm/s/s, biggest", round(max(sides), 1))
Both charts are a fuzz around zero, and that fuzz is noise: a different wrong number every reading, as likely to be high as low. Average enough of it and it cancels.
The averages are the interesting part. Run the same loop for 5, 10, 20, 40 and 55 seconds and the accelerometer's average comes out as 0.02, 0.13, -0.13, -0.03 and 0.07 cm/s/s: it hops about near zero and gets no worse. The gyroscope's average comes out as -0.17, -0.22, -0.18, -0.17 and -0.17 deg/s. It is not settling on zero. It is settling on -0.17, and it will still be -0.17 in an hour.
That is a bias: a fixed amount the sensor is out by, the same every reading, for as long as the chip stays at the same temperature. Every gyroscope has one. It is small enough to be invisible in a single reading, and it is the whole problem.
Why a gyroscope drifts
A rate on its own is not much use to a robot that wants to know which way it is facing, so the program adds it up. Every tenth of a second it takes the rate and adds rate × 0.1 to a running total. That is numerical integration, and it adds the bias up too. A bias of one degree per second is one degree of error after a second, sixty after a minute, and it never comes back.
This demo adds the total up for 55 seconds while the robot stands still, and draws it against the heading the BNO055 itself reports. BIAS is an extra error added to every reading, so the effect is easy to see on a short run. The BugBot's own gyroscope is about six times better than this.
The program
from bugbot import *
connect()
# change this number and press Run
BIAS = 1.0 # deg/s the gyroscope reads while the robot is still
gyro = 0.0 # the gyroscope's running total
for tick in range(550): # 55 seconds, standing still
fused, rate, right, ahead = imu()
rate = rate + BIAS
gyro = gyro + rate * 0.1 # add the turn rate up
plot("gyro only", gyro)
plot("fused heading", (fused + 180) % 360 - 180)
plot("truth", heading())
if tick % 100 == 0:
print(round(clock()), "s gyro", round(gyro, 1), " fused", round((fused + 180) % 360 - 180, 1))
wait(0.1)
stop()
The gyro only line is almost perfectly straight: 8.0 degrees after 10 seconds, 16.7 after 20, 25.4 after 30, 33.1 after 40 and 45.6 at the end. The robot has not moved a millimetre. The slope is the bias, 0.83 degrees per second once the robot's own -0.17 is taken off the 1.0 that was added.
The fused heading line is the BNO055's own answer, and it behaves differently. It is noisier from reading to reading, it wanders a few degrees over the minute, and it stays between -1.7 and +9.9. It does not grow. Set BIAS = 0 and the robot's own gyroscope still drifts, to -9.4 degrees after 55 seconds, which is roughly the same size as the wander in the fused reading. Over ten minutes it would be 100 degrees, and the fused reading would still be within about ten.
That is the difference that matters. The gyroscope's error grows with time. The absolute measurement's error does not.
An accelerometer does not drift, but adding it up does
It is tempting to conclude that accelerometers are the safe ones. They are not, if you treat them the same way. Adding up an acceleration gives a speed, and adding that up again gives a position, and each addition carries the noise with it.
This demo adds up the forward acceleration of a robot that never moves, and draws the answer against the optical flow sensor underneath, which measures speed directly by watching the mat slide past.
The program
from bugbot import *
connect()
speed = 0.0
for tick in range(550): # 55 seconds, standing still
fused, rate, right, ahead = imu()
speed = speed + ahead * 0.1 # add the acceleration up
plot("speed added up cm/s", speed)
plot("flow sensor cm/s", flow()[1])
if tick % 100 == 0:
print(round(clock()), "s from the accelerometer", round(speed, 1),
"cm/s, from the flow sensor", flow()[1])
wait(0.1)
stop()
print("it never moved")
The added-up line is a different shape from the gyroscope's. It does not march off in one direction: it staggers, up to 4.9 cm/s and back down, because the noise it is adding has no bias behind it. That staggering is a random walk, and it grows with the square root of the time rather than with the time. It is slower than a bias, and it still gets away in the end.
This is why nothing works out its position from an accelerometer alone for more than a few seconds. A phone dropped into a pocket would think it had walked off down the street. Aircraft and submarines do navigate this way, with gyroscopes and accelerometers costing more than a car, and even those are corrected every so often by something that looks outside.
The lesson is not that one sensor is good and the other bad. It is that adding up is what causes drift, and that the sensor to trust is the one measuring the thing you actually want. The flow sensor measures speed, so its speed does not drift. Its position, added up, does.
Fusing them: the complementary filter
The gyroscope is right about what just changed and wrong about where you started. The absolute measurement is right about where you are on average and wrong from one reading to the next. So use each for the part it is good at.
The complementary filter is the smallest way of writing that down:
angle = A × (angle + rate × dt) + (1 - A) × absolute reading
Take the estimate you already had, move it by what the gyroscope says just happened, and then pull it a little of the way towards the absolute reading. A is a number just under 1, usually between 0.9 and 0.99. The demo writes the same sum in a form that copes with the reading crossing 360 degrees.
The program
from bugbot import *
connect()
# change these two numbers and press Run
A = 0.95 # how much of the gyroscope the filter keeps each tick
BIAS = 1.0 # deg/s the gyroscope reads while the robot is still
gyro = 0.0 # the gyroscope's running total
both = 0.0 # the two sensors combined
for tick in range(550): # 55 seconds
fused, rate, right, ahead = imu()
rate = rate + BIAS
gyro = gyro + rate * 0.1
step = (fused - both - rate * 0.1 + 180) % 360 - 180
both = both + rate * 0.1 + (1 - A) * step
plot("gyro only", gyro)
plot("fused heading", (fused + 180) % 360 - 180)
plot("combined", both)
plot("truth", heading())
if tick == 100: # turn for three seconds
drive(0, 0, 25)
if tick == 130:
stop()
wait(0.1)
stop()
print("truth", round(heading(), 1), " gyro only", round(gyro, 1),
" fused", round(imu()[0], 1), " combined", round(both, 1))
The robot stands still for 10 seconds, turns for 3, and then stands still again. The truth is 95.5 degrees. The gyroscope alone says 141.2, out by 45.7 and getting worse. The chip's fused heading says 100.8. The combined estimate says 102.0, and on the chart it goes round the turn as cleanly as the gyroscope line and then stays put.
Notice what fusing does not do. The combined estimate does not beat the absolute reading in the long run, because that is what is holding it in place. If the absolute reading sits 5 degrees off, so will the filter. What fusion buys is a smooth, fast estimate that does not run away.
Choosing A
A decides how long the filter leans on the gyroscope before the absolute reading pulls it back. The time it takes to be pulled most of the way is A × dt / (1 - A), which with dt = 0.1 gives:
| A | leans on the gyroscope for | combined heading at the end |
|---|---|---|
| 0 (no gyroscope) | 0 s | 103.3 |
| 0.5 | 0.1 s | 101.9 |
| 0.8 | 0.4 s | 101.1 |
| 0.95 | 1.9 s | 102.0 |
| 0.98 | 4.9 s | 104.7 |
| 0.995 | 19.9 s | 115.7 |
The truth is 95.5 and the fused reading it is being pulled towards ends at 100.8. Above about 0.98 the bias starts to show: a filter that leans on the gyroscope for 19.9 seconds keeps 19.9 seconds' worth of drift, which at 0.83 deg/s is the 15 degrees the last row is out by.
Small values pay in noise instead. Standing still for 30 seconds, the fused reading on its own jumps 1.79 degrees from one tick to the next. The combined estimate jumps 0.74 degrees at A = 0.5, 0.28 at 0.8, 0.11 at 0.95 and 0.08 at 0.98. The higher A is, the smoother the answer.
So the choice is drift against noise, and the usual answer is a second or two. There is one more reason to keep A high on a real robot that this simulator does not show: an accelerometer measures every knock and shove as well as gravity, so while the robot is accelerating its idea of which way is down is wrong. A filter that leans on the gyroscope for a second or two rides straight over that.
What this looks like on a real robot
Reading which way is down and reading which way you are facing are two different jobs, and the accelerometer can only help with the first.
Tilt is easy. Gravity points down, the accelerometer sees it, and the angle falls out of the numbers with atan2. The gyroscope fills in the fast movement between readings, and a complementary filter does the joining. This is what balances a self-balancing robot and what keeps a drone level, and a 6-axis IMU, which is three gyroscope axes and three accelerometer axes, is all it needs.
Heading, also called yaw, is the hard one. Spin a robot on a flat floor and gravity does not change at all, so the accelerometer has nothing to say. The absolute reference has to come from somewhere else: a magnetometer reading the earth's field, which makes it a 9-axis IMU, or a camera, or wheel odometry against a map, or the tags on a wall. The filter is the same shape whichever it is. The BugBot's BNO055 has all nine axes and does the fusing inside the chip, which is why imu()[0] already has a heading in it.
Two other names worth knowing. Madgwick and Mahony filters do this job in three dimensions at once using quaternions, and they are what most flight controllers actually run. A Kalman filter does the same job with an explicit account of how much it trusts each sensor, and it can estimate the gyroscope's bias while it runs rather than assuming it away.
Where this is taught
- Truth and belief separates what the simulator knows from what the robot itself can measure, which is the distinction this whole page rests on.
- How the error grows has the shapes: noise walks, bias marches, and a heading error rotates everything after it.
- Calibration measures the gyroscope bias while the robot stands still and takes it back out again.
- A reading is a distribution is where the mean and the spread of a noisy sensor are put on a proper footing.
- The low pass filter is the same one-line update used on a single noisy signal.
- Two sources, one state builds a complementary filter from a drifting signal and a noisy one.
- Predict and correct is the shape every filter shares, and the step from here to a Kalman filter.
- Drift and correction is the same idea without the maths, early in the school course.
Questions
What is the difference between a gyroscope and an accelerometer?
A gyroscope measures the rate of turn, in degrees per second. An accelerometer measures acceleration, including gravity, so it can tell which way is down. A gyroscope has to be added up to give an angle, and the small errors add up with it; an accelerometer gives an absolute reading every time, but a noisy one.
Why does a gyroscope drift?
Because a program has to add the turn rate up to get an angle, and the sensor's bias, the fixed amount it reads when standing still, is added up too. On this page a bias of 0.83 degrees per second became 45.6 degrees of error in 55 seconds without the robot moving at all.
Can an accelerometer measure heading?
No, not on a flat floor. Turning on the spot does not change the direction of gravity, so the accelerometer reads the same all the way round. It can measure tilt, which is roll and pitch. Heading needs a magnetometer, a camera or something else that looks outside the robot.
Can you get position from an accelerometer?
Only for a few seconds. Position means adding the acceleration up twice, and the noise is added up twice with it. In the third demo on this page the robot stood perfectly still and the speed worked out from its accelerometer wandered to 4.9 cm/s, which would be metres of position error within a minute.
What is sensor fusion?
Combining two or more sensors so that the answer is better than either alone. Here the gyroscope is trusted for the fast changes and the absolute reading for the slow truth, which is exactly what each is good at.
What is a complementary filter?
The simplest fusion rule: angle = A × (angle + rate × dt) + (1 - A) × absolute, with A a little under 1. It is a high pass filter on the gyroscope and a low pass filter on the absolute reading, and the two halves add to 1, which is where the name comes from.
How do I choose A in a complementary filter?
A × dt / (1 - A) is how long the filter leans on the gyroscope. Pick a second or two, then check both ends: too high and the gyroscope's drift shows in the answer, too low and the noise of the absolute reading does. The table on this page shows both failures on the same robot.
What is an IMU, and what do 6-axis and 9-axis mean?
An inertial measurement unit: one chip carrying a gyroscope and an accelerometer, and often a magnetometer. 6-axis means three gyroscope axes and three accelerometer axes, which is enough for tilt. 9-axis adds three magnetometer axes, which gives heading as well.
Is a Kalman filter better than a complementary filter?
It does more. A Kalman filter keeps track of how uncertain it is and works out how much to trust each sensor from that, and it can estimate the gyroscope's bias as it runs. It costs more code and more arithmetic. For tilt on a small robot a complementary filter is usually enough, and many flight controllers use Madgwick or Mahony, which sit between the two.
Do gyroscopes and accelerometers appear on the GCSE or A level specification?
Not by name. No GCSE or A level Computer Science specification (AQA, OCR, Edexcel, Eduqas) names them. They turn up in Physics as examples of sensors, and in any robotics or electronics project. The programming on this page is a loop, a running total and one line of arithmetic, which is GCSE material.
Learn it step by step
These lessons build the same ideas one at a time, each with tasks the simulator marks.
- 2.5 Drift and correction Sensing, Robot club
- U1.3 Truth and belief The robot as a system, University
- U3.1 Integrating velocity Odometry and drift, University
- U3.3 How the error grows Odometry and drift, University
- U3.4 Calibration Odometry and drift, University
- U4.1 A reading is a distribution Noise and filtering, University
- U4.3 The low pass filter Noise and filtering, University
- U6.1 Two sources, one state State estimation, University
- U6.2 Predict and correct State estimation, University