[SOLVED] ISSUE: MPU9250 Sensor: Can't get correct data from Magnetometer

Hi,
I'm pretty new here so sorry if my mistake is too obvious..
I am learning about the sensor MPU9250 Acc & Gyro & Magnetometer.
I want to know how to get the raw data straight from the registers instead of using a library..
I manage to read the raw data from the registers of the accelerometer and gyro and convert them into the correct values in the units I wanted, so the accelerometer and gyro work good for me.

The problem is when I read the raw data from the Magnetometer registers.
it was quite a headache at the start just to communicate with the magnetometer but I manage to do it eventually after finding out that I need to turn the bypass.
After I did it I got the right values from the Device ID register (0x00) I also set the CONTROL 1 register into Fuse ROM Access Mode, got the adjustment sensitivity values for each axis, turned the CONTROL 1 into Sleep Mode and then into Continuous Measurement Mode 2 (I saw at some post that someone said after getting into Fuse ROM Mode you need to go to Sleep Mode before changing to any other mode)

The problem is this: I get the raw register data, convert it to MicroTesla(uT) units but the values seem to be too low. I read that earth magnetic field is somewhere between 20-65 uT and I get read between the values -3 and +3.. I searched everywhere at the internet but couldn't find why it doesn't work for me and where is my mistake. I think maybe it has something to do with the types I chose for the parameters but I got no idea why it's wrong..

anyone got an idea what is wrong?

I think it's important to say that I also used SpearkFun library just to check that the sensor is alright and when I used SparkFun library the values of the magnetometer were good.

I tried to find all over the internet the full code of this library (and also for Kriswiner library because I read that the SparkFun library is based on this) but no where to find the full code, I found some parts of the code but always the part I needed was not public..

Anyone know what is wrong? I am really lost over here..

I am adding my full code here + results from the serial monitor

Thanks a lot for reading this

My Code:

#include "Wire.h"


const int MPU_ADDR = 0x68; // 0x say that we are working with hex numbers and 68 is the acc & gyro sensor adress in hex (from data sheet)
const int MAG_ADDR = 0x0C; // 0x say that we are working with hex numbers and 0C is the magnetometer sensor adress in hex (from data sheet)

int8_t Device_ID;
int8_t mag_xL, mag_xH, mag_yL, mag_yH, mag_zL, mag_zH;

int16_t mag_x, mag_y, mag_z;
int16_t acc_x, acc_y, acc_z;
int16_t gyr_x, gyr_y, gyr_z;
int16_t temp;

int8_t Msens_x,Msens_y, Msens_z;

int8_t control_1;
int8_t status_1;
int8_t status_2;
float Yaw;
float asax, asay, asaz;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  
  // Acc & Gyro Registers********************************************
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6A);   // USER CONTROL  
  Wire.write(0x00);   // 0x00 is reset value
  Wire.endTransmission(true);

  Wire.beginTransmission(MPU_ADDR);   
  Wire.write(0x37);   //  IMU INT PIN CONFIG    
  Wire.write(0x02);   //  0x02 activate bypass in order to communicate with magnetometer
  Wire.endTransmission(true);
  delay(200);

  // Magnetometer Registers*****************************************
  Wire.beginTransmission(MAG_ADDR); 
  Wire.write(0x0B);   //  CONTROL 2
  Wire.write(0b01);   //  0 NORMAL OR 1 RESET
  Wire.endTransmission(true);
  delay(200);

  Wire.beginTransmission(MAG_ADDR);   //SLEEP MODE
  Wire.write(0x0A);   //  CONTROL 1
  Wire.write(0b00010000);   // 1 for 16 bit or 0 for 14 bit output, 0000 SLEEP MODE
  Wire.endTransmission(true);
  delay(200);

  
  Wire.beginTransmission(MAG_ADDR);   //ROM WRITE MODE
  Wire.write(0x0A);   //  CONTROL 1
  Wire.write(0b00011111); // 1 for 16 bit or 0 for 14 bit output, 1111 FUSE ROM ACCESS MODE
  Wire.endTransmission(true);
  delay(200);

  Wire.beginTransmission(MAG_ADDR);   //GET MAGNETIC SENSITIVITY DATA FOR CONVERTING RAW DATA
  Wire.write(0x10);     //  ASAX  
  Wire.endTransmission(false);
  Wire.requestFrom(MAG_ADDR, 3 , true);  //GET SENSITIVITY ADJUSMENT VALUES STARTS AT ASAX
  Msens_x = Wire.read();    //GET X SENSITIVITY ADJUSMENT VALUE
  Msens_y = Wire.read();    //GET Y SENSITIVITY ADJUSMENT VALUE
  Msens_z = Wire.read();    //GET Z SENSITIVITY ADJUSMENT VALUE
  Serial.println(Msens_x);
  Serial.println(Msens_y);
  Serial.println(Msens_z);
  Wire.endTransmission(true);
  asax = (((Msens_x-128))/256.0f)+1.0f;
  asay = (((Msens_y-128))/256.0f)+1.0f;
  asaz = (((Msens_z-128))/256.0f)+1.0f;
  Serial.print("Mx Sensitivity: ");  Serial.println(asax);
  Serial.print("My Sensitivity: ");  Serial.println(asay);
  Serial.print("Mz Sensitivity: ");  Serial.println(asaz); 
  delay(200);
  
  Wire.beginTransmission(MAG_ADDR);   //SLEEP MODE
  Wire.write(0x0A);   //  CONTROL 1
  Wire.write(0b00010000);  // 1 for 16 bit or 0 for 14 bit output, 0000 SLEEP MODE
  Wire.endTransmission(true);
  delay(200);
    
  Wire.beginTransmission(MAG_ADDR);   //CONT MODE 2
  Wire.write(0x0A);
  Wire.write(0b00010110); // 1 for 16 bit or 0 for 14 bit output, 0110 FOR CONT MODE 2 (X Hz?) 
  Wire.endTransmission(true);
  delay(200);
}

void loop() {
  Wire.beginTransmission(MAG_ADDR);
  Wire.write(0x00);
  Wire.endTransmission(false);
  Wire.requestFrom(MAG_ADDR, 1 , true);   
  Device_ID = Wire.read();
  Serial.print("Device_ID: "); Serial.println(Device_ID,DEC);  
  Wire.endTransmission(true);
 

  Wire.beginTransmission(MAG_ADDR);
  Wire.write(0x0A);
  Wire.endTransmission(false);
  Wire.requestFrom(MAG_ADDR, 1 , true);  
  control_1 = Wire.read();  // check DRDY bit if ready to read
  Serial.print("control_1: "); Serial.println(control_1,BIN);  
  Wire.endTransmission(true);

  Wire.beginTransmission(MAG_ADDR);
  Wire.write(0x02);
  Wire.endTransmission(false);
  Wire.requestFrom(MAG_ADDR, 1 , true);   
  status_1 = Wire.read();
  Serial.print("Status 1: "); Serial.println(status_1,BIN);  
  Wire.endTransmission(true);

  if(status_1 == 0b11) {
    Wire.beginTransmission(MAG_ADDR);
    Wire.write(0x03);
    Wire.endTransmission(false);
    Wire.requestFrom(MAG_ADDR, 7 , true);
   
    mag_xL = Wire.read();
    mag_xH = Wire.read();
    mag_x = (mag_xH << 8) | mag_xL;

    Serial.print("Available bytes left after reading mag x values: "); Serial.println(Wire.available(),DEC);    
    
    mag_yL = Wire.read();
    mag_yH = Wire.read();
    mag_y = (mag_yH << 8) | mag_yL;    
    
    Serial.print("LOW BITS Mag_Y: "); Serial.println(mag_yL,BIN);  
    Serial.print("HIGH BITS Mag_Y: "); Serial.println(mag_yH,BIN);
    Serial.print("FULL BITS Mag_Y: "); Serial.println(mag_y,BIN);    
    
    mag_zL = Wire.read();
    mag_zH = Wire.read();
    mag_z = (mag_zH << 8) | mag_zL;

    
    status_2 = Wire.read();   // check if there is a magnetic overflow 
    Serial.print("Status 2: "); Serial.println(status_2,BIN);  
    
  //if(status_2 != 0x08)
    
    
    
    Wire.endTransmission(true);

    Serial.print("  | mX = "); Serial.print(mag_x*asax*0.15); Serial.print(" [uT]");
    Serial.print("  | mY = "); Serial.print(mag_y*asay*0.15); Serial.print(" [uT]");
    Serial.print("  | mZ = "); Serial.print(mag_z*asaz*0.15); Serial.println(" [uT]");
  }
   
  
  //  Yaw = (atan2(mag_y,mag_x)*(180/3.141));
  //Serial.println(Yaw);  
  

  Serial.println("-------------------------------END OF THE LOOP ---------------------------");
  delay(500);

}

Results are added in a picture because I couldn't copy it for some reason

I was rotating the sensor while doing the measurements.

I have also added my code in a file so it will be maybe easier to see it

aaa.ino (5.36 KB)

Don't do any scaling with the raw values -- remove the asax*0.15, etc. terms in the code below.

    Serial.print("  | mX = "); Serial.print(mag_x*asax*0.15); Serial.print(" [uT]");
    Serial.print("  | mY = "); Serial.print(mag_y*asay*0.15); Serial.print(" [uT]");
    Serial.print("  | mZ = "); Serial.print(mag_z*asaz*0.15); Serial.println(" [uT]");

You do NOT need to know the units OR the scale factor to make a compass, only the relative X and Y values. You should expect raw vector magnitudes in the range of 50-100 for the magnetometer, depending on where you live.

The MPU-9250 accelerometer and magnetometer have poor performance and BOTH must be carefully calibrated before the data can be used, with individually redetermined relative scale factors and offsets FOR EACH AXIS. I recommend this tutorial for calibration.

I also recommend to use a well debugged library for the MPU-9250 and have attached a zip file with one, and a working Arduino program to collect raw data for proper calibration. Perhaps you will find the library code useful and informative.

Also included is code for a fully debugged tilt-compensated compass and an AHRS (Madgwick/Mahony 3D orientation filter).

This is for the Sparkfun MPU-9250 module but should work for others. Avoid the Sparkfun and Kris Winer MPU-9250 code -- both have major errors and generate nonsensical results.

New_MPU9250.zip (180 KB)

First, thanks a lot for you answer!

jremington:
Don't do any scaling with the raw values -- remove the asax*0.15, etc. terms in the code below.

    Serial.print("  | mX = "); Serial.print(mag_x*asax*0.15); Serial.print(" [uT]");

Serial.print("  | mY = "); Serial.print(mag_yasay0.15); Serial.print(" [uT]");
    Serial.print("  | mZ = "); Serial.print(mag_zasaz0.15); Serial.println(" [uT]");




You do NOT need to know the units OR the scale factor to make a compass, only the relative X and Y values. You should expect raw vector magnitudes in the range of 50-100 for the magnetometer, 
depending on where you live.

I changed it as you said, I still get very strange data.. I put the sensor on a horizontal table, and spin it around his axis while taking measures and printing only the raw data as you recommended.
the data is going like this for a full spin:
the values I get are like that for a full spin:
X axis: the raw data starts(from the angle I put it) at -160 going up continuously until about +125 the strange part is that once it reach +125 it jumps straight to -125 then going up continuously until about -70 then change direction and going back down until -125 and jump again from -125 to +125 and going down continuously this time from +125 until -125..
I cant understand why the raw data behave this way, shouldn't it be continuously? and idea where my mistake is?
Y axis: kind of the same problem, data jump at some values and no idea why... also the Y value I get from the raw data are at the range of about -120 to +370 isn't that out of the range I should be expecting to get from the raw data? maybe something is wrong with my code? I really have no idea and I searched everywhere, couldn't find the answer for it :S
Z axis: because I spin it around a horizontal table the Z values haven't changed that much just from the range of -30 to -100. when I spin it around other axis the values I got have the same problem and the values range are between -120 to +300

I added 5 pictures with the results I got from 1 spin.
any idea what is wrong? really breaking my head on that for few days already..

jremington:
The MPU-9250 accelerometer and magnetometer have poor performance and BOTH must be carefully calibrated before the data can be used, with individually redetermined relative scale factors and offsets FOR EACH AXIS. I recommend this tutorial for calibration.

Thank you for that information! I didn't know that the scale factors and offsets I get with the sensor are wrong.. I thought that the values I got with the sensor are kind of right and I will just need to adjust it abit according to my location in order to get more accurate results..
I read the tutorial you recommended few days ago, it helped me a lot for understanding better what is needed to be done in order to fix the hard iron and soft iron errors. but I thought that I will get to that part after the data I get from the sensor will make sense. For now I can't understand the raw data I get, it doesn't make any sense because it's not continuously and does strange things.

jremington:
I also recommend to use a well debugged library for the MPU-9250 and have attached a zip file with one, and a working Arduino program to collect raw data for proper calibration. Perhaps you will find the library code useful and informative.

Also included is code for a fully debugged tilt-compensated compass and an AHRS (Madgwick/Mahony 3D orientation filter).

This is for the Sparkfun MPU-9250 module but should work for others. Avoid the Sparkfun and Kris Winer MPU-9250 code -- both have major errors and generate nonsensical results.

Thanks alot for the library and the tip not using those libraries, I had no idea!

This line is why the magnetometer data values seem to jump:

int8_t Msens_x,Msens_y, Msens_z;

The range of values for int8_t data type is -128 to 127, which also means that the scaling is incorrect.

  asax = (((Msens_x-128))/256.0f)+1.0f;

Declare those variables "int" or "int16_t" instead and your code may be OK.

jremington:
This line is why the magnetometer data values seem to jump:

int8_t Msens_x,Msens_y, Msens_z;

The range of values for int8_t data type is -128 to 127, which also means that the scaling is incorrect.

  asax = (((Msens_x-128))/256.0f)+1.0f;

Declare those variables "int" or "int16_t" instead and your code may be OK.

Thank you!

That fixed my problem, I changed

 int8_t mag_xL, mag_xH, mag_yL, mag_yH, mag_zL, mag_zH;

to

 int16_t mag_xL, mag_xH, mag_yL, mag_yH, mag_zL, mag_zH;

and now it works!
I thought that because those values read only from one register each and a one register returns 8 bits so they should be also 8 bit but I guess I was wrong for some reason..

anyway thanks a lot!

The output of each axis of each sensor consists of two 8 bit register values, which must be combined into one 16 bit variable.

Here only mag_x needs to be a 16 bit integer variable. mag_xH and mag_xL can be 8 bit, of type char or uint8_t.

    mag_xL = Wire.read();
    mag_xH = Wire.read();
    mag_x = (mag_xH << 8) | mag_xL;

jremington:
The output of each axis of each sensor consists of two 8 bit register values, which must be combined into one 16 bit variable.

Here only mag_x needs to be a 16 bit integer variable. mag_xH and mag_xL can be 8 bit, of type char or uint8_t.

    mag_xL = Wire.read();

mag_xH = Wire.read();
    mag_x = (mag_xH << 8) | mag_xL;

You are right!
I change it also from int8_t to uint8_t or char and it also works!

So now I think that I understand not only where I was wrong but also why I was wrong:
I think that because the full 16 bit value (representing the sensor value for each axis) is stored inside two different 8 bit registers, and because I was reading 8 bit separately from each register and storing the values inside two different parameters, so I can't allow those parameters separately to be negative, only after I add those 8 bits together into the full 16 bit I should allow it to be negative, because only the full 16 bit number representing the real value from the sensor.

Thanks a lot for your help!