Tilt Compensation for Known Magnetic Heading

I am building a tilt-compensated magnetic compass for another autonomous vehicle project I am starting. The vehicle ground speed will be relatively slow and tilt should never exceed 20 degrees in the x or y axes for this application. I hope to use old hardware I had in my parts drawers, but that is presenting some questions.

My compass is a Devantech CMPS03 and my Accelerometer is a Dimension Engineering DE-ACCM2G2 Buffered 2G Accelerometer.

I have both units running a simple test program on A RedBoard Turbo SAMD21 board. I did some level shifting on the PWM output from the Compass because I wanted to be sure not to damage the SAMD21 board.

The Compass calibrates well and gives satisfactory headings while sitting level, and the accelerometer gives good steady indications of tilt in the x/y axes up to about 70 degrees, which is good for my application. My challenge now is in applying the pitch/roll readings into compensation coefficients for the heading angle, and this is presenting me with just one challenge: Even though prior to posting this I have done days of searching and research, and having found a bewildering array of formulas and code for magnetic compass tilt compensation, everything I have seen thus far addresses applying accelerometer (and sometimes gyro) pitch/roll (and sometimes yaw) factors to the x/y/z magnetometer values, and then deriving a heading angle.

My challenge, on the other hand, is that I do not have direct access to the x/y/z magnetometer outputs in my application. The CMPS03 compass module I am using only feeds the actual (but uncorrected) magnetic heading value. I would think there must be some way to apply the pitch/roll values to my heading angle as it is, without needing to know the x/y magnetometer values from which the heading was calculated.

I feel that if I was just a bit more comfortable with my trig functions I could work through this myself, but it is a bit much for me. I am thinking that by breaking down the problem in four functions, each addressing its own quadrant, I might simplify the task, but thus far I am still hitting a wall.

I had even considered simply building a number of lookup tables by experimentation and just looking up a given compensation angle based on heading/pitch and roll, but in order to build those lookup tables to apply through 360 degrees of rotation and possible +/- 20 degrees of tilt in the x/y axes, I decided that approach would be possible, but cumbersome.

I am hoping someone with stronger math skills than mine can provide a solution here. Again, I have magnetic heading being read out in degrees, I have pitch and roll being read out in degrees +/- 0 with 0 as the origin (level). FYI I am at around 46 degrees latitude with local magnetic declination around 15 degrees east and magnetic inclination (dip) of about 69 degrees.

Any help is much appreciated.- Rob

OK so sometimes just posting a question helps me to think through a solution. Here is what I was able to come up with overnight. I was considering the formula from a tilt compensation paper I have been referencing where they describe the basic heading formula applied to the magnetometer x/y values such that heading=atan(Hy/Hx). This got me thinking that applying the inverse of that function back to the angle would give me the ratio of the x/y values. I would then only need multiply an arbitrary x value to get a y value I could work with in the remainder of the tilt compensation equations from the article.

Here as an example I have worked out: Lets say my compass is reading 48 degrees to start. For all of my calculations I have decided 20 will be my x value. This value is arbitrary, as it is only being used as a multiplier to the x/y ratio to find the y value. So, I take the tangent of my angle 48, which is about 1.11. That is the ratio of my x/y values. Now I multiply that by my arbitrary x value of 20, so 20*1.11 gives me 22.2, which should be close to my y value. To check, I just plug x=20, y=22.2 back into my original heading formula heading=atan(Hy/Hx), which is heading=atan(22.2/20), which is heading=atan(1.11), which is heading=47.984. So far so good!

Thus far I have only worked this out in the 0-90 degree quadrant. The example formulas in the text I am referring to work out the quadrants later in a series of steps in order to return the appropriate 0-360 degree returns after tilt compensation. I will post back if and when I get the rest worked out. This may seem like a roundabout approach to the problem, but now that I have worked through it it is really only a few steps and not that much to add to the overall process. Perhaps this might help someone else working on the same problem. It also illustrates that a packaged magnetic compass might be convenient if all one is looking for is magnetic heading, but if one wishes to do further corrections from the x/y/z magnetometer readings, they might be better off just starting with a 3-axis magnetometer and using the raw values.

Here is the link to the paper I referenced. It is about as thorough an explanation of magnetic compass tilt compensation as I have ever found:

https://www.cypress.com/file/130456/download

Perhaps it might help others working with the same problem.- Rob

Thanks for posting your process Rob - I enjoyed reading about your epiphany. Your use case is interesting to me because I once upon a time I made a tilt-compensated compass but of course I had access to all 6 axes of accel/magnetometer data independently.

Keep us posted on how it turns out!

Pololu posted an elegant method of tilt compensation that uses vector math and if you are familiar with cross and dot products, is trivial to understand. Works very well, but in all cases it is critical that the magnetometer be properly calibrated.

Code excerpt: (See https://www.pololu.com/product/1250/resources)

// Returns a heading (in degrees) given an acceleration vector a due to gravity, a magnetic vector m, and a facing vector p.
int get_heading(const vector *a, const vector *m, const vector *p)
{
	vector E;
	vector N;
  //West, actually, not East.
	// cross magnetic vector (magnetic north + inclination) with "down" (acceleration vector) to produce "east"
	vector_cross(m, a, &E);
	vector_normalize(&E);

	// cross "down" with "east" to produce "north" (parallel to the ground)
	vector_cross(a, &E, &N);
	vector_normalize(&N);

	// compute heading
	int heading = round(atan2(vector_dot(&E, p), vector_dot(&N, p)) * 180 / M_PI);
	if (heading < 0)
		heading += 360;
	return heading;
}

Yes! To me that makes much more intuitive sense than listing all the trig functions (which are tightly related of course). The trick here is that for the vector math method you must know all the individual components. In Rob’s case he may assume zero z-component on the magnetometer and use his described technique to get x and y components. I can’t tell for sure but he might not even have access to individual acceleration components.

Still without a doubt in my mind this vector math method is the easiest to understand and the cleanest to look at.