using setScale with HMC5883L modifies returned raw values

Hello to all Arduino developpers.
I am using a HMC5883L connected to my arduino uno.
It seems that I understand the difference between scale and raw returned values when using the readRawAxis and the readScaledAxis functions.
The problem i get is that one : when I modify the scale with the setScale function (allowed scale values on the HMC5883L magnetometer are: 0.88, 1.3, 1.9, 2.5, 4.0, 4.7, 5.6 and 8.1 gauss), the returned values when using the readRawAxis are different. Of course, the HMC5883L does not move during the test.

Below if the test code I use.

Thanks for any information. Serge

/*****************************************************************************/	
//	Function:	 Get the Geographic direction of the X-axis.
//				If X-axis points to the North, it is 0 degree.
//				If X-axis points to the East, it is 90 degrees.
//				If X-axis points to the South, it is 180 degrees.
//				If X-axis points to the West, it is 270 degrees.
//  Hardware:   Grove - 3-Axis Digital Compass
//	Arduino IDE: Arduino-1.0
//	Author:	 Frankie.Chu		
//	Date: 	 Jan 10,2013
//	Version: v1.0
//	by www.seeedstudio.com
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//
/*******************************************************************************/

// Reference the I2C Library
#include <Wire.h>
// Reference the HMC5883L Compass Library
#include <HMC5883L.h>

MagnetometerRaw raw;
MagnetometerScaled scaled;

// Store our compass as a variable.
HMC5883L compass;
// Record any errors that may occur in the compass.
int error = 0;
int iLoop = 0;
float ratio;

// Out setup routine, here we will configure the microcontroller and compass.
void setup()
{
  // Initialize the serial port.
  Serial.begin(9600);

  Serial.println("Starting the I2C interface.");
  Wire.begin(); // Start the I2C interface.

  Serial.println("Constructing new HMC5883L");
  
  Serial.println("Setting measurement mode to continous.");
  error = compass.setMeasurementMode(MEASUREMENT_CONTINUOUS); // Set the measurement mode to Continuous
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.getErrorText(error));
}

// Our main program loop.
void loop()
{
  switch (iLoop) {
    case 0:
      ratio = 0.88;
      break;
    case 1:
      ratio = 1.3;
      break;
    case 2:
      ratio = 1.9;
      break;
    case 3:
      ratio = 2.5;
      break;
    case 4:
      ratio = 4.0;
      break;
    case 5:
      ratio = 4.7;
      break;
    case 6:
      ratio = 5.6;
      break;
    case 7:
      ratio = 8.1;
      break;
  }
  
  iLoop += 1;
  if (iLoop == 8) iLoop = 0;
  
  Serial.print("Setting scale to +/- ");
  Serial.println(ratio);
  error = compass.setScale(ratio); // Set the scale of the compass.
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.getErrorText(error));


  // Retrive the raw values from the compass (not scaled).
  raw = compass.readRawAxis();
  // Retrived the scaled values from the compass (scaled to the configured scale).
  scaled = compass.readScaledAxis();
  
  // Values are accessed like so:
  int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis)

  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  float heading = atan2(scaled.YAxis, scaled.XAxis);
  
  // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location.
  float declinationAngle = 0.02036;
  heading += declinationAngle;
  
  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*PI;
    
  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
    heading -= 2*PI;
   
  // Convert radians to degrees for readability.
  float headingDegrees = heading * 180/M_PI; 

  // Output the data via the serial port.
  Output(ratio, raw, scaled, heading, headingDegrees);

  // Normally we would delay the application by 66ms to allow the loop
  // to run at 15Hz (default bandwidth for the HMC5883L).
  // However since we have a long serial out (104ms at 9600) we will let
  // it run at its natural speed.
  delay(500);//of course it can be delayed longer.
}

// Output the data down the serial port.
void Output(float ratio, MagnetometerRaw raw, MagnetometerScaled scaled, float heading, float headingDegrees)
{
   Serial.print("Ratio:\t");
   Serial.print(ratio);
   Serial.print("\tRaw:\t");
   Serial.print(raw.XAxis);
   Serial.print("   ");   
   Serial.print(raw.YAxis);
   Serial.print("   ");   
   Serial.print(raw.ZAxis);
   Serial.print("   \tScaled:\t");
   
   Serial.print(scaled.XAxis);
   Serial.print("   ");   
   Serial.print(scaled.YAxis);
   Serial.print("   ");   
   Serial.print(scaled.ZAxis);

   Serial.print("   \tHeading:\t");
   Serial.print(heading);
   Serial.print(" Radians   \t");
   Serial.print(headingDegrees);
   Serial.println(" Degrees   \t");
}

Look in the library code. You did not mention where you got the library you are using.

Sorry about that.

the H and CPP files :
// Date: Jan 10,2013
// Version: v1.0
downloaded from http://www.seeedstudio.com
( http://www.seeedstudio.com/wiki/File:Digital_Compass.zip )

I have not found more up to date files,
If some exist, do you have a link to download them ?

Thanks a lot for your answer. Serge

I repost a different and simplified source code :

// Reference the I2C Library
#include <Wire.h>
// Reference the HMC5883L Compass Library
#include <HMC5883L.h>

MagnetometerRaw raw;
MagnetometerScaled scaled;

// Store our compass as a variable.
HMC5883L compass;
// Record any errors that may occur in the compass.
int error = 0;
int iLoop = 0;
float ratio;
int iRepeat;

// Out setup routine, here we will configure the microcontroller and compass.
void setup()
{
  // Initialize the serial port.
  Serial.begin(9600);

  Serial.println("Starting the I2C interface.");
  Wire.begin(); // Start the I2C interface.

  Serial.println("Constructing new HMC5883L");
  
  Serial.println("Setting measurement mode to continous.");
  error = compass.setMeasurementMode(MEASUREMENT_CONTINUOUS); // Set the measurement mode to Continuous
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.getErrorText(error));
}

// Our main program loop.
void loop()
{
  switch (iLoop) {
    case 0:
      ratio = 0.88;
      break;
    case 1:
      ratio = 1.3;
      break;
    case 2:
      ratio = 1.9;
      break;
    case 3:
      ratio = 2.5;
      break;
    case 4:
      ratio = 4.0;
      break;
    case 5:
      ratio = 4.7;
      break;
    case 6:
      ratio = 5.6;
      break;
    case 7:
      ratio = 8.1;
      break;
  }
  
  iLoop += 1;
  if (iLoop == 8) iLoop = 0;
  
  Serial.print("Setting scale to +/- ");
  Serial.println(ratio);
  error = compass.setScale(ratio); // Set the scale of the compass.
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.getErrorText(error));
  delay(1000);

  // Repeat reading 5 time to verify stability
  for (iRepeat = 0; iRepeat < 4; iRepeat++) {

    // Retrieve the raw values from the compass (not scaled).
    raw = compass.readRawAxis();

    // Retrieve the scaled values from the compass (scaled to the configured scale).
    scaled = compass.readScaledAxis();

    // Output the data via the serial port.
    Output(iRepeat, ratio, raw, scaled);
  
    // delay
    delay(1000);
  }
}

// Output the data down the serial port.
void Output(int iRepeat, float ratio, MagnetometerRaw raw, MagnetometerScaled scaled)
{
   Serial.print(iRepeat);
   Serial.print("\tRatio:\t");
   Serial.print(ratio);
   Serial.print("\tRaw:\t");
   Serial.print(raw.XAxis);
   Serial.print("   ");   
   Serial.print(raw.YAxis);
   Serial.print("   ");   
   Serial.print(raw.ZAxis);
   Serial.print("   \tScaled:\t");
   Serial.print(scaled.XAxis);
   Serial.print("   ");   
   Serial.print(scaled.YAxis);
   Serial.print("   ");   
   Serial.println(scaled.ZAxis);
}

and the result displayed in the serial windows :

Starting the I2C interface.
Constructing new HMC5883L
Setting measurement mode to continous.
Setting scale to +/- 0.88
0 Ratio: 0.88 Raw: 198 -298 -457 Scaled: 144.54 -217.54 -333.61
1 Ratio: 0.88 Raw: 198 -298 -456 Scaled: 144.54 -217.54 -332.88
2 Ratio: 0.88 Raw: 201 -299 -459 Scaled: 146.73 -218.27 -335.07
3 Ratio: 0.88 Raw: 198 -301 -457 Scaled: 144.54 -219.73 -333.61
Setting scale to +/- 1.30
0 Ratio: 1.30 Raw: 156 -236 -363 Scaled: 143.52 -217.12 -333.96
1 Ratio: 1.30 Raw: 158 -236 -364 Scaled: 145.36 -217.12 -334.88
2 Ratio: 1.30 Raw: 157 -238 -364 Scaled: 144.44 -218.96 -334.88
3 Ratio: 1.30 Raw: 158 -237 -364 Scaled: 145.36 -218.04 -334.88
Setting scale to +/- 1.90
0 Ratio: 1.90 Raw: 123 -184 -281 Scaled: 150.06 -224.48 -342.82
1 Ratio: 1.90 Raw: 122 -183 -280 Scaled: 148.84 -223.26 -341.60
2 Ratio: 1.90 Raw: 123 -183 -282 Scaled: 150.06 -223.26 -344.04
3 Ratio: 1.90 Raw: 126 -186 -283 Scaled: 153.72 -226.92 -345.26
Setting scale to +/- 2.50
0 Ratio: 2.50 Raw: 97 -145 -224 Scaled: 147.44 -220.40 -340.48
1 Ratio: 2.50 Raw: 98 -143 -221 Scaled: 148.96 -217.36 -335.92
2 Ratio: 2.50 Raw: 96 -144 -221 Scaled: 145.92 -218.88 -335.92
3 Ratio: 2.50 Raw: 97 -144 -221 Scaled: 147.44 -218.88 -335.92
Setting scale to +/- 4.00
0 Ratio: 4.00 Raw: 64 -98 -149 Scaled: 145.28 -222.46 -338.23
1 Ratio: 4.00 Raw: 65 -99 -148 Scaled: 147.55 -224.73 -335.96
2 Ratio: 4.00 Raw: 66 -98 -149 Scaled: 147.55 -222.46 -333.69
3 Ratio: 4.00 Raw: 65 -98 -148 Scaled: 147.55 -222.46 -335.96
Setting scale to +/- 4.70
0 Ratio: 4.70 Raw: 58 -87 -133 Scaled: 148.48 -222.72 -340.48
1 Ratio: 4.70 Raw: 57 -87 -133 Scaled: 145.92 -222.72 -340.48
2 Ratio: 4.70 Raw: 58 -87 -133 Scaled: 148.48 -222.72 -340.48
3 Ratio: 4.70 Raw: 57 -86 -133 Scaled: 145.92 -220.16 -340.48
Setting scale to +/- 5.60
0 Ratio: 5.60 Raw: 48 -72 -109 Scaled: 145.44 -218.16 -330.27
1 Ratio: 5.60 Raw: 48 -72 -110 Scaled: 145.44 -218.16 -333.30
2 Ratio: 5.60 Raw: 48 -72 -110 Scaled: 145.44 -218.16 -333.30
3 Ratio: 5.60 Raw: 48 -73 -110 Scaled: 145.44 -221.19 -333.30
Setting scale to +/- 8.10
0 Ratio: 8.10 Raw: 33 -50 -77 Scaled: 143.55 -217.50 -334.95
1 Ratio: 8.10 Raw: 34 -50 -77 Scaled: 147.90 -217.50 -334.95
2 Ratio: 8.10 Raw: 33 -51 -78 Scaled: 143.55 -221.85 -339.30
3 Ratio: 8.10 Raw: 34 -51 -77 Scaled: 147.90 -221.85 -334.95

It is like I completely misunderstand the way these functions work.
I thought that raw values were independent of the scale.

Thanks in advance for any explanation.
(It’s also possible that there is a huge error in the source code :slight_smile: )

Perhaps I should use the Adafruit Unified Sensor Driver ???

You should look in the library code for the problem.

Here is the cpp of the DigitalCompass library :

/*****************************************************************************/	
//	Function:	 Cpp file for HMC5883L
//  Hardware:    Grove - 3-Axis Digital Compass
//	Arduino IDE: Arduino-1.0
//	Author:	 FrankieChu		
//	Date: 	 Jan 10,2013
//	Version: v1.0
//	by www.seeedstudio.com
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//
/*******************************************************************************/

#include <Arduino.h> 
#include "HMC5883L.h"

HMC5883L::HMC5883L()
{
  m_Scale = 1;
}

MagnetometerRaw HMC5883L::readRawAxis()
{
  uint8_t* buffer = read(DATA_REGISTER_BEGIN, 6);
  MagnetometerRaw raw = MagnetometerRaw();
  raw.XAxis = (buffer[0] <<  8) | buffer[1];
  raw.ZAxis = (buffer[2] <<  8) | buffer[3];
  raw.YAxis = (buffer[4] <<  8) | buffer[5];
  return raw;
}

MagnetometerScaled HMC5883L::readScaledAxis()
{
  MagnetometerRaw raw = readRawAxis();
  MagnetometerScaled scaled = MagnetometerScaled();
  scaled.XAxis = raw.XAxis * m_Scale;
  scaled.ZAxis = raw.ZAxis * m_Scale;
  scaled.YAxis = raw.YAxis * m_Scale;
  return scaled;
}

int HMC5883L::setScale(float gauss)
{
	uint8_t regValue = 0x00;
	if(gauss == 0.88)
	{
		regValue = 0x00;
		m_Scale = 0.73;
	}
	else if(gauss == 1.3)
	{
		regValue = 0x01;
		m_Scale = 0.92;
	}
	else if(gauss == 1.9)
	{
		regValue = 0x02;
		m_Scale = 1.22;
	}
	else if(gauss == 2.5)
	{
		regValue = 0x03;
		m_Scale = 1.52;
	}
	else if(gauss == 4.0)
	{
		regValue = 0x04;
		m_Scale = 2.27;
	}
	else if(gauss == 4.7)
	{
		regValue = 0x05;
		m_Scale = 2.56;
	}
	else if(gauss == 5.6)
	{
		regValue = 0x06;
		m_Scale = 3.03;
	}
	else if(gauss == 8.1)
	{
		regValue = 0x07;
		m_Scale = 4.35;
	}
	else
		return ERRORCODE_1_NUM;
	
	// Setting is in the top 3 bits of the register.
	regValue = regValue << 5;
	write(CONFIGURATION_REGISTERB, regValue);
}

int HMC5883L::setMeasurementMode(uint8_t mode)
{
	write(MODE_REGISTER, mode);
}

void HMC5883L::write(int address, int data)
{
  Wire.beginTransmission(HMC5883L_ADDRESS);
  Wire.write(address);
  Wire.write(data);
  Wire.endTransmission();
}

uint8_t* HMC5883L::read(int address, int length)
{
  Wire.beginTransmission(HMC5883L_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.beginTransmission(HMC5883L_ADDRESS);
  Wire.requestFrom(HMC5883L_ADDRESS, length);

  uint8_t buffer[length];
  if(Wire.available() == length)
  {
	  for(uint8_t i = 0; i < length; i++)
	  {
		  buffer[i] = Wire.read();
	  }
  }
  Wire.endTransmission();

  return buffer;
}

char* HMC5883L::getErrorText(int errorCode)
{
	if(ERRORCODE_1_NUM == 1)
		return ERRORCODE_1;
	
	return "Error not defined.";
}

The involved functions are :

MagnetometerRaw HMC5883L::readRawAxis()
{
uint8_t* buffer = read(DATA_REGISTER_BEGIN, 6);
MagnetometerRaw raw = MagnetometerRaw();
raw.XAxis = (buffer[0] << 8) | buffer[1];
raw.ZAxis = (buffer[2] << 8) | buffer[3];
raw.YAxis = (buffer[4] << 8) | buffer[5];
return raw;
}

MagnetometerScaled HMC5883L::readScaledAxis()
{
MagnetometerRaw raw = readRawAxis();
MagnetometerScaled scaled = MagnetometerScaled();
scaled.XAxis = raw.XAxis * m_Scale;
scaled.ZAxis = raw.ZAxis * m_Scale;
scaled.YAxis = raw.YAxis * m_Scale;
return scaled;
}

As you can see, the readRawAxis function does not modify the returned value (raw) with the scale.
The readScaledAxis applies the scale.

???

Thanks for your help.

;D YESSS!!

Everything is normal !!!

The setscale function writes into the device :

// Setting is in the top 3 bits of the register.
regValue = regValue << 5;
write(CONFIGURATION_REGISTERB, regValue);

It is why the raw values are modified by the scale.
But the readScaledAxis function returns some calculated values, and it is why they are stable …

Have a nice day !