Magnetometer Easy Calibration

Hi everyone.
Can magnetometers like HMC5883L (or a different product, MPU9250, BNO055 etc.) be calibrated by rotating them only 360 degrees around their own axis? And then tilt compensation will be required. Since the vehicle will be heavy, I cannot rotate it in all axes. Jarzebski's HMC5883L library mentions that this can be done and is supported by a video. It also uses MPU6050 for tilt compensation.

Virtual Anchor and many RC autopilot systems perform calibration by rotating only around their own axis and can find their direction without any problems even in the ripples on the water. However, unfortunately I could not achieve this with Jarzebski's libraries. The purpose of giving Jarzebski's library as an example is because it is the only application that calibrates only by rotating around its own axis and that I have seen that applies tilt compensation.

If there is a different library or application, I can try that too. If anyone can guide me on this issue, I would really appreciate it.

How far are you going to move your vehicle from where you calibrate it?

It will need to move and stop on a specific route. So I'm thinking of adding a feature that will allow autonomous driving

For heavy vehicles, 2D calibration of the magnetometer and heading calculation using atan2() works fine, but you still need to rotate the vehicle+mounted magnetometer 360 degrees about the Z axis to obtain the calibration data. Tilt compensation is not needed if tilt is less than about 10 degrees.

Ellipse fitting code for offset removal and scale factor normalization, using either Matlab/GNU Octave or Python, is posted here.

Thanks for the answer.

 Vector norm = compass.readNormalize();

Here it takes the calibrated offset values.
I calculate the offsets I get with atan2 but I have no way of knowing how much the slope will be. There will be times when it is below 10 degrees, and times when it can be above. So I think I will need slope compensation.

void loop()
{

  Vector norm = compass.readNormalize();

  float heading = atan2(norm.YAxis, norm.XAxis);

  if (heading < 0)
  {
    heading += 2 * PI;
  }

  if (heading > 2 * PI)
  {
    heading -= 2 * PI;
  }

  float headingDegrees = heading * 180/M_PI; 

  Serial.print(" Heading = ");
  Serial.print(heading);
  Serial.print(" Degress = ");
  Serial.print(headingDegrees);
  Serial.println();

  delay(100);
}

Sorry, I have no idea what you mean by the above. Offsets are calculated from the raw data and atan2() is not involved. What is "slope"?

For help with code, post all of the code.

Got it. I will publish all the codes I used for testing.

Codes I used for calibration:

#include <Wire.h>
#include <HMC5883L.h>

HMC5883L compass;

int minX = 0;
int maxX = 0;
int minY = 0;
int maxY = 0;
int offX = 0;
int offY = 0;

void setup()
{
  Serial.begin(9600);

  // Initialize Initialize HMC5883L
  while (!compass.begin())
  {
    delay(500);
  }

  // Set measurement range
  compass.setRange(HMC5883L_RANGE_1_3GA);

  // Set measurement mode
  compass.setMeasurementMode(HMC5883L_CONTINOUS);

  // Set data rate
  compass.setDataRate(HMC5883L_DATARATE_30HZ);

  // Set number of samples averaged
  compass.setSamples(HMC5883L_SAMPLES_8);
}

void loop()
{
  Vector mag = compass.readRaw();

  // Determine Min / Max values
  if (mag.XAxis < minX) minX = mag.XAxis;
  if (mag.XAxis > maxX) maxX = mag.XAxis;
  if (mag.YAxis < minY) minY = mag.YAxis;
  if (mag.YAxis > maxY) maxY = mag.YAxis;


  // Calculate offsets
  offX = (maxX + minX)/2;
  offY = (maxY + minY)/2;

  Serial.print(mag.XAxis);
  Serial.print(":");
  Serial.print(mag.YAxis);
  Serial.print(":");
  Serial.print(minX);
  Serial.print(":");
  Serial.print(maxX);
  Serial.print(":");
  Serial.print(minY);
  Serial.print(":");
  Serial.print(maxY);
  Serial.print(":");
  Serial.print(offX);
  Serial.print(":");
  Serial.print(offY);
  Serial.print("\n");
}

These are the codes I read my direction as a result of calibration:

#include <Wire.h>
#include <HMC5883L.h>

HMC5883L compass;

void setup()
{
  Serial.begin(9600);

  Serial.println("Initialize HMC5883L");
  while (!compass.begin())
  {
    Serial.println("Could not find a valid HMC5883L sensor, check wiring!");
    delay(500);
  }

  // Set measurement range
  compass.setRange(HMC5883L_RANGE_1_3GA);

  // Set measurement mode
  compass.setMeasurementMode(HMC5883L_CONTINOUS);

  // Set data rate
  compass.setDataRate(HMC5883L_DATARATE_30HZ);

  // Set number of samples averaged
  compass.setSamples(HMC5883L_SAMPLES_8);

  // Set calibration offset. See HMC5883L_calibration.ino
  compass.setOffset(24, -97);
}

void loop()
{
  Vector norm = compass.readNormalize();

  // Calculate heading
  float heading = atan2(norm.YAxis, norm.XAxis);


  float declinationAngle = (5.0 + (55.0 / 60.0)) / (180 / M_PI);
  heading += declinationAngle;

  // Correct for heading < 0deg and heading > 360deg
  if (heading < 0)
  {
    heading += 2 * PI;
  }

  if (heading > 2 * PI)
  {
    heading -= 2 * PI;
  }

  // Convert to degrees
  float headingDegrees = heading * 180/M_PI; 

  // Output
  Serial.print(" Heading = ");
  Serial.print(heading);
  Serial.print(" Degress = ");
  Serial.print(headingDegrees);
  Serial.println();

  delay(100);
}

The min/max approach to calculating offsets often does not work well, and the offsets are only part of the total correction, which includes correcting for differences in X and Y scale factors, plus "soft iron" environmental distortions.

See code in reply #2.

Got it. With the codes I use, only hard iron offsets are taken. But for 2D calibration, both hard iron and soft iron offsets need to be applied.

In the project I am trying to create, when the system starts, if calibration has not been done before, calibration is started and these values ​​are saved to eeprom.
Necessary calculations will be made with the saved values ​​and tilt compensation needs to be applied with MPU6050..

Therefore, I will not be able to use any graphics. So I am trying to find something that will only work on Arduino.

For tilt compensation, the magnetometer data must be collected and corrected in 3D. Graphics are not needed for the correction calculations.

For a land vehicle, experiments will probably show that a 2D compass will work well enough, even with a small range of tilting.

Sorry if I used the wrong expression. The vehicle will be used on a sea vessel, not a land vehicle.

It will inevitably be affected by the waves. However, I cannot perform a 3D calibration on this vehicle. My best option is to perform calibration by rotating the vehicle on its own axis a few times while it is running.

If 2D calibration and tilt compensation is not successful, unfortunately the project must be terminated before it starts..

Unfortunately, 2D calibration and tilt compensation are mathematically incompatible.

Non-electronic marine compasses are 2D, but self-leveling.

I have endless respect for your knowledge on this subject. I always see that you are the biggest supporter on this kind of topics in the forum.

But I can't help but ask. There are Virtual Anchors (e.g. Minn Kota, Haswing) used on boats. Also, the vast majority of RC Autopilot systems (except Ardupilot) only rotate on their own axis, calibrate the magnetometer and reach their destination without any problems.

What do you think is the process in these, I wonder what you think about this?

True. They don't require tilt compensation to reach their destination, because the short term errors caused by wave action cancel out over the journey.

Here is a demo that I did many years ago using a BN0055 as a 2D compass. Watch the 1/2 meter LOA boat bounce on the waves, then return to within 2 meters of the starting point.

2 Likes

This is a great idea. Frankly, I thought it could be supported by GPS. But then I researched Virtual Anchors and thought it wouldn't be possible because GPS can't get direction when stationary (very small movements). So, how can a code structure be created as you mentioned?

I have this video, you also gave me the codes. I remember we talked about this in a different topic a while ago. But I still don't know how I can manage this project without slope compensation. So what kind of control mechanism should I set up so that the direction is not affected by the waves?

The code that I wrote does the "wave compensation" by simply pretending that there are no errors, except heading versus bearing.

The errors caused by random wave action cancel out on average! You are overthinking the problem, and some experiments will show that this really does work.

1 Like

I don't want to tire you out. But can you write a small code block as an example?

Here is the portion of the code (used in the video above) that navigates one leg of a course, following a bearing calculated between the current and target GPS coordinates. The magnetometer heading is used for the heading error correction. Steering in this case is twin screws, not rudder.

			gps_last_fix = millis();
			gps_latlon(&lat, &lon);
			gps_parse_enable(); //re-enable parsing

			gps_bearing = (int) (course_to(lat, lon, lat2, lon2, &distance)+0.5);
			dist_to_go = distance + 0.5;

// To take into account wind and currents, 
// substitute calculated GPS bearing for point_bearing

			point_bearing = gps_bearing;
			num_sats = gga_num_sats();

// leg navigation. Have course bearing from GPS coordinates of current loc and finish

		yaw = get_heading();  //read magnetometer
		
		imu_heading = wrap360(yaw + yaw_offset);  //declination correction and compass wrap

// update LCD display
		clear();
		print_long(point_bearing);
		print(" ");
		print_long(imu_heading);
		lcd_goto_xy(1,1);
		print_long(num_sats);
		print("s ");
		print_long(dist_to_go);

// heading error and PID steering 

		error = heading_error(point_bearing, imu_heading);
		
		//error is positive if current_heading > bearing (compass direction)
		//positive bias acts to reduce left motor speed, so bear left

		bias = (kp*error)/10;  //Kp in tenths (Ki, Kd not necessary in water)

		// the motor routines internally limit the argument to {-255, 255}

		set_m1_speed(motorSpeed-bias); //left motor
		set_m2_speed(motorSpeed+bias); //right motor

// check for recent GPS fix

		if (millis() - gps_last_fix > gps_timeout) //5 seconds, currently
			{
			set_motors(0,0); //stop motors
			clear();
			print(" GPS-to"); //timeout!
			printf("E,%lu,5,0,0,0,0\n",millis()); //error dump
			delay(300);//wait a bit
			}
		else {
			if ((millis()-timer) > timeout || dist_to_go < 3) 
			{

// leg timeout or at goal, stop motors

			clear();
			if (dist_to_go < 3) print("DIST");
			else print("TIME");
			set_motors(0,0);
			delay(1000); //drift a bit

			leg++;  //count course legs accomplished
                        }
1 Like

Thanks again for the codes. I understand the logic, I will complete the codes and start testing them as soon as possible. I will keep you informed about the project

Hello again.
Progress on my project:
I have currently calibrated the HMC5883L module in 2D and observed that there is not much deviation in the slope of the pitch movement with some filters.
However, unfortunately, in case of an error in the roll axis, serious deviations occur in the direction information. Therefore, unfortunately, I cannot use this module.
I always know your advice about the BNO055 modules, "a very old and problematic system", I have read this in many places, but I want to prefer this one because of the automatic slope compensation information. However, I have also read many articles about the automatic calibration it performs every time it is opened, and the fact that it gives 5-10 degrees of error every time it is opened.
I know that you recommend the ICM-20948 module, but unfortunately this product is not available in my country.
There is the AltIMU-10 v4 module, but I had so much difficulty with the slope compensation in other modules that I am now afraid of this type of module.
What do you think I should do? There is a module called BOARDOZA-BNO055, should I use this module?
AltIMU-10 v4 should I prefer this?
Please answer by keeping in mind that I will only do 2D calibration and I need tilt compensation..