Introduction: Automatic Magnetometer Calibration

There are several types of cheap magnetometer now available, which you can use to sense a compass direction, but all of them (as far as I know) need calibration before you can get sensible results.

I describe a manual method for doing this in another Instructable, but in this one I'm going to show you an automatic method that you can build into your project, removing the need to customise your sketch with fixed calibration data and enabling you to recalibrate the magnetometer at any time.

For reliable navigation there are around 4 corrections you'd need to make to the raw magnetometer output to get accurate results, but for many hobbyist purposes the so called "hard iron" correction is all you need to get a sensible output, this normally being the dominant error. Without the other corrections your magnetometer may show magnetic North a little East of the true direction when you're facing one way and a little West facing another. Also, if you try using it to make a 90 degree turn, you might find you've turned by something a little more or a little less. This may not matter even for a drone as you could only use the magnetometer for determining a rough heading, given that the wind may be blowing you off course, but you could still use it to maintain a given heading. Note that it could be dangerous to rely on the calibration methods I describe for navigation at sea, or anywhere else where navigation errors could have serious consequences.

The manual method relies on taking magnetometer readings with the magnetometer in 4 different positions, turning it through 180 degrees, then upside down, then turning it again through 180 degrees. By contrast the automatic method relies on the accelerometer to detect 4 positions all at 45 degrees to the horizontal and using a Neopixel ring to guide you, then calculates the offsets and stores them in non-voltile memory. The calibration function is invoked if there are no offsets stored, and can be recalled at any time simply by sharply twisting the device back and forth several times.

Step 1: What You Need

You may have an existing Arduino project which uses the MPU-9150, in which case you can probably load my sketch into that. The Neopixel ring only uses one Arduino output pin.

Alternatively you can use the setup described here. You can extend it with additional functions and if you like, using additional hardware for a variety of Neopixel ring-based projects.

You will need:

  1. ATMega328 - based Arduino and power supply (though you could probably use others)
  2. MPU-9150 Accelerometer/Gyro/Magnetometer
  3. Adafruit Neopixel ring, the 12 pixel size or any larger
  4. Solderless breadboard or stripboard and jumper wires.

The MPU-9150 inputs are not 5V tolerant, so running your Arduino at 5V you will need a 5V/3.3V level translator with 2 or more channels. These are very cheap from Far Eastern eBay sellers. Since Neopixels require the full 5V you can't simply run the whole project at 3.3V.

The level translator takes the SDA and SCL pins from the Arduino at 5V levels and presents them at 3.3V to the MPU-9150. To do so, it requires both a 5V supply on one side and a 3.3V supply on the other. Fortunately the common MPU-9150 boards contain a 3.3V regulator, but unfortunately its output is not presented at any of the pins. It is therefore necessary to solder a fine wire to the output pin on the regulator as shown in the photo.

Locate the regulator, which should be marked KB33 (at the top of the second picture). You will need a steady hand and a soldering iron with a fine pointed tip. Solder a piece of fine wire to the top right hand pin. Once you have made a good connection, cover it with a blob of superglue to stop it being easily pulled off.

Step 2: Wiring It Up

The wiring will depend on whether you use stripboard or a solderless breadboard, which sort of Arduino, and whether you use the level translator, and so I'll leave you to work out the wiring or the stripboard layout. (If you don't feel able to do that, go and try one or two simple Arduino projects, or projects using stripboard, and come back when you've got the idea.)

The level converter has two power inputs labelled HV and LV, in addition to Gnd. HV is connected to the 5V supply and LV to the 3.3V output of the MPU-9150 using the thin wire you soldered to it. The SDA and SCL pins from the Arduino use 5V logic levels and are connected to the HV1 and HV2 pins on the level converter. The LV1 and LV2 pins then present the same signals to the MPU-9150 at 3.3V logic levels. Your device may also have HV3, HV4, LV3 and LV4 connections, which are not used.

Step 3: Programming Your Arduino

The sketch comprises three files (attached). Copy all three into a new folder called "Compass" in your Arduino sketches folder. The sketch should then appear in your sketchbook in the Arduino IDE.

Before compiling, check the #define configuration options at the top of the file under the Compass tab and modify as required.

  • PIN defines the Arduino pin used for the Neopixel data input, by default, pin 8.
  • BRILL is the default pixel brilliance, which can be anything up to 255, but higher values are blinding and may take more current than your power supply can provide, especially with one of the larger sizes of ring. I never normally go above 30.
  • PIXELS is the number of pixels in the ring. Sizes currently available from Adafruit are 12 (which works very well), 16, 24 (a really nice size) and 60 (huge - great if you've got the cash).
  • REVERSED: leave this commented out if your ring is on the same side of the board as the MPU-9150. The first pixel should be in the positive accelerometer Y direction with respect to MPU-9150. Uncomment it of your ring is on the opposite side of the board (having flipped the board about the Y axis).

You will need to download and install the MPU6050 library (this also contains code for the MPU-9150, a superset of the MPU6050) and the Adafruit Neopixel library. Compile the sketch and upload to your Arduino.

Step 4: The Automatic Calibration Procedure

The first time you run the sketch it starts by entering the calibration procedure. Hold the completed device a few inches above the desk, keeping one edge of the board aligned with the edge of the desk, or any other fixed straight edge.

The Neopixel ring will show some green and some blue pixels. Level the board until they are all green. Remaining blue pixels indicate the side that is too high (the sky is blue). When it's satisfied that it's horizontal it will take several magnetometer readings then flash all pixels once.

It will now ask you to tip it to 45 degrees in each of 4 directions, indicating which way to tip it with green and blue pixels as before, taking measurements at each position and flashing all pixels 2, 3, 4 and finally 5 times. Take care always to keep the edge of the board aligned with the edge of the desk.

The whole procedure will be repeated 3 times, after which the magnetometer offsets will be calculated and stored.

Once calibrated, the sketch will simulate a compass. Level the board by eliminating blue pixels in order to get a reliable reading. A red dot is superimposed, indicating North. The dot fades from one pixel as it moves to the next.

You can cause it to re-enter calibration mode whenever you like, simply by twisting the board sharply one way then the other several times.

Step 5: Using the Sketch in Your Own Project

You can incorporate the calibration function in your own project either by merging my code into yours or adding your code to mine, depending on how how far you've already got with yours. Mine contains hooks to allow you to add additional functions.

If you merge mine into yours, take the Calibrate and Functions tabs complete. From the Compass tab, you will need to merge my Setup() function with yours and merge everything from before it into your code. You simply need to call the Calibrate() function in order to enter calibration mode.

The Calibrate tab includes two #define statements. NUMSAMPLES defines how many magnetometer readings are taken at each position. There's probably no particular reason to change it from 10. Such a number ensures that it is being held steady throughout. NUMTESTS defines how many times the complete procedure of 5 positions is to be repeated. A good number is 3 but for speed and if you just want a rough calibration, 1 might be sufficient.

Step 6: Using Different Hardware

The same technique can be used with a different magnetometer, and it isn't limited to an Arduino, but you'll have to adapt it and possibly translate it from C to another language.

If adapting the code to a different magnetometer, be aware that the MPU-9150 uses different coordinate systems for the magnetometer and accelerometer. Swapping from one to the other requires that you swap the X and Y values and negate Z. This is done in the readMag() function so that the rest of the code can work exclusively in the accelerometer's coordinate system. The offsets, once calculated, are converted back to magnetometer coordinates for storage.

Using a different magnetometer, in principle you should just need to replace the readMag() function.

Three arrays, mx[], my[] and mz[] each of 5 elements, hold the magnetometer readings at the 5 positions, namely horizontal, rotated by 45 degrees one way then the other about the X axis, and similarly about Y. At each position (except the first) the accelerometer outputs are rotated so that the displayCal() function just has to calculate the variables "angle" and "up" as the angle from the desired orientation and the direction (as a floating point pixel number) of the greatest slope. The magnitude of the error is converted to a logarithmic scale to give greatest sensitivity when the error is small. The displayCal() function returns a Boolean value of true when the error is within limits, and once it has done so a set number of times in a row Calibrate() stores the magnetometer readings.

The actual calculation of the offsets took several pages of algebra and trigonometry to derive but are embodied in just 4 lines of code. You may recognise the magic number 0.7071 as the half the square root of 2, or cos(45⁰). Subtract this from 1 and you get the second magic number 0.29289 and double it to get the 3rd 0.58579. Not magic after all.

There's nothing special about the 45 degree tilt that's used in the calibration but it does simplify the maths considerably. In principle you could roll the device around the vertical, continuously taking readings from the accelerometer and magnetometer and calculating the offsets until the average values calculated settled around a reliable result, but the maths would be much more complicated. The iOS Compass app does this, which is where I got the idea for automatic calibration.