11

I do not know if here is the best place to post this question. I have a 9-DOF sensor (MPU 9150) and I want to use its magnetometer to retrieve the rotation angle, from 0 to 359. However, its library returns three values: x, y and z.

I do not know how to transform this into a single value. There's any way to do this?

4
  • It is giving you the vector that runs along the local magnetic field -- ideally it points to magnetic north. The rotation mapping between the measurement and magnetic north should give you your heading.
    – BrettAM
    Dec 12, 2015 at 4:43
  • 3
    Reading the datasheet could be very enlightening. Have you tried it?
    – Majenko
    Dec 12, 2015 at 11:16
  • @Majenko Yes, but nothing really useful.
    – Guilherme
    Dec 13, 2015 at 1:56
  • So, basically, is it even possible to compute a heading (with tilt compensation) with a magnetometer only (no gyrometer or accelerometer)?
    – Max
    Apr 10 at 14:48

4 Answers 4

18

The answer given by Dave X, namely

az=90-atan(y/x)*180/pi

is on the right track. However, it cannot work as is for two reasons:

  1. The formula fails badly whenever x = 0,

  2. It gives the same azimuth for x = y = 1 and for x = y = −1. In other words, it cannot differentiate between north-east and south-west.

The formula could be fixed by replacing atan(y/x) by a function that returns:

  • atan(y/x) if x > 0
  • π/2 if x = 0 and y > 0
  • −π/2 if x = 0 and y < 0
  • atan(y/x) + π if x < 0 and y ≥ 0
  • atan(y/x) − π if x < 0 and y < 0

That would be essentially a two-argument, “quadrant-aware” version of the arctangent, yielding results in (-π, π] instead of (−π/2, π/2).

Fortunately, such function already exists. It is a very standard function of the C library and is called atan2(). Thus, the correct formula is

az = 90 - atan2(y, x) * 180 / M_PI;

Now, since atan(y/x) = π/2 − atan(x/y), the formula above can be simplified into

az = atan2(x, y) * 180 / M_PI;

This formula gives the direction of the (x, y) vector counted clockwise from the y axis. It is likely that the OP wants instead to know the heading of some kind of vehicle. The correct formula depends then on how the magnetometer has been mounted relative to the vehicle. If we assume that the x axis of the magnetometer points forward and the y axis points to the left, then:

  • the vehicle is heading north (0° magnetic heading) when the horizontal component of the magnetic field is along +x
  • the vehicle is heading east (90° magnetic heading) when the horizontal component of the magnetic field is along +y

From here, it follows that the vehicle heading is

heading = atan2(y, x) * 180 / M_PI;

Obviously, the magnetic declination has to be added if one is interested in the true (rather than magnetic) heading.


Edit: All this assumes the XY plane is horizontal. If it is not (due to the vehicle's pitch and roll), see Tails86's answer for a way to cope with that.

5

You first need calibration (either using the device calibration method, or, by rotating it in a complete circle to ascertain the full field strengths).

You can then use the X, Y, and Z readings to compute the location of "north" relative to whichever way you're pointing (keeping in mind that "north" changes as much as 30 degrees depending where on earth you are (called declination - see http://www.magnetic-declination.com/), and that it's not horizontal to the ground either (confusingly, called "inclination" - see https://physics.stackexchange.com/questions/283761/if-you-hold-a-compass-needle-vertical-does-it-point-down-or-up-differently-on-wh/283782 )

If you picture the X, Y, and Z readings as meaning "where on an imaginary circle is the direction of magnetism - where those 3 circles are on 3 different planes, you can compute the logical position of a vector pointing towards magnetic north in 3 dimensions.

The bad news - there's no simple way to do what you want accurately, and definitely not one that's going to work without trig math on all 3 readings (you can't just use X and/or Y alone).

The good news - you probably don't really need to know an actual "heading" - most applications just need some local relative direction as compared with something you already know.

Beware that strong magnets (such as ones in all modern motors) seriously mess with readings from a long way away (10 or more foot even) - grab an app from your phone store that shows your phone magnetometer readings, and wave a magnet around to see this easily!

3

This is a basic math problem.

az=90-atan(y/x)*180/pi

"atan()" is arctangent and converts from coordinate variables to angle in radians, *180/pi converts from radians to degrees, and 90- converts from degrees math angles (degrees CCW from the x axis) to azimuth (degrees CW from north).

Calibrating the sensor is a trickier problem.

3

I wanted to expand upon what Anon is getting at and provide some math as I've just gone through this process for a similar 9-axis device.

First, it's good to know what the magnetic field vector means. I like this source because it provides an image showing where magnetic vectors point as you move up and down earth. It points downward on the northern hemisphere and upward in the southern hemisphere. The text is otherwise not relevant as it more or less just explains how you can approximate laitudinal position from that data - the angle of the horizontal component is all that we need to be concerned with in order to obtain a heading.

The problem with Edgar's answer is that it assumes that your reference plane is flat against earth. As soon as you add any pitch or roll, that answer won't be right. To correct, you need to project the magnetic field vector onto your horizontal plane. Luckily, that sensor also provides you with an accelerometer which provides a way to tell you which way is down due to the force of gravity as long as you're not moving around too quickly.

What I did was project the magnetic field vector onto the acceleration vector then subtracted that from the magnetic field vector to get the horizontal component vector. Then I could retrieve the vector angle on the X/Y plane just as described by others.

First, a partial definition of a vector so I'm not depending on external libraries:

    class Vector3D
    {
    public:
        Vector3D() : mArr{} {}
    
        Vector3D(double x, double y, double z)
        {
            mArr[0] = x;
            mArr[1] = y;
            mArr[2] = z;
        }
    
        double dot(const Vector3D& rhs) const
        {
            double out = 0;
            for (int i = 0; i < NUM_DIMENSIONS; ++i)
            {
                out = out + mArr[i] * rhs.mArr[i];
            }
            return out;
        }
    
        Vector3D operator* (double x) const
        {
            Vector3D out;
            for (int i = 0; i < NUM_DIMENSIONS; ++i)
            {
                out.mArr[i] = mArr[i] * x;
            }
            return out;
        }
    
        Vector3D operator- (const Vector3D& x) const
        {
            Vector3D out;
            for (int i = 0; i < NUM_DIMENSIONS; ++i)
            {
                out.mArr[i] = mArr[i] - x.mArr[i];
            }
            return out;
        }
        
        double getX() const {return mArr[0];}
        double getY() const {return mArr[1];}
        double getZ() const {return mArr[2];}
    
    private:
        static const int NUM_DIMENSIONS = 3;
        double mArr[NUM_DIMENSIONS];
    };
    
    Vector3D operator* (double x, const Vector3D& y)
    {
        return (y * x);
    }

Here is how the compass angle can be computed:

    // mag: magnetic vector
    // accel: acceleration vector (due to gravity)
    double computeYaw(double mag_x, double mag_y, double mag_z, double accel_x, double accel_y, double accel_z)
    {
        const Vector3D vector_mag(mag_x, mag_y, mag_z);
        const Vector3D vector_down(accel_x, accel_y, accel_z);
        const Vector3D vector_north = vector_mag - ((vector_mag.dot(vector_down) / vector_down.dot(vector_down)) * vector_down);
        return atan2(vector_north.getX(), vector_north.getY()) * 180 / M_PI;
    }

EDIT: I changed the code so needed elements of a Vector are defined here so there is no confusion.

EDIT2: Here is a good resource on this subject if you need a more accurate solution. As stated in this resource, the above is only really useful if you're not moving around too much and your magnetic environment is clean.

4
  • 1
    tf2/LinearMath/Vector3.h: No such file or directory. Got a link to the library?
    – VE7JRO
    Feb 9, 2022 at 2:08
  • 1
    Very good point! Note that: 1. If the platform is moving, we can get a better estimate of the vertical by combining the accelerometer data with the gyro data. 2. For better accuracy, atan2() should be fed with the components of the magnetization in the horizontal plane, rather than the platform's XY plane. Feb 9, 2022 at 15:03
  • Edgar - That is indeed a big issue with this implementation. Any small movements will skew the result. I found that averaging the result over 20 samples was enough to smooth that out for my own needs as I'm dealing with a slow moving device.
    – Tails86
    Feb 9, 2022 at 18:40
  • Is there any open source lib that implements all the math of the article linked in the second edit?
    – Guilherme
    May 31 at 19:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.