LIS302 Digital Accelerometer

The LIS302 is a 3-axis accelerometer, accessible via a digital SPI interface with a selectable range of 2g or 8g.

Development

I've already had a play with the analog accelerometers, which were fine, but as they are analog I am naturally allergic to them.

Instead, my preference is the LIS302 3-axis accelerometer from STMicroelectronic. It is dynamically switchable from +/-2g to +/-8g by software, which is nice. It also has both SPI and I2C interfaces. SPI == Good, I2C == Bad, so I'll start with SPI.

This part also has free fall detection, and a gesture interface, so it will work out if you have "double clicked". One step at a time though.

I am using the breakout board from Sparkfun, http://www.sparkfun.com/commerce/product_info.php?products_id=8658 LIS302DL Breakout - The novelty of hand soldering crazy packages is wearing off.

Cut to the chase... library please!

If you have visited this page to get the class to use in your own code :

It just an experiment, but it works. All feedback welcome!

Hooking it up

There are no pin numbers on the breakout board, but the signal names instead - you can enumerate them how you wish :-) There are also two interrupt pins that are not part of the convenient SIL header, which are for Freefall detection interrupts. We'll not be using them just yet.

LIS302 Signal Namembed pin
VccVout
GndGnd
SCLp7
MOSIp5
MISOp6
CSp8

Hello World

The first and best resource for getting this going will be the datasheet for the LIS302 part :

#!cpp
#include "mbed.h"

SPI _spi (p5,p6,p7);
DigitalOut _cs (p8);
Serial pc (USBTX,USBRX);


int main() {

  int val=0;
  
  // Taken from datasheet
  // 8 bit data
  // High steady state clock
  // Second edge capture
  _spi.format(8,3);
  
  // 1MHz clock rate
  _spi.frequency(1000000);

  // Select the device, active low chip select
  _cs = 0;
  
  // Send the command to read the WHOAMI register
  _spi.write(0x8F);   // Send the "Read  WHOAMI Register
  
  // send a dummy byte to receive the contents of the WHOAMI register
  val = _spi.write(0x00);
  
  pc.printf("WHOAMI register contains 0x%X\n",val);

  // Deselect the device
  _cs = 1;

  }

Phut! WHOAMI reads back 0x0, instead of the expected 0x3B. Read the datasheet again...

Oh, you have to write to CTRL1_REG and set bit 6 to power the device up.

So the code now looks like :

#!cpp
#include "mbed.h"

SPI _spi (p5,p6,p7);
DigitalOut _cs (p8);
Serial pc (USBTX,USBRX);


int main() {

  int val=0;
  
  // Taken from datasheet
  // 8 bit data
  // High steady state clock
  // Second edge capture
  _spi.format(8,3);
  
  // 1MHz clock rate
  _spi.frequency(1000000);

  // Select the device, active low chip select
  _cs = 0;
    
  // Enable the device, and all three channels
  _spi.write(0x47);
  
  // end this transfer
  _cs = 1;
  
  wait (0.01);
  
  // start new transfer
  _cs = 0;

  // Send the command to read the WHOAMI register
  _spi.write(0x8F);   // Send the "Read  WHOAMI Register
  
  // send a dummy byte to receive the contents of the WHOAMI register
  val = _spi.write(0x00);
  
  pc.printf("WHOAMI register contains 0x%X\n",val);

  // Deselect the device
  _cs = 1;

  }

Bingo! WHOAMI now reads 0x3B, as it should.

Reading some acceleration numbers

Now I want to start reading OUTX, OUTY and OUTZ - the outputs of the accelerometer.

The output is a 8 bit signed number, so there is 7 bits of resoltion and the accelerometer is +/-2g, so I need to divide the signed number by 64.0. I'll put in a while loop so I can continuously read and make sure it looks sensible.

#!cpp
#include "mbed.h"

SPI _spi (p5,p6,p7);
DigitalOut _cs (p8);
Serial pc (USBTX,USBRX);


int main() {

  int val=0;
  
  // Taken from datasheet
  // 8 bit data
  // High steady state clock
  // Second edge capture
  _spi.format(8,1,1);
  
  // 1MHz clock rate
  _spi.frequency(1000000);

  // Select the device, active low chip select
  _cs = 0;
    
  // Enable the device, and all three channels
  _spi.write(0x47);
  
  // end this transfer
  _cs = 1;
  
  wait (0.01);
  
  // start new transfer
  _cs = 0;

  // Send the command to read the WHOAMI register
  _spi.write(0x8F);   // Send the "Read  WHOAMI Register
  
  // send a dummy byte to receive the contents of the WHOAMI register
  val = _spi.write(0x00);
  
  pc.printf("WHOAMI register contains 0x%X\n",val);

  // Deselect the device
  _cs = 1;
  
  // now read out the from the acceleration registers
  while (1) {
       
      _cs = 0;
      _spi.write(0xA9);   // Read X out reg
      signed char xraw =  _spi.write(0x0);
      _cs = 1;

      _cs = 0;
      _spi.write(0xAB);   // Read Y out reg
      signed char yraw =  _spi.write(0x0);
      _cs = 1;

      _cs = 0;
      _spi.write(0xAD);   // Read Z out reg
      signed char zraw = _spi.write(0x0);
      _cs = 1;

	  float xaxis = xraw/16.0;
	  float yaxis = yraw/16.0;
	  float zaxis = zraw/16.0;

      pc.printf("A X=%f, Y=%f, Z=%f\n",xaxis,yaxis,zaxis);
      wait(0.5);
  
      }
  }

Okay, so that is working. The numbers are not quite what I;d been hoping for :

X=0.875000, Y=0.000000, Z=-0.187500
X=-0.875000, Y=-0.187500, Z=-0.250000
X=0.125000, Y=0.937500, Z=-0.062500
X=0.000000, Y=-0.937500, Z=0.062500
X=0.000000, Y=0.062500, Z=0.812500
X=0.000000, Y=-0.125000, Z=-0.937500

Looking at the datasheet it does say that calibration at the factory can be affected when the part is soldered. It also says that the typical full scale is 2.3g, with 2.0g being minimum.

Bearing this in mind, i'll change the 64.0 fudeg factor to 128/2.3 = 55.6, and in 8g mode, it is actuall 9.3g

Give that a go...

X=1.007194, Y=-0.215827, Z=0.287770
X=-1.079137, Y=-0.215827, Z=-0.143885
X=0.000000, Y=1.007194, Z=-0.287770
X=0.143885, Y=-1.007194, Z=-0.143885
X=0.071942, Y=0.071942, Z=0.935252
X=0.071942, Y=0.071942, Z=-1.151079

Closer.

Time to wrap it up into a class.

Building the class

The API I'd want for this class is

  • LIS302(int mosi, int miso, int clk, int ncs);
  • float x(void);
  • float y(void);
  • float z(void);
  • int whoami(void);
  • int status(void);
  • void range(int g); (0=2g, 1=8g)

At the top of this page is a link to the subversion where this is kept. Go have a look at the code if you're interested.

Here is a HelloWorld using the class :

#!cpp
#include "mbed.h"
#include "LIS302.h"

LIS302 acc (p5,p6,p7,p8);
Serial pc (USBTX,USBRX);

int main() {
    while (1){
        pc.printf("%.3f %.3f %.3f\n",acc.x(),acc.y(),acc.z());
        wait(0.1);
    }
}


As an extension to the above code, I decided to link the accelerometer up to an 16x2 LCD text display, I adapted the code to include a calibration function, assuming it follows a linear trend.

Here is the Hello World for the adapted code:

#!cpp
#include "mbed.h"
#include "LIS302.h"

LIS302 acc (p11,p12,p13,p14);
Serial pc (USBTX,USBRX);
int main() {
acc.calibrate(1.043, -0.935, 0.989, -1.061, 0.9274, -1.1584);
    while (1){
        pc.printf("%.3f %.3f %.3f\n",acc.x(),acc.y(),acc.z());
        wait(1);
    }
}

The calibrate function takes the maximum and minumum values for each axis (X,Y&Z), when lying stationary on a flat surface, therefore they should be close to 1.0 and -1.0 respectively. Although they are calibrated accurately during manufacture, the soldering of the accelerometer itself onto the board warps it somewhat, so the calibrate functin I added accounts for that.

The values within the calibrate function are, in order, maximum for x, minimum for x, maximum for y, minimum for y, maximum for z, minimum for z.

So as to be able to read the uncalibrated maximum and minimum values, if no values are placed in the function, it will default to 1.0 and -1.0 for all maximums and minimums, therefore you can then manipulate the board, until the correct maximums and minimums for each axis are ascertained and enter those values into the function.

The section of code from the C preprocessor shows how the values of acc.x() are altered by the maximum and minimum values:

#!cpp
float gradient = (2.0/(maxx-minx));
  
  if (_range==0) {
      val = (gradient*(float)(raw)/FACTOR_2g)+((-gradient*maxx)+1);
  }
  else {
      val = (gradient*(float)(raw)/FACTOR_8g)+((-gradient*maxx)+1);
  }
   
  
  return(val);
  }

By using y=mx+c in the code shown above, I shifted and twisted the values displayed on the LCD display to their final, calibrated values. The updated files are in the trunk. NB the calibration function assumes a linear trend.


All wikipages