LSM9DS1 A 9 degree of freedom Inertial Navigation Chip

Posted on
Page
of 3
/ 3
Last Next
  • TestLSM9DS1.js
    21 Nov 2016
    Using a Pico 1v88

    I’m starting work on an I2C driver for the Sparkfun 9DoF Sensor Stick
    https://www.sparkfun.com/products/13944
    3-axis Magnetometer, Gyro and Accelerometer.
    As a starting point I’ve downloaded the Arduino Library from
    https://github.com/sparkfun/SparkFun_LSM­9DS1_Arduino_Library
    The board is scheduled to arrive tomorrow. The attached Espruino code has been derived from the Arduino library. TestLSM9DS1.js
    A few functions related to the I2C have been stubbed until the board arrives and testing can begin. Additional functions beyond the basics have yet to be added.
    Any suggestions on writing the stubbed I2C functions are welcome.

    TestLSM9DS1.js
    21 Nov 2016
    */
    ……
    LSM9DS1.prototype.mReadByte=function(sub­Address){
    //uint8_t LSM9DS1::mReadByte(uint8_t subAddress)
    //		return I2CreadByte(_mAddress, subAddress);
      console.log("mReadByte ",_mAddress, subAddress);
      return 0;
    };
    
    LSM9DS1.prototype.xgReadByte=function(su­bAddress){
    //uint8_t LSM9DS1::xgReadByte(uint8_t subAddress)
     //return I2CreadByte(_xgAddress, subAddress);
      console.log("xgReadByte ",_xgAddress, subAddress);
    return 0;
    };
    …..
    LSM9DS1.prototype.xgWriteByte=function(s­ubAddress,data){
    //void LSM9DS1::xgWriteByte(uint8_t subAddress, uint8_t data)
    ////I2CwriteByte(_xgAddress, subAddress, data);
      console.log("xgWriteByte ",_xgAddress, subAddress, data);
    };
    ……
    LSM9DS1.prototype.mWriteByte=function(su­bAddress,data){
    ////		return I2CwriteByte(_mAddress, subAddress, data);
      console.log("mWriteByte ",_mAddress, subAddress, data);
      return 0;
    };
    …..
    LSM9DS1.prototype.xgReadBytes=function(s­ubAddress,dest,count){
    //void LSM9DS1::xgReadBytes(uint8_t subAddress, uint8_t * dest, uint8_t count){
    ////	I2CreadBytes(_xgAddress, subAddress, dest, count);
      console.log("xgReadByte ",_xgAddress, subAddress, dest, count);
    };
    …..
    LSM9DS1.prototype.readMag=function(){
    //void LSM9DS1::readMag(){
    	var temp=Uint8Array(6); // We'll read six bytes from the mag into temp	
    	this.mReadBytes(Mags.OUT_X_L_M, temp, 6); // Read 6 bytes, beginning at OUT_X_L_M
     this.mx = (temp[1] << 8) | temp[0]; // Store x-axis values into mx
     this.my = (temp[3] << 8) | temp[2]; // Store y-axis values into my
     this.mz = (temp[5] << 8) | temp[4]; // Store z-axis values into mz
    };
    
    LSM9DS1.prototype.mReadBytes=function(su­bAddress,dest,count){
    //void LSM9DS1::mReadBytes(uint8_t subAddress, uint8_t * dest, uint8_t count){
     ////I2CreadBytes(_mAddress, subAddress, dest, count);
      console.log("mReadBytes ",_mAddress, subAddress, dest, count);
    };
    

    The current output of the stubbed code:

    >15
    mReadByte  30 15
    xgReadByte  107 15
    xgWriteByte  107 16 192
    xgWriteByte  107 17 0
    xgWriteByte  107 18 0
    xgWriteByte  107 30 58
    xgWriteByte  107 19 0
    xgWriteByte  107 31 56
    xgWriteByte  107 32 192
    xgWriteByte  107 33 0
    mWriteByte  30 32 28
    mWriteByte  30 33 0
    mWriteByte  30 34 0
    mWriteByte  30 35 12
    mWriteByte  30 36 0
    0
    xgReadByte  107 40 new Uint8Array(6) 6
    xgReadByte  107 24 new Uint8Array(6) 6
    mReadBytes  30 40 new Uint8Array(6) 6
    Acceleration  0 0 0
    Gyro          0 0 0
    Magnetometer  0 0 0
    187
    LSM9DS1 {
      "i2c": I2C {
        "_options": { "scl": B6, "sda": B9 }
       },
      "addr": 72, "gain": 2048,
      "gBias": [ 0, 0, 0 ],
      "aBias": [ 0, 0, 0 ],
      "mBias": [ 0, 0, 0 ],
      "gBiasRaw": [ 0, 0, 0 ],
      "aBiasRaw": [ 0, 0, 0 ],
      "mBiasRaw": [ 0, 0, 0 ],
      "commInterface": 0, "agAddress": 107, "mAddress": 30, "gyro_enabled": true, "gyro_enableX": true,
      "gyro_enableY": true, "gyro_enableZ": true, "gyro_scale": 245, "gyro_sampleRate": 6, "gyro_bandwidth": 0,
      "gyro_lowPowerEnable": false, "gyro_HPFEnable": false, "gyro_HPFCutoff": 0, "gyro_flipX": false, "gyro_flipY": false,
      "gyro_flipZ": false, "gyro_orientation": 0, "gyro_latchInterrupt": true, "accel_enabled": true, "accel_enableX": true,
      "accel_enableY": true, "accel_enableZ": true, "accel_scale": 2, "accel_sampleRate": 6, "accel_bandwidth": -1,
      "accel_highResEnable": false, "accel_highResBandwidth": 0, "mag_enabled": true, "mag_scale": 4, "mag_sampleRate": 7,
      "mag_tempCompensationEnable": false, "mag_XYPerformance": 3, "mag_ZPerformance": 3, "mag_lowPowerEnable": false, "mag_operatingMode": 0,
      "temp_enabled": true, "ax": 0, "ay": 0, "az": 0, "gx": 0,
      "gy": 0, "gz": 0, "mx": 0, "my": 0, "mz": 0 }
    
    

    1 Attachment

  • LSM9DS1

    Finally the board arrived a week later than expected. I also ordered an ESP32 board which required using a for profit shipping company instead of the post office for some reason. They shipped it from Colorado to Chicago and then mailed it to the suburbs.
    The pony express would have been faster.

    Problems encountered when implementing the I2C interface:

    LSM9DS1.prototype.xgReadBytes=function(s­ubAddress,count){
    var dest= new Uint8Array(count);
    //  console.log("xgReadBytes ",this.xgAddress, subAddress|0x80, dest, count);
      var x=this.xgAddress;
      this.i2c.writeTo(x, subAddress|0x80);
     dest=this.i2c.readFrom(x, count);
    //  for(var i=0;i<count;i++)console.log(dest[i]);
      return dest;
    };
    
    LSM9DS1.prototype.mReadBytes=function(su­bAddress,count){
    // console.log("mReadBytes ",this.mAddress, subAddress|0x80,count);
      var x=this.mAddress;
      var dest=new Uint8Array(count);
      this.i2c.writeTo(x, subAddress|0x80);
      dest=this.i2c.readFrom(x, count);
    //  for(var i=0;i<count;i++)console.log(dest[i]);
      return dest;
    };
    

    Notice the var x=this.address.
    If this.address was used in the this.i2c.ReadFrom() function timeout errors occurred.
    I tried to create a simplified version of the problem but have not succeeded in reproducing the error.
    For reads of multiple bytes bit 7 of the sub-address is set to 1.

    LSM9DS1.prototype.readAccel=function(){
    var temp=this.xgReadBytes(Regs.OUT_X_L_XL, 6); // Read 6 bytes, beginning at OUT_X_L_XL
     this.ax=this.twos_comp(temp[0],temp[1]);­
     this.ay=this.twos_comp(temp[2],temp[3]);­
     this.az=this.twos_comp(temp[4],temp[5]);­
    /*
     this.ax = (temp[1] << 8) | temp[0]; // Store x-axis values into ax
     this.ay = (temp[3] << 8) | temp[2]; // Store y-axis values into ay
     this.az = (temp[5] << 8) | temp[4]; // Store z-axis values into az
    */
      if (_autoCalc)
    	{
    		ax -= aBiasRaw[X_AXIS];
    		ay -= aBiasRaw[Y_AXIS];
    		az -= aBiasRaw[Z_AXIS];
    	}
    };
    
    LSM9DS1.prototype.twos_comp=function(low­,high){
     var t=(high << 8) | low;
      return(t & 0x8000 ? t - 0x10000 : t);
    };
    

    The values returned are in two’s compliment.

    LSM9DS1.prototype.readTemp=function(){
    //void LSM9DS1::readTemp(){
     // We'll read two bytes from the             temperature sensor into temp
    var temp=this.xgReadBytes(Regs.OUT_TEMP_L, 2); // Read 2 bytes,     beginning at OUT_TEMP_L
    //  console.log("TT= ",temp[0].toString(16),temp[1].toString(­16));
    this.temperature = temp[1];//(temp[1] << 8) | temp[0];
    //temperature = ((int16_t)temp[1] << 8) | temp[0];
    
    //https://gist.github.com/jimblom/08b333­892ee383d6e443
    //temperature = (((int16_t) temp[1] << 12) | temp[0] << 4 ) >> 4; // Temperature is a 12-bit signed integer
    //var t= (((temp[1]^0xf)<<12)|temp[0])>>4;
    //this.temperature=(t & 0x8000 ? t - 0x10000 : t);
    };
    

    There is quite a bit of discussion on the web about reading the temperature.
    Having tried various solutions, I’m still not sure it is working properly.
    The file TestLSM9DS1_d.js is attached.
    Some sample output:

    Acceleration  -0.0087890625 -0.09417724609 1.02032470703
    Gyro          -810 294 75
    Magnetometer  1379 1413 -2029
    Temperature  246
    Level -0.49353282241 -5.27352997140
    heading 44.30230651281
    Acceleration  -0.01861572265 -0.09045410156 1.02691650390
    Gyro          -605 283 41
    Magnetometer  1425 1387 -2030
    Temperature  246
    Level -1.03853188194 -5.03380446489
    heading 45.77422016492
    Acceleration  0.00439453125 -0.08666992187 1.02276611328
    Gyro          -429 -248 8
    Magnetometer  1385 1459 -2064
    Temperature  246
    Level 0.24618193820 -4.84371267720
    heading 43.50951784922
    Acceleration  -0.00036621093 -0.08117675781 1.03289794921
    Gyro          -501 21 -87
    Magnetometer  1373 1453 -2027
    Temperature  246
    Level -0.02031404967 -4.49371112382
    heading 43.37847185456
    

    The readall function:

    function readall(W){
    W.readAccel();
    W.readGyro();
    W.readMag();
    W.readTemp();
    var pirate=180.0/Math.PI;
    console.log("Acceleration ",W.ax/16384,W.ay/16384,W.az/16384);
    console.log("Gyro         ",W.gx,W.gy,W.gz);
    console.log("Magnetometer ",W.mx,W.my,W.mz);
    console.log("Temperature ",W.temperature);
    console.log("Level", pirate*Math.atan2(W.ax,W.az),pirate*Math­.atan2(W.ay,W.az));
    console.log("heading",pirate*Math.atan2(­W.mx,W.my));
    }//end readall
    

    1 Attachment

  • Great - thanks for posting up!

    Could your timeout error have been from doing something like:

    doSomething(this.data);
    setTimeout(function() {
      doSomething(this.data);
    }, 100);
    

    That's a common 'gotcha' in JS, since this doesn't stay the same inside a setTimeout or interval.

  • So far the Arduino code for the LSM9DS1 has been ported to Espruino, but to make it really useful it should be worked into module form.
    There are a lot of knobs on the virtual control panel for this chip. In the previous version any changes to the knobs needed to be edited in the LSM9DS1.prototype.init function.
    Today the code is modified so that the knobs are initialized to default values and an optional set of options can be supplied to the LSM9DS1.prototype.init function.
    This is accomplished by changing the init function to the following

    LSM9DS1.prototype.init=function(options)­{
      var i,j;
      for (i=0; i<3; i++){
      this.gBias[i] = 0;
      this.aBias[i] = 0;
      this.mBias[i] = 0;
      this.gBiasRaw[i] = 0;
      this.aBiasRaw[i] = 0;
      this.mBiasRaw[i] = 0;
     }
    //process and changes from options
    if(typeof options === "undefined" )
      console.log("options undefined");
    if(typeof options !== "undefined"){
      console.log("options defined");
     for(i in options){
    //console.log(options);
      if(options.hasOwnProperty(i)){
      for(j in this){//LSM9DS1){
    //     console.log(i,j);//,options[i],LSM9DS1[j­]);
       if(j==i){
         console.log(i,j,options[i],this[j]);
         this[j]=options[i];
       }
      }
     }
    }
    }
    };
    

    The optional options are outlined in the attached file options.js.
    For example to change the acceleration sample rate from 6 to 5 :

    	// accel sample rate can be 1-6
    	// 1 = 10 Hz    4 = 238 Hz
    	// 2 = 50 Hz    5 = 476 Hz
    	// 3 = 119 Hz   6 = 952 Hz
    	accel_sampleRate: 6,
    

    The code that calls the init function uses an option object containing only the options we wish to change.

    function start(){
    I2C3.setup({ scl :A8, sda: B4} );
    var W=new LSM9DS1(I2C3);
    
    var myoptions={ //make changes to basic configuration here
      xgAddress: 0x6B,
      mAddress: 0x1e,
      test: 1,
      accel_sampleRate: 5,
    };
    
    W.init(myoptions);
    //W.init();
    console.log(W.begin());
    var nn=setInterval(function () {
        readall(W);
    }, 200);
    }
    

    The output of the substitutions made in the init function:

    >options defined
    xgAddress xgAddress 107 107
    mAddress mAddress 30 30
    test test 1 0
    accel_sampleRate accel_sampleRate 5 6
    

    2 Attachments

  • This looks great - thanks! Looks like it could be ready to publish as a module soon? You could put options.js into the documentation file as a bit of code...

  • Hi @Gordon. It’s tempting to go to module on this project but it’s not quite ready for that as yet. There are a number of functions that have yet to be translated. The most important one being the gyroscope calibration function. There are interrupt functions that would be difficult to use with the Sparkfun board but could be of use with the more expensive Adafruit board that brings forth the interrupt pins.
    https://www.sparkfun.com/products/13944
    https://www.adafruit.com/product/2021

    As to the topic of modules, I’m looking at a module within a module design for the final product.
    The lowest level module uses a table to initialize the chip and functions to read the data.

    var xgstack=
    [
      { "address": 107, "sub": 16, "data": 0 },
      { "address": 107, "sub": 17, "data": 0 },
      { "address": 107, "sub": 18, "data": 0 },
      { "address": 107, "sub": 30, "data": 58 },
      { "address": 107, "sub": 19, "data": 0 },
      { "address": 107, "sub": 31, "data": 56 },
      { "address": 107, "sub": 32, "data": 160 },
      { "address": 107, "sub": 33, "data": 0 }
     ];
    var mstack=
    [
      { "address": 30, "sub": 32, "data": 28 },
      { "address": 30, "sub": 33, "data": 0 },
      { "address": 30, "sub": 34, "data": 0 },
      { "address": 30, "sub": 35, "data": 12 },
      { "address": 30, "sub": 36, "data": 0 }
     ];
    LSM9DS1.prototype.run=function(){
     var i;
     // To verify communication, we can read from the WHO_AM_I register //of each device. Store those in a variable so we can return them.
    var mTest = this.mReadByte(Mags.WHO_AM_I_M);// Read the gyro 
    var xgTest = this.xgReadByte(Regs.WHO_AM_I_XG);//Read­ the accel/mag 
    var whoAmICombined = (xgTest << 8) | mTest;
    console.log("who= ",whoAmICombined);
    if (whoAmICombined != ((WHO_AM_I_AG_RSP << 8) | WHO_AM_I_M_RSP))
    		return 0;
     for(i=0; i<this.xgstack.length;i++){
       console.log(this.xgstack[i].address, this.xgstack[i].sub,this.xgstack[i].data­);
      this.i2c.writeTo(this.xgstack[i].address­, this.xgstack[i].sub,this.xgstack[i].data­);
     }
     for(i=0; i<this.mstack.length;i++){
      console.log(this.mstack[i].address, this.mstack[i].sub,this.mstack[i].data);­
      this.i2c.writeTo(this.mstack[i].address,­ this.mstack[i].sub,this.mstack[i].data);­
     }
    return whoAmICombined;
    };
    

    See attached file: aTestLSM9DS1_a.js
    At the next level of module, which invokes the first level module, the init function parses the options and the begin function populates the xgstack and mstack arrays.
    This mod level module uses more memory resources but allows the tweaking of the options. Once the user is satisfied with the tweak, the stacks and some other variables are copied and pasted into a new program that uses only the first module. This will reduce the memory usage in a final program.
    A similar approach seems possible for adding the calibration and interrupt functions.
    A lowest level module example

    //TestM1.js 1 Dec 2016
    function LSM9DS1() {
    }
    LSM9DS1.prototype.add=function(){
      this.a=1;
      this.b=20;
      console.log(this.a+this.b);
    };
    
    // Create an instance of LSM9DS1
    exports.connect = function() {
      return new LSM9DS1();
    };
    

    A module one level up:

    /TestM2.js 1 Dec 2016
    exports.connect = function() {
      var x=  require("testm1").connect();
    x.d=98;
    x.sub= function() {
       console.log(this.a-this.b);
      };
      return x;
    };
    

    Invoke TestM1

    //tryTestM1.js 1 Dec 2016
    var ads =require("testm1").connect();
    ads.add();
    

    Invoke TestM2

    //tryTestM2.js 1 Dec 2016
    var ads =require("testm2").connect();
    ads.add();
    console.log(ads.d);
    ads.sub();
    

    Some useful resource links on the topic of using IMU sensors:
    https://www.intorobotics.com/acceleromet­er-gyroscope-and-imu-sensors-tutorials/
    http://tutorial.cytron.com.my/2012/01/10­/measuring-tilt-angle-with-gyro-and-acce­lerometer/
    Kalman filter simulation
    https://www.cs.utexas.edu/~teammco/misc/­kalman_filter/


    5 Attachments

  • LSM9DS1 4 Dec 2016
    Some progress on the calibration functions has been made. There are some problems that need to be addressed.
    The first item is the word calibration itself. The functions only address part of a full calibration, the zero point. The scale is not calibrated, but simply uses the advertised values from the data sheet.
    The second item is the method used to determine the zero point. For this chip there are two different calibration functions. One calibrates the accelerometer/gyro and the other calibrates the magnetometer. Addressing the accelerometer/gyro function first.
    The accelerometer/gyro calibration function for the LSM9DS1 chip makes use of the FIFO hardware. The function sets up the FIFO to read 32 samples each from the accelerometer and gyro, then it averages these values to find the zero offset. The accelerometer makes the assumption that the x-y plane is parallel to a level surface, and that the z-axis is on a plumb line. It doesn’t actually find a zero offset for the z-axis.
    This can be corrected by doing the x-y calibration, reposition the chip so that either the x-z or y-z plane is level and finding the zero for the z-axis.
    Of note is Figure 1 in the LSM9DS1 datasheet. Notice the dot on the chip is positioned differently for the magnetometer illustration. Also as drawn the axis directions are a left-hand coordinate system.
    http://www.st.com/content/ccc/resource/t­echnical/document/datasheet/1e/3f/2a/d6/­25/eb/48/46/DM00103319.pdf/files/DM00103­319.pdf/jcr:content/translations/en.DM00­103319.pdf
    A bug was found in the previous code that froze the gyro readings. The gyro sample rate has been added to fix the bug.

    this.gyro_scale = 245;
    this.gyro_sampleRate = 6;
    this.gyro_bandwidth = 0;
    

    Once calibrated using calls to the read accelerometer and read gyro functions with the FIFO produce an unreasonable variation in the at rest gyro readings. This variation is greatly reduced by using the FIFO in continuous mode, even limiting the FIFO to 2 samples. My hypothesis is that without the FIFO the values returned contain incomplete samples. To use the FIFO in continuous mode:

    // fifoMode
    // FIFO_OFF = 0, FIFO_THS = 1,	FIFO_CONT_TRIGGER = 3, 
    //FIFO_OFF_TRIGGER = 4, FIFO_CONT = 5
    W.enableFIFO(true);
     W.setFIFO(5,10); // ( fifoMode ,length<32)
    

    The magnetometer calibration function acquires 128 readings and for each axis determines the maximum and minimum reading, and then averages these two values to produce the zero offset value for each axis. These values are stored on the chip using a call to the magOffset function.
    I’m not sure if this method is of any value. I can imagine placing the chip inside a pair of A.C Helmholtz coils as one method. I finally performed a zero offset calibration manually.
    I attached the chip to a cube. I placed the cube on a level non-magnetic surface (wood) with a wooden fence (think table saw) oriented North South. I zeroed the chip offsets using a call to the magOffset function. Then I collected six sets of data.

    1. X-axis level and pointing North
    2. X-axis level and pointing South
    3. Y-axis level and pointing North
    4. Y-axis level and pointing South
    5. Z-axis level and pointing North
    6. Z-axis level and pointing South
      Then I used a spreadsheet to find the maximum and minimum values in pairs of data sets and averaged them. For the X offset use sets 1 and 2, for Y sets 2 and 3 and finally for Z sets 5 and 6.
      These offsets are then sent to the chip after initialization using the magOffset function.
      If the chip is powered down the offset values are lost (volatile).
      This procedure finally produced reasonable compass heading values when the x-y plane of the chip is level. Yet to address is the heading in 0 to 360 degrees instead of -180 to 180 degrees in the wrong direction. Also I hope to add the necessary rotation matrix math that gives the compass heading in any chip orientation.

    Attached is the code as of today. I hope to incorporate changes that will reflect the issues discussed.

  • Problem posting the file.
    I posted the wrong file and then removed it.
    It wouldn't take the correct file.
    When I did the post I got an error page.
    It did post but no attached file.
    Attached is the file


    1 Attachment

  • Thanks! Yeah, calibrating is a real pain. I've even noticed with the Pucks that if you bring a magnet too close (<2cm from the case) then it can partially magnetise something on the Puck and you have to recalibrate!

    For working out the heading, have you come across Math.atan2? It's amazingly helpful.

    I really quick hack would be something like this:

    if (is_biggest(accel.x)) return sign(accel.x)*Math.atan2(mag.y, mag.z);
    if (is_biggest(accel.y)) return sign(accel.y)*Math.atan2(mag.z, mag.x);
    return sign(accel.z)*Math.atan2(mag.x, mag.y);
    

    But I guess to do it properly you want to do it better than the nearest 90 degrees :)

  • That's a cool hack. What we call Q&D (quick and dirty).
    Aran2 gives -180 to 180.
    theta=atan2(z,x)
    if(theta<0) theta+=360. Then theta ranges from 0 to 360
    For the left handed axis on this chip theta= atan(-z/x)

    Using the accelerometer readings gx, gy, gz
    theta = atan2(gz,gx), phi=atan2(gz,gy)
    rotate around yaxis
    x1=gx, y1=cos(theta)*gy-sin(theta)*gz, z1=sin(theta)*gy+cos(theta)*gz
    the rotate around the xaxis
    x2=cos(phi)*x1+sin(phi)*z1, y2=y1, z2=-sin(phi)*x1+cos(phi)*z1
    At which point z2 should be -1. X2 and Y2 should be zero
    If the rotations are them applied to the magnetometer readings the compass can be leveled.
    Some gotchas:
    May need a minus sign in the atan2(-a/b)
    May need to rotate the opposite direction by changing the sign on the sin terms( +sin becomes -sin, and -sin becomes +sin)
    I captured some data earlier this morning and did some of this in a spreadsheet. It looks like it works but I want to finish the mounting fixture and change the code to load the zero calibrations rather than recalibration on each run.

                         X  Y   Z   atan2(x/z)  atan2(y/z)
    

    Acceleration -0.000249863 0.003465652 1.000553131 -0.014308162 0.198456693

  • Tab made it post so I'll try again

    	                     X	                             Y              	Z	                 atan2(z,x)	atan2(z,y)
    Acceleration	-0.000249863	0.003465652	1.000553131	-0.014308162	0.198456693
    
    sin(theta)	cos(theta)	sin(phi)	cos(phi)
    -0.000249725	0.999999969	0.003463716	0.999994001
    
               X1	                     Y1                	Z1	            X2	          Y2	              Z2
    -0.000249863	0.003715515	1.000552234	0.003215767	0.003715515	1.000546232
    

    Preview takes out the spaces that I tried to make the headings line up.
    Better examples to follow.

  • If you treat your stuff like code (with the three backticks) then you should be able to get it to appear a bit better (I changed it above).

    I think it's just a typo, but you wrote atan2(y/z), when you need either atan(y/z) or atan2(y,z)

    atan2 is clever, because 12/34 and -12 / -34 should be different angles, but obviously evaluate to the same number. atan2 checks and makes sure that it outputs a value that represents all 360 degrees, and not just 180.

  • NormalizeG.js
    NormalizeG.wxm
    6 Dec 2016
    Attached are two files that use matrix rotations on an xyz vector to produce a vector where x1=0,y1=0,z1= - sqrt(x^2+y^2+z^3)
    It’s still need some work as the matrix direction values depend on the vector quadrant.
    This is likely the result of computerizing the trig and algebra.
    The goal is to use the matrices and accelerometer readings to level the magnetometer readings and produce a compass heading.
    The file with the wxm extension can be opened using the Maxima software.
    This allows an analytic derivation as well as a numerical one.
    http://maxima.sourceforge.net/


    2 Attachments

  • Now working for all 8 quadrants.
    On to doing the rotations for the magnetometer vector to do final proof of concept.
    Then to see if pre-multiplying the matrices (using Maxima) and using simpler code will work.
    Finally how well will it work with data from the chip?
    Didn't use atan2. used the abs(atan) and then the sign of the original x,y values to set the matrix rotation direction and the z value to control if the final matrix is needed.


    1 Attachment

  • I am trying to use LSM9DS1 with JavaScript. My setup is Sparkfun LSM9DS1 connected to Arduino, and then connected via Serial port to a laptop. I have node.js on a laptop. I tried Johnny-five, but it does not support LSM9DS1. Your code seems to be the only JavaScript code for that IMU.
    Any advise on getting it to work on my current setup?

  • Hi Eduard. The Sparkfun site has C code for Arduino. This JavaScript code is under development at the moment. I plan to post some revisions in a few days. Currently I'm using a Pico with Espruino. I have not tested it on other versions of hardware. It seems plausible that it would run on other hardware running Espruino that has I2C interface. Defining the I2C pins would be the first item on a list of things to get the code running on different hardware running Espruino.
    I have zero experience with the Johnny-five system.

  • Got it. So, to use your code as is, I'll need to get Espruino board instead of my Arduino one. Alternatively, I would need to adapt your code to somehow work on my laptop in Node.js environment. I guess something like SerialPort that would transfer raw data that Arduino board gets through I2C interface. And then apply your calculations to result from SerialPort.
    I am very new to this and appreciate your help.

  • @ClearMemory041063, could that be useful in Puck's using the built-in magnetometer?

    I'm quite interested in such code to handle 'oscillations' around the heading on a bout in choppy sea... Of course, the device could be put in a gimbal device... but that's not what I'm looking for, though probably the most simple solution... and when running out of computing capacity the only solution... :(

  • Attached are two modules that implement an I2C interface with the LSM9DS1.
    These modules should be placed in the modules directory of your WebIDE project.
    The module slimLSM9DS1.js is tested by the program testslimLSM9DS1.js
    The module calibrateLSM9DS1.js is tested by the program testcalibrateLSM9DS1.js and loads the slimLSM9DS1.js module as well. The test program performs the calibration that the Sparkfun code performed. Options to perform a more robust calibration are coded but not yet tested as this requires a menu system.
    Several functions have been added to allow the scales, output data rate and filters to be changes on the accelerometer, gyro and magnetometer.
    The calibrations in the attached code has not been performed on every scale of the three sensors. In the next development phase a menu program will allow a full calibration to be performed.
    The moff, goff and aoff contain the zero offsets for each axis for each scale
    The mres, gres and ares contain the scale factors for each axis for each scale
    g=gyro, a= accelerometer, m= magnetometer

    this.mScale=0;//0,1,2,3 index into mscale
    this.mscale=[ //the mres is the scale factor for each axis
      {scale:4,regvalue:0,
       mres:[0.000122,0.000122,0.000122],
       moff:[0,0,0] //zero offset in counts
      },
    ////
      this.gScale=0;//0,1,2 index into gyroscale
      this.gyroscale=[ //the gres is the scale factor for each axis
      {scale:245,regvalue:0,
       gres:[245/32768.0,245/32768.0,245/32768.­0],
       goff:[0,0,0] //zero offset
      },
    //// this.aScale=0;//0,1,2,3 index into accelscale
    this.accelscale=[ //the ares is the scale factor for each axis
      {scale:2,regvalue:0,
       ares:[2/32768.0,2/32768.0,2/32768.0],
       aoff:[0,0,0]
      },
    

    These structures along with autocalc variable determine the type of output. The following shows this for the accelerometer. Similar code is used for the magnetometer and gyro.

    LSM9DS1.prototype.readAccel=function(){
    var temp=this.xgReadBytes(Regs.OUT_X_L_XL, 6);
    var i;
    // Read 6 bytes, beginning at OUT_X_L_XL
     this.a[0]=this.twos_comp(temp[0],temp[1]­);
     this.a[1]=this.twos_comp(temp[2],temp[3]­);
     this.a[2]=this.twos_comp(temp[4],temp[5]­);
     if (this.autoCalc>0){
      for(i=0;i<3;i++)
       this.a[i] -= this.accelscale[this.aScale].aoff[i];
         //this.aBiasRaw[i];
     }//endif
     if (this.autoCalc>1){
      for(i=0;i<3;i++)
       this.a[i]=this.a[i]*this.accelscale[this­.aScale].ares[i];
     }//endif
    };//end readAccel
    

    4 Attachments

  • Hi @allObjects I hope to get back to the leveling code once I've got a calibrate IMU to work with.
    I think it should exist as a separate object from the IMU code so it can be applied to other systems.
    As to the viability on your boat, that remains to be seen.
    Using the accelerometer to level the magnetometer readings is subject to accelerations such as experienced when turning, or an airplane in a banked turn.
    Thanks for your interest.

  • Some links:
    https://en.wikipedia.org/wiki/I%C2%B2C

    http://johnny-five.io/news/introducing-i­2c-component-backpacks/

    https://github.com/sparkfun/SparkFun_LSM­9DS1_Arduino_Library

    The mreadbyte, xgreadbyte, mwritebyte, xgwritebyte, mreadbytes, and xgreadbyte functions using the I2C Wire class in Arduino are defined in the following:
    https://github.com/sparkfun/SparkFun_LSM­9DS1_Arduino_Library/blob/master/src/Spa­rkFunLSM9DS1.cpp

    Like I said, I know very little about the environment to which you want to port the code.

  • 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
    

    3 Attachments

  • 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


    5 Attachments

  • For multi byte read, did you try enabling the IF_ADD_INC of CTRL_REG8.
    I faced some issue with python i2c readList, but after setting the CTRL_REG8 properly i got it through the multi byte read.

    FIFO in continuous mode is 6 and not 5 as per the datasheet. There is no entry for 5.

  • Interesting, thanks.
    April 1 my PC power supply emitted the magic smoke. I'm on a different one now and hoping that the a new PC power supply will fix the old one and lots of files will still be there. If not it's going to take a while to get most everything restored.
    Then I will be able to try your fix.

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

LSM9DS1 A 9 degree of freedom Inertial Navigation Chip

Posted by Avatar for ClearMemory041063 @ClearMemory041063

Actions