Hello everyone.

I need tilt compensation for a project I'm working on.

I am working with Arduino Nano and GY87 (10 DOF) module.

I'm trying to do tilt compensation by following the instructions here at GitHub - jarzebski/Arduino-HMC5883L: HMC5883L Triple Axis Digital Compass Arduino Library as its calibration is pretty easy and understandable. However, it does not compensate for tilt in any way.

I can read the HMC5883 and MPU6050 data just fine, but what I need most is tilt compensation because the project will be used on a boat and there will be ripples on the water.

Is there a resource you can refer to? Can you help me?

In order to use the magnetometer as a compass, you first need to calibrate it.

The best overview tutorial is this one: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

For tilt compensation you need to make certain that the magnetometer and accelerometer X, Y and Z axes are correctly aligned with each other, and both form right handed coordinate systems. That is NOT the case for many IMUs.

Once those details are observed, the simplest tilt compensation code uses vector algebra. One Arduino example for the MPU-9250 IMU can be found here:

First of all, I'm sorry if there are any mistakes in what I wrote, because English is not my mother tongue. Thanks for the answer. Frankly, I wrote my question anticipating and hoping that you would answer it, because whenever I look for an answer on a topic, I get inspired by your posts.

We talked a little before, we talked about your GPS Boat project. I'm working on the same project right now. The point I'm stuck with so far is to get the correct compass angle with tilt compensation.

Since GY-87 is a ready module, I don't know if axis alignment is needed because both modules are located on the same board (MPU6050 HMC5883).

You need to know. Look closely at the chips and the diagrams in their data sheets, figure out where all the sensor axes are pointed and whether they agree between sensors.

The HMC5883 hasn't been manufactured for some years, so you have either an imitation (QMC5883) or a counterfeit.

Sorry for the late reply, but I just got to the computer.

You're right when you say, on the module I understand that the alignment of the axes is not correct, but I really couldn't find how to change it according to the library I'm using and I'm very confused. Could you please take a look?

```
float tiltCompensate(Vector mag, Vector normAccel)
{
// Pitch & Roll
float roll;
float pitch;
roll = asin(normAccel.YAxis);
pitch = asin(-normAccel.XAxis);
if (roll > 0.78 || roll < -0.78 || pitch > 0.78 || pitch < -0.78)
{
return -1000;
}
// Some of these are used twice, so rather than computing them twice in the algorithem we precompute them before hand.
float cosRoll = cos(roll);
float sinRoll = sin(roll);
float cosPitch = cos(pitch);
float sinPitch = sin(pitch);
// Tilt compensation
float Xh = mag.XAxis * cosPitch + mag.ZAxis * sinPitch;
float Yh = mag.XAxis * sinRoll * sinPitch + mag.YAxis * cosRoll - mag.ZAxis * sinRoll * cosPitch;
float heading = atan2(Yh, Xh);
return heading;
}
float correctAngle(float heading)
{
if (heading < 0) { heading += 2 * PI; }
if (heading > 2 * PI) { heading -= 2 * PI; }
return heading;
}
void loop()
{
Vector mag = compass.readNormalize();
Vector acc = mpu.readScaledAccel();
heading1 = noTiltCompensate(mag);
heading2 = tiltCompensate(mag, acc);
if (heading2 == -1000)
{
heading2 = heading1;
}
heading1 = correctAngle(heading1);
heading2 = correctAngle(heading2);
heading1 = heading1 * 180/M_PI;
heading2 = heading2 * 180/M_PI;
Serial.print(heading1);
Serial.print(":");
Serial.println(heading2);
delay(100);
}
```

Thanks for the answer.

But I really don't know how to do these calculations. Of course, this should not be perceived as ready, but if I can create a correct slope compensation by making changes in the codes I wrote above, it will be much easier for me to fully understand it.

The MPU-6050 has a right handed coordinate system for the accelerometer, so call that the reference. For the magnetometer you need to rename/invert axes to agree.

I can't make sense of the diagram for the magnetometer, because there are two "X-". But looking at the photo of the PCB, it appears that the magnetometer has X and Y parallel to those of the MPU-6050, which is OK. The question remains as to which direction Z points.

I can't make sense of the trigonometric formula of tilt compensation either, because there is no standard convention for which direction of rotation corresponds to a positive angle. You have to try both conventions until you get it right.

That is why I so strongly prefer the vector method of tilt compensation. There is no confusion about angle signs.

Finally, you can deduce the axes of the magnetometer by measuring the Earth's magnetic field (print out the three axial components). In the northern hemisphere, the magnetic field points North and steeply Down.

This is the code I use for tilt compensation:

```
// Returns a heading (in degrees) given an acceleration vector a due to gravity, a magnetic vector m, and a facing vector p.
// accelerometer and magnetometer data vectors must be right handed and normalized.
// applies local magnetic declination in degrees
int get_heading(float acc[3], float mag[3], float p[3], float magdec)
{
float W[3], N[3]; //derived direction vectors
// cross "Up" (acceleration vector, g) with magnetic vector (magnetic north + inclination) with to produce "West"
vector_cross(acc, mag, W);
vector_normalize(W);
// cross "West" with "Up" to produce "North" (parallel to the ground)
vector_cross(W, acc, N);
vector_normalize(N);
// compute heading in horizontal plane, correct for local magnetic declination in degrees
float h = -atan2(vector_dot(W, p), vector_dot(N, p)) * 180 / M_PI; //minus: conventional nav, heading increases North to East
int heading = round(h + magdec);
heading = (heading + 720) % 360; //apply compass wrap
return heading;
}
// subtract offsets and apply correction matrix to accel and mag data
void get_scaled_IMU(float Axyz[3], float Mxyz[3]) {
byte i;
float temp[3];
Axyz[0] = imu.agmt.acc.axes.x;
Axyz[1] = imu.agmt.acc.axes.y;
Axyz[2] = imu.agmt.acc.axes.z;
Mxyz[0] = imu.agmt.mag.axes.x;
Mxyz[1] = imu.agmt.mag.axes.y;
Mxyz[2] = imu.agmt.mag.axes.z;
//apply offsets (bias) and scale factors from Magneto
for (i = 0; i < 3; i++) temp[i] = (Axyz[i] - A_B[i]);
Axyz[0] = A_Ainv[0][0] * temp[0] + A_Ainv[0][1] * temp[1] + A_Ainv[0][2] * temp[2];
Axyz[1] = A_Ainv[1][0] * temp[0] + A_Ainv[1][1] * temp[1] + A_Ainv[1][2] * temp[2];
Axyz[2] = A_Ainv[2][0] * temp[0] + A_Ainv[2][1] * temp[1] + A_Ainv[2][2] * temp[2];
vector_normalize(Axyz);
//apply offsets (bias) and scale factors from Magneto
for (int i = 0; i < 3; i++) temp[i] = (Mxyz[i] - M_B[i]);
Mxyz[0] = M_Ainv[0][0] * temp[0] + M_Ainv[0][1] * temp[1] + M_Ainv[0][2] * temp[2];
Mxyz[1] = M_Ainv[1][0] * temp[0] + M_Ainv[1][1] * temp[1] + M_Ainv[1][2] * temp[2];
Mxyz[2] = M_Ainv[2][0] * temp[0] + M_Ainv[2][1] * temp[1] + M_Ainv[2][2] * temp[2];
vector_normalize(Mxyz);
}
// basic vector operations
void vector_cross(float a[3], float b[3], float out[3])
{
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
}
float vector_dot(float a[3], float b[3])
{
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
void vector_normalize(float a[3])
{
float mag = sqrt(vector_dot(a, a));
a[0] /= mag;
a[1] /= mag;
a[2] /= mag;
}
```

Looks more like a dot rather than a cross, so I'd assume you are supposed to assume they are doing it right and Z is up away from the board and -Z is down through the board.

Understood. I will try to calibrate this module with the libraries you wrote in the github repository. and I will try to make the slope compensation as you described there. I hope I can run it by adapting with the module named GY-87.

I thank each and every one of you. I will inform you again.

Hello again.

First of all, when I check with I2C Scanner, I can see that my compass is HMC5883 (0x1E), it works stably with HMC5883 Libraries.

I calibrated the GY-87 module by running your "MPU9250_cal.ino" file without making any changes to your library. I divided the values I received into "acc.csv" and "mag.csv" and ran the Magneto program.

Text.txt (11.0 KB)

For the acc.csv file, I set the sigma value to "2" and the vector Hm value to "10000".

For the mag.csv file, I set the sigma value to "2" and the vector Hm value to "100".

I wrote the incoming values in the relevant section of the "MPU9250_tiltcomp.ino" file.

```
float A_cal[6] = {779.62, -352.45, -607.14, 0.59616, 0.60699, 0.59702};
float M_cal[6] = {-626.40, 240.89, -53.66, 0.00305, 0.00297, 0.00306};
```

But right now the compass unfortunately doesn't work as it should, it doesn't make any degree changes while turning the module around.

Could it be related to the axial directions of the compass, as you mentioned above? Although I tried many times, I still can't solve it and I really need some serious help

I always test whether the corrections were properly applied by either plotting the corrected data in 3D, or running the corrected data back through Magneto to see if the corrections are negligible.

You may have made a mistake in the code, too, but no one can see it.

How can I control it by drawing in 3D?

Also, I couldn't figure out how to do this.

I use Matlab or Mathematica. Example

Other programs are available to make similar plots.

Also, I couldn't figure out how to do this.

Modify the compass program to print out the data after applying the corrections, in the same format used by MPU9250_cal.ino

I could not find the programs you mentioned in use, I guess I will not be successful because I am not experienced enough to write the programs.

I really don't know what to do and I'm stuck here.

One option is to buy a *modern* 9DOF sensor, rather than a lashup of obsolete parts from Alibaba, for which a reasonably complete, working set of programs is available for calibration and application, e.g. AHRS or tilt compensated compass.

Adafruit, Pololu and Sparkfun sell them, among others.

You are so right when you say it, there is nothing to say about it. However, the module I have is a stable, readable module. I'm not sure, of course you know much more, but as I understand there is an axial difference. You mentioned this above. If the necessary changes are made in the library according to this axial difference, I think the module will start to work literally. Because your library is written in a completely smooth and understandable way.

Do you think the current problem may be due to the axis difference, should I try to make these changes in the library? Should I focus on this?

After making the calibration, the angle needs to change as the module rotates parallel to the ground. But when I tilt the module 100-110 degrees on the roll (right) axis and rotate it on the pitch axis, the angle starts to change..

When I read the HMC5883L datasheet, I came across the addresses shown in the image below.

This is **another example**.

However, these addresses were different in our 9250 library. Do you think this is the place I'm looking for?

The MPU-9250 library has nothing to do with the HMC5883. You have to use the HMC5883 library to read the data from it.

Yes, but I don't know how to change the library like this, how to calibrate when I change it, and then combine them.

That's why I guess there is no question/answer in the article I haven't read for days..