• Q&D Magnetometer Calibrations

    The Ideal Magnetometer

    From an ideal three axis magnetometer with readings X, Y and Z from orthogonal axis, we can compute the Radius R of a sphere. For easy coding let us represent the three components X, Y, and Z in a vector M=[X, Y, and Z]. This M[0] is the X axis reading, M[1] is the Y axis reading and M[2] is the Z axis reading. The radius R = Sqrt(M[0]^2+M[1]^2+M[2]^2).
    For an ideal magnetometer the radius R is a constant for a given external magnetic field.
    In practice each axis of the magnetometer will not be centered and may have a different gain from the other two axis. Centering consist of finding an offset value to subtract from the raw readings for each axis. Gain adjustment finds a value that makes the output of each axis equal when positioned in the same magnetic field.

    Coding note

    I’m using a LSM9DS1 IMU chip connected to a Pico. Since there is recent interest in the Puck magnetometer, I will try to point out where there are differences and give my best guess as to how to use this technique with a Puck.

    How the calibrations are implemented

    We start with a raw reading from the magnetometer. Mr[i], where Mr is the raw reading vector containing raw values for the X, Y and Z, axis. To get the calibrated reading in vector Mc, the following equation is used:
    Mc[i]=( Mr[i] – Moffest[i] ) * Scale * Mgain[i],
    Where Mc[i] is the calibrated vector component,
    Mr[i] is the raw vector component,
    Scale is used to convert ADC count to engineering units such as Gauss. (Can omit for Puck),
    Mgain[i] is the gain factor for the vector component,
    and i is the component index 0...2.
    The raw magnetometer readings from the LSM9DS1 IMU are in ADC counts and the Scale (there are 4 different ones) is the full scale engineering units divided by 32768.
    For example 4 Gauss/ 32768 = 0.0001220703125 = Scale.

    The min/max Method

    function minmax(){
     this.min=[0,0,0];
     this.max=[0,0,0];
     this.avg=[0,0,0];
     this.R=0;
     this.amp=[0,0,0];
     this.gain=[1,1,1];
    }
    
    minmax.prototype.process=function(A){
      var i,S=[0,0,0];
      this.R=0;
      for(i=0;i<3;i++){
       if(A[i]>this.max[i])this.max[i]=A[i];
       if(A[i]<this.min[i])this.min[i]=A[i];
       this.avg[i]=(this.min[i]+this.max[i])/2;
       this.amp[i]=(this.max[i]-this.min[i])/2;
      }
      for(i=0;i<2;i++){
       this.gain[i]=this.amp[2]/this.amp[i];
       S[i]=4/32768*this.gain[i];
      }
      console.log("A= ",A);
      console.log("max= ",this.max);
      console.log("min= ",this.min);
      console.log("avg= ",this.avg);
      console.log("amp= ",this.amp);
      console.log("Gain= ",this.gain);
      console.log("S= ",S);
    };
    

    For the LSM9DS1 I used the minmax class in a loop that can be stopped using a menu system.

        case "4"://read and display data
         menulevel=10;sst="";
         readall(W);
         nn=setInterval(function () {
           readall(W);
           count++;
           if(count>1000)clearInterval(nn);
          }, 100);
        break;if(count>1000)clearInterval(nn);
    xxxx
    function readall(W){
    if(W.magAvailable()){
      W.readMag();
      T.process(W.m);
    }//endif
    }//end readall
    

    Due to budget I don't own a Puck yet, so this code may need some enhancement.
    For a Puck the Puck.on('mag', function() { ... }); would be used something like this:

    var T=new minmax[]; 
    var M=[0,0,0];
    var Moffset=[0,0,0];
    var Mgain=[1,1,1];
    
    function onMag(p) {
      M[0]=(p.x-Moffset[0])*Mgain[0];
      M[1]=(p.y-Moffset[1])*Mgain[1];
      M[2]=(p.z-Moffset[2])*Mgain[2];
    T.process(M);
    }
    

    Running the code

    The idea is to run the code and then point each end of each axis towards the magnetic pole. For my location in North America it is North and about 60 degrees below the horizon due to magnetic dip. This process is continued until the readings for each axis stop changing.
    At the start: (readings are in raw ADC counts)

    A=  [ 2145, 2153, -1557 ]
    max=  [ 2145, 2201, 0 ]
    min=  [ 0, 0, -1581 ]
    avg=  [ 1072.5, 1100.5, -790.5 ]
    amp=  [ 1072.5, 1100.5, 790.5 ]
    Gain=  [ 0.73706293706, 0.71830985915, 1 ]
    S=  [ 0.00008997350, 0.00008768430, 0 ]
    

    After readings stop changing:

    A=  [ 1883, 1919, -1572 ]
    max=  [ 6327, 3363, 4486 ]
    min=  [ 0, -3755, -2141 ]
    avg=  [ 3163.5, -196, 1172.5 ]
    amp=  [ 3163.5, 3559, 3313.5 ]
    Gain=  [ 1.04741583688, 0.93101994942, 1 ]
    S=  [ 0.00012785837, 0.00011364989, 0 ]
    

    At this point for the LSM9DS1, I used a different program to write the offsets (avg)to ROM. For the Puck copy the offsets (avg) to the Moffset[].

    To evaluate the calibration data were collected using code like this:

    function readall(W){
    var avg=  [ 3163.5, -196, 1172.5 ];
    var Gain=  [ 1.04741583688, 0.93101994942, 1];
    var R1,R2,R3;
    var raw=[0,0,0];
    var Roff=[0,0,0];
    var Rcal=[0,0,0];
    var i;
    if(W.magAvailable()){
      W.readMag();
    //  T.process(W.m);
    for(i=0;i<3;i++){
      raw[i]=W.m[i];
      Roff[i]=raw[i]-avg[i];
      Rcal[i]=Roff[i]*Gain[i];
      R1=Math.sqrt(raw[0]*raw[0]+raw[1]*raw[1]+raw[2]*raw[2]);
      R2=Math.sqrt(Roff[0]*Roff[0]+Roff[1]*Roff[1]+Roff[2]*Roff[2]);
      R3=Math.sqrt(Rcal[0]*Rcal[0]+Rcal[1]*Rcal[1]+Rcal[2]*Rcal[2]);
    }//next i
    console.log(R1,R2,R3);
    }//endif
    }//end readall
    

    The magnetometer was pointed in many different orientations as data were collected. R1 is the radius from raw data, R2 is the radius from raw data - offset, and R3 is the radius from the gain times the raw data - offset. Excel was used to perform descriptive statistics on the radius values R1, R2, and R3.

    R1 R2 R3
    Mean 4093.694998 3522.325582 3446.806677
    Standard Error 82.29402041 33.33800845 31.38490953
    Median 4348.209172 3585.501569 3508.904969
    Mode #N/A #N/A #N/A
    Standard Deviation 1261.543444 511.0619921 481.1215526
    Sample Variance 1591491.862 261184.3597 231477.9484
    Kurtosis -0.645447113 0.85299825 0.453653148
    Skewness -0.525200694 -0.538526 -0.405032898
    Range 5344.130082 2606.674164 2376.33701
    Minimum 715.4453159 2087.480659 2163.026205
    Maximum 6059.575398 4694.154823 4539.363215
    Sum 962018.3246 827746.5118 809999.569
    Count 235 235 235
About