How to Calibrate a Magnetometer
2022-06-20 | By ShawnHymel
License: Attribution Compass Magnetic Arduino
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.
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.
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.
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.
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:
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.
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.
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:
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:
geographic_heading = magnetic_heading declination_offset
Digital Compass Example
Copy the following program to your Arduino IDE:
/**
* 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).
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:
- Adafruit Magnetometer Calibration Tutorial
- How to Calibrate a Magnetometer?
- Tutorial: How to calibrate a compass (and accelerometer) with Arduino
- Magnetometer Hard & Soft Iron Calibration
- Calibrate an eCompass in the Presence of Hard- and Soft-Iron Interference
Code samples from this tutorial and the video can be found in this GitHub repository.
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum