-
Notifications
You must be signed in to change notification settings - Fork 190
Simple and Effective Magnetometer Calibration
Magnetometers are wonderful devices and absolutely essential for correcting gyro drift in applications that require absolute orientation through sensor fusion. The bane of magnetometer usage is, however, their non-ideal response surfaces. The ideal response surface for a three-axis magnetometer is a sphere centered at the 3 D origin. This means the response to an external magnetic field of, let's say 400 mGauss in the z-direction would be exactly Mz = 400 mG when the magnetometers z-axis was normal to the floor, My = 400 mG when the magnetometer's y-axis was normal to the floor, and Mx = 400 mG when the magnetometer's x-axis was normal to the floor. More simply, the ideal response surface no matter the orientation of the magnetometer is a sphere with radius 400 mG centered on the origin. In practice, MEMS magnetometers are rarely so well calibrated when you receive them. Here is a measure of a typical response from an uncalibrated AK8963C magnetometer embedded in the popular MPU9250 9 DoF motion sensor.
Magnetic field in mG along various 2D slices measured for an uncalibrated MPU9250. Note the clumps of data representing the field values when the magnetometer was at rest. These should measure, Mx ~ 200 mG, My ~ 50 mG, and Mz ~420 mG in my area of California.
This data was taken by spewing properly-scaled (mG) Mx, My, and Mz values to my Serial monitor at 1 Hz while I was slowly turning the sensor board with the MPU9250 and Teensy 3.1 microcontroller through a variety of figure-eight patterns. I am plotting slices through 3D space to illustrate the facts that 1) the response between axes is not centered at the origin and 2) the response sensitivity is different along each axis. These are often referred to as hard iron and soft iron errors or biases. There is an additional common type of magnetometer bias often encountered which is due to the presence of man-made sources of magnetic field like big steel buildings and current carrying wires, etc. Encounters with these environmental sources of magnetic field can be interpreted as changes in orientation if a magnetic anomaly algorithm is not employed to detect and prevent it. This is beyond the scope of this note but we will be taking a look at this later.
Hard iron biases are typically the largest and the easiest errors to correct for. The simplest way to correct for them is to record a bunch of magnetometer data as the sensor is moved slowly in a figure eight pattern and keep track of the minimum and maximum field measured in each of the six principal directions; +/- Mx, +/- My, +/- Mz. Once the min/max values along the three axes is known, the average can be subtracted from the subsequent data which amounts to re-centering the response surface on the origin. here is what the code to do this looks like:
`
void magcalMPU9250(float * dest1, float * dest2) { uint16_t ii = 0, sample_count = 0; int32_t mag_bias[3] = {0, 0, 0}, mag_scale[3] = {0, 0, 0}; int16_t mag_max[3] = {0xF000, 0xF000, 0xF000}, mag_min[3] = {0x7FFF, 0x7FFF, 0x7FFF}, mag_temp[3] = {0, 0, 0};
Serial.println("Mag Calibration: Wave device in a figure eight until done!"); delay(4000);
sample_count = 128;
for(ii = 0; ii < sample_count; ii++) {
MPU9250readMagData(mag_temp); // Read the mag data
for (int jj = 0; jj < 3; jj++) {
if(mag_temp[jj] > mag_max[jj]) mag_max[jj] = mag_temp[jj];
if(mag_temp[jj] < mag_min[jj]) mag_min[jj] = mag_temp[jj];
}
delay(135); // at 8 Hz ODR, new mag data is available every 125 ms
}
// Serial.println("mag x min/max:"); Serial.println(mag_max[0]); Serial.println(mag_min[0]); // Serial.println("mag y min/max:"); Serial.println(mag_max[1]); Serial.println(mag_min[1]); // Serial.println("mag z min/max:"); Serial.println(mag_max[2]); Serial.println(mag_min[2]);
// Get hard iron correction
mag_bias[0] = (mag_max[0] + mag_min[0])/2; // get average x mag bias in counts
mag_bias[1] = (mag_max[1] + mag_min[1])/2; // get average y mag bias in counts
mag_bias[2] = (mag_max[2] + mag_min[2])/2; // get average z mag bias in counts
dest1[0] = (float) mag_bias[0]*MPU9250mRes*MPU9250magCalibration[0]; // save mag biases in G for main program
dest1[1] = (float) mag_bias[1]*MPU9250mRes*MPU9250magCalibration[1];
dest1[2] = (float) mag_bias[2]*MPU9250mRes*MPU9250magCalibration[2];
// Get soft iron correction estimate
mag_scale[0] = (mag_max[0] - mag_min[0])/2; // get average x axis max chord length in counts
mag_scale[1] = (mag_max[1] - mag_min[1])/2; // get average y axis max chord length in counts
mag_scale[2] = (mag_max[2] - mag_min[2])/2; // get average z axis max chord length in counts
float avg_rad = mag_scale[0] + mag_scale[1] + mag_scale[2];
avg_rad /= 3.0;
dest2[0] = avg_rad/((float)mag_scale[0]);
dest2[1] = avg_rad/((float)mag_scale[1]);
dest2[2] = avg_rad/((float)mag_scale[2]);
Serial.println("Mag Calibration done!"); }
`