LSM303D tilt compensation problem


i am using LSM303D with an arduino. the I2C is chatting fine, i’ve calibrated using calibrate.ino and i’m getting all the outputs that the code libraries provide.

so i’m most of the way to getting my tilt-compensated compass (it’s for a sailing project) to work, however the heading() is fluctuating when i hold the break-out board’s heading the same, but add roll and pitch.

you can see in the .pdf Screen Shot 2017-01-04 at 3.19.14 PM.pdf (37.4 KB) i have heading plotted- the yellow line is raw and the blue line is smoothed for clarity. i leave the break-out board stationary for a while (heading is stable at 100deg), then i change roll -20deg,+20deg. the heading should stay constant but it doesn’t … you can see it fluctuates then returns to the 100deg heading. this is not tilt-compensated :frowning: The same happens when i pitch the break-put board too, but i havent shown that in the .pdf

the code is pretty much straight from the library, with some simple ‘smoothing’ added:

#include <Wire.h>
#include <LSM303.h>

LSM303 compass;

const int numReadings = 5;

int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

void setup() {
  compass.m_min = (LSM303::vector<int16_t>) {  -6540,  -6286,  -6171 };
  compass.m_max = (LSM303::vector<int16_t>) {  +6443,  +5933,  +6141 };

  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;

void loop() {;
  total = total - readings[readIndex];// subtract the last reading:
  float heading = (compass.heading()); // get the decimal valuer:
  readings[readIndex] = heading;
  total = total + readings[readIndex];  // add the reading to the total:
  Serial.println( average + String("    ") + heading );

  readIndex = readIndex + 1;  // advance to the next position in the array:

  if (readIndex >= numReadings) {  // if we're at the end of the array...
    readIndex = 0;    // ...wrap around to the beginning:

  average = total / numReadings;  // calculate the average:



I think heading() output tilt compensation should be better than this. So is is better to use raw data + try to source other equations instead OR using am i doing something wrong with heading() ?



Hi, sixD.

Bad magnetometer calibration values can cause the heading calculation to not work well, so you might try doing the magnetometer calibration again to see if it improves your results at all. Note that our example sketches use a very basic calibration method (trying to find an average offset for each axis and subtracting it from the readings), and there are more complex calibration approaches that might give better results. The formula we use to calculate the tilt-compensated heading should give good results if provided with accurate gravity and magnetic field vectors.


Hi K,

thanks for the reply. i have tried re-calibrating a number of times already
and there is only a little variance in the max and mins each time.
Additionally none of these changes have noticably worsened or improved the
problem anyway.

questions for you:

  1. should the variances in heading for tilt compensation be of the type
    and size you are seeing in my example i sent in the first post? i.e. is
    what i’m getting as good as heading() gets?
  2. i have sourced these first
    principle equations. can you pls explain how to get from the serial output
    to the raw values ‘pitch’, ‘heading’(uncompensated), and ‘roll’ for this
    breakout board?

thanks much!


  1. From some testing I did with an LSM303 here, I could get the same behavior of poor tilt compensation (i.e. the reported heading changing when the board is rolled or pitched) when the calibration constants were not set well, and I was able to improve it by calibrating more carefully. The heading() function should give perfect results if given perfect data, so I would say that’s as good as it gets unless you can improve the quality of your magnetometer data (or unless there’s a bug in the code that we haven’t noticed). A tip for calibrating with our method is that you should try to point the sensor in as many directions as possible - imagine painting the inside of a sphere with it.

  2. Out of curiosity, I tried implementing a heading calculation using those equations and found that it gave the exact same result when given the same data (as expected). Here’s my sketch if you want to look at or try it: Heading2.ino (2.3 KB) You can see how to calculate pitch and roll in the code.


1 Like

Hi Kevin,
thanks for the thoughtful reply. i will try heading2.ino and let you know
ho it goes. I will also try recalibrating and then recalibrating in a
different place to see if that makes a difference too.
by the way are you OK if i post your reply to the arduino forum so other
people have the information too?


It’s fine to post what I wrote to the Arduino forum. Could you share a link to your discussion there?


Hi, l have tried various methods to get better tilt compensated compass heading with the v3 including using merlins/Sail boat Instruments code/calibration (made maybe 1 degree better error result).
I have posted my earlier results on this site,How to use cal data from MagCal/Magneto into AHRS
I live in Tasmania with dip at 72 degrees up and your readings/error -10, +12 (mine at 45 degree roll, pitch had very little error)is as good as l could get.
I built a 2D (roll/tilt)gimbal which has enabled precision to be < 2, more like <1 degree tilt accuracy (because there is no tilt/roll)
I believe you will be dreaming getting better results than what you have depending on your location and mag cal.
The best l know with a V3 in the real world tilting to +35 degrees in New Mexico is -3, +6.
Would love to see one “in the real world which is not on the equator” perform better than above .Read this about tilt error passes/
If you want the best accuracy build a gimbal, mine was made from 3 different sized PVC pipe with non magnetic (check with a magnet) threaded rod
as the pivoting points. This is working brilliantly on my yacht auto pilot, using the V3 as the IMU heading sensor.
Regards Kevin

Hi Kevin C and Kevin 1961,

thanks for the updates.

The reason for my recent silence is that i have been busy trying to get
better results. I have written a couple of filters that are working well to
remove noise without too much lag (things like banging the table the gimble
is on used to badly affect heading output). Although my telepathy seems to
be working great, because - and this was BEFORE i saw Kevin1961’s post- i
ALSO made myself a gimble, although mine out of wood, not PVC. Just for
clarity did you use your gimble to stop the tilt in the first place (i.e.
is the PVC thing-o on your boat?) or was it to experiment with the effects
of tilt?

Anyway regarding the tilt effect, my results are better, but still not good
enuff for me yet. The gimble has allowed me to standardise the various
tilts, so i can get more systematic about solutions. One issue is that i
found the tilt effect more dramatic at higher headings, which is
frustrating that it is not consistent. I’m not sure if that problem is
repeatable & predictable. Also after a tilt, the heading ‘wander’ reduces
over time. weird!

i’ll check out those links, thanks!


By the way for those that want it, here is the link to the Arduino forum where i started

SixD, l built the gimbal because of the poor tilt compensation, so yes it is to remove as much tilt and pitch as possible. As the poor tilt compensation (in my case) was making the compass heading too inaccurate to be a good heading sensor to control the yacht Auto Pilot.
I have it on the boat and it has been perfect, still to test in a big seaway but on another boat l have also a unit and this boat has a horrendous roll motion and it works well, just guesstimating its accuracy maybe ± 4 degrees.
The AP at times had a lot of drift, but believe this was due to the gyro which was sensing ROT when there was very little. Changed setting from 2000 dps (also correct sensitivity) to 245 dps and haven’t had an issue.
You may want to play with the Accel and Mag, which l have also altered away from default.
The issue on higher headings is a hemisphere related problem, Northern Hem Northerly headings effected more, in the Southerner Hem, southerly headings more affected.
What sort of accuracy are you after, best cheaper IMU is this one

I have a prototype GPS version of this, but Kris thought/gets <± 2 degree tilt compensated heading, but here in Tas the best l got was <±7. Which is still way better than the V3 (sorry Pololu) but l haven’t tested it in a dynamic situation, static only.
As in another post l thought the BNO 055 was perfect. Tilt compensated heading ±1, but put it a dynamic environment and it looses the plot. Bosch haven’t answered posted questions re this problem.
I have recently bought some more V3 because they seem to be the best of a bad bunch (IMU that don’t auto compensate, which l don’t wont), unless you want to shell out $700+ to go to better quality IMU’s, this is the only way to get a <± 2 degree dynamic heading sensor (down here), or use a gimbal and a V3.
The AP designer has tried V5 IMU but their results were twice as bad as the V3.
You can remove most of the Deviation you may have ( non linear headings around 360 degree) by using Merlin’s ( Sail Boat Instrument) code and NLREG software.
That has reduced my Southerly headings from 192 degrees back to 180 and made the compass readings ± 1 degree for the full 360 of the compass (tested by a static 360 degree compass rose and move the IMU around it)
Regards Kevin

Update: For a few bearings around the compass, i have used my gimble to see the deviation in the “tilt-corrected” reading from true bearing (it was held absolutely constant) while i changed the Pitch (tilt). Note i kept the roll at zero degrees throughout.

see attached

The bearings are across the x-axis. The deviations SHOULD all be zero. (if it’s tilt-compensated correctly). I dunno about you, but i can sort of see a sine wave for the tips of each tilt angle (coloured the same) that need to be subtracted from the heading output at these bearings to account for each tilt.

i guess now i have to work out an equation to approximate the necessary corrections :confused:

anyone with any ideas, please feel free to chime in here…


Hi, l had little in accuracy in Pitch ± 2 degree, roll only.

so i’m clear @kevin1961, you can change your pitch/tilt by 24degrees and get only 2deg of heading variance??

what am i doing wrong!

l’m going on memory here, but yes it was a low level of inaccuracy when pitching (this is with a IMUmin V3 (Mag, Accel, Gyro) not just the Mag).
IMU with x axis facing forward, silk screen up, Pitch tilt 35 degrees nose up/down,very little tilt error,
the same direction, right wing up/down (roll) up to ±12 inaccuracy.
I imagine if there was a problem with your code Kevin would have seen, l’m no guru on coding so can’t help

Interersting. mine is the other way: roll is not too bad (maybe about like yours, probably a bit worse) but the effect of pitch is crazy.

can you kindly wot you mean by “IMUmin V3” i’m using the LSM303D silk screen up and X axis forward

So l’m using there now redundant IMU,

ah. ok, thanks. i see this board has Gyro, Accelerometer, and Compass so thats 9DoF whereas my board (the LSM303D) has only has accelerometer and magnetometer, i.e. i guess 6DoF .

Question for KevinC@Pololu : could this be the reason for the differences. i.e. does the tilt-compensation use gyro in its calculations?

i chose the LSM303D because the Pololu website says “…is ideal for making a tilt-compensated compass”


Update: so in my quest for a tilt compensated compass (!) i started with zero tilt, zero pitch and double checked my heading. (so that actual headings = measured headings. ) then after that i’ll implement my fix-up algorithm…

Huston, we have a problem.

even un tilted and unpitched the heading is lousy! (worse on some headings than others): see chart:

so now i need a fix-up for heading BEFORE i even get to the tilt-compensation. The yellow line is the error (diff. from what it SHOULD be times 10 so you can see the variance at each heading.

HONESTLY i’m thinking that i’m starting so far behind the 8ball, that maybe starting from a different sensor would be smarter :frowning:


I doubt very much that the magnetometer is properly calibrated.

When held level, with Z down and rotated 360 degrees, the readings from the X and Y axes should ideally fall on a perfect circle, centered at (0,0) when plotted on the XY plane.

Here are examples and plots from my own work with a 2D magnetometer:

Please show us the plot from your magnetometer.

thank you @Jim_Remington for your input. i will definitely do the perfect circle/oval thing. A question, though: i have read your link… but which script to get these X,and Y vals?

The arduino library files that accompany the LSM303D are called : 1.Calibrate, 2.Heading and 3. Serial.

  1. I use calibrate.ino to get max and min values that i put into
  2. heading.ino to get ‘tilt-compensated’ heading, that isnt very well tilt-compensated at all! NOTE: this heading value turns out to be tested to be identical to working out heading by equations see kevin C’s input earlier in this post. This code used and it also outputs Pitch and Roll too.
  3. serial outputs accel (compass.a.x, compass.a.y, compass.a.z), and Magnetometer (compass.m.x, compass.m.y, compass.m.z) raw data

But none of these are X,Y values as you describe. The circle shown in the link you post goes from roughly -250 to+250. what does 250 represent? Wot am i missing? Thanks