Unusual Behavior with HMC5883L

Been banging my head on this all weekend. Thought I would throw it out here in case I am missing something obvious. The code is based on the sample code on the SparkFun web site. I’ve used my HMC5883L with my Netduino board and no issue. It seems to be very sluggish changing values when turning it around to new headings. The wiring is the same I used on my Netduino with the pull up resistors. Let’s start with the code and serial output… any thoughts?

#include <math.h>

#define address 0x1E //0011110b, I2C 7bit address of HMC5883
#define PI 3.14159265358979323846

void setup() {
  //Initialize Serial and I2C communications
  Serial.begin(9600);
  Wire.begin();
  
  //Put the HMC5883 IC into the correct operating mode
  Wire.beginTransmission(address); //open communication with HMC5883
  Wire.write(0x02); //select mode register
  Wire.write(0x00); //continuous measurement mode
  Wire.endTransmission();
}

void loop() {
  int x,y,z; //triple axis data

  //Tell the HMC5883L where to begin reading data
  Wire.beginTransmission(address);
  Wire.write(0x03); //select register 3, X MSB register
  Wire.endTransmission();
  
 
 //Read data from each axis, 2 registers per axis
  Wire.requestFrom(address, 6);
  if(6<=Wire.available()){
    x = Wire.read()<<8; //X msb
    x |= Wire.read(); //X lsb
   z = Wire.read()<<8; //Z msb
    z |= Wire.read(); //Z lsb
    y = Wire.read()<<8; //Y msb
    y |= Wire.read(); //Y lsb
  }
  

  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  double heading = atan2(y, x);

  // Correct for when signs are reversed.
  if (heading < 0)
    heading += 2 * PI;

  // Convert radians to degrees for readability.
  double headingDegrees = heading * 180 / PI;

  //Print out values of each axis
  Serial.print("x: ");
  Serial.print(x);
  Serial.print("  y: ");
  Serial.print(y);
  Serial.print("  z: ");
  Serial.print(z);
  Serial.print(" Heading: ");
  Serial.print(headingDegrees);
  Serial.print(" / ");
  Serial.println(heading);
  
  delay(1000);
}

The output on serial:

x: 65478  y: 65330  z: 65192 Heading: 44.94 / 0.78
x: 65475  y: 65329  z: 65190 Heading: 44.94 / 0.78
x: 65477  y: 65328  z: 65191 Heading: 44.93 / 0.78
x: 65475  y: 65331  z: 65197 Heading: 44.94 / 0.78
x: 65477  y: 65329  z: 65194 Heading: 44.94 / 0.78
x: 65474  y: 65320  z: 65189 Heading: 44.93 / 0.78
x: 65485  y: 65423  z: 65150 Heading: 44.97 / 0.78
x: 65528  y: 65479  z: 65147 Heading: 44.98 / 0.79
x: 69  y: 65526  z: 65122 Heading: 89.94 / 1.57
x: 69  y: 65526  z: 65123 Heading: 89.94 / 1.57
x: 91  y: 65527  z: 65125 Heading: 89.92 / 1.57
x: 48  y: 65512  z: 65154 Heading: 89.96 / 1.57
x: 14  y: 65509  z: 65132 Heading: 89.99 / 1.57
x: 65456  y: 65432  z: 65110 Heading: 44.99 / 0.79
x: 65450  y: 65329  z: 65106 Heading: 44.95 / 0.78
x: 65488  y: 65269  z: 65115 Heading: 44.90 / 0.78
x: 65490  y: 65266  z: 65119 Heading: 44.90 / 0.78

It occurred to me I have not upgraded my core firmware yet. Going to download latest release and see if it helps.

Flashed to the latest firmware… issue remains.

Hi @cloris,

I don’t know anything about this sensor but I see something in the code that needs to change for :spark:. When you define x, y, and z as int on Arduino you get 16-bit integers but on the Spark Core, these will be 32-bit. When you build the 16-bit result from the two 8-bit Wire.read’s with the shift and OR operation, the result can never be negative on :spark: whereas it can on Arduino. You can see this in the serial output where x and y and z are all greater than 32767, which would be a negative number on a machine with 16-bit integer types.

Maybe you could try changing the type of x, y, and z to int16_t which is a true 16-bit integer on all platforms.

Also I am not sure how atan2() is working for you–it normally takes only doubles so I think you should need a cast on y and x. Something like:

double heading = atan2( (double)y, (double)x );

Good luck!

1 Like

You were right on the money! I need to load the two bytes into a int16_t. Loading it into a 32 bit int was causing it to miss the the 2’s compliment signing on the 16 bit int. Hence the funky numbers.

THANK YOU! Corrected code below.

#include <math.h>

#define address 0x1E //0011110b, I2C 7bit address of HMC5883
#define PI 3.14159265358979323846

void setup() {
  //Initialize Serial and I2C communications
  Serial.begin(9600);
  Wire.begin();
  
  //Put the HMC5883 IC into the correct operating mode
  Wire.beginTransmission(address); //open communication with HMC5883
  Wire.write(0x02); //select mode register
  Wire.write(0x00); //continuous measurement mode
  Wire.endTransmission();
}

void loop() {

  int16_t x,y,z; //triple axis data

  //Tell the HMC5883L where to begin reading data
  Wire.beginTransmission(address);
  Wire.write(0x03); //select register 3, X MSB register
  Wire.endTransmission();
  
 
 //Read data from each axis, 2 registers per axis
  Wire.requestFrom(address, 6);
  if(6<=Wire.available()){
    x = Wire.read()<<8; //X msb
    x |= Wire.read(); //X lsb
   z = Wire.read()<<8; //Z msb
    z |= Wire.read(); //Z lsb
    y = Wire.read()<<8; //Y msb
    y |= Wire.read(); //Y lsb
  }

  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  double heading = atan2((double)y, (double)x);

  // Correct for when signs are reversed.
  if (heading < 0)
    heading += 2 * PI;

  // Convert radians to degrees for readability.
  double headingDegrees = heading * 180 / PI;

  Serial.print("x: ");
  Serial.print(x);
  Serial.print("  y: ");
  Serial.print(y);
  Serial.print("  z: ");
  Serial.print(z);
  Serial.print(" Heading: ");
  Serial.print(headingDegrees);
  Serial.print(" / ");
  Serial.println(heading);
  
  //delay(1000);
}
1 Like