Magnetometer Soft- and Hard-Iron Calibration

I'm using an LSM303DLM on a Pololu MinIMU-9 board as part of an AHRS. The software is ported to mbed from Pololu's example code which is in turn ported from ArduPilot 1.5. But that's no what this is about.

This is a saga of one man... with meager math skills... in a desperate fight to calibrate his compass... it's a timeless tale of truimph and defeat...ahem.

Actually, this is mainly a story about soft-iron calibration.

I'm curious to see how accurate the magnetometer can be if properly calibrated since it'll be a primary source of heading reference for my AHRS due to the very short distances, high speeds, and poor GPS reception in the area of operation. So, rather anal proper calibration seems like the right path.

In attempting to calibrate my compass, plain hard iron (offset) and gain calibration didn't help as much as it has on other sensors in other configurations. I wondered why.

I've captured and saved data from the sensor using Hon Bo Xuan's 3D Scatter Processing script (download

And am using Yury Petrov's Ellipsoid_fit (download) in Octave to attempt to figure out how to calibrate for hard and soft iron offsets. The hard iron offsets are ridiculously easy of course. It's the soft-iron stuff that's frying my feeble mind. :)

Both the ellipsoid fit and a gnuplot rendering of the XY and YZ plots show minimal 'tilt' of the ellipsoid about 2-5 degrees.



The XZ axis is another story. And yes, I've removed nearby ferrous objects to no avail. 


Ellipsoid_fit says the eigenvectors (apparently these are the 3 axes of the ellipsoid) reported are:

Ex = -0.471 0.095 0.877

Ey = -0.865 0.145 -0.481

Ez = -0.173 -0.985 0.014

I'm still working out how to figure out the tilt of the x-z ellipse.

Another script, fit_ellipse by Ohad Gal (download) -- it's a 2d fit -- reports approximately -25 degree tilt (for some reason I had to correct x axis mirrored between the two plots; stretched out to resemble the x-z plot above) I don't think the tilt estimate is accurate.

The tilt estimation at least for this latter script is quite sensitive to axis gain. Hm.


I've been trying to wrap my head around the math involved in soft-iron calibration and found a few particularly good articles (several more that are, I think, difficult). I guess the main point is to have a soft-iron compensation matrix involved. Calculating the matrix eludes me as yet.

Freescale AN4246.pdf

MEMSense Compensating for Tilt, Hard Iron and Soft Iron Effects.pdf

LSM303DLM App Note.pdf

I've also read a paper or two on swinging a compass (as done in the airline industry) and I'm keeping that approach in my back pocket.

I'd happily ignore soft-iron as I've done in the past but even with offset and gain calibration the sensor is pretty clearly inaccurate at various orientations and I have convinced myself that it's due to soft iron distortion.

If you skip ahead to this reply, it talks about the approach used for soft-iron calibration and shows that, yes indeedy, it seems to be working at least on paper.

You need to be a member of diydrones to add comments!

Join diydrones

Email me when people reply –


  • I have also been using Yury Petrov's ellipsoid fit to calibrate my magnetometer for a tilt compensated e-compass (freescale AN4248).

    Even with this compensation for hard and soft iron, I found the heading accuracy to be very unsatisfactory (a heading change of 90 degrees for the magnetometer lying flat results in an output of between 70-110 degrees). I have also ordered the radii in the manner described by previous posters in this thread.

    One test I ran which seems strange was to rotate the device in a circle in increments of 90 degrees  (four times). The resulting heading output would vary, with results sometimes close to 90, and sometimes quite off (~70).

    If I plot the magnetometer x vs y output for my test after compensation, there is little to no skew (major minor axes at 90 deg), but there does seem to be some offset. If I manually remove this, the heading results look good.

    I would be curious to know if anyone had tested heading accuracy after these compensations, and if they managed to achieve superior results?

  • Hi! This really is a great discussion. For me, it has been really useful and informative. 

    Still, I am struggling with something. I am using MPU-9150 and I have used an algorithm quite similar to Yury Petrov's Ellipsoid_fit. Algorithm works perfectly for 7 out of 10 compasses (given a rough estimate), but in other 3 there is an error in heading when changing compass roll. It goes up to 10 degrees of difference each way. 

    1. I've checked different algorithms, which all gave me same result for (hard and soft iron) as mine did. 
    2. I've also tested the tilt-compensation algorithm (I am sure everything works fine in this part of code)
    3. I've analysed behaviour of different "working" and "non-working " compasses and there is nothing special.

    Raw data that I get from compass are very sphere-like even before calibration.

    Has anybody else experienced something like this? Could the reason be, that because of the very sphere-like data, the rotation of "ellipsoid" is really inaccurate and therefore, calibration doesn't work? Although, all of the compasses I've checked have sphere-like raw data, and most of them work perfectly after calibration. 

  • Here is the results of the calibration with using my program.

    Calibrated magnetometer data:


  • See my version of the soft and hard iron magnetometer calibration:

  • Great discussion. I tried and tried to come up with an easy way to find the offsets and W^-1. After much searching I stumbled across this great little program:

    You feed it the strength of the local magnetic field and a text file with raw compass values. It then produces the offsets and W inverse as discussed in the invensense app note. This is a much easier method than installing pyton etc or using matlab since it is a stand alone executable. Hope this helps someone.


  • I have a conceptual problem with magnetometer calibration. Let's assume my magnetometer has both hard-iron and soft-iron problems. After compensating for the hard-iron effects (i.e. after offset has been removed from measurements), I have a rotated ellipsoidal response. I am dealing with a simulation of 3-axis magnetometer, but for simplicity, let's assume the 2D situation. In the picture below, my offset-compensated magnetometer measurement is located at A, i.e. in the end of the major semi-axis of the ellipsoid.


    The problem: is my soft-iron calibration supposed to map measurement A to point B or to point C (or somewhere else)? 



  • i'm also using Yury Petrov's Ellipsoid_fit as my magnetometer calibration procedure.

    i collect 1000 raw values, and then launch the script.

    then i use this formula to get calibrated values, and my magnetometer is calibrated

    xt_raw = x_raw - offsetx;

    yt_raw = y_raw - offsety;

    zt_raw = z_raw - offsetz;

    x_calibrated = scalefactor_x[1] * xt_raw + scalefactor_x[2] * yt_raw + scalefactor_x[3] * zt_raw;

    y_calibrated = scalefactor_y[1] * xt_raw + scalefactor_y[2] * yt_raw + scalefactor_y[3] * zt_raw;

    z_calibrated = scalefactor_z[1] * xt_raw + scalefactor_z[2] * yt_raw + scalefactor_z[3] * zt_raw;

    but my question are:

    1) is this procedure right?

    2) if i install my magnetometer in a device with metal near, can i calibrate again (maybe runtime on atmega) only the scale factor, or should i do a new complete calibration of my device?



  • Thank you for sharing your work.  I have some hardware with extreme magnetic errors and also wrote some code using the linked ellipsoid fit in an attempt to make the magnetometer usable.  I thought it would be worth sharing these results in this thread.

    This first plot shows the uncalibrated data.  The grey line is arbitrary rotations and the red, green and blue lines are rotations on a flat level surface around x, y and z (axis pointing down each time).  You can see that this is an extreme situation, I found the magnetometer data was useless with hard-iron calibration only.
    This next plot shows the measurements calibrated using the ellipsoid method.  The ellipsoid fit has left the magnetometer axes at an incorrect orientation. The plot also shows additional RGB vectors drawn to the centroid of each x, y and z rotation dataset.  These vectors represent a a non-orthogonal rotation matrix would use to correct for this orientation error.
    This final plot shows the calibrated measurements corrected for the orientation error. The x, y and z rotation datasets are now concentric with the principle axes.  The plot looks as if a high level of accuracy has been achieved.  However, I implemented this within the AHRS hardware and found errors of up to 20 degrees.  I wonder if the extreme initial magnetic distortions are too much for this calibration model.

    For reference, the calibration parameters for the above data are:

    softIronMatrix =

        1.2475   -0.0470    0.0429

        0.0309    0.8271   -0.0291

       -0.1009   -0.6458    0.7490

    hardIronVector =




    calibrated  = softIronMatrix * uncalibrated - hardIronVector

  • I have just started working on to calibrate my magnetometer. I used magnetometer for my heading correction using extended kalman filter. I see that the errors are coming to around 20 deg, and I am very unhappy with this large errors. I considered only the offsets/bias during my simulations. To improve my heading accuracy, I am looking info soft iron and scaling effects.

    What is the heading accuracy you have obtained after the corrections? Is your approach working well?

    Thanks & Regards,


  • So what *should* I be seeing?  

    Suppose I create a series of samples that fell exactly on a sphere of radius 1. It simulates a perfect magnetometer with zero noise and magnitude 1. Turns out the eigenvectors exx/exy and eyx eyy are all over the place. exz, eyz and ez are all locked in. As I generate more and more points, the eigenvectors converge to ex=[0 0 1] ey=[0 1 0] ez=[1 0 0]. I need 10,000 or more points before it starts to look close which is utterly impractical for manual calibration. So, guess I cannot  trust the estimated eigenvectors for this situation.

    Next I created a 1000 simulated magnetometer samples with magnitude noise but 0 offset and 0 tilt and ran it through Ellipsoid_fit and got:

    offset = [1.6196e-004 -2.5503e-003 -7.0838e-004]

    radii = [ 1.00560 1.00101 0.99894 ]


    I haven't quantified the relationship between offset/magnitude error and number of points and noise. For now I'll assume that with a few hundred points and typical sensor noise it'll be "close enough".

    I wanted to look at magnitude values versus x, y, and z values respectively. With the perfect sphere I get a line from (-x,1) through (x,1) as one might expect. Same for y vs. magnitude and z vs. magnitude. In a perfect sphere, magnitude is 1. As you might imagine, a noisy magnitude produces a noisy plot, not a clean line.


    I wonder what my real magnetometer's plots will look like? I did offset correction and scaling based on Ellipsoid_fit. I ran into some problems. The offset and radii were incorrect. The error was particularly bad for the z axis.


    Under these conditions the x,y,z vs magnitude plots are a disaster so can't tell what's going on I don't think. More later.

This reply was deleted.


DIY Robocars via Twitter
RT @Jetsonhacks: And I helped with the course! JetBot, JetBot, happy little JetBot! JetBot ... GO! Meet the Jetson Specialists Inspiring a…
17 hours ago
DIY Robocars via Twitter
RT @a1k0n: Designed RPi + camera mounts + new bumper for the new car. Fits great on the car first try, except my longest camera cable doesn…
17 hours ago
gotham liked gotham's profile
DIY Robocars via Twitter
RT @RoboticMasters: Monaco GP Circuit in the Donkey Sim (coming soon). Including buildings, tunnel and all! @diyr…
DIY Robocars via Twitter
RT @breadcentric: Here are the details of #AWSDeepRacer finals: #awsreinvent2020 #AWSreInvent…
DIY Robocars via Twitter
RT @breadcentric: #AWSDeepRacer League #awsreinvent2020 Open race is on Dec 1st - Dec 31st in three categories, 15 DeepRacer Evo (with LIDA…
DIY Robocars via Twitter
RT @a1k0n: @SmallpixelCar @diyrobocars It's just something that's easy to track with chroma keying. I ended up using different colors on th…
DIY Robocars via Twitter
DIY Robocars via Twitter
RT @TinkerGen_: "The Tinkergen MARK ($199) is my new favorite starter robocar. It’s got everything — computer vision, deep learning, sensor…
Nov 23
DIY Robocars via Twitter
Nov 23
DIY Robocars via Twitter
RT @roboton_io: Join our FREE Sumo Competition 🤖🏆 👉 #sumo #robot #edtech #competition #games4ed…
Nov 16
DIY Drones via Twitter
First impressions of Tinkergen MARK robocar
Nov 16
DIY Robocars via Twitter
Our review of the @TinkerGen_ MARK robocar, which is the best on the market right now
Nov 15
DIY Robocars via Twitter
RT @Ingmar_Stapel: I have now explained the OpenBot project in great detail on my blog with 12 articles step by step. I hope you enjoy read…
Nov 15
DIY Robocars via Twitter
RT @DAVGtech: This is a must attend. Click the link, follow link to read the story, sign up. #chaos2020 #digitalconnection #digitalworld ht…
Nov 15
DIY Robocars via Twitter
RT @a1k0n: Got a new chassis for outdoor races (hobbyking Quantum Vandal) but I totally didn't expect that it might cause problems for my g…
Nov 11