How to Calibrate a Magnetometer

By ShawnHymel

A magnetometer allows you to detect the presence and strength of a magnetic field. A 3-axis magnetometer gives you the strength of that magnetic field in 3 dimensions: along the X, Y, and Z axes. Combined, these give you a vector dictating the strength and direction of the magnetic field.

Beyond just detecting nearby magnets, a magnetometer can also measure the Earth’s magnetic field. As a result, we can use these tiny sensors as a method of constructing a digital compass. However, it’s not quite as easy as just reading the strength of an ambient field.

This guide will walk you through the process of performing 3 types of calibration for a magnetometer: hard-iron calibration, soft-iron calibration, and magnetic declination. With these, you should be able to build a functioning digital compass out of your magnetometer readings!

If you’d like to watch this tutorial in video form, see here:

 

Hardware Connections

I’ll be using an Adafruit Feather M0 and a LIS3MDL 3-axis magnetometer. The code we will use works for a number of Adafruit magnetometer breakout boards, but the concepts should apply to all magnetometers.

Important! The Adafruit Sensor Lab code requires a good amount of flash memory. As a result, it will not fit on many older Arduino boards (e.g. UNO). I recommend using a SAMD21 or better for this exercise. That being said, you are welcome to write your own program that sends raw magnetometer (in uTesla) data to MotionCal.

Connect your magnetometer to your Arduino board. I’ll be using I2C to communicate with the sensor.

Connect magnetometer to Arduino

Hard-iron and Soft-iron Calibration Concepts

Hard-iron distortions are caused by nearby permanent magnets. These nearby magnetic fields result in a simple additive offset to our readings and are relatively straightforward to correct. We take a bunch of X, Y, and Z raw readings while moving the magnetometer about in all directions. We can plot the X/Y, Y/Z, and Z/X values, which would appear as 3 separate circles. With hard-iron distortion, these circles will appear in separate areas of the sample plot.

Hard-iron distortion effects

Next, we find the halfway point between the minimum and maximum values of each axis. These become our offsets. When we subtract these offsets from future raw readings, we have successfully corrected for hard-iron distortions. If we take a new series of readings and plot the results, the circles should overlap each other.

Calibrated for hard-iron effects

Soft-iron distortions result from the presence of iron, steel, and other ferrous materials near the sensor. Non-ferrous materials can also cause some distortions, as different objects and materials can deflect and warp magnetic fields.

When plotted, the raw X, Y, and Z samples will look elliptical rather than spherical.

Elliptical pattern from soft-iron distortions

Note that MotionCal will automatically re-scale the display to make the collected points look more spherical, so I had to manipulate the image some to demonstrate the concept.

The math to perform soft-iron calibration is a little more involved than we have time for in this article. You can read about it here if you would like to dig into the math. Fortunately, we have software tools to help us out.

When complete, you end up with 2 sets of values: a vector for the hard-iron offsets and a matrix for the soft-iron scale values. To compensate for the hard-iron effects, you simply need to subtract the hard-iron offset values from your raw readings:

Accounting for hard-iron distortions

To compensate for soft-iron effects, you should perform a matrix multiplication using the soft-iron scale values. Note that you can compensate for hard-iron distortion as well to create a set of completely calibrated values.

Accounting for soft-iron and hard-iron distortions

Magnetometer Calibration with Motion Cal

In the Arduino IDE, go to File > Preferences and add the following URL to the board manager list:

https://adafruit.github.io/arduino-board-index/package_adafruit_index.json

Go to Tools > Board > Board Manager. Search for and install Adafruit SAMD Boards

Go to Sketch > Include Library > Library Manager. Search for and install Adafruit Sensor Lab. This should install the required library dependencies.

Open File > Examples > Adafruit Sensor Lab > calibration > imucal_nosave. Upload this program to your Arduino board. Note that this program is intended to calibrate 9 DoF inertial measurement units (IMU), but we can use it on singular sensors like our LIS3MDL magnetometer. It outputs raw accelerometer, gyroscope, and magnetometer data over the serial port. If one or more sensor is not present, those values will be 0.

Download and install MotionCal from https://www.pjrc.com/store/prop_shield.html for your operating system. Run it, and connect to the serial port of your Arduino (make sure that the serial port is not being used by another program). When the connection is established, begin turning and twisting your magnetometer board in all directions.

Using MotionCal to calibrate a magnetometer

The idea is to make a sphere with all of the captured x, y, and z magnetometer points. Aim to have gaps less than 1%. When you’re done, you can select “none” under the serial port to have it stop collecting data.

Copy down the values from the “Magnetic Offset” (hard iron calibration) and “Magnetic Mapping” (soft iron calibration).

Magnetic Declination

If you are hoping to use the magnetometer as a compass, you should be aware of the differences between magnetic heading and geographic heading. The north and south geographic poles are different from the earth’s magnetic poles. Also, the magnetic poles shift continuously. You can find a fun animation of how the magnetic poles shift here: https://geomag.colorado.edu/historical-main-field-change-and-declination.

To use your magnetometer like a regular compass (magnetic heading), you do not need to account for magnetic declination (as a regular compass will point to magnetic north). However, if you’d like to find your heading based on the geographic poles, you’ll need to convert from a magnetic heading to a geographic heading.

You can use this tool (https://www.ngdc.noaa.gov/geomag/calculators/magcalc.shtml#declination) from NOAA to find the magnetic declination in your area. Simply enter your location (city, latitude/longitude, etc.), and you’ll get a number in degrees and minutes.

To compute the floating point value of your declination offset:

Copy Code
declination_offset = degrees + minutes / 60

If your offset is east, the number should be positive. If the offset is west, the number should be negative.

From here, you can apply the offset as follows:

Copy Code
geographic_heading = magnetic_heading + declination_offset

Digital Compass Example

Copy the following program to your Arduino IDE:

Copy Code
/**
* Compass Demo
*
* Print heading (in degrees) to attached I2C OLED display. Demonstrate
* how to use magnetometer calibration data and convert magnetic heading
* to geographic heading.
*
* Author: Shawn Hymel
* Date: May 5, 14
*
* License: 0BSD (https://opensource.org/licenses/0BSD)
*/

#define DEBUG 1
#define OLED 0

#include <Wire.h>
#include <Adafruit_LIS3MDL.h>
#include <Adafruit_Sensor.h>

#if OLED
#include <SFE_MicroOLED.h>
#endif

// Pins
const int pin_reset = 8;

// Hard-iron calibration settings
const float hard_iron[3] = {
-32.34, -1.19, 6.25
};

// Soft-iron calibration settings
const float soft_iron[3][3] = {
{ 0.993, 0.040, -0.002 },
{ 0.040, 1.003, -0.009 },
{ -0.002, -0.009, 1.006 }
};

// Magnetic declination from magnetic-declination.com
// East is positive (+), west is negative (-)
// mag_decl = (+/-)(deg + min/60 + sec/3600)
// Set to 0 to get magnetic heading instead of geo heading
const float mag_decl = -1.233;

// Globals
Adafruit_LIS3MDL lis3mdl;
#if OLED
MicroOLED oled(pin_reset);
#endif

void setup() {

// Pour some serial
#if DEBUG
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("LIS3MDL compass test");
#endif

// Initialize magnetometer
if (!lis3mdl.begin_I2C()) {
#if DEBUG
Serial.println("ERROR: Could not find magnetometer");
#endif
while (1) {
delay(1000);
}
}

// Initialize OLED
#if OLED
delay(100);
Wire.begin();
oled.begin(0x3D, Wire);

// Clear display
oled.clear(ALL);
oled.display();
delay(1000);
oled.clear(PAGE);
#endif
}

void loop() {

static float hi_cal[3];
static float heading = 0;

// Get new sensor event with readings in uTesla
sensors_event_t event;
lis3mdl.getEvent(&event);

// Put raw magnetometer readings into an array
float mag_data[] = {event.magnetic.x,
event.magnetic.y,
event.magnetic.z};

// Apply hard-iron offsets
for (uint8_t i = 0; i < 3; i++) {
hi_cal[i] = mag_data[i] - hard_iron[i];
}

// Apply soft-iron scaling
for (uint8_t i = 0; i < 3; i++) {
mag_data[i] = (soft_iron[i][0] * hi_cal[0]) +
(soft_iron[i][1] * hi_cal[1]) +
(soft_iron[i][2] * hi_cal[2]);
}

// Calculate angle for heading, assuming board is parallel to
// the ground and +Y points toward heading.
heading = -1 * (atan2(mag_data[0], mag_data[1]) * 180) / M_PI;

// Apply magnetic declination to convert magnetic heading
// to geographic heading
heading += mag_decl;

// Convert heading to 0..360 degrees
if (heading < 0) {
heading += 360;
}

// Print calibrated results
#if DEBUG
Serial.print("[");
Serial.print(mag_data[0], 1);
Serial.print("\t");
Serial.print(mag_data[1], 1);
Serial.print("\t");
Serial.print(mag_data[2], 1);
Serial.print("] Heading: ");
Serial.println(heading, 2);
#endif

// Display heading (rounded) to OLED
#if OLED
oled.clear(PAGE);
oled.setFontType(1);
oled.setCursor(5, 20);
oled.print(int(heading + 0.5));
oled.display();
#endif

delay(100);
}

Change the hard_iron array to match the “Magnetic Offset” values you obtained from MotionCal. Change the soft_iron array to match the “Magnetic Mapping” values you copied from MotionCal. Finally, change the mag_decl value to the magnetic declination value you obtained earlier.

Run the code, and open a Serial monitor. You should see the magnetic heading being printed to the console. Note that the direction of heading is given by the +Y axis, assuming +Z is pointing to the sky.

If you attach an I2C OLED screen to your Arduino and enable the OLED code (#define OLED 1), you can see the heading printed out to the OLED. The heading should match up with the readings from other digital compasses (note that you might need to change their settings to report the geographic/true heading instead of a magnetic heading).

Testing the magnetometer as a digital compass

Conclusion and Going Further

Because the hard iron and soft iron distortions will change depending on the environment of the magnetometer (due to nearby speakers, motors, and ferrous items), you should ideally recalibrate the sensor any time it changes location. Additionally, such distortions can be produced by nearby electronics. So, it’s a good idea to recalibrate if your magnetometer is placed on a new PCB with or near other electronics.

I recommend checking out the following guides if you would like to learn more about calibrating magnetometers:

Code samples from this tutorial and the video can be found in this GitHub repository.

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1528-1531-ND
  • 1528-4479-ND
  • 1528-2751-ND
TechForum

Have questions or comments? Continue the conversation on TechForum, Digi-Key's online community and technical resource.

Visit TechForum