[Heading] Calculating heading with tilted compass

Here i provide some formulas and calculations in a short and easy way to calculate your compassbearing when you’re compass is not leveled (when it is not on a flat surface). Essentially it’s based on [1].

[latex]X[/latex] is the gravitational magnitude in X direction, and [latex]Y[/latex] the gravitational magnitude in Y direction Both are one dimensional, but using both we can obviously make a two dimensional figure; and thus distill the desired angular heading pointing to magnetic north ( [latex]theta[/latex] ) in respect to the heading of the object.

Compass on flat surface

If the compass is on a flat surface in which X and Y are perpendicular to eachother, and both parallel to the earth’s surface.

[latex]theta=tan^{-1}(frac{Y}{X})[/latex]

This works just fine. If this does not, you could calibrate it (see this page).
But it only works when the compass is flat. When you tilt it it will give measurements with a fairly big error. To compensate for those, you will need a three axis compass (introducing [latex]Z[/latex] in Z direction). And, we will also need an accelerometer to see how the compass is tilted. (We do not necessarily need a gyro, unless the object is moving too dynamically.)

Compass on a titled surface

Now, we also introduce the accelerometer, which has three axis, pitch ( [latex]alpha[/latex] ), jaw ( [latex]beta[/latex] ) and roll ( [latex]gamma[/latex] ) ; see my fancy illustration for graphical definitions.
With tilting, the new formulas become:

[latex]X_h=X*cos(alpha)+Y*sin(gamma)sin(alpha)-Z*cos(gamma)*sin(alpha)[/latex]

[latex]Y_h=Y*cos(gamma)-Z*sin(gamma)[/latex]

Now, we can use these new X and Y horizontal components, and put them in the first formula. But, we have to account for the arctangent limits. Because we have to account for the bugging sign changes, we can include the following pseudocode:

  1. heading=arctan(Yh/Xh);
  2. if    (Xh<0)         {heading=180-heading;}
  3. elseif(Xh>0  && Yh<0){heading=-heading;}
  4. elseif(Xh>0  && Yh>0){heading=360-heading}
  5. elseif(Xh==0 && Yh<0){heading=90}
  6. elseif(Xh==0 && Yh>0){heading=270}

References
[1] – Applications of Magnetic Sensors for Low Cost Compass Systems, Michael J. Caruso, Honeywell, SSEC.

Arduino IDE >1.0 users should read Martinux’s comment below.

Update

I have now made a tilt compensated compass with two very cheap modules, the HMC5883 and the MMA7660. I have used my own two libraries for these, and this is the code to use the data to create the tilt compensated heading. So this the code for compass tilt compensation using arduino in cpp.

  1. int getcompasscourse(){
  2.   int ax,ay,az,cx,cz,cy;
  3.   MMA7660.getValues(&ax,&ay,&az);
  4.   HMC.getValues(&cx,&cz,&cy);
  5.  
  6.   float xh,yh,ayf,axf;
  7.   ayf=ay/57.0;//Convert to rad
  8.   axf=ax/57.0;//Convert to rad
  9.   xh=cx*cos(ayf)+cy*sin(ayf)*sin(axf)-cz*cos(axf)*sin(ayf);
  10.   yh=cy*cos(axf)+cz*sin(axf);
  11.  
  12.   var_compass=atan2((double)yh,(double)xh) * (180 / PI) -90; // angle in degrees
  13.   if (var_compass>0){var_compass=var_compass-360;}
  14.   var_compass=360+var_compass;
  15.  
  16.   return (var_compass);
  17. }

Two modules One function

Tim Zaman

MSc Biorobotics. Specialization in computer vision and deep learning. Works at NVIDIA.

You may also like...

16 Responses

  1. Pierce Nichols says:

    I just found this post while searching for more information on, well, exactly what it’s about for the Hackerboat project I’m working on. It’s great and it was really helpful for me getting my head around some compass issues we were having.

  2. Kontiki says:

    How should the axis of the HMC5883 and the MMA7660 be aligned? I currently have them all aligned together (so +X to +X, +Y to +Y, +Z to +Z) but the tilt correction doesn’t kick in. Should they be aligned differently?

  3. Charlie says:

    In the code presented in the Update that shows the entire algorithm, is there a type-o on lines 7 and 8? Seems like ayf should be equal to the radians of ay. Like-wise for axf should be equal to radians of ax.

    Are those lines correct?

    • Tim says:

      I stand corrected indeed. will edit.

    • Diane says:

      I am trying to make a tilt compensated compass for a robotic sailboat. I am using a 3-axis magnetometer/3 axis accelerometer called the LSM303DLHC on a breakout board from Adafruit https://www.adafruit.com/product/1120. It should accomplish what the 2 modules you used did.
      I’d like to adapt the code you show above and have not gotten to work yet. Probably because I am not sure what units the readings come in, or what they should be for the calculations. Were the typos mentioned in an earlier post fixed? Can you tell me more about the appropriate units for your calculations?
      thank you !

  4. Caoimhín says:

    What units does it take for compass and accelerometer???
    I want to use this code with another comp and acc to see if it still works, but I’m not entirely sure what units to use.

  5. Steve Yates says:

    Hi
    I put this project aside for a while and have restarted it and using your sketch and changing all the wire terms due to the Arduino update I am getting values from it and all seems to be working well.
    I do no have access to Matlab, can the calibration be done with Feemat? I am on a Mac.
    How can I be confident the compass is reasonably accurate?
    I dont need super accuracy

  6. Martinux says:

    Hi Tim

    Thanks very much for the write-up.

    I’d just like to add, for those who are using Arduino 1.0+, that all references to TwoWire.send and TwoWire.receive should be replaced with TwoWire.write and TwoWire.read

    Additionally, WProgram.h should be replaced with Arduino.h in the code.

    Happy hacking. 🙂

  7. George says:

    Hi,

    Impressed on how you did it. Since a couple of days I’m playing around with arduino uno and the hmc6352 to make a compass with nmea output for my boat. I noticed I’m missing the tilt compensation. See this video X

    Do you have a suggestion on how I can tackle this?

    Many thanks

    • Tim says:

      Well yeah you need a 3DOF accelerometer and a 3DOF magnetometer. then you can use my functions, otherwise you can’t do it.. read this paper: Applications of Magnetic Sensors for Low Cost Compass Systems, Michael J. Caruso, Honeywell, SSEC.

  8. Steve Yates says:

    When you say above that this is the code for tilt compensation using arduino in cpp how have you combined the sketches for the 2 modules?
    I have gotten them both working individually.
    Could you give me a schematic and final sketch please?

    • Tim Zaman says:

      You can just combine the sketches, i do that too. And, you hook them up to exactly the same points. They both go to SDA, SCL, ground and V. SCL is the clock, they dont have an internal one. They both use the same clock ofcourse, and they also share the ground and the power V.
      So that leaves SDA, but that also, you hook up to the same line. Because the sketches use the SDA line, to ‘ask’ information to the specific module. If one module is spoken to, the other one shuts up. More on that here, its called “I2C” or “wire” http://www.arduino.cc/en/Reference/Wire

  9. Steve Yates says:

    Wow. Those codes are so small.
    Only a few lines.
    Thanks Tim. I never saw that last page.
    I have ordered a MMA7660 module and will have a play using your methods once it arrives.

  10. Steve Yates says:

    Hi
    This is very helpful Where can I access the 2 libraries please?
    I bought this compass in error and now need to tilt compensate it so this post looks like just what I am searching for.

    Where is the rest of the code?
    Is it simply a matter of getting the magnetoimeter and the accelerometer working first and then adding the above code?

    Thanks
    Steve