MMC5883MA-B interfacing using I2C : magnetometer readings inaccurate

Hey everyone,
This is my first time trying to interface using I2C, and I seem to be having trouble reading accurate data. I don't quite understand LSB and MSB, so it might be a problem with my bit shifting.

I am trying to use the magnetometer to calculate the magnitude of the Earth's magnetic field, and instead of getting ~500 milligauss the data I get is closer to ~14000 milligauss.

I attached my code below and wondered if anyone had any suggestions.

#define ADD_MAIN 0x30 //0110000b, address of magnetometer
//REGISTER ADDRESSES
#define ADD_LX 0x00
#define ADD_HX 0x01
#define ADD_LY 0x02
#define ADD_HY 0x03
#define ADD_LZ 0x04
#define ADD_HZ 0x05
#define ADD_TEMP 0x06
#define ADD_STAT 0x07
#define ADD_IC0 0x08
#define ADD_IC1 0x09
#define ADD_IC2 0x0A
#define ADD_TX 0x0B
#define ADD_TY 0x0C
#define ADD_TZ 0x0D


#include "Wire.h"
#include "math.h"

void prntAll ();

uint8_t stat = 0b00000000;
uint16_t xm, ym, zm;
uint8_t xl, yl, zl, xh, yh, zh = 255;
double x, y, z;

void measure()
{
   //initiate measurement
  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC0);  
  Wire.write(0x07);
  Wire.endTransmission();

  //check status for measurement completion
  do 
  {
    Wire.beginTransmission(ADD_MAIN);
    Wire.write(ADD_STAT); 
    Wire.endTransmission(true);
    Wire.requestFrom(ADD_MAIN,1);
    stat = Wire.read();
    Wire.endTransmission();
  }while ((stat & 0b00000001) != 0b00000001);
}

void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  Serial.begin(9600);

  //set 
  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC0);  
  Wire.write(00001000);
  Wire.endTransmission();

  //set measurement values: make sure none of the measurements are disabled
  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC1);
  Wire.endTransmission(true);
  Wire.requestFrom(ADD_MAIN,1);
  uint8_t cont = 0b00000000;
  cont = Wire.read();
  Serial.print ("cont1: ");
  Serial.println (cont);
  Wire.endTransmission();

  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC1);
  Wire.write(0b01100000);
  Wire.endTransmission();

  //set measurement values: make sure none of the 
  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC2);
  Wire.endTransmission(true);
  Wire.requestFrom(ADD_MAIN,1);
  cont = 0b00000000;
  cont = Wire.read();
  Serial.print ("cont2: ");
  Serial.println (cont);
  Wire.endTransmission();

  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_IC2);
  Wire.write(01100001);
  Wire.endTransmission();
}

void loop() {

  measure();
  Wire.beginTransmission(ADD_MAIN);
  Wire.write(ADD_LX);
  Wire.endTransmission(true);
  Wire.requestFrom(ADD_MAIN,6);
  if (6 <= Wire.available())
  {
    xl = Wire.read();
    xh = Wire.read();
    yl = Wire.read();
    yh = Wire.read();
    zl = Wire.read();
    zh = Wire.read();
  }
  Wire.endTransmission(false);
  Serial.print("xlsb: " );
  Serial.println(xl);
  Serial.print("xmsb: " );
  Serial.println(xh);
  Serial.print("ylsb: " );
  Serial.println(yl);
  Serial.print("ymsb: " );
  Serial.println(yh);
  Serial.print("zlsb: " );
  Serial.println(zl);
  Serial.print("zmsb: " );
  Serial.println(zh);
  xm = ((xh<<8) | xl);
  x = xm * .25;
  ym = ((yh<<8) | yl);  
  y = ym * .25;
  zm = ((zh<<8) | zl);
  z = zm * .25;

  //calculate the magnitude of the magfield
  double magField = sqrt(pow(x, 2.0) + pow(y, 2.0) + pow(z, 2.0));
  

  //print results
  Serial.print("x: ");
  Serial.print(x);
  Serial.println(" mG");
  Serial.print("y: ");
  Serial.print(y);
  Serial.println(" mG");
  Serial.print("z: ");
  Serial.print(z);  
  Serial.println(" mG");
  Serial.print("magnetic field: ");
  Serial.print(magField);
  Serial.println(" mG");
  Serial.print("magnetic field: ");
  Serial.print(magField * 100);
  Serial.println(" nT");
  
  delay(3000);
}

Thank you

Also here's the link to the MEMSIC MMC5883MA-B data sheet

All consumer grade magnetometers must be calibrated before they can be used for quantitative measurements.
Tutorial here.

When using the Wire library, you are reading or writing. You have mixed them together.
See my alternative explanation of the functions of the Wire library.

I suggest to test in setup() the Product ID of the MMC5883.

Thank you for replying!
I have a few questions about I2C:
Do you not use beginTransmission() and endTransmission() when reading in I2C?
Also when reading from a slave device in I2C, how do you specify which register you want to read from using the Wire library?

First, the register-address is written to the sensor with Wire.beginTransmission(), Wire.write(register-address) and Wire.endTransmission().
The sensor will remember that register-address.
Next, the Wire.requestFrom() reads data from the sensor.

I am currently working with the same device. The inaccurate results are definitely because it is not calibrated, it seems complicated from the guides but it's actually very simple. Also, after you read the data in your code you have a Wire.endTransmission(); which should not be there, it is only used when writing to the slave device I believe.

to read data I used something like this:

 Wire.beginTransmission(Dev_address);
  Wire.write(0x00); //start measuring at xlsb
  Wire.endTransmission();
  
  Wire.requestFrom(Dev_address, 6);
 if ((devstat & 0x01)==(0x01)){ //check the status register to make sure measurement is done
//read 6 bytes of data, starts at xlsb, internal memory address pointer automatically moves to the next byte
   
    xl = Wire.read();//xlsb
    xh = Wire.read();//xmsb
    yl = Wire.read();//ylsb
    yh = Wire.read();//ymsb
    zl = Wire.read();//zlsb
    zh = Wire.read();//zmsb
    
   
 }

To write to the slave:

Wire.beginTransmission(Dev_address);
  Wire.write(0x08);//control register 0
  Wire.write(0x09);// initiate magnetic field measurement and SET
  Wire.endTransmission();

Thank you! I'll fix that, and try to calibrate the magnetometer

ch862721:

if ((devstat & 0x01)==(0x01)){ //check the status register to make sure measurement is done

@ch862721, how do you get the Device Status register from the sensor into the 'devstat' variable ?

@Koepel, I use the "read" code I posted above but instead of reading the magnetometer data registers, I read just one value from the status register which has the address 0x07 on my device. The status register also has some other information in it but I am only interested in checking if data is available at that time, hence the logical AND to check that the data bit is high.

So, you issue a wire.requestFrom() command regardless of whether data are ready, and don't bother to read the status flag again, after making the requestFrom() call.

Do you think this will work, and is programming practice to recommend to others?

Oops, I just saw this disclaimer:

to read data I used something like this:

Better idea: post complete code that has been tested, and actually works.

I'm having trouble storing the magnetometer data for calibration. I'm trying to use the serial port to transfer the data from the Arduino to Processing and then to a .csv file.

I was wondering if anyone had more user friendly suggestions for moving serial data to a .csv or .exl file for data visualization?

I use a terminal program like TeraTerm or Putty to capture and log serial data, each line formatted with Serial.print(), something like this: "121.3,-225.7,19.5". Add a .csv extension and you are finished.

Or, use PLX-DAQ to capture serial data directly to an Excel spreadsheet.

Cool, thanks! I ended up being able to get my Processing code working, so no worries. I'll keep it in mind for the future though

lsssm:
I am trying to use the magnetometer to calculate the magnitude of the Earth's magnetic field, and instead of getting ~500 milligauss the data I get is closer to ~14000 milligauss.

Hi!

I'm recently working on the same sensor, and reproduced your problem using your code. Notice on page 7 of the datasheet that all bytes are unsigned format, meaning that they are inherently positive, while the sensor can measure both plus and minus 8 Gauss. This suggests that the output 0000 0000 is actually -8000 mGauss, rather than 0 mG. My solution is then subtract 8000 for your computed x, y, z, i.e.:

xm = ((xh<<8) | xl);
x = xm * .25 - 8000;
ym = ((yh<<8) | yl);
y = ym * .25 - 8000;
zm = ((zh<<8) | zl);
z = zm * .25 - 8000;

I'm not exactly certain that this is correct, but this does give a sensible result.

Hope this may help!