Calibration for 3-axis accelerometer and magnetometer

For my latest project, I’m using an 3-axis accelerometer / magnetometer, specifically Adafruit’s Triple-axis Accelerometer+Magnetometer (Compass) Board that uses the LSM303DLHC chip. .

The 3-Axis Magnetometer and Accelerometer Board

The 3-Axis Magnetometer and Accelerometer Board


My specific project may not require much precision, but I still decided it would be a good idea to calibrate the outputs. There are a number of  both sophisticated and simpler tools for magnetometer bias measurements, but I wanted a simple solution I could run on my headless Pi zero W and that dealt with calibrating both the magnetometer and accelerometer.  An excellent discussion that I borrowed heavily from is the blog post “How to Calibrate a Magnetometer.”

There are two general types of distortions in the outputs. One is a straight shift (bias) in a given direction, while the other distorts the response. The former is the easiest to deal with computationally. One can rotate the unit around all axes (a figure 8 movement with rotation is what I use) and measure the minimum and maximum values reported for the 3 axes (six values in all). If there is no bias, the absolute values for the + and – direction of each axis would be the same. If the response is shifted towards one or the other, there’s a bias present. One can simply compute the average of the min and max for each axis, and then subtract that value from the reported output for that axis.

plot showing an offset circle.

Offset bias. Called “hard iron distortion” in a magnetometer, as it is often due to the presence of exterior ferrous substances. (source: Vectornav)

The other type of error distorts the shape of the response. 3D matrix algebra is needed to fully address this type of error, but a simpler approach that provides an approximate correction is to just look at the 3 axes. Calculate the average delta between the maximum and minimums for each axis, then compute the scaling factor for each individual axis that scales the delta for that axis to match the average.

Plot showing rotated and offset ellipse

Plot showing both offset bias and non-linear response. The non-linear response is called “soft iron” bias in a magnetometer. (source: Vectornav)

My code then does the same thing for the accelerometer. For that calibration, you want to slowly (so as not to introduce large motion accelerations) position the board so that each of the 6 faces points up while the calibration program is running.

One that is done, the code then writes the magnetometer and accelerometer offsets and scaling factors for each axis (so 12 values in total) to a .ini file so that they can be called and used by the application program that will be making the measurements.

In my initial measurements, I found appreciable bias errors for the magnetometer (on the order of 12%), with much smaller bias errors for the accelerometer (on the order of 1.5%). The scaling factor corrections were  smaller for the magnetometer than the bias (the correction factors were 7.5%, 1%, and 6%). For the accelerometer, I measured 0.03%, 6%, and 5.5%).

The code is published at

A Simple Low-Pass Filter (Concluded)

The last post covered the concept of the Exponentially Weighted Moving Average Filter and illustrated how it worked on a theoretical example, both with and without noise.  To wrap up, I want to include an actual set of data from the Devantech CMPS10 Tilt Compensated Compass on my current robot.  Although, as the name says, it has tilt compensation, I’m using the raw magnetometer output, as the robot is running on flat floors, and rotations and accelerations are liable to introduce more error than the practically non-existent tilt.

In my code, I’ve set α to a fairly high value of 0.33.  Here’s a plot of both the raw and filtered output for a case where the robot stayed fixed, then was manually rotated rather quickly to a new position:

As you can see form the plot, the filtered response, as expected, lags the raw response after the turn.  However, the raw output overshoots (I’m not sure why), so the lag actually results in the filtered output more closely matching reality, even right after the turn.  It’s not possible to see the effects of the filter on the noise from this plot, so this second graph shows just the raw and filtered data before the rotation, with the scale blown up:

The filter’s ability to modulate the noise is clearly evident.  The raw output bounces around +/- about 4.6 degrees, while the filtered output jumps within the much narrower range of +/- 1.5 degrees.

Entering the Well… Duh Zone: Refrigerator Magnet(ism)

My vehicle originally relied solely on odometry for dead reckoning, with rather poor precision for turns, which quickly leads to large errors. So I added an electronic compass for determining the heading. This didn’t improve things as much as I thought, with the vehicle often heading off in what was clearly the wrong direction. I determined that at least once source of the problem was sensor lag. When the vehicle pivoted in place to turn to it’s next heading, it turns fairly quickly, and the compass lags a fair bit. So the vehicle would overshoot the desired heading without realizing it. The technical term for this lag is rate dependent hysteresis.

To address this problem, I added a rough check on the heading, and if in the right ballpark, the vehicle stops, waits a bit for the compass to settle, then redetermines it’s heading. If not pointed in the right direction, it slowly turns by small increments, with pauses in between, until it is on the correct heading. This isn’t fast, but it seemed to work fine in preliminary tests in in my finished basement, which is carpeted. However the tracks didn’t get great traction when moving, while at the same time, had a lot of friction during turns. So, set the waypoints further apart and move to a wood laminate floor in the kitchen where the robot runs great. The result: worse then when using just odometry! The vehicle kept thinking it was veering well off course when it was traveling almost in a straight line, and the bearings were way off reality.

As you might guess from the title of this post, the problem is obvious in retrospect: lots of large metal objects in the kitchen, including the fridge about 18″ from where I was starting the vehicle. This threw the compass off by a fair bit. Once I realized this and retested outside, all worked fine.

Now that the compass is finally working, here are my plans:

  1. 1) Add PID control as the vehicle travels. Right now, it determines a heading, and drives off with equal power to both motors. It just runs open loop until either reaching the waypoint, traveling the calculated distance to the waypoint, or traveling a preset max. distance. At that point it checks its own location versus the waypoint and re-orients. This isn’t bad if running on an even surface, although it still tends to turn a bit. But if one tread slides or has different traction, it heads off in the wrong direction for some time. I want to close the loop with PID control.
  2. 2) Once that works, switch over or add a GPS for heading as an option, just to checkout the GPS unit and programming to interface to it. The main use of the GPS will be for a faster, outdoor autonomous vehicle to be developed after this.