Magnetometer headings won’t change more than 10 degrees

I’m using the 15423 Microcontroller at https://www.amazon.com/dp/B081QXS6VY/re … 50_TE_item, and the MMC5983MA Magnetometer at https://www.amazon.com/dp/B0B75WCC15/re … 50_TE_item, with the example sketch called “Example2-I2C_Digital_compass.ino”. There is a LED stick between the microcontroller and the magnetometer. The only change I made to the example sketch is add this line ```
#define Serial SerialUSB

Magnetometers must be calibrated before they can be used as a compass.

Best method, tutorial and overview: https://thecavepearlproject.org/2015/05 … r-arduino/

Quick and dirty, simple approach: https://www.appelsiini.net/2018/calibrate-magnetometer/

Thank you for those articles. The easiest code (that isn’t Python, which I don’t know yet) I could find was Kris Winer’s (that the second article, that you gave me, has a link to). However, I don’t know whether that code (at https://github.com/kriswiner/MPU6050/wi … alibration) can be used with my magnetometer and sketch, and what the following two parameters (in Kris’s code) are.

void magcalMPU9250(float * dest1, float * dest2)

Thank you.

That code is specific for the MPU-9250. You will need to read the raw data from your magnetometer, and either process them with simple min/max code or with Magneto.

Here is a step-by-step tutorial showing the results of both approaches, for yet a different magnetometer: https://forum.pololu.com/t/correcting-t … eter/14315

I couldn’t find the simple min/max code, and I don’t know if I’m advanced enough to use it. I wish calibration was mentioned in the Hookup Guide (at https://learn.sparkfun.com/tutorials/qw … al-compass), to let me know that this might be too advanced for me. But I still want to try, and would appreciate your help.

As described, the program calibrate2.ino was in the zip file balboa_mag.zip, appended to the post.

#include <Wire.h>
#include <LIS3MDL.h>
// mods by SJR 3/2018

LIS3MDL mag;
LIS3MDL::vector<int16_t> running_min = {32767, 32767, 32767}, running_max = { -32768, -32768, -32768};

char report[80];
LIS3MDL::vector<int16_t> m_off, m_scl;

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  
  // enter keyboard character and hit "send" to start
  while (!Serial.available()); //wait for keyboard entry

  if (!mag.init())
  {
    Serial.println("Failed to detect and initialize magnetometer!");
    while (1);
  }

  mag.enableDefault();
  unsigned long start = millis();

  // calibration loop. Enter a keyboard character to start, then rotate Balboa in 3D, exploring all orientations
  // loop until no change in max/min withing 10 seconds

  Serial.println("Collecting calibration data");

  while (millis() - start < 10000UL) {
    delay(100);
    mag.read();
    snprintf(report, sizeof(report), "%+6d,%+6d,%+6d",
             mag.m.x, mag.m.y, mag.m.z);
    Serial.println(report);
    if (mag.m.x < running_min.x) {
      running_min.x = mag.m.x;
      start = millis();
    }
    if (mag.m.x > running_max.x) {
      running_max.x = mag.m.x;
      start = millis();
    }
    if (mag.m.y < running_min.y) {
      running_min.y = mag.m.y;
      start = millis();
    }
    if (mag.m.y > running_max.y) {
      running_max.y = mag.m.y;
      start = millis();
    }
    if (mag.m.z < running_min.z) {
      running_min.z = mag.m.z;
      start = millis();
    }
    if (mag.m.z > running_max.z) {
      running_max.z = mag.m.z;
      start = millis();
    }
  }

  Serial.println("Done collecting calibration data");
  snprintf(report, sizeof(report), "min: {%+6d, %+6d, %+6d}   max: {%+6d, %+6d, %+6d}",
           running_min.x, running_min.y, running_min.z,
           running_max.x, running_max.y, running_max.z);
  Serial.println(report);
  m_off.x = (running_max.x + running_min.x) / 2;
  m_off.y = (running_max.y + running_min.y) / 2;
  m_off.z = (running_max.z + running_min.z) / 2;
  m_scl.x = (running_max.x - running_min.x) / 2;
  m_scl.y = (running_max.y - running_min.y) / 2;
  m_scl.z = (running_max.z - running_min.z) / 2;
  snprintf(report, sizeof(report), "off: {%+6d, %+6d, %+6d}   scl: {%+6d, %+6d, %+6d}",
           m_off.x, m_off.y, m_off.z,
           m_scl.x, m_scl.y, m_scl.z);
  Serial.println(report);
  delay(10000); //show for 10 seconds
}

// print out some centered and scaled magnetometer values, while user rotates Balboa in space.
// This simple scaling approach does not work well due to severe hard iron distortion

void loop()
{
  float m[3];
  mag.read();
  m[0] = ((float) mag.m.x - m_off.x) / m_scl.x;
  m[1] = ((float) mag.m.y - m_off.y) / m_scl.y;
  m[2] = ((float) mag.m.z - m_off.z) / m_scl.z;
  for (int i = 0; i < 3; i++) {
    Serial.print(m[i]);
    Serial.print(",");
  }
  Serial.println();
  delay(200);
}

Thank you. :: and <> (as in <int16_t>) and %+ (which are in that code) are new to me. Before I learn about those three and the rest of that code, would you please confirm whether part of that code can be used with “Example2-I2C_Digital_compass.ino”? I compared one of those two codes (on the left of the screen) to the other code (on the right), and I can’t figure out how to combine the two.

jb’s_sf:
I wish calibration was mentioned in the Hookup Guide (at https://learn.sparkfun.com/tutorials/qw … al-compass), to let me know that this might be too advanced for me.

Can I get your opinion about that?

Is learning Python the easiest way?

You really don’t need to work about <int16_t>, or the sprintf formatting commands. The important part of that code is to analyze several hundred raw data points that you get from orienting the magnetometer in many different ways, and accumulate the minimum and maximum values for each axis.

Those values are then used to compute offsets and scale factors for each axis.

Here:

    if (mag.m.x > running_max.x) {
      running_max.x = mag.m.x;
      start = millis();
    }
    if (mag.m.y < running_min.y) {
      running_min.y = mag.m.y;
      start = millis();
    }
    if (mag.m.y > running_max.y) {
      running_max.y = mag.m.y;
      start = millis();
    }
    if (mag.m.z < running_min.z) {
      running_min.z = mag.m.z;
      start = millis();
    }
    if (mag.m.z > running_max.z) {
      running_max.z = mag.m.z;
      start = millis();
    }
  }

  Serial.println("Done collecting calibration data");
  snprintf(report, sizeof(report), "min: {%+6d, %+6d, %+6d}   max: {%+6d, %+6d, %+6d}",
           running_min.x, running_min.y, running_min.z,
           running_max.x, running_max.y, running_max.z);
  Serial.println(report);
  m_off.x = (running_max.x + running_min.x) / 2;  // x offset
  m_off.y = (running_max.y + running_min.y) / 2; // y offset
  m_off.z = (running_max.z + running_min.z) / 2; // z offset
 
  m_scl.x = (running_max.x - running_min.x) / 2;  // x scale
  m_scl.y = (running_max.y - running_min.y) / 2;  // y scale
  m_scl.z = (running_max.z - running_min.z) / 2;  // z scale

Python is easier to learn than C/C++ but for microprocessors, it is very slow, limited in applications and IMO simply not useful. Python is fine on PCs or large, fast computer systems.

Thank you. The example sketch only does the following for Y, but not X or Z.

  // Magnetic north is oriented with the Y axis
  // Convert the X and Y fields into heading using atan2 (Arc Tangent 2)
  heading = atan2(normalizedX, 0 - normalizedY);

I’m not sure if my magnetometer can give me data in three dimensions, but maybe it can give me data in two dimensions. If I give you the full sketch (part of which is in the above reply), if you know trigonometry, would you consider giving me two lines of code, one for the X dimension and the other for Z? That way, I might be able to figure out how to use those two lines with the code (for calibrating) that you already gave me. Thank you.

Is it a problem that SparkFun’s example magnetometer sketch doesn’t give numbers in as many dimensions as the sketch (for calibrating) that you gave me needs, if that is the case? If I showed it to you, would you tell me how many dimensions the example gives numbers in?

It seems like you might get this question so often that you are too tired to explain the answer, but I would really appreciate an answer. Are example sketches supposed to be complete, as in complete without calibration? The answer may let me know what I may get into if I try an example sketch that I think might do something, before I decide whether to buy the board that goes with the example sketch. Again, the example sketch I’m having a problem with is at IDE → File → Examples → SparkFun MMC5983MA Magnetometer Arduino Library → Example2-I2C_Digital_compass. I’m not sure whether I should show raw values, normalized values, and headings. Thank you.

How can I tell whether the board is broken or just needs calibration? Does the answer have to do with whether the heading accuracy is plus-minus 0.5 degrees or better, as advertised at SparkFun’s Description page, Features page, and Hookup Guide? Can I share raw values with you? Thank you.

This recent thread on the SparkFun Micro Magnetometer - MMC5983MA (Qwiic) should be useful:

https://forum.sparkfun.com/viewtopic.php?f=83&t=59578