Getting a compass heading with a LIS3MDL

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
bsautner
 
Posts: 12
Joined: Tue Jul 19, 2016 3:04 pm

Getting a compass heading with a LIS3MDL

Post by bsautner »

I've been really stuck trying to get a compass heading of 0 to 360 degrees from an LIS3MDL Magnometer.
When i rotate the sensor on X/Y i get numbers that don't make sense. Right now a full rotation gives me seemingly random values between 100 and 120 degrees.

Simple python but i'm missing something here, any help would be appreciated

Code: Select all

import time
from math import atan2, degrees

import board
import adafruit_lis3mdl

i2c = board.I2C()  # uses board.SCL and board.SDA
sensor = adafruit_lis3mdl.LIS3MDL(i2c)

def vector_2_degrees(x, y):
   angle = degrees(atan2(y, x))
   if angle < 0:
        angle += 360
   return angle

def get_heading(_sensor):
     magnet_x, magnet_y, _ = _sensor.magnetic
     return vector_2_degrees(magnet_x, magnet_y)

def run():
    while True:
        print("heading: {:.2f} degrees".format(get_heading(sensor)))
        time.sleep(1.0)

if __name__ == '__main__':
    run()
.

I was able to calibrate it but I also can't find a doc on how to apply these calibration numbers to the above code.
Screenshot-20220630142835-896x620.png
Screenshot-20220630142835-896x620.png (156.68 KiB) Viewed 141 times

I also am getting the same results from this arduio - North is 115, East and West are both 104 etc

Code: Select all

void loop() {
  lis3mdl.read();      // get X Y and Z data at once
  sensors_event_t event; 
  lis3mdl.getEvent(&event);
  float heading = (atan2(event.magnetic.y, event.magnetic.x) * 180) / M_PI;

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

  delay(100); 
  Serial.println();
}

User avatar
sj_remington
 
Posts: 998
Joined: Mon Jul 27, 2020 4:51 pm

Re: Getting a compass heading with a LIS3MDL

Post by sj_remington »

Unfortunately, magnetometers do not work "out of the box" as compasses. It is absolutely critical to apply the calibration constants (subtract the offsets from the raw readings, and apply the correction matrix).

I'm not a python person, but here is generic C/C++ Arduino code that I use to correct an LSM9DS1 magnetometer, among others:

Code: Select all

    float M_B[3] = { -922.31, 2199.41,  373.17}; //offsets

    float M_Ainv[3][3] =
    { {  1.04492,  0.03452, -0.01714},
      {  0.03452,  1.05168,  0.00644},
      { -0.01714,  0.00644,  1.07005}
    };

    ...
 // Mxyz[] are the raw magnetometer readings, which are corrected in place

  //apply offsets (bias) and scale factors from Magneto

  for (int i = 0; i < 3; i++) temp[i] = (Mxyz[i] - M_B[i]);
  Mxyz[0] = M_Ainv[0][0] * temp[0] + M_Ainv[0][1] * temp[1] + M_Ainv[0][2] * temp[2];
  Mxyz[1] = M_Ainv[1][0] * temp[0] + M_Ainv[1][1] * temp[1] + M_Ainv[1][2] * temp[2];
  Mxyz[2] = M_Ainv[2][0] * temp[0] + M_Ainv[2][1] * temp[1] + M_Ainv[2][2] * temp[2];
As I imagine you are aware, the expression below gives accurate results only if the magnetometer Z axis is close to vertical:

Code: Select all

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

User avatar
bsautner
 
Posts: 12
Joined: Tue Jul 19, 2016 3:04 pm

Re: Getting a compass heading with a LIS3MDL

Post by bsautner »

Thanks, I think that did it. Here is a gist of the complete solution with my calibrations

https://gist.github.com/bsautner/c0ebca ... 4a31416149

User avatar
sj_remington
 
Posts: 998
Joined: Mon Jul 27, 2020 4:51 pm

Re: Getting a compass heading with a LIS3MDL

Post by sj_remington »

I'm surprised that the X and Z diagonal terms show such large deviations from 1, indicating very significant differences in the gains of the X, Y and Z magnetometers. However, if you are happy with the accuracy of the resulting compass, great!

Code: Select all

    float M_Ainv[3][3] =
    { {  0.880410, 0.007441, -0.086474},
      {  0.007441, 1.015980, 0.021557},
      { -0.086474, 0.021557, 0.793583}
    };

User avatar
bsautner
 
Posts: 12
Joined: Tue Jul 19, 2016 3:04 pm

Re: Getting a compass heading with a LIS3MDL

Post by bsautner »

thanks for all of your help. Still not perfect but today's facepalm moment was when I pressed down on the magnetometer and found the connection in my breadboard was wonky.

another re-calibration and things are getting tighter, thanks again

M_B = [ -7.55,-15.43, 27.08]
M_Ainv = [[0.993, 0.017, -0.016], [0.017, 0.995, -0.009], [-0.011, -0.011, 1.021]]

User avatar
sj_remington
 
Posts: 998
Joined: Mon Jul 27, 2020 4:51 pm

Re: Getting a compass heading with a LIS3MDL

Post by sj_remington »

That looks much better!

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”