Trying to find compass heading from magnetometer

Posted on
  • I've been (unsuccessfully) trying to convert the magnetometer readings to a compass headings. I've been googling, and due to my maths education stopping at GCSE level, I don't understand most of the conversions. I tried following the adafruit guide but it's for a different magnetometer sensor, and the readings don't seem the same, Adafruit guide says the readings are in micro-Teslas and will be between 20 and 60 micro Teslas, and in the Puck guide it's saying a similar number, but that the increments are in 0.1 micro Teslas. I am getting readings of { "x": -3431, "y": 4167, "z": 508 } so even divided by 10 these are off.

    I thought that the following code might work:

    Puck.on('mag', function(xyz){
      console.log((Math.atan2(xyz.y/10,xyz.x/1­0) * 180) / Math.PI);

    But I don't get the readings I would expect.

    Am I missing something or being a dope?

  • Don't have a Puck here but I am working with magnetometer elsewhere in the forum.
    Try atan2(x,y)*180/pi
    First the divide by 10 in your atan2 function isn't needed. The ratio of the numbers is what's important for the trig to work.
    Second set up a non magnetic level surface with a method of aligning the x or y axis due North and due South. Point the X axis North and take a reading, then point it South and take a reading. Do the same for the Y axis. If North reading equals - South reading the axis is zeroed. If not you will need to correct the readings with an offset value for each axis. (average 10 North reading with 10 South readings to get the offset value.
    Do the math with the offsets applied to the raw readings. x1= x_raw-x_offset, y1= y_raw - y_offset.
    Third the atan2 *180/PI gives -180 to +180 and you likely want 0 to 360 with 90 being East.
    If atan2*180/pi <0 add 360.
    If West is 90 degrees try atan2(-x,y).

    xy plane has to be level to get a heading.

  • I follow you to a point, but I don't know what you mean in this bit

    Point the X axis North and take a reading, then point it South and take a reading. Do the same for the Y axis.

    Do I have the puck flat on a level surface and align it to North and South do the X axis (like turning a knob), then do I tip it on it's side and turn it (like a spinning coin) to do the same for Y?

  • Try this:

    var pirate=180.0/Math.PI;
    console.log("Level", pirate*Math.atan2(x,z]),pirate*Math.atan­2(y,z));
    var heading; // if level
    if(heading <0) heading+=360;
  • Not having a puck I'm not sure how the axis are laid out.
    If you set up to continuously output the raw x,y,z readings and then rotate the puck
    you should see the X-axis reach a maximum, and the same for the Y and Z axis.
    Note the position of the puck at each maximum relative to North.

    See puck.mag at

  • The code posted above does a calculation called level and that uses the accelerometer in the chip I'm using, not the magnetometer. Sorry about that.
    The code expects -Z to be down and if +Y points North the heading is 0.0 degrees.
    You may need to swap the axis in the formulas if you want to orient the Puck differently

  • I think the main thing here is that you have to find 'zero' for the magnetometer.

    All kinds of things can effect the reading - for instance when I've been using a magnet, if I get too close it can presumably magnetise some part of the Puck, and that then changes the readings from then on.

    But once you have the zero, you can subtract it from the magnetometer value and you should get a much cleaner reading.

    Easiest way I'd say is to take 4 readings - one normally, one rotated 90 degrees, one 180, and one 270. Add the x, y and z's separately and divide by 4 - then always subtract that value from the magnetometer.

  • Digging up an old thread here, but I kind of got it working enough for what I want.

    I did what was suggested an took four readings at N,S,E,W, averaged them and deducted them from what Puck.mag() gave me back, then I ran it through the function I had before and it gave me accurate enough readings for the headings (0 for North, 180 for South, -90 for West and 90 for East). They aren't super accurate, but I'm guessing that's because of the nearby magnetic fields etc.

    My code is below:

    // Be sure to take your own readings for your own magnetic environment!
    const avg = { "x": -967, "y": 514, "z": 1762 };
    Puck.on('mag', function(xyz){
      xyz.x -= avg.x;
      xyz.y -= avg.y;
      xyz.z -= avg.z;
      console.log((Math.atan2(xyz.y, xyz.x) * 180) / Math.PI);
  • Thanks - this looks great.

    Just to add: If you're using strong magnets near your Puck the you can magnetise it slightly which will knock off your calibration value (and you'll have to take the 4 readings again).

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview

Trying to find compass heading from magnetometer

Posted by Avatar for Bennr @Bennr