Introduction
One of the most popular I2C-compatible magnetometer is the Honeywell HMC5883L. These sensors’ solid-state construction with very low cross-axis sensitivity is designed to measure both the direction and
the magnitude of Earth’s magnetic fields, from milli-gauss to 8 gauss.
In this tutorial I'll try to:
- Introduce how a magnetometer works
- Explain how to retrieve the heading from the magnetometer data
- Provide the little library I wrote for Arduino IDE
How does a magnetometer work?
An electronic magnetometer like the HMC5883L is based on the Anisotropic Magnetoresistance phenomenon. Mastering the physics that descibe the phenomenon is not an easy task, since this is a huge field whose depths we cannot hope to begin to plumb in these few words.
Basically, the a magnetic field interacts with the path of the current flowing through a ferrous material, according to the Lorentz Law hence the resistance of the material seems to change to the observer. You can imagine as if the bar of ferrous material (e.g InSb) grows longer, raising its electric resistance. Therefore measuring the change in the resistance we can estimate the magnetical field! The Equation that rules the phenomenon is in the image below. For a further investigation of the matter, especially on the electronics a magnetometer is based upon, you could read this.
From the raw data to the north!
![]() |
Supercomputer models of Earth's magnetic field from nasa.gov |
In a compass, the magnetic field you measure is the earth's one. It is tangential to the surface of the planet and it flows from north to south. The HMC5883L has three different axis to calculate the headings, as you may not know the tilt of your device (i.e. our quadcopter) when you need the data! Anyway for this example we will assume that the sensor is flat on a table, so we don't have to worry about its tilt. Therefore we'll use only X and Y axes data.
![]() |
We'll assume Hz =0 |
Hence the angle between the Y axis and the magnetic north will be, according to the quandrant:
Direction (y>0) = 90 - [arctan(x/y)] * 180 / π
Direction (y<0) = 270 - [arctan(x/y)] * 180 / π
Direction (y=0, x<0) = 180.0
Direction (y=0, x>0) = 0.0
First of all we have to scale the raw data according to the scale we chose.
The valid gauss values are: 0.88, 1.3, 1.9, 2.5, 4.0, 4.7, 5.6, 8.1. Of course for a geo-compass we just need 1.3 Ga, that leads us to a 0.92 [mG/LSb] of resolution and a gain of 1090 [LSb/Gauss]. The code I provide with this post is based on the code found here, but at the time this post is written, the original code won't work. There are some huge bugs as floating point number comparison that will not allow you to change the scale factor of the sensor, and some queer bugs on error handling (basically that code doesn't check for error at all, as you can easily prove executing it: it will always display an error setting the scale, and setting the measurement mode. More oddly this latter error display the same message because the error variable is not reset after its use). Of course even my library will have some bugs too, and it's not complete at all, but it's a good start to familiarize with the sensor itself.
Here is the code with a lots of comments:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
HMC5883L_Example.ino - Example sketch for integration with an HMC5883L triple axis magnetometer. | |
Copyright (C) 2013 BluLemonLabs (bluelemonlabs.blogspot.com) | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the version 3 GNU General Public License as | |
published by the Free Software Foundation. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
// Reference the I2C Library | |
#include <Wire.h> | |
// Reference the HMC5883L Compass Library | |
#include <HMC5883L.h> | |
// Store our compass as an object. | |
HMC5883L compass; | |
// Record any errors that may occur in the compass. | |
int error = 0; | |
// Out setup routine, here we will configure the microcontroller and compass. | |
void setup() | |
{ | |
// Initialize the serial port. | |
Serial.begin(9600); | |
Serial.println("Starting the I2C interface."); | |
Wire.begin(); // Start the I2C interface. | |
Serial.println("Constructing new HMC5883L"); | |
compass = HMC5883L(); // Construct a new HMC5883 compass. | |
//The implementation of the class is provided in the library | |
// Now we have an istance of the class! | |
//Let's initializate it... | |
Serial.println("Setting scale to +/- 1.3 Ga"); | |
error = compass.SetScale(1.3); // Set the scale of the compass to 1.3Ga | |
if(error != 0){ // If there is an error, print it out. | |
Serial.println(compass.GetErrorText(error)); | |
error =0; | |
} | |
Serial.println("Setting measurement mode to continous."); | |
error = compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous | |
if(error != 0) {// If there is an error, print it out. | |
Serial.println(compass.GetErrorText(error)); //Todo: Error handling for this method in .h and .cpp | |
error=0; | |
} | |
} | |
// Our main program loop. | |
void loop() | |
{ | |
// Retrieve the raw values from the magnetometer (not scaled). | |
MagnetometerRaw raw = compass.ReadRawAxis(); | |
// Retrieve the scaled values from the magnetometer (scaled to the configured scale). | |
MagnetometerScaled scaled = compass.ReadScaledAxis(); | |
// Values are accessed like so: | |
int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis) | |
// Calculate heading when the magnetometer is level, then correct for signs of axis. | |
// Atan2() automatically check the correct formula taking care of the quadrant you are in | |
float heading = atan2(scaled.YAxis, scaled.XAxis); | |
// Once you have your heading, you must then add your 'Declination Angle', | |
// which is the 'Error' of the magnetic field in your location. Mine is 0.0404 | |
// Find yours here: http://www.magnetic-declination.com/ | |
// If you cannot find your Declination, comment out these two lines, your compass will be slightly off. | |
float declinationAngle = 0.0404; | |
heading += declinationAngle; | |
// Correct for when signs are reversed. | |
if(heading < 0) | |
heading += 2*PI; | |
// Check for wrap due to addition of declination. | |
if(heading > 2*PI) | |
heading -= 2*PI; | |
// Convert radians to degrees for readability. | |
float headingDegrees = heading * 180/M_PI; | |
// Output the data via the serial port. | |
Output(raw, scaled, heading, headingDegrees); | |
// By default the HMC5883L reads the data 15 time per second (15Hz) | |
// However since we have a long serial out (104ms at 9600) we will let | |
// it run at its natural speed. | |
// delay(66); | |
} | |
// Output the data down the serial port. | |
void Output(MagnetometerRaw raw, MagnetometerScaled scaled, float heading, float headingDegrees) | |
{ | |
Serial.print("Raw:\t"); | |
Serial.print(raw.XAxis); | |
Serial.print(" "); | |
Serial.print(raw.YAxis); | |
Serial.print(" "); | |
Serial.print(raw.ZAxis); | |
Serial.print(" \tScaled:\t"); | |
Serial.print(scaled.XAxis); | |
Serial.print(" "); | |
Serial.print(scaled.YAxis); | |
Serial.print(" "); | |
Serial.print(scaled.ZAxis); | |
Serial.print(" \tHeading:\t"); | |
Serial.print(heading); | |
Serial.print(" Radians \t"); | |
Serial.print(headingDegrees); | |
Serial.println(" Degrees \t"); | |
} |
On the image below you can see how the heading measured with an iPhone 4 is quite close to the one we read from Arduino. There are many margin of improvement. First, we ought compensate the potential tilt of the device using the accelerometer data from the ADXL345, for example using the info on my previous post! Moreover, my breadboard has an aluminium ground plane at its bottom, which can obviously make harder for the structure to sense the magnetic field and/or could drift it and create an offset.