Saturday, February 20, 2016

Arduino: Working with GY-9255 (MPU-9255) sensor - The Magnetometer

So I was looking for a really sensitive low field magnetic sensor for one of my research projects and I didn't want to spend an arm or a leg paying for it.  After couple days of searching and digging  the cheapest ready to use sensor is around $120 bucks, which is ... OKKKKK.  The bad news is that it is from Europe and the guy does not do credit card or Paypal.  A little more digging I bought a Honeywell HMC1001 that seems to be really good for what I want to do, except that I have to invest my time in buying passive and active components to build the circuit myself.  Then I went across the GY-9255  that yields +-4800 uT, not that great but it can get me started.  So I got the sensor, spent couple more days looking and trying free example codes that I can find.  Nah dah, couldn't get it going.  So I decided to get the specs and start from there, which I glad I did because I got it to work quicker than trying to find free code that works.  So I decided to write this little tutorial.

The GY-9255 sensor uses MPU-9255 chip.  The MPU9255 is a 9-axis sensor which packages the accelerometer, gyroscope, magnetometer together each provides data for x, y and z axes.  Other than the three mentioned sensors, it also has other features like Pedometer,  Motion and Tap detection, and Temperature (if I recall correctly).



Explanation of the pins:


- VCC: This pin goes to 3.3 volt pin of the Arduino
- GND: This pin goes to GND pin
- SCL: Clock line for I2C or SPI communication.  I use it as I2C which connects to pin 5 of the Arduino or pin 21 of the Mega 2560.  In SPI mode this is the clock line.
- SDA: Data line for I2C or SPI.  For I2C it connects to Arduino pin 4 or Mega 2560 pin 20.  For SPI it is data input line.
- EDA and ECL: These two pins are for serial communication to external sensor or device.
- ADO: In I2C mode, it is used to define the address of the sensor.  In SPI mode, this is data output line.
- INT: Interrupt line where the sensor can notify the Arduino when data is ready or motion is detected, depending on the configuration.
- nCS: Chip select pin, used in SPI mode.
- FSYNC: Can be used for different purposes depending on the configuration.

Summing up on the pins, to get this device working in I2C mode, pins VCC, GND, SCL, SDA, ADO and FSYNC are used.  Before going into programming, I want to briefly mention a little information about the device.  There are two specification for the MPU-9255 that you may want to read.  The first one is the MPU-9255 Product Specification (PS-MPU-9255) and the second one is the MPU-9255 Register Map and Description (RM-MPU-92551).  In general you would need the Register Map document most but there are scattered information in the Product Specification that you would need.  First lets look at the block diagram of the MPU-9255:


There is one thing I want to point out in the diagram above.  It has the master I2C and Slave I2C.  The master I2C connects to the AUX_CL and AUX_DA (which are the ECL and EDA pins) through the Serial Interface Bypass Mux.  When enabled, the master I2C talks to the external sensor that is connected to AUX_CL and AUX_DA, feed to data register to be read from the SCL and SDA lines.  When disabled, aka the Serial Interface Bypass Mux is set to bypass mode, the external sensor's data go directly to the SDA line.  The compass sensor is really the "external sensor" according to the block diagram.  In this tutorial, I will disable the master I2C and have the Arduino or Mega board talking to the magnetometer directly. In the I2C mode, the ADO pin is used as part of the sensor address designation.  When ADO grounded (LOW) the sensor address is 0x68  and when pulled up  to VDD (HIGH) the address is 0x69.

Steps to configure the sensor:


- Initialize the Power Management Register 0x6B
- Configure User Control Register 0x6A to disable master I2C
- Configure Interrup Configuration Register 0x37 to bypass master I2C
- Setup magnetic sensor's Control Register 0x0A to take continuous measurement.


Arduino has the I2C library called Wire that does all of the heavy labor.  To read the sensor you need to specify the address of the sensor and the register where data to be read.  To write to the sensor you will need address of the sensor, the register to write to and the value.  Here I write the two functions so that they can be used in the code.

byte read(int reg)
{
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x68, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
}

void write(int reg, int data)
{
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
}

Since I use bypass mode, the Arduino will talk to the magnetic sensor directly.  Thus I need the read and write for the magnetic sensor also.  The magnetic sensor address is 0x0C (from the Product Specification).

byte readMag(int reg)
{
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x0C, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
}

void writeMag(int reg, int data)
{
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
}

The main code:



#include <Wire.h>

void setup()
{
    Serial.begin(115200);  // Setup serial output to 115.2k
    Wire.begin();  // Initialize I2C communication
    write(0x6B, 0); //Initialize Power Manager Register to 0
    write(0x6A, 0);  // Disable master I2C, enable value is 0x20, so disable it should be just bit 5 = 0, since I don't use other bits and they are all 0.
    write(0x37, 0x02); // Enable bypass mode
    writeMag(0x0A, 0x12); // Setup magnetic sensor to measure contiuously.
}

void loop()
{
    int xh = readMag(0x04); // read x axis, high byte
    int xl = readMag(0x03); // read x axis, lowbyte
    int yh = readMag(0x06);
    int yl = readMag(0x05);
    int zh = readMag(0x08);
    int zl = readMag(0x07);
              readMag(0x09); //This tells the Mag Module to take another measurement.
    // Combine the values
    int x = (xh << 8) | (xl & 0xff);
    int y = (yh << 8) | (yl & 0xff);
    int z = (zh << 8) | (zl & 0xff);
    // Print out
    Serial.print("X,Y,Z=");
    Serial.print(x);
    Serial.print(",");
    Serial.print(y);
    Serial.print(",");
    Serial.println(z);
   // delay(1000);
}
Goodluck on your project!



21 comments:

  1. hi there
    thank you for tutorial
    can you complete this code for acc and gyro ,please??
    we are trying add this module to multiwii platform but there is no source code about acc and gyro for this module...
    best regards

    ReplyDelete
    Replies
    1. Sorry for the late response. I was so busy and forgot to go back to check for this. If you still has problem with the acc and gyro let me know.

      Delete
  2. Hi,

    in setup function you write the code like this:

    write(0x68, 0x6B, 0);
    write(0x68, 0x6A, 0);

    it must be like this, right??

    write(0x6B, 0);
    write(0x6A, 0);

    ReplyDelete
    Replies
    1. Yes, I added another parameter later and I forgot to correct it. Basically 0x68 is the address of the module, which was hardcoded in the write() function.
      Thanks for pointing it out.

      Delete
  3. Hi. thanks for your writing, But i have one problem. When I followed your code and run it, the value of X, Y, Z showed the value and never changed when i moved the sensor. For example, When Serial shows like X,Y,Z=-96,-72,16 , It never changed and print the same value again and again when i moved the sensor. I saw some videos that when user move the megnetometer sensor, the X, Y, Z values changed. But I can't find the solution to get the proper answer. Would you answer it for me?

    ReplyDelete
    Replies
    1. Hi, Can you post your code here and your pin connectivity if you don't mind.
      Try to add this to your X, Y and Z values:

      if (val < 0)
      return -(((float)(val*-1)) / 6.825);
      else
      return ((float)val) / 6.825;

      Where val is the X, Y or Z. I remember I had this problem before but forgot what I did to solve it.



      Delete
    2. Thank you for answering. I solved it.

      Delete
    3. This comment has been removed by the author.

      Delete
    4. my pin SDA=A4, SCL=A5 and vcc=3.33 gnd=gnd that my configuration pin

      Delete
  4. woosung kim can you show you program because i cant solve it , my problem same with you

    ReplyDelete
  5. Ismoyo,

    Add readMag(0x09); after reading the x,y,z.
    Also add:
    if (val < 0)
    return -(((float)(val*-1)) / 6.825);
    else
    return ((float)val) / 6.825;

    to the x,y,z value to convert to uT. val is the x, y, z.

    ReplyDelete
  6. sorry sir, where actually program if (val<0)...... added,in void setup or void loop ,because i add in readMag function its same


    sorry sir yhis my program




    #include
    byte read(int reg)
    {
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x68, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;

    }

    void write(int reg, int data)
    {
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
    }
    byte readMag(int reg)
    {
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x0C, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    if (val < 0)
    return -(((float)(val*-1)) / 6.825);
    else
    return ((float)val) / 6.825;


    }

    void writeMag(int reg, int data)
    {
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
    }

    void setup()
    {
    Serial.begin(115200); // Setup serial output to 115.2k
    Wire.begin(); // Initialize I2C communication
    write(0x6B, 0); //Initialize Power Manager Register to 0
    write(0x6A, 0); // Disable master I2C, enable value is 0x20, so disable it should be just bit 5 = 0, since I don't use other bits and they are all 0.
    write(0x37, 0x02); // Enable bypass mode
    writeMag(0x0A, 0x12); // Setup magnetic sensor to measure contiuously.
    }

    void loop()
    {
    int xh = readMag(0x04); // read x axis, high byte
    int xl = readMag(0x03); // read x axis, lowbyte
    int yh = readMag(0x06);
    int yl = readMag(0x05);
    int zh = readMag(0x08);
    int zl = readMag(0x07);

    readMag(0x09); //This tells the Mag Module to take another measurement.

    // Combine the values
    int x = (xh << 8) | (xl & 0xff);
    int y = (yh << 8) | (yl & 0xff);
    int z = (zh << 8) | (zl & 0xff);
    readMag(0x09); //This tells the Mag Module to take another measurement.
    Serial.print("X,Y,Z=");
    Serial.print(x);
    Serial.print(",");
    Serial.print(y);
    Serial.print(",");
    Serial.println(z);
    // delay(1000);

    }

    ReplyDelete
  7. Hi Phuc Ly , this is really helpful. did you get a chance to try on Teensy3.6 because many are moving to teensy for smart applications. please let us know if you try mpu-9255 on teensy3.6. I am working on teensy and facing issues..

    Thanks,
    Satya.

    ReplyDelete
    Replies
    1. Hi Satya,
      At the moment I have no plan to use teensy. I will definitely try in the future to see what capability it has.
      Thanks,
      Phuc

      Delete
  8. Hello Phuc Ly,
    Thank you for post,it is very useful but I have a question for you,
    are these X,Y,Z values in degrees or raw values.

    ReplyDelete
  9. This post really helped me! Thank you!

    ReplyDelete
  10. Hei Phuc Ly!
    I tried your code here but i only get heading from ca 200 to 350 degress. Can you please help me out?

    #include

    void setup()
    {
    Serial.begin(9600); // Setup serial output to 115.2k
    Wire.begin(); // Initialize I2C communication
    write(0x6B, 0); //Initialize Power Manager Register to 0
    write(0x6A, 0); // Disable master I2C, enable value is 0x20, so disable it should be just bit 5 = 0, since I don't use other bits and they are all 0.
    write(0x37, 0x02); // Enable bypass mode
    writeMag(0x0A, 0x12); // Setup magnetic sensor to measure contiuously.
    }

    void loop()
    {
    int xh = readMag(0x04); // read x axis, high byte
    int xl = readMag(0x03); // read x axis, lowbyte
    int yh = readMag(0x06);
    int yl = readMag(0x05);
    int zh = readMag(0x08);
    int zl = readMag(0x07);
    readMag(0x09); //This tells the Mag Module to take another measurement.
    // Combine the values
    int x = (xh << 8) | (xl & 0xff);
    int y = (yh << 8) | (yl & 0xff);
    int z = (zh << 8) | (zl & 0xff);
    // Print out
    Serial.print("X,Y,Z=");
    Serial.print(x);
    Serial.print(",");
    Serial.print(y);
    Serial.print(",");
    Serial.print(z);

    // gjore om til grader:


    float grader=atan2(y,x);


    if(grader<0)
    {

    grader+=2*PI;
    Serial.print(" Grader: ");
    Serial.println(grader*180/PI);

    //delay(1000);
    }
    }



    byte read(int reg)
    {
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x68, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
    }

    void write(int reg, int data)
    {
    Wire.beginTransmission(0x68); // starting the transmission to sensor address 0x68
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
    }


    byte readMag(int reg)
    {
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(0x0C, 1, false); // requestone byte of data
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
    }

    void writeMag(int reg, int data)
    {
    Wire.beginTransmission(0x0C);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
    }

    ReplyDelete
  11. Thank you, thank you very much! Thanks to your logic to read the magnetic value, I finally can write a MATLAB code with i2cdev to read and process the raw data. I cannot thank you enough ^^
    And it seems like you're a Vietnamese too :D

    ReplyDelete
  12. Hi,

    Can you tell me where mpu-9255 FSYNC is connected on Arduino? You wrote that it is used but I could not find out where it is connected.



    Thanks,
    Zoltan

    ReplyDelete