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!