-
Software Gimbals
Comparison of theoretical and actual accelerometer readings
N Troll Tpitch Tyaw Aroll Apitch Ayaw 0 0.00001 0 1 -0.003021269 -0.041954586 0.999114951 1 -1 0 0.00001 -0.997628186 -0.025433701 0.063961937 2 0.00001 0 -1 -0.045201791 0.000406194 -0.998977794 3 1 0.00001 1 0.993574107 -0.033400851 -0.108142854 T = Theory, A= Accelerometer data
Notice the signs match.Comparison of theoretical and actual magnetometer readings
N Troll Tpitch Tyaw Mroll Mpitch Myaw 0 0.866 0.5 1.732 0.167571285 0.470716976 -0.866224793 1 -1.732 0.5 0.866 -0.865231458 0.404036655 -0.296865129 2 -0.866 0.5 -1.732 -0.163571745 0.52435959 0.835638262 3 1.732 0.5 -0.866 0.735023669 0.362766964 0.572835348 T=Theory, M= Magnetometer data
Notice that the Z axis signs don’t’match.Results using the sign change on the Z-axis data on data.
Data are from a LSM9DS1 IMU.
N Aroll Apitch Ayaw Kroll Kpitch Kyaw Mroll Mpitch Myaw M1roll M1pitch M1yaw tilt heading dip 0 -0.003 -0.042 0.999 -0.997 0.072 0.000 0.168 0.471 0.866 0.170 0.507 0.845 2.411 288.565 57.694 1 -0.998 -0.025 0.064 -0.025 1.000 0.000 -0.865 0.404 0.297 0.231 0.432 0.872 86.333 298.100 60.679 2 -0.045 0.000 -0.999 0.009 1.000 0.000 -0.164 0.524 -0.836 0.135 0.522 0.842 -2.591 284.511 57.393 3 0.994 -0.033 -0.108 -0.034 -0.999 0.000 0.735 0.363 -0.573 0.504 0.371 0.780 -83.792 323.682 51.273 A= Accelerometer Data
K= Rotation Axis from Rodrigues Rotation Formula
M= Magnetometer Data
M1= Transformed Magnetometer Data
Tilt= angle from Rodrigues
Heading and Dip angles in degrees calculated from transformed magnetometer data.Conclusion
This data set shows the transformation of real world accelerometer and magnetometer data to achieve the target of a software gimbaled compass. It is a limited data set and needs further testing to confirm the results. (This stuff is like a Rubic’s cube!)
-
Result of the QD calibration of the LSM9DS1 magnetometer using the 8 Gauss scale.
The calibration results:
8gcal.txt A= [ 1055, 1059, -774 ] max= [ 2787, 2318, 2211 ] min= [ -282, -2189, -1154 ] avg= [ 1252.5, 64.5, 528.5 ] amp= [ 1534.5, 2253.5, 1682.5 ] Gain= [ 1.09644835451, 0.74661637452, 1 ] S= [ 0.00013384379, 0.00009113969, 0 ]
Statistical results of using the calibrations
R1 R2 R3 Mean 2177.63886 1737.768628 1600.029522 Standard Error 37.24718705 18.20304123 9.234868336 Median 2164.406154 1697.652129 1581.39782 Mode #N/A #N/A #N/A Standard Deviation 503.870607 246.2461775 124.9269833 Sample Variance 253885.5886 60637.17993 15606.75115 Kurtosis 1.775293165 -0.51651582 -0.552808728 Skewness -1.068292004 0.516047324 0.674115736 Range 2610.07284 992.2192807 517.1070694 Minimum 262.2422544 1337.931519 1411.795858 Maximum 2872.315094 2330.1508 1928.902927 Sum 398507.9114 318011.659 292805.4026 Count 183 183 183 cv 0.231383916 0.141702511 0.078077924 For the 8 Gauss range the use of the Gain term reduced the variance in the data.
R1 uses raw readings. R1= sqrt( X1^2 + Y1^2 +Z1^2)
R1 uses raw readings - offset
R2 uses (raw -offset) * gain
. -
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 -
Software Gimbals
Numerical Gotchas
The code doesn't like zero magnitude vectors. The current module version detects these and returns a zero vector [0,0,0]. These can occur when the arguments to the functions are zero vectors.
An additional case occurs when the K vector is zero. This occurs when the cross product of the arguments to the Setup function is a zero vector.
Let A= [0,0,1] and B= [0,0,1] Setup(A,B) returns [0,0,0].I'm considering a flag that if true would cause the zero K vector to be detected and a small dither values to each axis of on of the arguments and start the function over again.
Sorting Out Issues Theory vs Application
My first approach was to collect data from an IMU and apply the Rodrigues rotation formula. Some difficulty getting this to work has led to a different approach.
- Find a small cardboard box and draw the various axis on the sides of the box with a magic marker, along with positive direction arrows.
- For a given heading and dip angle calculate theoretical values for the magnetometer readings. Use the cardboard box on each of it's six sides to apply the theroretical values to the axis fot that orientation. The direction of the axis detemine the sign of the value.
- Assuming positive results using the theoretical values, obtain data from an IMU and compare it to the theoretical values and take appropriate action to produce positive results.
First results of this proceedure:
//Accelerometer data var Adata=[ [0.00001,0,1], [-1,0,0.00001], [0.00001,0,-1], [1,0,0.00001], [0,0.00001,1], [0,-1,0.00001], [0,0.00001,-1], [0,1,0.00001], ];
Note use of 0.00001 to avoid the zero value K vector problem previously disscussed
and//Magnetometer data var Mdata=[ [0.866,0.5,1.732 ], [-1.732,0.5,0.866], [-0.866,0.5,-1.732 ], [1.732,0.5,-0.866], [0.5,0.866,1.732 ], [0.5,-1.732,0.866], [0.5,-0.866,-1.732 ], [0.5,1.732,-0.866], ];
The Rodrigues' Rotation Formula was applied to this data and produced:
N= sample
A= accelerometer vector
K= roatation axis vctorN Aroll Apitch Ayaw Kroll Kpitch Kroll 0.00 0.00 0.00 1.00 0.00 -1.00 0.00 1.00 -1.00 0.00 0.00 0.00 1.00 0.00 2.00 0.00 0.00 -1.00 0.00 -1.00 0.00 3.00 1.00 0.00 0.00 0.00 -1.00 0.00 4.00 0.00 0.00 1.00 1.00 0.00 0.00 5.00 0.00 -1.00 0.00 -1.00 0.00 0.00 6.00 0.00 0.00 -1.00 1.00 0.00 0.00 7.00 0.00 1.00 0.00 1.00 0.00 0.00 and
Normed vectors
M= test magnetometer data
M1= transformed magnetometer data
and Scalars
Tilt degrees
Heading degrees
Dip degreesMroll Mpitch Myaw M1roll M1pitch M1yaw Tiltdeg Heading Dip 1.0.43 0.25 0.87 0.43 0.25 0.87 0.00 330.00 60.00 -0.87 0.25 0.43 0.43 0.25 0.87 90.00 330.00 60.00 -0.43 0.25 -0.87 0.43 0.25 0.87 0.00 330.00 60.00 0.87 0.25 -0.43 0.43 0.25 0.87 90.00 330.00 60.00 0.25 0.43 0.87 0.25 0.43 0.87 0.00 300.00 60.00 0.25 -0.87 0.43 0.25 0.43 0.87 90.00 300.00 60.00 0.25 -0.43 -0.87 0.25 0.43 0.87 0.00 300.00 60.00 0.25 0.87 -0.43 0.25 0.43 0.87 90.00 300.00 60.00 A sucessful test so far!
The code follows:function test(){ FF=require("Rodrigues").connect(); var i; for(i=0;i<Adata.length;i++){ // for(i=0;i<4;i++){ A=FF.Setup(Adata[i],[0,0,1]); Theta=FF.theta; Kn=FF.Kn; A=FF.An; B=FF.RotVector(Mdata[i]); An=FF.An; heading=pirate*Math.atan2(-B[1],B[0]); if(heading<0)heading+=360; dip=pirate*Math.atan(B[2]/Math.sqrt(B[0]*B[0]+B[1]*B[1])); console.log(i,',',A,',',Kn,',',An,',',B,',', Theta,',',heading,',',dip); }//next i }
- Find a small cardboard box and draw the various axis on the sides of the box with a magic marker, along with positive direction arrows.
-
Thoughts on magnetometer calibrations.
Use a min/max program to find the minimum and maximum readings for each axis and then average the minimum and maximum to obtain the offset for each axis.
Point both ends of each axis towards the magnetic pole. Remember the pole is actually below the horizontal by the dip angle.//Cal_idea.js //5 Jan 2017 function minmax(){ this.min=[0,0,0]; this.max=[0,0,0]; this.avg=[0,0,0]; } minmax.prototype.process=function(A){ var i; 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; } console.log("max= ",this.max); console.log("min= ",this.min); console.log("avg= ",this.avg); }; var i; var reading=[0,0,0]; var T=new minmax(); for(i=-4;i<4;i++){ //take magnetometer readings here //fake some data for demo reading[0]=i; reading[1]=i*i; reading[2]=i*i*i; //process data while moving the magnetometer // until the max and min for each axis is at the limit //use the average as the offset value T.process(reading); }//next i
In the module MAG3110.js, does the Puck use this function?
http://www.espruino.com/modules/MAG3110.js/** * Performs a manual magnetic sensor reset (One-Shot). * Initiates a magnetic sensor reset cycle that will restore correct operation after exposure to an excessive magnetic * field wich exceeds the Full Scale Range of +/-1000µT, e.g. near a speaker magnet. */ MAG3110.prototype.magneticReset = function () { var value = this.read8(MAG3110.CTRL_REG2); value |= 0x10; // set Mag_RST this.write8(MAG3110.CTRL_REG2, value); };
Finding the best fit sphere regression using Solver in Excel
-
Software Gimbals for your compass
Introduction
Hardware gimbals have been used to compensate for the rockling of a boat any many other applications. Here is a brief glimpse:
https://en.wikipedia.org/wiki/Gimbal
The idea for this project is to use the three axis readings from an accelerometer to provide a software gimbal for a three axis magnetometer.
Several math techniques can be used to perform this task, such as rotation matrices, quaternions, and the one implemented here the Rodrigues Rotation Formula.
https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions
https://en.wikipedia.org/wiki/Rotation_matrix
https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
https://en.wikipedia.org/wiki/Rotations_in_4-dimensional_Euclidean_space#The_Euler.E2.80.93Rodrigues_formula_for_3D_rotationsThe module Rodrigues.js
The module Rodrigues creates an instance containing the following parameters:
/** Define the vector rotation object*/ function Rodrigues(){ //magnitudes of vectors this.Ra=0; this.Rb=0; this.Rk=0; //unit vectors this.An=[0,0,0]; this.Bn=[0,0,0]; this.Kn=[0,0,0];//the rotation axis // vectors this.K=[0,0,0]; //Scalars this.theta=0;//rotation angle in degrees this.ct=0;// cos(theta) this.st=0;//sin(theta) //Result vectors this.F=[0,0,0]; // a unit vector this.FF=[0,0,0];// scaled vector }
Functions in Rodrigues:
There are two functions in the Rodrigues module.
• Setup(A,B)
The vectors A and B are the arguments and the function performs the following task.- Calculate the magnitudes and unit vectors from vectors A and B, giving
Ra,Rb, An and Bn. - Calculate K the vector cross product of A and B, and the magnitude Rk and the unit vector Kn
- Determines the angle theta between vectors A and B, the sin st and cos cs.
- Rotates vector A so it points the same direction as vector B
Returns the norm of the rotated vector A, the scaled rotated vector is contained in FF
• Having called setup to relate the two frames of reference, the function D=FF.RotVector(C) can be called to rotate vector C about the axis Kn by angle theta.Usage.
The following is an example
var pirate=180/Math.PI; var FF; var A,An,Kn,Theta,B,C ,D,E,F; function test(){ FF=require("Rodrigues").connect(); //A=[-0.70710678118,-0.70710678118,0]; A=[0.70710678118,0.70710678118,0]; B=[0,0,1]; console.log("A= ",',',A); console.log("B= ",',',B); C=FF.Setup(A,B); console.log("C= ",',',C,',',FF.theta); console.log("Kn= ",',',FF.Kn); console.log("ct= ",',',FF.ct,','," st= ",',',FF.st); D=FF.RotVector(C); console.log("D= ",',',D); E=FF.RotVector(D); console.log("E= ",',',E); F=FF.RotVector(E); console.log("F= ",',',F); }
First attempts to gimbal a magnetometer
//testRodrigues1.js // 3 Jan 2017 /** Use the Rodrigues Formula and data to level a compass */ //The data are from a LSM9DS1 IMU chip //The Y axis is pointing roughly North //Readings with the XY plane level, X down and then X up //The output shows the accelerometer data Z pointing down //and the magnetometer data leveled /* https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula */ var pirate=180/Math.PI; var FF; function test(){ FF=require("Rodrigues").connect(); var i; // negate X axis for fun for(i=0;i<Adata.length;i++){ Adata[i][0]=-Adata[i][0]; Mdata[i][0]=-Mdata[i][0]; } for(i=0;i<Adata.length;i++){ console.log(i,',',FF.Setup(Adata[i],[0,0,1]),',',FF.theta,',',FF.RotVector(Mdata[i]),',',FF.Kn); }//next i } setTimeout(function () { test(); }, 1000); //Accelerometer data var Adata=[ [-0.049159472, -0.001571133 , 0.944969535 , ], [-0.035622872, -0.009391533 , 0.968284963 , ], [-0.039314672, -0.006627933 , 0.981773731 , ], [-0.034509472, -0.005157933 , 0.977928516 , ], ]; //Magnetometer data var Mdata=[ [-0.004785037,0.214759059,-0.35996205, ], [-0.002195637,0.217274343,-0.35459097, ], [-0.006668237,0.216994867,-0.36106068, ], [-0.012317837,0.218112771,-0.35825307, ], ];
Poor results more is needed
One issue is the inversion problem, which can be demonstrated using paper, pen and paper clip. On the paper write North at the top, South at the bottom, East on the right and West on the left. Turn the paper over left to right (not top to bottom). On the paper write North at the top, South at the bottom, East on the left and West on the right.
Clip the paper clip to one corner. This represents the North magnetic pole position relative to our paper compass. Now flip the paper over, left to right or top to bottom and note that the paperclip heading changes.
The software needs to level the compass and then detect if the compass is inverted and then flip the readings by 180 degrees along the rotation axis Kn.
Results to follow. - Calculate the magnitudes and unit vectors from vectors A and B, giving
-
-
Using a 3-Axis Magnetometer as a compass.
An example:
First Zero the magnetometer
For each axis X, Y and Z point the axis North and take a reading R1, then turn the magnetometer 180 degrees and take reading R2. Take the average of the two readings as the offset for that axis.
Your code will then take a reading and subtract the offset for each axis. X= Xraw- Xoffset, Y= Yraw- Yoffset, Z= Zraw- Zoffset.
Use the resulting Z, Y and Z values in all calculations that follow. If you repeat the North and then rotate proceedure then
Xn =-Xs, Yn=-Yz, and Zn=-Zs, where the subscripts s and n stanf for North and South.Example
- Assign the Z axis to the Yaw axis.
- Assign the X axis to the roll (heading) axis.
- Assign the Y axis to the pitch axis.
- With the X and Y axis level and the Z axis pointing in the position for use ( Think the model airplane is not inverted):
- Point the roll (X) axis North and note the sign of the X axis reading. If it is negative, you need to multiply X by -1 in the assignment statement of the code
- Point the roll (X) axis East and note the sign of the Y axis. The reading should be negative, if not you need to multiply Y by -1 in the assignment statement of the code
The yaw axis should be positive in the Northern hemisphere and negative in the Southern menisphere. If not negate it.
var X=1,Y=-1,Z=1; var X1,Y1,Z1; var Xoffset=0,Yoffset=0,Zoffset=0; //Read the magnetometer X,Y, and Z //Subtract the offsets X1=X-Xoffset; Y1=Y-Yoffset; Z1=Z-Zoffset; //Assign the XYZ to roll pitch and yaw axis var Sroll=1; var Spitch=-1; var Syaw=1; roll=Sroll*X1; pitch=Spitch*Y1; yaw=Syaw*Z1; heading=180/Math.PI * Math.atan2(pitch,roll); if(heading <0) heading+=360; console.log("Heading= ",heading); dip=180/Math.PI*Math.atan(Math.sqrt(roll*roll+pitch*pitch),yaw); console.log("Dip= ",dip); //add the declination to get true heading
Note the change in the argument order of the atan2 function from the orignal post.
- Try it out to see that 90 degree heading is East, 180 is South, 270 is West and 0 is North.
- Repeat using the Y axis as the roll axis and the X axis as pitch.
https://en.wikipedia.org/wiki/Magnetic_dip
- Assign the Z axis to the Yaw axis.
-
@benddennis a fine job on the compass.
-
https://www.espruino.com/Puck.js
says MAX3110 but that part isn't a magnetometer.
Is it MAG3110? -
@allObjects The Table appeared as markdown source text in the preview, but came out fine in the post.
I used the markdown editor at:
https://jbt.github.io/markdown-editor/#bVNBbtswELzzFVs4gO3GltpremqTpgkQA0WTnoICoUVSpC1yBXJlJyn69y4pw84hgAnJ5HBmd2Y1gdkPRzfDenndyR1GreawknGrcB/gu3KEUYhvMrkGhqTN0IHRkoaooXOJLoSAj3BJsTu/hxouveInISS500BWg3GdPkGsM3QCHv4xvLGISR8vynQqASPcPKzuMsdVlC3IoEBF7EEWbnCBb1kdy+0OpQJHGXydDxsMpAMlkHyeqfk0lLp+/7qDhPCCAzQyQLIZkQmTEOJ26iEgtIgKJME+OnKhZQLfM2mdcZ2OQPqZFpmlxYJhCfSabMYycUy6M5UQd4jbBZeb/fpQ/DKI+bGWcXy8CvGV28pdTFMh4cqV/gAX55/Z4aenp43cydRE15M4m5khNOQwzOZ/BcDZbKrcbjqvLPluNr0F6VmMt6rp/Iv4x4vvC/FgXQL+PWKAMfA/M0vUp4u6bh3ZYV016OvNmmp/8H6pS/zz3GKnCbisbcA9OAO3U45p/Slus6NUSt7n8rld8ZPTSTmNVazgCnFdMsvyj7krGFlHdRb3UTGmiPcRN7qhVGfcQb2eL8BE9LC3rrEiZ+dC6l2U2YIsw4anRdEozll+ewGXo/Kc/QjjTIhBjfS5xMlkAvc0GJNHWmUOL7e6EI0D/Xi0wNG7Nr05f/s+52jjaXZ7GRMPQ2G85JZWLsY3necufdmqgqZ6vJwblHtdWkkvXP/z0rrWdryIax1NKYzH7WqTjpwJDe15lr0MTjapwtiWvfqIrnU4SI30cDzJc8ufBw7UD1QmENYdNttU5DZpqbTpJOl3HVEybLGVrj7hRpX21fV9pkYDSpI82s1zY3ixIn+MSfwH
to create the document and the cheat sheet as a guide found at:
https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet -
Using a 3-Axis Magnetometer as a compass.
- Vertical axis is the Yaw axis
This leaves two remaining axis - The heading or roll axis points in the direction of travel
- The remaining horizontal axis is the pitch axis that points left and right from the heading axis.
- To get a heading we use the atan2 function and then adjust to a 0 to 360 degree range
- If(ww<0) ww+=360
- Atan2 produces values in radians so we define pirate= 180/pi to get degrees
57.29577951 - In the following table pay attention to the sign of the axis values
Heading Roll Pitch Pirate*Atan2(Roll,Pitch) Adjust North 1 0 0 0 NorthEast 1 1 45 45 East 0 1 90 90 SouthEast -1 1 135 135 South -1 0 180 180 SouthWest -1 -1 -135 225 West 0 -1 -90 270 NorthWest 1 -1 -45 315 *Magnetometer axis usually have an offset that needs to be corrected so that no field produces a zero value. x1= x - xoffset, y1= y-yoffset, z1= z-zoffset;
- You must assign the X,Y, and Z axis to the Roll, Pitch and Yaw axis based on the mounting of the magnetometer.
- The roll or pitch readings from the mgnetometer may need to be negated to get the proper heading, as the direction of the assigned axis may be reversed from the Roll, Pith and Yaw axis as define in the table.
Use the attached trycompass.js file as a starting point. Attention Puck experimenters!
(still puzzling as to why the markdown table above doesn't display correctly?) - Vertical axis is the Yaw axis
-
Calibration of three-axis sensors such as accelerometers, gyroscopes and magnetometers presents a unique problem. Such sensors are typically used with trigonometric functions to derive orientation angles. The use of trigonometry relies on the accuracy of the calibration of each axis. The ideal is a sphere, centered at the origin with a radius R= sqrt( X^2+Y^2+Z^2). Raw values from sensors tend not to be centered nor have equal gains (slopes) for each axis. Picture an off-center ellipsoid.
https://en.wikipedia.org/wiki/Ellipsoid
Several approaches are seen in usage.
• Finding a zero-offset vales for each axis and relying on published values for the scale factor.
• Applying a linear regression model to each axisLinear regression finding best fit of a line through the data:
https://en.wikipedia.org/wiki/Linear_regressionhttp://seismo.berkeley.edu/~kirchner/eps_120/Toolkits/Toolkit_10.pdf
Multiple linear regression finding the best plane through the data:
http://reliawiki.org/index.php/Multiple_Linear_Regression_Analysishttp://www.hull.ac.uk/php/ecskrb/METRICS/mrg-deriv-5.ppt
Experimentation with both of these methods has produced poor results as they both rely on accurate positioning of the sensor axis relative to a known flux.
We want to find the best sphere through the data:
The math gives six simultaneous non-linear equations in six unknowns to solve. One unknown may be eliminated by setting the gain to one. The Solver function in the Excel data analysis tool pack was used to find the solution from raw accelerometer data.In any orientation and in a constant field the sum of squares of the three axis should be a constant. Define R as the radius of a sphere. R =sqrt(x^2+Y^2+z^2). The statistical merit is then based on the deviation from this spherical surface.
Raw readings were collected from a three-axis accelerometer attached to a cube. Data were collected as the cube was positioned with each of the six sides facing down. The data were listed in three columns of a spreadsheet, labeled as X, Y and Z. The radius of the sphere for each point calculated in an additional column in the spreadsheet labeled R.
Six cells of the spreadsheet were used to define the three zero-offset values and the three gains for the regression model. The offset values were initialized to zero and the gains were initialized to a value of one.
Three columns labeled X1, Y1, and Z1 were added along with a column labeled R1.
For each row the X1 value= (X-XOffset)* Xgain, Y1=(Y-Yoffset)*Ygain, and Z1=(Z-Zoffset)*Zgain. The value of R1= sqrt(X1^2+Y1^2+Z1^2).The accelerometer tested produces 16-bit signed integer values. Full scale is 32768 counts. The accelerometer has a range pf +- 2g. Dividing 32768 by 2 gives the target radius of the sensor sphere of 16384.
A new column labeled error was added, with cell values = R1(row)-16384. Another column labeled error^2 is used to calculate the square of the error. And finally a cell to calculate the sum of the error squared column.
The solver function of the data analysis tool pack was then used to minimize the sum of the error squared by varying the offset values and the X and Y gain values.
The offset and gain values derived from this procedure were then coded into the Espruino program used to acquire the accelerometer data and a new set of data were collected while orienting the sensor in the six positions of the attached cube as previously described. The data were posted in a spreadsheet and the radius R was calculated for each data point. Descriptive statistics were performed on the radius R values.
See the attached files:
Aregression.ods
Acallibrated.ods -
It worked in the markdown editor.
http://forum.espruino.com/conversations/297631/#comment13378748 -
Putting Math Equations into Markdown Document
I copied an HTML example from the page
mathjax/start
and pasted it here.<!DOCTYPE html>
When `a != 0`, there are two solutions to `ax^2 + bx + c = 0` and they are
`x = (-b +- sqrt(b^2-4ac))/(2a) .`
Look at mathjax.md with a text editor such as wordpad. It's the file I used to create this post.
Looks like it didn't work on this site. Perhaps it's the way I pasted the html?
Trying code block<!DOCTYPE html> <html> <head> <title>MathJax AsciiMath Test Page</title> <script type="text/javascript" async src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=AM_CHTML"></script> </head> <body> <p>When `a != 0`, there are two solutions to `ax^2 + bx + c = 0` and they are</p> <p style="text-align:center"> `x = (-b +- sqrt(b^2-4ac))/(2a) .` </p> </body> </html
-
Started writing up a module today on GitHub.
https://www.espruino.com/Writing+Modules
I ran across this Markdown Editor.
https://github.com/jbt/markdown-editor
If you pull up the link in the README file,
The Markdown is on the left screen
The formatted text is on the right.
It contains the instructions to save to a file.
I saved it as a HTM file and it seems to work offline as well.
Copy and paste the markdown example from the writing modules page into the left side of the screen as a start. -
Updating the software.
The calibration menu system is now working after a rewrite of the module and additions to the menu program.
The calibrations are now stored in ROM page 99. To change this:
Look for: var CalPage=99; //ROM page where calibration is saved
savecal.js is used to write the calibration structure to the ROM, and should be the first program that you run to prime the data structure for the menu programs.
The modules should be copied to your local project module directory
slimLSM9DS1.js has not changed,
calibrateLSM9DS1.js has significantly changed. Callbacks are used and the calibration covers all the scales of the sensors-
BRICK CAUTIONS still apply to the menu programs. Be sure that the exits from these programs work before doing a save to a PICO as they redirect the USB port from the Console. Most important if you edit these programs!
AmenuCalSlimLSM9DS1.js will allow you to perform calibrations, save them to ROM, view the calibration values, change sensor scales and data rates and collect data. It uses the calibrateLSM9DS1.js module which loads the slimLSM9DS1.js module as well.
AtestmenuSlimLSM9DS1.js leaves out the calibration procedures and uses the slimLSM9DS1.js module.
The only outstanding issue are the accelerometer readings on the 16g scale.
The other scales move the one-g reading from axis to axis as the sensor is moved.
The 1-g on the 16g scale moves but the value is diminished on the X and Y axis for some reason -
At this point the module slimLSM9DS1.js needs to be tested.
In order to do the testing the menutestSlimLSM9DS1.js has been written, with another program to test the calibration module to follow.
WARNING WARNING WARNING
The menu system redirects the USB port away from the console. Without a method to restore the USB-Console connection or reset a Pico you can brick the Pico if you save the menu program. As written the menu program provides three exits. Control-C, Menu option 9, and the pushbutton on the Pico. When you load the program it tells you to type startPGM(); in the left pane of the WebIDE. This is safe. Only save the program once you are convinced that all the exit methods work. This is especially true if you make modifications to the menu program.
Here is an example after the menu program is loaded, started and menu option 9 is selected.>In left pane type startPgm(); Use startPgm() first and make sure you can exit back to the console Once you are sure that a proper exit works then type save(); =undefined >startPgm(); =undefined -> LoopbackB Select option or use control-C to exit 0 Configure Gyro 1 Configure Accelerometer 2 Configure Magnetometer 3 Select Display Units 4 Read and Display Data 5 Show Calibration Structure 9 Exit <- USB >9 <- LoopbackB Exit -> USB
The code to use the pushbutton to reset the Pico is in nobrick.js
It does not redirect the USB port.//NoBrick.js // 15 Dec 2016 // //How not to brick a PICO when reassigning the console // away from the USB port to do menu programs //Setup the button to light the LED and reset the Pico //Always include this code when a save() and oninit() functions are // used. setWatch(function(e) { digitalWrite(LED1, e.state); USB.setConsole(); reset(); }, BTN, { repeat: true }); E.on('init', function() { console.log("Hello"); startPgm(); }); function startPgm(){ var count=0; var nn=setInterval(function () { console.log(count); count++; if(count>1000)clearInterval(nn); }, 200); }//end startPgm console.log("In left pane type startPgm();"); console.log("Use startPgm() first and make sure you can exit back to the console"); console.log("Once you are sure that a proper exit works then type save();");
Here are some more menu screens.
-> LoopbackB Select option or use control-C to exit 0 Configure Gyro 1 Configure Accelerometer 2 Configure Magnetometer 3 Select Display Units 4 Read and Display Data 5 Show Calibration Structure 9 Exit <- USB >0 Configure Gyro Select option or use control-C to exit 0 Set Gyro Scale 1 Set Gyro Output Data Rate 8 Main Menu 9 Exit 0 Set Gyro Scale Select option or use control-C to exit Select Gyro Scale 0 for 245 deg/s 1 for 500 deg/s 2 for 2000 deg/s 8 Main Menu 9 Exit /////// use control-c to exit <- LoopbackB _____ _ | __|___ ___ ___ _ _|_|___ ___ | __|_ -| . | _| | | | | . | |_____|___| _|_| |___|_|_|_|___| |_| http://espruino.com 1v88 Copyright 2016 G.Williams >
Read and display data. Pressing the return key twice stops the collection and returns to the main menu. The average data is from a running average of 20 samples.
Select option or use control-C to exit 0 Configure Gyro 1 Configure Accelerometer 2 Configure Magnetometer 3 Select Display Units 4 Read and Display Data 5 Show Calibration Structure 9 Exit <- USB >4 Acceleration, -0.00655438823 , 0.01201211252 , 1.05891861454 Avg Acceleration, -0.00655438823 , 0.01201211252 , 1.05891861454 Gyro, -0.03014841387 , -0.77131702053 , 0.19415578534 Avg Gyro, -0.03014841387 , -0.77131702053 , 0.19415578534 Magnetometer, -0.006588 , 0.197274 , -0.217526 Avg Magnetometer -0.006588 , 0.197274 , -0.217526 Temperature 26.24 Level -0.35463914029 0.64992136537 heading 1.91269183128 avg heading 1.91269183128 > Select option or use control-C to exit 0 Configure Gyro 1 Configure Accelerometer 2 Configure Magnetometer 3 Select Display Units 4 Read and Display Data 5 Show Calibration Structure 9 Exit
-
-
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 -
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
https://www.espruino.com/Reference#Puck -
-
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.
Magnetometer, Turntable, Wireless, Plot Circle, Calibrate
The hardware
A magnetically agnostic turntable is used to spin a three axis magnetometer. The magnetometer readings are read by a Pico (or equivalent) and transmitted wirelessly to a web page (served by the Pico). Puck?
The webpage has the following elements:
As the data from the magnetometer are acquired the selected axis pair are plotted as an asteroid belt around the origin of the graph. The offset sliders are adjusted to center the asteroids. The cursor circle and the gain sliders are adjusted to visually best fit the data to a circle. The values in the gain and offset boxes are then used for the magnetometer calibration values. It is conceivable that local distortions in the magnetic field could be detected and visualized with such a system.