# Magnetometer Calibration

Calibrating magnetometers is critically important. Without a good mag cal, you’ll get rubbish out of your magnetometers. Fortunately it isn’t that hard to do a basic calibration.

If you were to create a scatter plot of your magnetometer readings, it would probably show both hard iron and soft iron errors. An ideal plot is circular and is centred on the chart. Hard iron errors cause the measurements to be off centre. Soft iron errors cause the shape to be elliptical rather than circular.

To remove these errors, you need to find the minimum and maximum points for each axis of the sensor. This means gathering data. Lots of data. So get your magnetometer and move it around until you’ve got a few thousand samples in as many different orientations as possible.

Hard iron errors are the simplest to remove. Find the maximum and minimum measurements for each axis and average them. This gives the amount of offset for each axis. Every time you take a reading from your magnetometers, you should subtract this offset from each axis.

1 2 3 |
raw_values[i].x() -= (min_x + max_x)/2.0; raw_values[i].y() -= (min_y + max_y)/2.0; raw_values[i].z() -= (min_z + max_z)/2.0; |

Soft iron errors are slightly harder to remove. There are some more advanced techniques that involve calculating rotation matrices and bla bla, but it’s computationally expensive (for my brain) and simply scaling each axis to remove the elliptical shape works quite well too.

First the hard iron errors are removed from the maximums and minimum magnetometer vectors. These minimum and maximum vectors are the same as the ones being used to correct for hard iron errors.

1 2 3 4 5 6 7 8 9 |
imu::Vector<3> vmax; vmax.x() = max_x - ((min_x + max_x)/2.0); vmax.y() = max_y - ((min_y + max_y)/2.0); vmax.z() = max_z - ((min_z + max_z)/2.0); imu::Vector<3> vmin; vmin.x() = min_x - ((min_x + max_x)/2.0); vmin.y() = min_y - ((min_y + max_y)/2.0); vmin.z() = min_z - ((min_z + max_z)/2.0); |

The average distance from the centre is now calculated. We want to know how far from the centre, so the negative values are inverted.

1 2 3 |
imu::Vector<3> avgs; avgs = vmax + (vmin*-1); //multiply by -1 to make negative values positive avgs = avgs / 2.0; |

The components are now averaged out

1 2 |
float avg_rad = avgs.x() + avgs.y() + avgs.z(); avg_rad /= 3.0; |

Finally calculate the scale factor by dividing average radius by average value for that axis.

1 2 3 |
float x_scale = (avg_rad/avgs.x()); float y_scale = (avg_rad/avgs.y()); float z_scale = (avg_rad/avgs.z()); |

With these scale values we can correct for soft iron errors by multiplying them with the relevant magnetometer axis reading. Here’s an example

1 2 3 4 5 6 7 8 9 |
imu::Vector<3> mag_reading = magnetometer.read(); mag_reading.x() -= (min_x + max_x)/2.0; mag_reading.y() -= (min_y + max_y)/2.0; mag_reading.z() -= (min_z + max_z)/2.0; mag_reading.x() *= x_scale; mag_reading.y() *= y_scale; mag_reading.z() *= z_scale; |

It doesn’t use any matrix maths and the magnetometers are now quite accurate. In fact, using a map I can’t see any errors. Without any proper test equipment, I’d guess that it’s good to +/- 2 degrees.

Pingback: IMU Maths – How To Calculate Orientation pt2 - CamelSoftware

Hi Sam,

I’d like to discuss this code with you, can you email me directly?

Kind regards,

Liz