Sunday, 17 November 2013

Connecting and calibrating a HMC5883L Compass on the Raspberry Pi

Here is how to connect a HMC5883L Compass to the Raspberry Pi, calibrate it and read the data. Connecting the compass is simple enough, follow the steps here which show how to connect a similar I2C device.

Simple Python Code

Some simple code to read the data, calculate a bearing and print it out
import smbus
import time
import math

bus = smbus.SMBus(0)
address = 0x1e

def read_byte(adr):
    return bus.read_byte_data(address, adr)

def read_word(adr):
    high = bus.read_byte_data(address, adr)
    low = bus.read_byte_data(address, adr+1)
    val = (high << 8) + low
    return val

def read_word_2c(adr):
    val = read_word(adr)
    if (val >= 0x8000):
        return -((65535 - val) + 1)
        return val

def write_byte(adr, value):
    bus.write_byte_data(address, adr, value)

write_byte(0, 0b01110000) # Set to 8 samples @ 15Hz
write_byte(1, 0b00100000) # 1.3 gain LSb / Gauss 1090 (default)
write_byte(2, 0b00000000) # Continuous sampling

scale = 0.92

x_out = read_word_2c(3) * scale
y_out = read_word_2c(7) * scale
z_out = read_word_2c(5) * scale

bearing  = math.atan2(y_out, x_out) 
if (bearing < 0):
    bearing += 2 * math.pi

print "Bearing: ", math.degrees(bearing)
When you run the script you'll get something like
    Bearing:  70.0168934781

Calibrating the compass

Well that was easy wasn't it. Well not quite, if you start to rotate your device you might notice the values don't seem right. Start by rotating it until you get close to 0 degrees, then rotate it physically through four 90 degree steps, taking a reading at each step. Continue until you have gone through 360 degrees. Here is my output
    Bearing:  0.292322869229
    Bearing:  89.4543424066
    Bearing:  175.645778937
    Bearing:  266.66908262
    Bearing:  0.298412819995
So what's happening?  We can see the readings are off, but not by much.  Make a change to the code by replacing lines 35-43 with
re-run the program and direct the output to a file. While the program is running repeatedly rotate your compass backwards and forwards through 360 degrees, make sure you keep it flat otherwise you'll get some odd results.
    sudo ./ > compass-plot.dat
Now lets plots the data and look at what's going on, here is a gnuplot program and the command to run it
set terminal wxt persist size 800,800 background '#000000' 

    set style line 99 linecolor rgb "#ffffff" linetype 0 linewidth 2
    set key top right textcolor linestyle 99 
    set grid linestyle 99
    set border linestyle 99

    plot filename using 1:2 title "Raw compass values" linecolor rgb "green" 
Save this to gnuplot-compass.plg and then run the following command
    gnuplot -e "filename='compass-plot.dat'" gnuplot-compass.plg
You'll then be presented with a graph similar to this one

Scatter digram of the raw compass data
We can see that the circle isn't quite centered around the origin, although in this case it's not off by much. My previous compass was off by a much larger value. This plot shows the offset in Y to be about 150.

Scatter diagram of my old compass which had a much bigger offset value

Now we can use this data to calculate the standard offset that we apply to the compass readings to correct things. Replace the for loop in the Python program with

minx = 0
maxx = 0
miny = 0
maxy = 0

for i in range(0,500):
    x_out = read_word_2c(3)
    y_out = read_word_2c(7)
    z_out = read_word_2c(5)
    if x_out < minx:
    if y_out < miny:
    if x_out > maxx:
    if y_out > maxy:
    #print x_out, y_out, (x_out * scale), (y_out * scale)

print "minx: ", minx
print "miny: ", miny
print "maxx: ", maxx
print "maxy: ", maxy
print "x offset: ", (maxx + minx) / 2
print "y offset: ", (maxy + miny) / 2
run the program again rotating the compass through 360 degrees. Once the program finishes it will print out the offsets you need to apply to your calculations, these are the values I got
    minx:  -216
    miny:  -193
    maxx:  197
    maxy:  213
    x offset:  -10
    y offset:  10
We are almost done, back to the first program and include the offset in the calculations, changes lines 35-37 to
x_offset = -10
y_offset = 10
x_out = (read_word_2c(3) - x_offset) * scale
y_out = (read_word_2c(7) - y_offset) * scale
z_out = (read_word_2c(5)) * scale
Then re-run the test by taking a reading every 90 degrees of rotation, here are my newly adjusted values
    Bearing:  0.278132667296
    Bearing:  90.0
    Bearing:  180.290839022
    Bearing:  272.501622814
    Bearing:  359.725859606
As you can see these values are much better, there will always be a small variance as the data is a bit noisy.


As you can see it's relatively easy to attached a compass module to your Raspberry Pi, calibrate it and start to get meaningful readings.  Oh and don't have any large metal objects or large bits of electrical equipment close to the compass when testing.

1 comment:

  1. I just wanted to leave a note here since I'm building my second Raspberry Pi with an HMC5883L. Luckily I bought 2 when I worked on the project years ago.

    I have a weather station on a boat moored in the middle of a lake. The boat rotates around the mooring ball depending on the wind direction. Any "normal" weather station needs to have the base point due North so it can calculate the correct wind direction. Since the boat rotates the wind direction always indicates North because the boat is facing in to the wind. Using what I learned here I write the heading to a file once a minute and then use an awk command to replace the wind direction in the weather station's data stream with the heading from the HMC5883L.

    If you want to see it in action, you can check out


Temperature logging with a DS18B20 and a Raspberry Pi

I wanted to do some temperature logging so I hooked up a DS18B20 temperature sensor to a Raspberry Pi. About the DS18B20 Dallas DS18B...