LSM303D tilt compensation problem

I suggest you re read Merlin’s posts, he shows how it is to be rotated at the top of the page, best of luck.
kevin1961 out

I have gone back to first principles and re-read all information here and in the links, but i am still finding the LSM303D uncalibrate-able!!

i even closed off the possibility that my unit was faulty and wired in a new unit, but same outcome :frowning: There must be something that i am overlooking, but for the life of me i cant see what it is. PLEASE SEND HELP!.. i just want a tilt-compensated compass.

The problem (as with each run earlier in this thread) is that when the heading is constant but the unit tilts or rolls the heading does not remain constant.

Here is my process:

  1. Gather up the RAW data from the LSM303D’s Magnetometer and Accelerometer by rotating the unit in equal increments around each axis
  2. Put data into Magneto software to get the Mag & Accel compensation factors
  3. Insert the compensation factors into heading.ino (being very careful about the + & - signs)
  4. Test

OK can you see any problems with this process??


There are three data gathering runs (see pic.):
A: is with the unit on the flat. (this is for heading) and rotated about a vertical axis
B: the unit is horizontal and rotated incrementally around a horizontal axis
C: the unit is horizontal but with a different face parallel to the axis and also rotated around the horizontal axis.

For each of A,B and C

  • my increments are 10 degrees each, so that’s 36 stationary points around a circle.
  • at each reading i average 50 readings back to one data output for that position - the code for this is earlier in this thread.
  • this gives 108 orientations for Mag & Accel affected ONLY by gravity (they are stationary data reads)

For completeness here i can graph the RAW data (all 108 rows of x,y and z outputs, to see if the outputs are something that kinda ‘looks right’.

Here is MagRAWdata for x-y, then y-z then x-z:

…and here is AccelRAWdata for x-y, then y-z then x-z:

For one extra check:

i can graph ONLY the relevant parts of each of the 108 data rows [i.e. only the 36 ‘A’-orientation points for x-y, only the 36 ‘B’-orientation for y-z, and only the 36 ‘C’ orientation for X-Z] then this is the graphed output:

for mag:

and then for Accel:

Magneto can make the ovals into circles and centre the data and as you can see every one of the charts are like this EXCEPT FOR accel x-y which is weird! could this be where the problem lies?


i separate the output into two tab-delimited .txt files, one Mag and one for Accel. I assume that i do this for all 108 points, not just the 36 for each orientation??

Can anyone kindly clarify if the process this far is correct?

many thanks


Unless there is an error in the code or the mounting arrangement (with a nearby bit of iron or magnet), the “accel x-y” plot suggests that the sensor is defective. It should look similar to the other two accel plots.

Magneto fits an ellipsoid, not ellipses, and can use all the data points you provide. The more points, and the more evenly they are spaced over the 3D orientation sphere, the better.

It is in fact undesirable to consider only certain orientations of the sensor.

sixD, I don’t know a lot about this method of calibration, but I don’t think your accelerometer is defective. From looking at your test setup and your graphs, it doesn’t look like you ever subject both the X and Y axes (blue and green) to gravitational acceleration at the same time. That is why you only see a cross pattern on your accel X-Y graph, and I think it explains why the software is having such a hard time “circularizing” it.

You should be able to get better data by adding another run (i.e. take your “A” setup and tip it over so the axis is parallel to the ground), but I think Jim is right that you want to get readings in as many orientations as you can; it might actually be better to wave the sensor around freely than to try to constrain its rotation to certain axes.


Hi SixD, good to see you finally worked out the difference between turning and rotation of the axis.

Hi Kevin, l would thinking waving the IMU around for Cal won’t work for the Accel as it would have a acceleration component as well as gravity??
Merlin states that you only need to do approx 24 steps i.e 15 degree increments for Magneto to give a good enough correction ratio.
My previously attached photo shows a V3 in the position you indicated for the X,Y axis data to be taken.
That all being said, after magneto correction to Mag and accel, instead of max/min correction, my tilt error decreased from 13 degree to 11 degree error at 45 degree tilt. So not worth the effort. Better off building a gimbal and decreasing error to <2 at 45 degrees(when you are not constrained by size).
What l can’t understand is the Accel gives accurate degrees of tilt, which l would have though meant the complete DCM formula would give just as accurate tilt compensated heading, which it doesn’t.

thankyou @Jim_Remington thats i i first thought it was a defective unit too, but thinking a bit more on it, and as per @kevin…[quote=“kevin, post:72, topic:11611”]
but I don’t think your accelerometer is defective
[/quote] as he says:

REASON: the X and Y are producing reasonable results in other orientations, so they must be working. BUT it’s def-o a problem and needs to be fixed. maybe i’m missing one orientation in my calibration.

re calibrate ‘loose’ or on the rig:
@kevin, regarding waving the IMU instead, i’ll keep it in mind, but from my testing i’d say that i agree with @kevin1961 that it would introduce more problems than it’s worth.

##new check
so instead of plotting values against each other, i plotted my previous data against their dataNumber (for 1 thru 108 data points) and got this:

For MAG:

and for ACCEL:

so, while i thought i had them all covered, the first third (being 1-36) is flat-as-a-pancake so this data says that i am missing one IMU orientation. riiight.

##adding the missing orientation:
so i took away the flat heading rotation that i called “A” above (rotated around a vertical axis) and substituted the data from the missing IMU position for one another rotation (around a horizontal axis)

and the new data now looks like this

for MAG:

and for ACCEL:

hurrah, this looks to me like i think the RAW data should. hopefully you guys agree?

rerunning thru magneto:

so i re-run all 108 data points thru magneto and it produces different calibrations values, but not very different. i input them into heading, carfully checking the +,- signs.
OUTCOME: unfortunately the heading output is still NOT tilt-compensated :frowning:

WHERE TO NOW?: this is some progress, no doubt, but i’m still not in the land of a tilt-compensated-compass. my current hunch is that the Xs and Ys should be reversed somehow. (i think this was true of early versions of LSM303 c/w later versions??. @kevin can you shed light on this?) its the only way i can see how a roll or pitch can make heading so much WORSE.

any other things to try would be gratefully received!



Hey SixD

I was having trouble with tilt compensation of my LSM303DLHC compass for an astronomy use.

For now I’ve managed a workaround involving limiting the position of the sensor, but I’m interested in figuring out the whole thing.

One quick question:

is this the code you are using for calculating the heading:?

fXm_comp = fXmcos(pitch)+fZmsin(pitch);
fYm_comp = fXmsin(roll)sin(pitch)+fYmcos(roll)-fZmsin(roll)*cos(pitch);

I’ve noticed in your graphs that you calculate pitch and roll, which tells me you are using these formulas (which involve lots of trigonometric functions like sin and cos).

There is another way to calculate the heading, using vector math (dot products and cross products). that’s the one I’m using, the bases is contained in the Pololu LSM303 library, ¿have you checked that out?

Hey @Adun,

yep, thats the one! A number of the tilt-compensated heading calcs i’ve seen use those formulae.

I would add this though: One of the three LSM303 library files is called heading.ino (i’ve taken out the remarks to keep it short):

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

LSM303 compass;

void setup() {
  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};

void loop() {;

  float heading = compass.heading();

yup, it doesn’t get much simpler than that. in this trouble-shooting saga, i compared the heading output from the above sketch to the heading that outputs from the equation(s) approach (the ones you mention) and they come out exactly the same. i guess the same equations are onboard the IMU too. Anyway that was a) nice to know b) not helpful for solving any part of my problem.

i’m glad you are also [quote=“Adun, post:76, topic:11611”]
interested in figuring out the whole thing.

…after all this time all i can add is me too!

can you tell me more about the [quote=“Adun, post:76, topic:11611”]
another way to calculate the heading, using vector math (dot products and cross products). that’s the one I’m using, the bases is contained in the Pololu LSM303 library,

either my library isnt uptodate or i’m missing something!

thanks much



I extended the class from LSM303.h file, which contains the body of the heading() method.

The template for the heading() method expects a “from” vector (which should be of magnitude 1 or -1), and it returns a heading relative to it.

The library always uses [0, -1, 0] which is the Y axis of the sensor for this, but as the sensor aims higher near zenith, it’s heading makes no sense. Above 45° you should use the X axis (and add 90° to the result). This requires modifying (or extending) the Pololu library.

Try this experiment: Modify the library to calculate heading using 3 different “from” vectors: X (-1,0,0) Y(0, -1,0) Z(0,0,1), and then plot the data, like you’ve done (tilting from zero to zenith). You will see what I mean.

My workaround was to switch to using the Z axis (0,0,-1) and use the sensor inclined, so that Z should is always be horizontal and give a good reading, it works for me because it’s for a telescope (not a boat) and it can keep the sensor vertical (zero rolling)


i understand and as you say @Adun it makes perfect sense for a telescope. and if i needed such a big range i think what you’re suggesting would be perfect. if that happened on a boat, the mast is in the water.:worried:

in regular use for a boat the tilt (roll) would go around 45 degrees but rarely and only momentarily and pitch would only be +/-10 ish i’d guess

the .h file

Hats off to you, sir for getting into the .h file. something i’ve never done. I’m interested, though that you say… [quote=“Adun, post:78, topic:11611”]
The library always uses [0, -1, 0] which is the Y axis of the sensor for this,

…and i’m wondering if that is a clue to part of my problem/situation.

see, with the board ‘on the flat’ it’s the Z axis which is up/down so heading would be based on X and Y axes, right? i wonder why the library would default to Y?? does that mean all this time that i should have my board on its ‘edge’ as its starting orientation, rather than on-the flat??

plus it might explain why the outputs for pitch and roll have always seemed so accurate compared with the wild errors that get introduced into the heading output - something i have always wondered how 2 outputs can be so reliable, but the third is lousy.

##one other thing

i have noticed that the tilt compensation amount varies around the heading. for example if the heading - that’s the red line below - is at 100 (east-ish) and held constant and then the pitch goes up say 20deg - the first blue peak - then heading will wrongly adjust by MINUS 60degrees -thats the first red ‘dip’.

but if heading is 270 (west) and held constant and then the pitch goes up by the same 20deg - the second blue peak -
then the heading will wrongly adjust by PLUS 60deg that’s the red ‘bump’.

Your experiment

I’m happy to try ANY experiment, but have never dived into the .h file. can you kindly post some code of what you are suggesting?

or maybe the above gives you some clue as to what’s going on?!?!?

thanks much


Come On Jim, how about posting your complete code that can deliver what l would call amazing results that you have been able to get out of IMU’s.
I ask, because Kris has his code used by possible the biggest supplier of product at our level, has it openly available to all and l have found even his can’t, down where l live, come anywhere near the the level of accuracy that you say you can get.
Com on supply your code please.
Thanks Kevin

Next assault. sixD, have you tried Kris’s IMU as l said in earlier post’s. I know it might be $50 but it will prove one way or the other. He backs his and is the person who shows all codes and fixes everyone else is.
He says ±2, which they best l could get was 7.
Buy one and prove one way or the other.
You shouldn’t need to be going into the .h (apart from looking and learning, not needing to change the code, maybe preferances) unless of course you can understand it all and if this was the case, you wouldn’t need to be asking these questions.


My code is here.

It obviously won’t work for you, because it’s meant for a telescope, and as you said, they don’t move like boats.

My point is, instead of the code you’re using (which has so many trigonometric functions) you could try Pololu’s driver.

It has a “heading” method which according to the documentation:

float LSM303::heading(vector from)

“Returns the angular difference in the horizontal plane between the ‘from’ vector and north, in degrees”

Maybe you could try it.

Hi Jim, by the look of things you aren’t willing to show the code you get the great results of zero tilt error with???
l am very keen to have a look and you could help a lot of people with it, so please post and help the IMU community.
Regards Kevin

I’ve been using RTIMULib, which has a decent calibration routine built in, and have no need to revisit code for the LSM303D. I imagine that it wouldn’t be difficult to modify RTIMULib to use that sensor, though.

My efforts were described here: State of the art AHRS for $25 and I highly recommend this software.

Of course, the accuracy of any IMU depends on doing the very best you can to calibrate both the magnetometer and the accelerometer, and in the latest posts, I don’t see evidence that you folks have really accomplished that. I would like to see before and after calibration plots, showing nice circles (not ellipses) or spheres perfectly centered on the origin.

Thanks Jim, by the sounds of it you are using Rtimulib2 with soft and hard iron correction.
Using Magneto l have centred circles on the Mag and Accel and as l said it only improved tilt error correction by 1-2 degrees to about ±10 of error (at worst).
Also my guru Jack, devised an even more accurate means of axis measurement (data thru a whole sphere) that we put thru Magneto with no better results. His V3 went from ±7(max/min) to about ±5(magneto), he is in the Northern Hemisphere.
Regards Kevin

Although this is an old thread, I think I found a possible solution to the problems that many of you were having.

The application note from for the LSM303, has some example matrix description for rotations by roll and pitch, which I think was used to generate most of the examples and libraries. There seems to be an error in that application note, which has flowed through to the examples. If you look at the line from many of the examples provided for the LSM303 you should find something like this:

fXm_comp = fXmcos(pitch)+fZmsin(pitch);

I think it should be:
fXm_comp = fXmcos(pitch)-fZmsin(pitch);

When I made this change, and used good calibration data, my compass seems to be very accurate, and I don’t see the heading errors with pitch changes that were discussed extensively above.

Hope this helps someone else.


Hi Chris, good detective work.
I haven’t tried it as yet but, in my case there is nothing wrong with the pitch tilt error, it is the roll which is the issue, but it all goes into the heading formula.
Will trial shortly and let you know the results.
What is your worst case error from tilting pitch and roll?

Hi cholm
Where can I locate the coded line equivalent to the “cos(pitch)-fZmsin(pitch);” within the LSM303 library?
Regards KWings