Tilt Compensated Compass

52K7169

Intro: Tilt Compensated Compass

a.articles { font-size: 110.0%; font-weight: bold; font-style: italic; text-decoration: none; background-color: red; } a.articles:hover { background-color: black; }

This instructable explains how to make a tilt compensated compass using an Arduino UNO R3, an LCD display, and an IvenSense MPU-9250 multi-chip-module that contains an MPU-6050 accelerometer / gyro and an AK8963 magnetometer within the same package.

The LCD simultaneously displays the Heading, (P)itch, and (R)oll.

The heading accuracy is within 2 degrees depending on how well the compass has been calibrated.

Without tilt compensation the compass headings vary significantly ... sometimes by as much as 100 degrees.

When stabilised, the tilted compass headings only vary by one or two degrees ... the improvement is amazing.

The tilt stabilization may be disabled by placing a jumper wire between Arduino pins A0 and GND.

The estimated cost for this project is $20 USD.

Images

The opening photo shows the tilt compensated compass resting on a 30-60 degree set square. The pitch angle is approximately 30 degrees, the roll angle is zero, and the heading is 100 degrees magnetic.

The video shows the compass both “with” and “without” tilt compensation.

Warning

Do not use this compass in situations involving safety to life, such as navigation at sea, as violent shaking (rapid movement) can affect the gyro accuracy requiring a system reset.

STEP 1: Circuit Diagram

Photo 1 shows how the Arduino UNO R3, the LCD display, and the MPU-9250 accelerometer/gyro/magnetometer are wired together.

The tilt stabilization may be disabled by placing a jumper wire between Arduino pins A0 and GND.

The MPU-9250 must:

  • be flat when the compass is placed on a level surface.
  • be clear of any ferrous metals.

STEP 2: Parts List

The following parts where obtained from https://www.aliexpress.com/

  • 1 only Arduino UNO R3 and USB cable
  • 1 only serial LCD display
  • 1 only MPU-9250 accelerometer/gyro/magnetometer
  • 2 only Arduino female-to-female jumper cables
  • 6 only Arduino male-to-female jumper cables

The following parts were obtained locally:

  • 9 volt battery
  • 9 volt battery clip/leads
  • Scrap plastic sheet for base
  • 12 only threaded nylon spacers
  • 20 only M3 x 5mm bolts

The estimated cost for this project is $20 USD.

STEP 3: Theory

Tilt compensated compass

A conceptual view of a “tilt compensated compass” is shown in photo 1.

The InvenSense MPU-9250 chip contains two separate integrated circuits within the same package:

  • an MPU-6050 gyro / accelerometer and
  • an AK8963 magnetometer from Asahi/Kasei

Calculating pitch and roll

Assume that:

  • We are in an aircraft that is heading north (up the page) along the gyro X axis.
  • The Z (yaw) axis is pointing down, and
  • the X (pitch) and Y (roll) axes are both horizontal
  • The pitch and roll readings are both zero

Let’s now raise the aircraft nose by 45 degrees:

  • The roll reading remains at zero since there is no rotation about the roll axis.
  • The pitch reading becomes 45 degrees due to the rotation about the pitch axis.
  • The yaw axis is now tilted by 45 degrees.

Let’s now yaw the aircraft by 90 degrees:

  • The Yaw axis remains tilted .
  • The pitch should now read zero as the aircraft body is now horizontal.
  • The roll should now read 45 degrees as the wings are now tilted.

Since there was no rotation about either the pitch or roll axes, the pitch angle still reads 45 degrees, and the roll angle is still reads zero ... we have a problem!

Enter the accelerometer which is able to measure acceleration about each of the XYZ axes. As the aircraft yaws the accelerometer allows us to gradually transfer the pitch reading to the roll reading, and vice versa.

The Accelerometer is also able to correct for gyro drift ... our pitch and roll readings are now correct!!

The following two Youtube videos explain how this is done:

Determining the compass heading

The X and Y outputs from the AK8963 magnetometer allow us to determine the compass “heading” using the formula:

  • Heading = atan2(Y, X) * RAD_TO_DEG;............................................................................. (1)

This “heading” is only valid while the compass is level ... any tilt and the reading will change dramatically. This variation can be greater than 100 degrees !!!!

The solution is to fool the compass into thinking that it is always horizontal.

We do this by plugging the “pitch” and “roll” values from the MPU-6050 into equations (2) and (3) and using these new (compensated) values in equation (1):

  • Xhorizontal = X*cos(pitch) + Y*sin(roll)*sin(pitch) – Z*cos(roll)*sin(pitch) ............................(2)
  • Yhorizontal = Y*cos(roll) + Z*sin(roll) .................................................................................. (3)

The above formulas are found in several of the reference papers below.

When applying these formulas, the orientation of the MPU-6050 and AK8963 XYZ axes within the MPU-9250 package must be taken into consideration.

From the magnetometers point of view:

  • X equates to the gyro Y
  • Y equates to the gyro X
  • and, since the Z axes point in opposite directions, the pitch is negative.

This is fixed by preceding the above equations with the following code:

  • Mag_pitch = -Gyro_roll_output * DEG_TO_RAD; ............................................................... (4)
  • Mag_roll = Gyro_pitch_output * DEG_TO_RAD; ................................................................ (5)

Theoretically, any variations in the compass heading due to tilt will disappear. In practice there is still a small variation of one or two degrees as shown in photo 2.

Inspiration

The “pitch” and “roll” code for this compass is modeled on the MPU-6050 IMU (Inertial Management Unit) described by Joop Brokking, http://www.brokking.net/imu.html.

The algorithm for calibrating the magnetometer is described in the following article by Kris Winer, https://github.com/kriswiner/MPU6050/wiki/Simple-a...

The remaining code is my own.

References

STEP 4: Software Installation

It is essential that you perform this step BEFORE uploading the compass code to your Arduino

Editing your I2C Wire Library.

According to the breakout board schematic (photo 1), the MPU-9250 chip has 10K ohm pull-up resistors connected to 3.3 volts on each of the SDA (data) and SCL (clock) lines.

The Arduino, however, has internal pull-up resistors to 5 volts. These pull-up resistors are not required and should be disabled to prevent the I2C lines from rising above 3.3 volts and damaging the MPU-9250.

I recommend editing lines 75 ,76, & 77 in file “C:Users\...\Documents\Arduino\libraries\Wire\utility\twi.c” to read:

  • // deactivate internal pullups for twi.
  • digitalWrite(SDA, 0);
  • digitalWrite(SCL, 0);

These commands could be placed inside the Arduino setup() function following the Wire.begin() function but it is still possible for the I2C lines to rise above their safe voltage level until the code lines are run.

Use a text editor such as Notepad++ when editing any files. Do NOT use a word processor.

Installing the compass code

  • Disconnect the I2C lines from your Arduino [1]
  • Edit your I2C Wire library as described above.
  • Copy the contents of the attached “"tilt_comp_compass.ino"” file into an Arduino sketch.
  • Save the Arduino sketch as “"tilt_comp_compass” (without the quotes).
  • Compile and upload the sketch to your Arduino.
  • Unplug the Arduino.
  • Connect the Arduino I2C lines.
  • Apply power to your project.

Notes

[1]

The reason for disconnecting the I2C SDA (data), and SCL ( lines is that Arduino pins A4 & A5 may be in an output “high” state from a previous project. Disconnecting these wires eliminates the possibility of 5 volts damaging the MPU-9250.

[2]

16 September 2019

The code "tilt_comp_compass_v1.01.ino" contains a minor bug-fix.

STEP 5: Calibrating

Depending on their orientation with respect to the Earth’s magnetic field, the XYZ outputs from the magnetometer change from +ve to -ve (positive to negative) as the magnetometer is rotated.

If you rotate the MPU-9250 about each axis, the XYZ outputs should each plot a perfect circle centered about the 3D XYZ coordinate (0,0,0).

Hard-iron distortion

In practice these circles are NOT centered over the 3D coordinate (0,0,0) but are displaced either up or down, or to the left or right.

These displacements are due to “Hard-iron” distortion from, say, a magnetised object such as a speaker. Such distortions are always additive and the offsets can be calculated (then subtracted) using the following code: [1]

  • Mag_x_offset = (mag_x_max + mag_x_min) / 2;
  • Mag_y_offset = (mag_y_max + mag_y_min) / 2;
  • Mag_z_offset = (mag_z_max + mag_z_min) / 2;

Soft-iron distortion

There is also another form of distortion called “Soft-iron” distortion” that arises from the proximity of ferrous, and other materials, that disturb the earth’s magnetic field.

The effect of “soft-iron” distortion is to turn the ideal circles into ellipses which has the effect of altering the compass heading.

The solution to this problem is to scale the X and Y readings in such a way as to form perfect circles. This is achieved using the following code: [1]

  • chord_x = ((float)(mag_x_max - mag_x_min)) / 2;
  • chord_y = ((float)(mag_y_max - mag_y_min)) / 2;
  • chord_z = ((float)(mag_z_max - mag_z_min)) / 2;
  • chord_average = (chord_x + chord_y + chord_z) / 3;
  • Mag_x_scale = chord_average / chord_x;
  • Mag_y_scale = chord_average / chord_y;
  • Mag_z_scale = chord_average / chord_z;

The “calibrate_magnetometer()” function does this for you and MUST be run before you can use the compass. Theoretically this function is not required again unless you change your location.

Instructions for doing this are given in the “tilt_comp_compass.ino” code header.

Simply change the line “bool Record_data = false;” to read “bool Record_data = true;”, upload “tilt_comp_compass.ino” to your Arduino, then tumble the compass in all directions until a set of data values appear on your screen. This process will take about one minute.

Copy the screen values into the matching header positions, set “bool Record_data = false;”, upload “tilt_comp_compass.ino” once more and you are ready to go.

Calibrating the gyro

The gyro calibration is automatic and MUST be done each time you power-up the compass. [2]

Place the compass on a level surface and apply power ... a progress bar will appear on the LCD display after which the compass display will appear.

The compass display shows the:

  • Heading
  • Pitch
  • Roll

The pitch and roll headings should both indicate 000.0 +/- 0.1 when the calibration process is complete. If not then edit the following code lines in the main loop() until the residual pitch and roll readings are zero:

    • Accel_pitch -= -0.2f;
    • Accel_roll -= 1.1f;

    True (Geographic) North

    By default the compass indicates Magnetic North.

    The heading can be changed to True North by uncommenting the following code line that appears in the main loop():

    • // Heading += Declination;

    You will also have to replace the “Declination” value in the Arduino “header” with that of your own location.

    References

    [1]

    "Simple and Effective Magnetometer Calibration", Kris Winer, https://github.com/kriswiner/MPU6050/wiki/Simple-...

    [2]

    The gyro calibration assumes that a yaw angle of exactly 360 degrees is obtained when the MPU-9250 is rotated one full revolution.

    Assuming that the crystal (xtal) frequency in your Arduino UNO is exactly 16 MHz, then a main loop() time of 8000 us (microseconds) is required. Unfortunately my Arduino crystal frequency is off by about 5%. For this reason I have set my loop time to 8400 uS.

    Instructions for adjusting your gyro sample (loop) time are found at the end of the main “loop() function.

    The following (temporary) code will display the yaw reading on your Serial Monitor:

    • Serial.println(Mag_z);

    STEP 6: Summary

    The tilt compensated compass:

    • Uses a single MPU-9250 chip that contains two integrated circuits within the same package.
    • The tilt and roll is determined by the internal MPU-6050 gyro / accelerometer chip.
    • The internal AK8963 magnetometer chip provides X,Y outputs from which the compass heading may be calculated.
    • The compass heading is only accurate if the magnetometer X,Y outputs are taken when the compass is level.
    • The pitch and roll values from the MPU-6050 gyro / accelerometer are used to cancel the variations in the magnetometer X,Y outputs due to tilt.
    • Once calibrated the resulting compass headings are accurate to within 2 degrees.
    • Tilt compensation can be disabled using a jumper between Arduino pin A0 and GND.
    • The Arduino I2C pull-up resistors should be disabled in the "Wire" library.
    • The Compass wiring should be connected after the "tilt_comp_compass.ino" code has been uploaded.
    • Violent shaking (rapid movement) can affect the gyro accuracy requiring a system reset.
    • Accordingly, do not use this compass in situations involving safety to life, such as navigation at sea.

      Click here   to view my other instructables.

    48 Comments

    If you are worried about the 3v3 / 5v conflict might I suggest you look at the GY-91 IMU board, same IMU chip 9250/9255 plus a barometer, which you may or may not use, its shorter by 5mm, but lacks an interrupt pin.
    see https://www.hotmcu.com/gy91-10dof-mpu9250-and-bmp280-multisensor-module-p-341.html
    and https://www.haoyuelectronics.com/Attachment/GY-91/GY-91_SCH.jpg .. here the schematic shows a 5v/3v3 level shifter on SDA and SCL lines, which the board you are using doesn't have.
    They are cheap on https://www.aliexpress.com/item/32654511799.html nz$6.50 / us$3.60
    I have been using a couple of EDTrackers for a few years now, they use the same IMU PCB as yours and a 5v Arduino Pro Micro Leonardo
    see https://www.youtube.com/watch?v=5XD2XMuHfLs&t=165s
    In their code
    they have a definition
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
    and the following in the .. void setup()
    // join I2C bus (I2Cdev library doesn't do this automatically)
    Wire.begin();
    TWBR = 12;//12; // 12 400kHz I2C clock (200kHz if CPU is 8MHz)
    // Disable internal I2C pull-ups
    cbi(PORTD, 0);
    cbi(PORTD, 1);
    see https://raw.githubusercontent.com/brumster/EDTracker2/master/EDTracker2_9250/EDTracker2_9250/EDTracker2_9250.ino
    or https://github.com/brumster/EDTracker2/blob/master/EDTracker2_9250/EDTracker2_9250/EDTracker2_9250.ino
    thanks for sharing your work, an hi from Auckland, NZ
    https://www.youtube.com/antdavisonNZ
    Thanks for this info :)

    Ouch ... that's a shame.
    Thanks for sharing this info.
    I re-purchased another 5 GY-91s which were genuine mp9250s (WHOAMI 0x71) with correctly working barometers, the previous purchase detailed above only 2 of the 5 BMP280s functioned correctly as well as having fake IMUs (WHOAMI 0x70)/
    The genuine ones are at
    https://www.aliexpress.com/item/32591987418.html
    so first of 5 headmice built and functioning as expected
    Thank you for your update.
    Your link to genuine MPU9250's is appreciated :)
    Thanks for the inspiration! I'm working to use this project as a basis for a 'wearable' compass. Ultimately hoping to build this as a small, light, low power/battery power unit in a baseball cap. I'd like to have 5, 7 or 9 tiny LEDs at the outer edge of the cap bill so I can face a certain direction and press a button to 'lock on' to that heading and illuminate the center LED. As I walk/run, the LED would shift right or left to indicate the direction I need to go to maintain that 'locked' heading. Ideally, this will be a 'heads up' / hands free way to follow a heading without having to physically hold a compass and wait for the needle to stop bobbing around.

    Guess I should try to build it 'as shown' first, but ultimately, I'd like to get it running with an arduino mini, a short neo LED strip and runtime of ~7 hours on a relatively small battery.
    Thank you for your interest in my instructable :)

    I've written two compass instructables. You may wish to consider the following version https://www.instructables.com/Quaternion-Compass/ which is more stable and less suscepible to vibration.

    Both versions use the same parts.

    Good luck with your project.
    Trying to get the tilt compass to work. Pitch and roll display ok but heading is always 0. When i do a I2C scan it comes back with 0x3f for the LCD,,0x68 for the 9250 and 0x76 which I am not sure what it is pointing to. also see 0x6a master enable and 0x37 interface bypass. Is there good way to test teh 9250 and confirm it contains a AK8963?
    You can check that the MPU9250 contains a working AK8963 by downloading and installing "quarternion_compass_new_v8.ino" from Step 4 of my instructable https://www.instructables.com/Quaternion-Compass/

    This software reports whether it has found all chips within the MPU9250 ... no hardware changes are required ... the circuit for both compasses is the same.

    The quarternion compass has greater long-term stability than the tilt stabilised compass.

    Check also the comments section for both compasses as these contain solutions to problems that others have experienced.



    Any reason why I can't use a Nano? It will make a much smaller package for me.
    Thanks
    Hi there!
    I am trying unsuccessfully to run it on a nano....
    Works perfectly on the Uno, but no luck at all on the Nano!
    So I would appreciate help with the required code mod and the wiring!
    Cheers! and thanks for sharing such a great project!
    Without checking through my code I can't see why not.
    Check for voltage incompatibilities ... in particular my comments in Step 4 regarding the SDA and SCL lines and modifiying the wire library.
    My instructable https://www.instructables.com/Quaternion-Compass/ may also be of interest ... both compasses uses the same hardware configuration. The Quaternion compass has better long term stability.
    Thanks for taking the time to post all your hard work! I took your example and tweaked it a bit. Made a button to place in calibration mode on startup and stored/retrieve the calibration data in EEPROM. You also have a quaternion compass project here: https://www.instructables.com/id/Quaternion-Compass/ Which is more stable or more accurate between the two?
    Thank you for your interest in my project :)

    The operating principle for each compass is different.
    Both compasses have similar accuracy.
    The quaternion compass has best long term stability.

    The circuit for both compasses is the same which means the software for one will work on the other.
    Hi, i get confused Data on all. Heading is verry bad. Roll and pitch are working not 100% For example I roll to 90, thats fine. Roll to -90 get 80 and 0 is than 10. When I screw the heading a little Roll goes back to 0. My Hardware is a uno (smd cpu) Latest Arduino (1.8.13) and the MPU Modul looks 100% like that shown here. Where can I start to search the mistake?
    Thank you for your interest in this project :)

    The symptoms that you are experiencing are likely to arise if your sensor is not properly calibrated.

    Calibrating a sensor is not an easy task .

    If you think of your sensor as a tennis-ball then each surface-point on the tennis ball must be pointed towards a fixed distant object for perfect calibration ... miss a few points and calibration will be out. The action of rotating your compass in all directions means that each of the compass XYZ axes will at some stage be aligned with the Earth's magnetic field at your particular location.

    Since writing this article I have written another compass https://www.instructables.com/id/Quaternion-Compa... that has better long-term stability. The good news is that the code is 100% compatible with you existing hardware ... no hardware changes are required.

    The calibration routines described in https://www.instructables.com/id/Quaternion-Compa... may be used to calibrate your Tilt Stabilised Compass should you wish

    Hopefully this will solve your problem.
    I tried out the Quaternion Compass and it works good. Problem is that when I move from 0 to 180 the compass shows 170, 90 shows 75 and 270 shows 255. Is that a Problem of calibration? Pitch and Roll is perfect.

    U used the libs from Adafruit. The original sample from Adafruit doesn't works like Your Tilt Compensated Compass. What did You change in code to original Adafruit?

    Thanks a lot for Your help.




    The code changes that I made are explained in detail in Step 3 of my instructable https://www.instructables.com/id/Quaternion-Compa... which says:

    " The problem disappeared when I modified the Sparkfun MahonyQuaternionUpdate() function to read:
    MahonyQuaternionUpdate( myIMU.ax, -myIMU.ay, myIMU.az,
    myIMU.gx * DEG_TO_RAD, -myIMU.gy * DEG_TO_RAD, myIMU.gz * DEG_TO_RAD,
    myIMU.my, -myIMU.mx, -myIMU.mz,
    myIMU.deltat); "

    As for your headings, there are a number of things that could be causing the errors:
    - calibration
    - sensor orientation
    - proximity of your sensor to something
    - magnetic radiation from say a TV.
    so now I made Your Quaternion Code running on VS Code with PlattformIO and a ESP32 Board without Problems. (I'm sorry, but I don't like the Arduino IDE.;-) )
    I printed a Cube to place the sensor and calibrated twice. That s works fine now. Now I have to put Your code in a separate file to integrate it to my application. Thanks a lot for Your help !!!
    More Comments