Magnetometer heading error

Hello,
i have built an rc boat and iam using the hybrid neo-m8n for gps+compass sensor. I have tested the gps on u-center and it is working fine. The problem is the compass. The heading iam receiving is completely off. Ι will post 2 pictures. One is showing the position of the gps on the boat which is away from the other electronics and as parallel as it could with the earth's plane and the other the gps from above with an arrow on the covering. I measured the heading with my iphone compass and when i was supposed to get 265 degrees heading i got 359 from the gps compass. I am using Adafruit_Sensor.h and Adafruit_HMC5883_U.h libraries for the programming. Can we please find a solution to this huge heading error? The code iam using for the testing is shown below (the libraries are supposed to take care of the error caused by tilting the compass sensor):

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>
#include <stdlib.h>
#include <Servo.h>

static const int OPin1 = 22, OPin2 = 23, OPin3 = 24;
static const uint32_t GPSBaud = 9600;
float gpslat, gpslon, headingDegrees;
uint8_t sats, hours, mins, secs, day, month;
uint16_t year;
uint32_t start,end,time,endgpsdatawaitmS, startGetFixmS, endFixmS, i = 0, j = 0, k, angle;
static char varbuf[32], sendbuf[32];
bool received = false, newData = false;

// Assign a Uniquej ID to the HMC5883 Compass Sensor
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);

// The TinyGPS++ object
TinyGPSPlus gps;


void setup()
{
  pinMode(OPin1, OUTPUT);
  digitalWrite(OPin1, HIGH);
  pinMode(OPin2, OUTPUT);
  digitalWrite(OPin2, HIGH);
  pinMode(OPin3, OUTPUT);
  digitalWrite(OPin3, HIGH);
  
  Serial.begin(GPSBaud);
  Serial3.begin(GPSBaud);
  Serial2.begin(GPSBaud);
  
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  sensor_t sensor;
  mag.getSensor(&sensor);

  startGetFixmS = millis();
  endgpsdatawaitmS = millis() + 5000;
}

void loop()
{
  //delay(1000);
  //Serial.println("Data from gps:");
  if (gpsWaitFix(5))
  {
    if (mag.begin())
    {
    displayCompassInfo();
    }

    gpslat = gps.location.lat();
    gpslon = gps.location.lng();
    sats = gps.satellites.value();
    hours = gps.time.hour();
    mins = gps.time.minute();
    secs = gps.time.second();
    day = gps.date.day();
    month = gps.date.month();
    year = gps.date.year();
    
    if (millis() > endgpsdatawaitmS) { //send every 5 seconds
    Serial.println("Sending data to feather...");
    Serial2.print("<");
    Serial2.print(gpslat, 6);
    Serial2.print(" ");
    Serial2.print(gpslon, 6);
    Serial2.print(" ");
    Serial2.print("Sat: ");
    Serial2.print(sats);
    Serial2.print(">");
    delay(100);
    Serial2.print("< Compass Heading: ");
    Serial2.print(headingDegrees);
    Serial2.print(">");
    delay(100);
    Serial2.print("< Gps Heading: ");
    Serial2.println(gps.course.deg()); // heading from gps (vehicle need to be moving or heading is random)
    Serial2.print(">");
    delay(100);
    endgpsdatawaitmS = millis()+5000;
    }
    startGetFixmS = millis(); //have a fix, next thing that happens is checking for a fix, so restart timer
  }
  else
  {
    Serial2.println();
    Serial2.print("No gps fix");
  }
}


bool gpsWaitFix(uint16_t waitSecs)
{
  //waits a specified number of seconds for a fix, returns true for good fix
  uint32_t endwaitmS;
  uint8_t GPSchar;

  Serial.print(F("Wait GPS Fix "));
  Serial.print(waitSecs);
  Serial.println(F(" seconds"));

  endwaitmS = millis() + (waitSecs * 1000);

  while (millis() < endwaitmS)
  {
    if (Serial3.available() > 0)
    {
      GPSchar = Serial3.read();
      gps.encode(GPSchar);
    }
    if (gps.location.isUpdated() && gps.date.isUpdated())
    {
      endFixmS = millis();  //record the time when we got a GPS fix
      newData = true;
      return true;
    }
  }

  return false;
}


void displayCompassInfo()
{
  /* Get a new sensor event   */
  sensors_event_t event;
  mag.getEvent(&event);

  /* Display the results (magnetic vector values are in micro-Tesla (uT)) */
  Serial.print("X: "); Serial.print(event.magnetic.x); Serial.print("  ");
  Serial.print("Y: "); Serial.print(event.magnetic.y); Serial.print("  ");
  Serial.print("Z: "); Serial.print(event.magnetic.z); Serial.print("  "); Serial.println("uT");

  // Hold the module so that Z is pointing 'up' and you can measure the heading with x&y
  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  float heading = atan2(event.magnetic.y, event.magnetic.x);
  // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location.
  // Find yours here: http://www.magnetic-declination.com/
  float declinationAngle = 0.081;
  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;

  Serial.print("Heading (degrees): "); Serial.println(headingDegrees);
  delay(1000);
}


Where in the world are you?
Is it attributable to magnetic declination (true north vs. magnetic north)?

Magnetometers need to be calibrated in their final place of installation. Any nearby current carrying wires, magnets or iron will distort the Earth's magnetic field. And they either must be kept level, or tilt-compensated using an accelerometer.

Best and most comprehensive calibration tutorial here: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

There are simpler procedures, if you look around.

I am really at a dead end here. I calibrated my compass with a good code that i found on the internet:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>
#include <stdlib.h>

static const int OPin1 = 22, OPin2 = 23, OPin3 = 24;
static const uint32_t GPSBaud = 9600;

// Assign a Uniquej ID to the HMC5883 Compass Sensor
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);

void setup()
{
  pinMode(OPin1, OUTPUT);
  digitalWrite(OPin1, HIGH);
  pinMode(OPin2, OUTPUT);
  digitalWrite(OPin2, HIGH);
  pinMode(OPin3, OUTPUT);
  digitalWrite(OPin3, HIGH);
  
  Serial.begin(GPSBaud);
  Serial3.begin(GPSBaud);
  Serial2.begin(GPSBaud);
  
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  sensor_t sensor;
  mag.getSensor(&sensor);

}

void loop()
{
  if (mag.begin()) {
  /* Get a new sensor event   */
  sensors_event_t event;
  mag.getEvent(&event);

  float xMax, yMax, xMin, yMin = 0.0;

if (xMax == 0.0) {
  xMax = event.magnetic.x;
}

if (yMax == 0.0) {
  yMax = event.magnetic.y;
}

  xMax = max(xMax, event.magnetic.x);
  yMax = max(yMax, event.magnetic.y);
  xMin = min(xMin, event.magnetic.x);
  yMin = min(yMin, event.magnetic.y);
//Serial.println(event.magnetic.x);
Serial.print("xMax: ");
Serial.println(xMax);
Serial.print("yMax: ");
Serial.println(yMax);
Serial.print("xMin: ");
Serial.println(xMin);
Serial.print("yMin: ");
Serial.println(yMin);
  }
}

and then put the max and min here:

  float heading = atan2((event.magnetic.y - ((yMax + yMin) / 2.0)), (event.magnetic.x - ((xMax + xMin) / 2.0)));

Now when the compass is pointing from west to north i get very accurate heading (error +/- 5 degrees). But when it goes away from this range the heading is completely of like 180 degrees off. This is not normal. Calibration is supposed to fix lets say an error of max 10 degrees not 180 degress error. I tried calibrating in different places, in different positions on the boat but the same thing happens. Another strange thing is that if i turn the compass upside down some heading calculations may be fixed, some not. HMC5883 is widely used. Havent found any post that complains about such a big error.

Since you are using the Adafruit sensor library, check whether their magnetometer calibration code supports the HMC5883.

Hello again,
i bought a new module as a compass which has more sensors. The GY-801. It also has the HMC5883 so i didnt need to change the code much. I used this awesome video for calibrating (which i recommend for all new users) Magnetometer Errors and Calibration - YouTube and passed the offsets to my code using your code part at Magnetometer Calibration - #6 by jremington and all worked perfectly. One last question though. Should i calibrate the magnetometer every time i change something? For example i changed the wiring a bit and i had to recalibrate because of huge error. If i change location of a few killometers ( ex 10 killometers) should i recalibrate?

Compare your electronic compass heading to that from a traditional magnetic needle compass. If they agree, no need to recalibrate.

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