HMC5883L Abnormal Z Axis Reading

Hi Guys, I am a relatively new Arduino hobbyist, and I have a GY-87 10DOF Sensor working with arduino nano, it has the MPU6050, HMC5883L, and BMP180.

Important detail here is that the magnetometer cannot be accessed directly, it is connected to the external bus of the MPU6050, so data is read through MPU6050's registers allocated to slaves.

Everything is going well, but the z-axis reading for the magnetometer which seems pretty abnormal. It constantly says -6.21 when converted to Gausse. or around 4096 otherwise. I have no idea how big of a unit Gausse is, but according to sources online, it should be 0.2-0.7 Gausse on the earth's surface. Also, the reading does not change at all with a difference in altitude of 2 meters (my usb cable is 1 meter). This might not be all that important, I plan to use these values to build a gyro assisted airplane. Can someone tell me if this value is indeed abnormal or it's perfectly fine?

Here is my code(I dont think something is wrong with it, everything done according to the documentation):

/* When page number is specified, it refers to the register maps of these
modules, comment will refer to page number of the respective module that 
is being communicated with i2c.
MPU6050 datasheet: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf
MPU6050 register map: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf
HMC5883L: https://cdn-shop.adafruit.com/datasheets/HMC5883L_3-Axis_Digital_Compass_IC.pdf*/

#include <Wire.h>

int bmpAddress = 0x77;  // BMP180 i2c address 0x77
int compassAddress = 0x1E;  // HMC5883L i2c address 0x1E
int mpuAddress = 0x68;  // MPU6050 i2c address 0x68

float gForceX, gForceY, gForceZ, rotX, rotY, rotZ, mpuTempC, magnetX, magnetY, magnetZ;



void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin();           // Initialize i2c
  Wire.setClock(400000);  // Set I2C to 400kHz, if does not work, set to 400000UL
  setupMPU();
  setupCompass();
}

void loop() {
  // put your main code here, to run repeatedly:
  recordMPURegisters();
  printData();
}

void setupMPU() {
  // 0x6B Register 107 - Power management 1, pg 40. 0x00 exit sleep.
  i2c_write(mpuAddress, 0x6B, 0x00);
  // 0x19 Register 25 – Sample Rate Divider, pg 11-12. 109 sample rate = 8kHz / 110 = 72.7Hz
  i2c_write(mpuAddress, 0x19, 109);
  // 0x1B Register 27 - Gyroscope Configuration, pg 14. 0x00 gyro full scale = +/- 250 degrees/second
  i2c_write(mpuAddress, 0x1B, 0x00);
  // 0x1C Register 28 - Accelerometer Configuration, pg 15. 0x00 accelerometer full scale = +/- 2g
  i2c_write(mpuAddress, 0x1C, 0x00);
}

void setupCompass() {
  // 0x6A Register 106 - User Control, pg 38. 0x00 Disables i2c master mode
  i2c_write(mpuAddress, 0x6A, 0x00);
  // 0x37 Register 55 - INT Pin/Bypass Enable Configuration, pg 26. 0x02 enables i2c master bypass mode
  i2c_write(mpuAddress, 0x37, 0x02);
  // 0x00 Register 0 - Configuration Register A, pg 11. 0x18 sample rate = 75Hz
  i2c_write(compassAddress, 0x00, 0x18);
  // 0x01 Register 1 - Configuration Register B, pg 13. 0x60 full scale = +/- 2.5 Gauss
  i2c_write(compassAddress, 0x01, 0x60);
  // 0x02 Register 2 - Mode Register, pg 13. 0x00 Continuous measurement mode
  i2c_write(compassAddress, 0x02, 0x00);
  // 0x37 Register 55 - INT Pin/Bypass Enable Configuration, pg 26. 0x00 disable i2c master bypass mode
  i2c_write(mpuAddress, 0x37, 0x00);
  // 0x6A Register 106 - User Control, pg 38. 0x20 enable i2c master mode
  i2c_write(mpuAddress, 0x6A, 0x20);

  // Configure mpu to auto read values from the compass
  // 0x25 Register 37 - I2C Slave 0 Control, pg 19-21 !IMPORTANT. 0b10011110 slave 0 i2c address, first bit sets read mode
  i2c_write(mpuAddress, 0x25, 0b10011110);
  // 0x26 Register 38 - I2C Slave 0 Control, pg 19-21. 0x03 Slave 0 register 3 = 0x03 (x-axis)
  i2c_write(mpuAddress, 0x26, 0x03);
  // 0x27 Register 39 - I2C Slave 0 Control, pg 19-21. 0b10000110 slave 0 transfer size = 6, 1st bit enables I2C transaction
  i2c_write(mpuAddress, 0x27, 0b10000110);
  // 0x67 Register 103 - I2C Master Delay Control, pg 36. 1 enable slave 0 delay. If error, try setting 0x80
  i2c_write(mpuAddress, 0x67, 1);
}

void recordMPURegisters() {
  Wire.beginTransmission(mpuAddress);  // I2C address of the MPU
  Wire.write(0x3B);                    // Starting register for Accel Readings
  Wire.endTransmission();
  Wire.requestFrom(mpuAddress, 20);  // Request Accel Registers (3B - 40)
  while (Wire.available() < 20)
    ;                                         // Wait for 20 registers to be available
  gForceX = Wire.read() << 8 | Wire.read();   // Store first two bytes into accelX
  gForceY = Wire.read() << 8 | Wire.read();   // Store middle two bytes into accelY
  gForceZ = Wire.read() << 8 | Wire.read();   // Store last two bytes into accelZ
  mpuTempC = Wire.read() << 8 | Wire.read();  // Store the two bytes of mpuTemp
  rotX = Wire.read() << 8 | Wire.read();      // Store first two bytes into accelX
  rotY = Wire.read() << 8 | Wire.read();      // Store middle two bytes into accelY
  rotZ = Wire.read() << 8 | Wire.read();      // Store last two bytes into accelZ
  magnetX = Wire.read() << 8 | Wire.read();
  magnetZ = Wire.read() << 8 | Wire.read();
  magnetY = Wire.read() << 8 | Wire.read();
  // Convert accel values
  gForceX /= 16384.0;
  gForceY /= 16384.0;
  gForceZ /= 16384.0;
  // Convert temperature to degrees Celsius
  mpuTempC = mpuTempC / 340.0 + 36.53;
  // Convert Gyro values
  rotX /= 131.0;
  rotY /= 131.0;
  rotZ /= 131.0;
  // Values converted to Gauss(see page 13 of hmc5883l documentation)
  magnetX /= 660.0;
  magnetY /= 660.0;
  magnetZ /= 660.0;
}

void printData() {
  Serial.print("Temperature = ");
  Serial.print(mpuTempC);
  Serial.print("\t");
  Serial.print("Gyro (deg) X = ");
  Serial.print(rotX);
  Serial.print("\tY = ");
  Serial.print(rotY);
  Serial.print("\t Z= ");
  Serial.print(rotZ);
  Serial.print("\tAccel (g) X = ");
  Serial.print(gForceX);
  Serial.print("\tY = ");
  Serial.print(gForceY);
  Serial.print("\tZ = ");
  Serial.print(gForceZ);
  Serial.print("\t");
  Serial.print(magnetX);
  Serial.print("\t");
  Serial.print(magnetY);
  Serial.print("\t");
  Serial.println(magnetZ);
}

void i2c_write(uint8_t deviceAddress, uint8_t registerAddress, uint8_t data) {
  Wire.beginTransmission(deviceAddress);
  Wire.write(registerAddress);
  Wire.write(data);
  Wire.endTransmission();
}

P.S.If anyone is wondering why i didnt use a library, it's just because this was a good learning experience, and I will be short on storage after implementation of BMP180 and other stuff

This is how my module looks:

The HMC5883L was discontinued many years ago, and there are lots of fakes on the market, as well as QMC5883L magnetometers, mislabeled as HMC5883L.

Use the I2C address scanner program to check, as the QMC5883L has a different address, and requires different commands to read out the data.

my code(nothing wrong with it

There are many problems with the code, such as not checking function return values, so you cannot know whether the operation was successful.

Edit: I just noticed that the code doesn't even read the magnetometer. This reads some registers from the MPU-6050 instead:

  rotZ = Wire.read() << 8 | Wire.read();      // Store last two bytes into accelZ
  magnetX = Wire.read() << 8 | Wire.read();
  magnetZ = Wire.read() << 8 | Wire.read();
  magnetY = Wire.read() << 8 | Wire.read();

I am not using a library, so I am not using any functions that will return a error checking value, and yes, I have spent like 2 weeks on this, the i2c addresses and everything is functioning perfectly, x and y axis are giving correct data, z axis is giving -4096, which is seemingly impossible according to the documentation. It is limited to -2048-2047

Wire is a library. Most functions in the Wire library return values.

See above, the code does not read the magnetometer.

1 Like

I did not think of checking for values in the wire library, Thanks.

So I should check if my Wire.beginTransmission is successful for all the config commands, or do the Wire.write commands also return a bool value?

Just wondering how you would know that. Also, x and y values seem to be correct.

I know how the Wire library works, and your code attempts to use the Wire library to read "magnetX", etc. from the MPU-6050, which does not have a magnetometer.

I suggest to start over, and learn how to use the magnetometer as the only sensor. There are many tutorials posted on the web.

The HMC5883L is connected to the External SDA and SCL of the MPU6050. By reading the MPU6050's documentation, MPU6050 is set into master bypass mode and master mode is disabled, then the devices directly connected(compass in this case) are configured directly. After configuration is done, MPU6050's registers allocated to slaves(upto 5 can be on the EDA and ECL i2c bus) are accessed to read data.

This did not go well, as the compass is connected on the external bus of the mpu6050 and cannot be directly accessed. Here is the information if you wanna see: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf#page=32
See section 4.20 Registers 73 to 96.
I am just highlighting this information because you might not know this.

You forgot to mention that extremely important detail in your first post.

My mistake, I will edit initial post now. Thanks.

The documentation mentions this in the x, y and z reading registers:

In the event the ADC reading overflows or underflows for the given channel, or if there is a math overflow during the bias
measurement, this data register will contain the value -4096. This register value will clear when after the next valid
measurement is made.

https://cdn-shop.adafruit.com/datasheets/HMC5883L_3-Axis_Digital_Compass_IC.pdf#page=15

Please someone clarify when this could happen, there is also a status register and DRDY pin, I will see if I can identify any abnormal values according to the documentation too.
If someone can help me work with DRDY pin and interrupts, that would be appreciated. I have never worked with interrupts.


Using the documentation, I have looked at the status register and thats either showing 0, 1 or 3. Nothing out of the ordinary.

Examined the ID registers and the ASCII values align with what the documentation says.

Please someone tell me what causes an overflow in the ADC, I have tried setting the gain to maximum and the minimum. However, I have not tried calibrating, idk if that makes a difference in ADC overflow, and I have no idea how to do that.

The many differences between the available QMC5883L and the long discontinued HMC5883L may account for the problem.

Frankly, that board is a piece of junk, and a complete waste of time. Any modern 9DOF IMU breakout from a reputable supplier will work properly, and vastly outperform the antique or fake chips on the one you have.

Can you suggest any IMU Board that's easily available for a reasonable price?
Thanks

Should I pick up the MPU9250 9 axis IMU, with BMP180?

Also obsolete.

Adafruit, Sparkfun and Pololu (among others) sell modern 9DOF IMUs. LSM9DS1, ICM-20948, etc.

Again, problem arises with availibility and affordability, I can get the MPU9250 here in Karachi, Pakistan for about 4-5 usd, shipping included. Of course, these would be chinese clones. Meanwhile anything from Adafruit and Sparkfun costs $20 and above, and that's excluding shipping.

Personally, I have had no problems with buying cheap chinese clones and this was the first problem I had. Something like LSM9DS1 isnt easily available on Daraz.pk, which is the website that I use. I dont use AliExpress, mainly because there is no cash on delivery, but these modules might be available there. Anyway, thanks for the suggestions, I will buy the GY-87 module again, cuz other people seem to get it to work. If that doesnt work out, i will get the MPU9250.

Thanks for your help.

Meanwhile, if anyone thinks it could be a software issue and some problem in my code thats causing the adc overflow, do let me know. Thanks Again.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.