LSM9DS1 - Heading values are affected by pitch and roll (after calibration)

I bought the 9DoF stick (LSM9DS1) a few days ago, I was using MPU6050 but I wanted to get accurate Yaw angle data and the yaw on the MPU6050 was drifting so I understood that I need an IMU combined with a magnetometer.

I calibrated it and I can get pitch and roll values but I cannot get valid heading results,
if I'm rotating the board on a flat surface then I can get 180 to -180 values using your Sparkfun's library example.
But if I'm tilting the device while keeping it in the same direction the values are changing to nothing to make sense.

I want to be able to know the direction of the device so it's very important to know the accurate direction (I don't care about true north, just relative direction comparing where the device was started).
I wasted days on it, am I missing some calculations out of all the values?
Hope someone can assist, as I'm going nuts.

My MCU is Adafruit HUZZAH 32.

Thank you.

Have you considered how much more helpful seeing your well formatted code in code tags would be to those who would help you ?

I use a LSM(DS1 on a ESP32. I wrote my own library for the LSM9DS1 and I am using the ESP32 SPI API.

am I missing some calculations out of all the values?

Undoubtedly.

Post the code, using code tags, tell us what it is supposed to do, and what it does instead.

Thanks for the replies, here's the code - I tried multiple libraries / examples , this is the Spakrfun library taken from here
I only changed the format the values are printed so it would fit to the serial plotter.

Setup:

void setup()
{
  Serial.begin(115200);

  Wire.begin();

  if (imu.begin() == false) // with no arguments, this uses default addresses (AG:0x6B, M:0x1E) and i2c port (Wire).
  {
    Serial.println("Failed to communicate with LSM9DS1.");
    while (1);
  }
    // tried with both of these and without, still values doesn't makes sense
    imu.calibrate(true);      
    imu.calibrateMag(true);      
}

YPR Calculation:

// Calculate pitch, roll, and heading.
// Pitch/roll calculations take from this app note:
// http://cache.freescale.com/files/sensors/doc/app_note/AN3461.pdf?fpsp=1
// Heading calculations taken from this app note:
// http://www51.honeywell.com/aero/common/documents/myaerospacecatalog-documents/Defense_Brochures-documents/Magnetic__Literature_Application_notes-documents/AN203_Compass_Heading_Using_Magnetometers.pdf
void printAttitude(float ax, float ay, float az, float mx, float my, float mz)
{
  float roll = atan2(ay, az);
  float pitch = atan2(-ax, sqrt(ay * ay + az * az));

  float heading;
  if (my == 0)
    heading = (mx < 0) ? PI : 0;
  else
    heading = atan2(mx, my);

  heading -= DECLINATION * PI / 180;

  if (heading > PI) heading -= (2 * PI);
  else if (heading < -PI) heading += (2 * PI);

  // Convert everything from radians to degrees:
  heading *= 180.0 / PI;
  pitch *= 180.0 / PI;
  roll  *= 180.0 / PI;

  Serial.print("Pitch:");
  Serial.print(pitch, 2);
  Serial.print(",");
  Serial.print("Roll:");
  Serial.print(roll, 2);
  Serial.print(",");
  Serial.print("Heading:"); 
  Serial.println(heading, 2);
}

Main Loop:

void loop()
{
  // Update the sensor values whenever new data is available
  if ( imu.gyroAvailable() )
  {
    imu.readGyro();
  }
  if ( imu.accelAvailable() )
  {
    imu.readAccel();
  }
  if ( imu.magAvailable() )
  {
    imu.readMag();
  }

  if ((lastPrint + PRINT_SPEED) < millis())
  {
    // Print the heading and orientation for fun!
    // Call print attitude. The LSM9DS1's mag x and y
    // axes are opposite to the accelerometer, so my, mx are
    // substituted for each other.
    printAttitude(imu.ax, imu.ay, imu.az,
                  -imu.my, -imu.mx, imu.mz);
    Serial.println();

    lastPrint = millis(); // Update lastPrint time
  }
}

At first I got Pitch, Roll that looked ok but now the values are not consistent - The pitch looks consistent but the roll changes with pitch as I'm tilting the device up and I got some roll peaks here and there.

The Heading is moving with the pitch and roll , if the the device is rotating on a flat surface the heading looks ok (except of some peaks sometimes- screenshot attached) but when I'm tilting the device up or down the heading moves.

I attached some screenshot from the serial plotter, hope it's enough info to understand the issue.

Serial monitor values while the devices sits on a flat surface (table):

Pitch:2.85,Roll:5.07,Heading:-33.18

Pitch:2.97,Roll:5.05,Heading:-33.40

Pitch:3.49,Roll:4.94,Heading:-33.75

Pitch:3.70,Roll:5.24,Heading:-33.59

Pitch:3.16,Roll:5.25,Heading:-34.67

Pitch:3.32,Roll:5.13,Heading:-34.56

Pitch:3.95,Roll:5.09,Heading:-35.26

Pitch:3.02,Roll:5.00,Heading:-34.31

Many thanks

I don't know what the calibration routines in that library do, but I've never seen a library that does it correctly.

If you want the sensor to work well, it MUST be correctly calibrated. The best procedure is somewhat complicated, but is very well covered in this tutorial: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

The particular heading calculation you are using works only if the compass is held level. In order to make a compass that works in any orientation, you must perform tilt correction using the accelerometer. The math is described here, but there is a much simpler approach using vectors, which you can download from Pololu for their LSM303-based magnetometers.

Magnetometer calibration is done by moving the magnetometer in a blah blah blah... You've done that, right?

And thanks jremington for posting.

jremington:
I don't know what the calibration routines in that library do, but I've never seen a library that does it correctly.

If you want the sensor to work well, it MUST be correctly calibrated. The best procedure is somewhat complicated, but is very well covered in this tutorial: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

Yes I've seen this tutorial, I should read it entirely .
I tried multiple calibrations, using Adafruit tutorial with Motioncal and after that I think I got good Pitch and Roll but the heading values were just decreasing even without moving (using Adafruit library for this sensor)...

I also tried to calibrate using this library which is a fork of the Arduino LSM9DS1, he has a nice video showing the calibration process...
Will check out the vectors approach for the tilting correction.

Do you have a recommendation for a good library that I can use to get good YPR values to see if I'm calibrated properly ?

I also tried to calibrate using this library which is a fork of the Arduino LSM9DS1

All those methods are very crude and a waste of time and effort, as the resulting directions will be inaccurate.

If you want to do it right, the only option is to collect a full 3D ellipsoid of data, and use a PC program to calculate the offsets and scale factors correctly. And, the calibration should be done in the final installation, as offsets produced by magnetic fields from motors, current in wires and iron containing objects will severely distort the Earth's magnetic field.

Here is a blog post showing how calibration is done for the related LIS3DLM magnetometer, and also how a tilt compensated compass can be implemented using an accelerometer as well.

jremington:
All those methods are very crude and a waste of time and effort, as the resulting directions will be inaccurate.

If you want to do it right, the only option is to collect a full 3D ellipsoid of data, and use a PC program to calculate the offsets and scale factors correctly. And, the calibration should be done in the final installation, as offsets produced by magnetic fields from motors, current in wires and iron containing objects will severely distort the Earth's magnetic field.

Here is a blog post showing how calibration is done for the related LIS3DLM magnetometer, and also how a tilt compensated compass can be implemented using an accelerometer as well.

Thank you, I will follow this blog post.
Just thinking maybe it will be easier to do with the MPU9250 that I already ordered and waiting for it (and was way cheaper than this SparkFun IMU).
I saw you wrote a library for it with tilt compensation, so maybe it will be easier and faster to switch to 9250.
Thanks again

I ordered a breakout board for the LSM9DS1 from Adafruit, because I wanted to see how well it performs (should be significantly better than the MPU-9250), and should be done with the testing in a few days.

I plan to post Arduino code for both a tilt compensated compass and the Mahony AHRS filter using it, along with performance characteristics.

jremington:
I plan to post Arduino code for both a tilt compensated compass and the Mahony AHRS filter using it, along with performance characteristics.

Amazing, looking forward to It !

jremington:
I plan to post Arduino code for both a tilt compensated compass and the Mahony AHRS filter using it, along with performance characteristics.

Hi, still struggling with the tilt compensation, tried different libraries and calculation and still no go.
Did you manage to finish your tests with the Adafruit board?
Thanks

Working sensor calibration and tilt compensated compass code for the Adafruit LSM9DS1 breakout is posted at

This sensor performs better than any that I have yet tested. The Mahony AHRS is still being tested and will be posted soon.

The data sheet is very misleading, as the axis illustration implies that the magnetometer X and Y axes are rotated about Z with respect to the accelerometer axes. This is not the case. However, the handedness of the accelerometer axial system must be corrected in the code.

Thanks a lot.
I ran the LSM9DS1_ca_data and took the result to Magneto.
I got the following output from Magneto:

Average magnitude (default Hm) and sigma of 301 vectors = 16393.5,  488.3

Reject outliers? (0 or d, reject if > d*sigma from mean) 0
Rejection level selected:  0.0

301 measurements processed, expected 301

Expected norm of local field vector Hm? (Enter 0 for default  16393.5) 0

Set Hm =  16393.5

Combined bias vector B:
 -206.12   -76.49   -13.99

Correction matrix Ainv, using Hm= 16393.5:
  0.99792  -0.00671  -0.00104
 -0.00671   1.00133   0.00053
 -0.00104   0.00053   0.99279

Where Hcalc = Ainv*(H-B)

Code initialization statements...

 float B[3]
 { -206.12,  -76.49,  -13.99};

 float Ainv[3][3]
  {{  0.99792, -0.00671, -0.00104},
  { -0.00671,  1.00133,  0.00053},
  { -0.00104,  0.00053,  0.99279}};
 output filename for corrected values (csv) results.csv

RMS corrected vector length 16393.467758

RMS residual -0.000000

I tried to put it in the LSM9DS1_tiltcomp but I noticed I should have also Both Accelerometer & Magnetometer values?
I couldn't understand how to get these? looks like the Magneto results only gave me B and A_INV
So am I missing the Magnetometer values?

//Accel scale 16457.0 to normalize
float A_B[3]
{ -133.33,   72.29, -291.92};

float A_Ainv[3][3]
{ {  1.00260,  0.00404,  0.00023},
  {  0.00404,  1.00708,  0.00263},
  {  0.00023,  0.00263,  0.99905}
};

//Mag scale 3746.0 to normalize
float M_B[3]
 { -206.12,  -76.49,  -13.99};
 
float M_Ainv[3][3]
{ {  0.99792, -0.00671, -0.00104},
  { -0.00671,  1.00133,  0.00053},
  { -0.00104,  0.00053,  0.99279}
};

You are on the right track!

The Arduino program LSM9DS1_cal_data.ino posted on github outputs both magnetometer and accelerometer data on one line (six values). First accelerometer, then magnetometer:

    Serial.print(imu.ax);
    Serial.print(", ");
    Serial.print(imu.ay);
    Serial.print(", ");
    Serial.print(imu.az);
    Serial.print(", ");
    Serial.print(imu.mx);
    Serial.print(", ");
    Serial.print(imu.my);
    Serial.print(", ");
    Serial.println(imu.mz);

You have to cut out the individual magnetometer and accelerometer data columns, either using an editor or spreadsheet, and create two separate .csv files, one each for the magnetometer and accelerometer, as shown in the folder named /example_calibration_data

The final step is to run magneto twice, once with each data set and cut/paste/rename the offset and matrix initialization data lines so that they are correctly named, as found in the tiltcomp program.

Thank you @jremington !
I got it to work thanks for your help.

The heading results are much better now and I can work with them but it's not so good when tilting (though so much better from before!)
I mean - when tilting up or down the heading values changes significantly, I wanted to ask you how accurate it should be ?
I also feel it if I'm shacking the board a bit, values drop significantly.

Maybe this is due my calibration ? maybe I should do the calibration process again ...

Thanks again

Has anyone tried comparing the mag calibration tools between MotionCal and Magneto? MotionCal provides a visual representation of the data set as it is acquired.

This arduino library uses MotionCal ...

IXMATIS/lxmatix_LSM9DS1_arduino

hi lover30.
I played this weekend with my 33ble (LSM9DS1 inside) To get orientation (heading ?) with cardinal point.
took time To get familiar with hearth filed vector orientation.

I live in France where magnetic field is 60° bended from vertical line.
when x axis is north oriented, y axis is east oriented and z must point To floor.
there, considering a 0.5 gauss earth magnetic field, x will equal To Bcosinus (60°), z will equal To Bsinus(60°).
and y will be near 0 as it is perpzndicular with x.
note z will constant if you are flat whereas x will vary from +Max (north oriented ) To - Max (south oriented).

this is it for horizontal displacment.
as soon as you pitch / roll/ yaw / boogie or whatever, earth field remains constant in direction and intensity so measurements you get from moving sensor are (vectorialy) influenced by actual angle. a calculation is required to evaluate actual space orientation (meaning what is the expected value of horizontal position compared To real values)

this only ccorrected orientation can be processed with accelerometer data.

I also feel it if I’m shacking the board a bit, values drop significantly.

accelerometer records actual acceleration imposed To a object.
freefall is defined by no force applied on a object except for its own mass.
if youhave no fear, drop the sensor from a couch High. 3 axis tend to 0g : it is freefall.
by the way, automated fall detection (see your datasheet ) is triggered by a null value on the 3 axes.

I mean - when tilting up or down the heading values changes significantly, I wanted to ask you how accurate it should be ?

Post some actual numbers. What do you mean by "changes significantly"?

It is not easy to tilt the sensor by hand and be certain that you are maintaining the same heading. When I am very careful, I do not see a significant change in heading, even when the sensor is tilted by close to 90 degrees.

Beware of measurement noise. It is best to average several readings. After careful calibration in place, heading values from a single measurement should be accurate to about +/- 2 degrees (based on device data sheet specifications).