Trouble with LSM303DLHC as Compass

I am trying to use an LSM303DLHC as a compass, but I the readings are not correct. I am not sure if the problem is with my setup or the math. I am using the Compass example sketch from Adafruit.\

I have the LSM303 attached to a Stepper motor. The LSM303 is mounted horizontally, so the sensor chip is facing down. I would say the LSM303 is not perfectly level, nor perfectly centered on the axis of the Stepper Motor, but it's close. The motor turns 180*, pauses for 3 seconds and I record the x, y, z and calculated heading readings. Visually, it looks like the stepper is turning 180* - it may not be perfect, but it's very close. I can see it lands in the same spot with each rotation.

Here are the mag bits:

#include <Adafruit_LSM303DLH_Mag.h>
#include <Adafruit_Sensor.h>

Adafruit_LSM303DLH_Mag_Unified mag = Adafruit_LSM303DLH_Mag_Unified(12345);

/* Initialise the sensor /
if (!mag.begin()) {
/
There was a problem detecting the LSM303 ... check your connections */
Serial.println("problem...");
while (1)
;
}
}

void loop(void) {
sensors_event_t event;
mag.getEvent(&event);

float Pi = 3.14159;

// Calculate the angle of the vector y,x
Serial.print(event.magnetic.x);
Serial.print(" ");
Serial.print(event.magnetic.y);
Serial.print(" ");
Serial.print(event.magnetic.z);
Serial.print(" ");

float heading = (atan2(event.magnetic.y, event.magnetic.x) * 180) / Pi;

// Normalize to 0-360
if (heading < 0) {
heading = 360 + heading;
}

Serial.print(heading);
Serial.println();
}

The problem I'm having, it it takes 6 readings (3 rotations) for the readings to be the same. I would think that turning 180*, the readings should be the same every other pause. This makes me think it's the math, but again - I took this code from the Adafruit Compass example.

I have included the output below. You can see that it repeats the same 6 readings (within a degree or so). If it starts at 180*, i would expect the next reading to be 0*, and the third to be 180*, then 0*, etc...

Again, I'm trying to use this as a fairly accurate compass. Any suggestions on what is wrong?

Output Readings:

X Y Z Heading
-33.09 -0.18 63.88 180.31
-21.27 -101.82 71.63 258.2
-23.91 31.82 94.59 126.92
11.55 -33.91 63.57 288.8
0.36 67.45 67.04 89.69
9.36 -67.09 90.1 277.95
-33.73 0 63.98 180
-21.55 -101.55 71.84 258.02
-23.36 32.27 93.98 125.9
11.09 -33.27 61.22 288.43
0.45 68.36 66.73 89.62
8.64 -65.73 90.31 277.49
-33.45 -0.36 63.78 180.62
-21.27 -101.82 71.53 258.2
-23.82 31.82 94.59 126.82
11 -34.18 64.29 287.84
0.73 68.18 67.04 89.39
9.09 -67.36 90.2 277.69
-32.73 0.45 64.18 179.2
-21.73 -101.18 72.24 257.88
-23 32.36 94.49 125.4
11.91 -33.45 61.02 289.59
0.09 68.36 66.43 89.92
9.27 -65.91 91.12 278.01
-33.27 0.09 63.78 179.84
-21.18 -101.73 71.33 258.24
-24.18 31.73 94.59 127.31
11.73 -34.09 64.08 288.98
0.36 67.73 66.63 89.69
9.55 -67.18 90 278.09
-33.82 -0.27 63.78 180.46

I did another test, and rather than rotating 180*, pausing/reading and then continuing another 180*, I instead rotate 180*, then rotate backwards 180*.

In this test, the numbers are consistent. For example:

Rotate forward 180*: x = 32, y = -15, z = -84

Rotate back 180*: x = -42, y = -38, z = -45

Rotate forward 180*: x = 32, y = -15, z = -84

Rotating back and forth 180* gives me the same sets of readings at teach location.

So it is consistent, but if it takes 3 full rotations to get back to the same number, how can I use it as a compass?

Thanks,
-Matt

Magnetometers don't work "out of the box" and must be individually calibrated, in place, to work. If I correctly understand the data you posted, an XY magnetometer data plot should ideally be a circle centered on the origin. The compass program assumes that to be the case, when using atan2().

Here is what that plot looks like instead:
xy.png

This forum post describes a difficult case and goes over some of the options for calibration: https://forum.pololu.com/t/correcting-the-balboa-magnetometer/14315

I recommend Pololu's code to make a tilt-compensated compass. Assuming the sensor is calibrated, the code you have still requires that it be held level, with Z vertical, to get accurate readings.

xy.png

I've started reading some articles on calibration. The literature on Adafruit's website for this sensor says that "The LSM303 chips are factory calibrated to a level of accuracy sufficient for many purposes." I guess a compass isn't one of those purposes.

I have the chip level, with the Z axis vertical, at least as I understand the label on the board itself. I would say it's very close to level, but maybe not perfect to 0.00 degrees.

I've included a couple of 3D graphs of the output. This represents 3 full rotations of the sensor, with a coordinate recorded at every step. (500 steps per rotation). Where the colors change represents a the end/start of a rotation. Neat looking, but this won't be usable until i figure out the calibration.

Anyway, thanks for your response. I will attempt calibration and see what I come up with.

The very best calibration tutorial can be found here: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

I tried the calibration steps on the Adafruit website. I took the LSM303 and flipped and rotated it all around for a few minutes, while recording. I was surprised to see the output was pretty close to a sphere.

I used the numbers and plugged them into the equation for deriving the correct coordinates. I guess it seems closer, but still the fact remains that it take 3 full rotations to get back to the original heading.

I will try the second link you sent for calibration, and see if the results are better.

The sphere has to be centered on the origin to be useful.

I do not see how it is possible that three rotations could get you back to the start. The atan2() function doesn't work that way.

For informed help, post the complete revised code, using code tags as described in the "How to use this forum" post.

I am using a stepper motor to rotate the LSM303DHLC. There are 200 steps in a rotation, and I am using gears with a 2.5:1 ratio. So every 500 steps is 1 complete rotation of the LSM303. After 3 complete rotations (1500 steps), the heading reading is the same as when it began. The same pattern repeats itself.

I've included a graph of the heading readings. You can see the pattern starts to repeat at 1500, which is the beginning of the 3rd rotation.

If i do one full roatation forward, and then one full rotation back, the heading is the same. However, if I do one full rotation forward, and then a second full rotation forward, the reading is different, even though the LSM303 is in the same position as when it started. If it is sensing magnetic field, i don't understand how that can be. But the max x, y, and z values are different.

This is all true after trying two different methods to calibrate.

I have also attached the entire sketch (rather than paste it in here).

Compass_Test_Sketch.txt (2.04 KB)

To get to the bottom of this, please take the compass module off of the stepper and while it is flat on the table, away from iron or magnetic objects, slowly rotate it by hand through 360 degrees. Print out the magnetometer x,y and heading readings and post that.

Your sketch is posted properly below. In the future, please use code tags to post code.

What do you think the "- -" would do in the following line? It is certainly not something I would write.

(event.magnetic.x - -151.34)

Since those lines are commented out, where are you correcting your raw readings? Where are x and y calculated?

Please post the code you are actually using.

#include <Adafruit_LSM303DLH_Mag.h>
#include <Adafruit_MotorShield.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

#define OLED_RESET  16  // Pin 15 -RESET digital signal

#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16

Adafruit_LSM303DLH_Mag_Unified mag = Adafruit_LSM303DLH_Mag_Unified(12345);
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 2);

float minval = 360;
float maxval = -1;

int i = 0;
int dir = 0;

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

  AFMS.begin();

//  Serial.println("Magnetomeeter Test!");


 // Serial.println("Deg     X     Y     Z     Heading     ");
  
  /* Initialise the sensor */
  if (!mag.begin()) {
    /* There was a problem detecting the LSM303 ... check your connections */
 //   Serial.println("problem...");
    while (1)
      ;
  }
}

void loop(void) {
 
  myMotor->step(1, FORWARD, MICROSTEP); 
  /* Get a new sensor event */
  sensors_event_t event;
  mag.getEvent(&event);

  float x = 0.00;
  flo y = 0.00;
  flo z = 0.00;

  float Pi = 3.14159;
if (i == 499 || i == 999 || i == 1499 || i == 1999) {
  delay(2000);
}
//  if (i > 359) {
//    i -= 360;
//  }

//(((RawValue – RawLow) * ReferenceRange) / RawRange) + ReferenceLow
//  x = (((event.magnetic.x - -151.34) * 360) / 316.65) + -180;
//  y = (((event.magnetic.y - -181.08) * 360) / 300.01) + -180;
//  z = (((event.magnetic.z - -80.82) * 360) / 224.99) + -180;

  Serial.print(i);
  Serial.print("     ");
  // Calculate the angle of the vector y,x
  Serial.print(event.magnetic.x);
  Serial.print(" ");
  Serial.print(event.magnetic.y);
  Serial.print(" ");
  Serial.print(event.magnetic.z);
  Serial.print(" ");

  float heading = (atan2(y, x) * 180) / Pi;

  // Normalize to 0-360
  if (heading < 0) {
    heading = 360 + heading;
  }

  if (heading > maxval) {
    maxval = heading;
  }
  if (heading < minval) {
    minval = heading;
  }
  Serial.print(heading);
  Serial.println();

  i++;
  delay(100);
}

So i guess you are on to something with where i have it mounted. When i put it flat on the table and turn it by hand, it seems pretty consistent and accurate.

I had a spacer where I mounted the LSM303 to use with the stepper. In my setup, the LSM is not directly mounted to the stepper. I use a gears and chains. The LSM is about 6 inches away from the motor itself. I re-mounted it, adding another spacer and the results are improved. There is still a pattern every 3 rotations, but i guess this is due to interference from the motor/and or mount?

as for that bit of code, i am subtracting a negative. I guess i can just add? Or use variables? It seemed to work the way I had it. I commented out those calibrations to re-capture raw-data, in case my equations were wrong and i was further complicating things.

I am going to try different mounting positions, since that seems to have a bigger impact than I realized. If i can get it consistent, maybe i'll have better luck with calibrations.

Thanks for all your help on this. I appreciate you taking the time.

To subtract a negative number, you add the positive number. "--" is the C/C++ decrement operator.

Please revise your code so that it actually applies the corrections.

Make sure that the corrected result makes sense by printing out the corrected x,y readings, and plotting them.

After re-mounting the LSM303 a little further away from the motors, and changing out the screws for nylon, then running the calibration procedures again, it is much more consistent, and seems pretty accurate. When i tell it to point north, it's with in a couple degrees of my digital compass.

Thanks for all your help

Glad you solved the problem! Two degrees error is about the best you can expect.