ADXL345: How to use it for Movement Sensing and More. Using this Accelerometer you can detect up to 16g! Detect gravity for orientation (which way is up?), or make a Knock-Knock detector.

The ADXL345 is a 3 axis accelerometer. You can use it for:

  • Orientation: Determine which way is up using gravity.

  • Instantaneous force: Detect applied forces.

  • Built in algorithm for Knock-Knock detection.

  • Free Fall detection algorithm.

The ADXL345 I.C. is a chip that can sense movement in 3 axes. It can measure the total force applied in the x, y or z directions. You can think of it as either a sense for for force of gravity, or as a sensing tool for applied external forces. So there are two main uses for this device

  1. Sensing gravity - for orientation detection.
  2. Sensing external forces - make a drop detector for sensitive packages.

A third application is Knock - Knock detection (a subset of external force detection) - the chip has support for this built in!

Typical applications include tilt and motion sensing e.g. self leveling platforms, drone orientation, angle of tilt measurement, drop sensing. 

It can measure accelerations up to 16g with 13bit resolution translating to a resolution of 3.9mg (g representing gravity at 9.8m/s2).

adxl345 processing view on PC

To make it more useful, it has several built in functions:

  • Thresholds.
  • Tapping.
  • Falling.
  • Self test.

You can setup thresholds on each axis to allow detection of movement i.e. above a certain acceleration level it will generate an interrupt. You could use that for detecting if the contents of a box of sensitive equipment had been mishandled.

ADXL345 Breakout board

adxl345 breakout board

You can also detect single or double taping in any direction. Another built in function can detect free fall for drop sensing.

ADXL345 Specification

  Parameter
Value
Voltage Supply (Vs)
2V0 ~ 3V6
Digital Supply voltage (Vdd i/o)
1.7V ~ Vs
Interface
I2C / SPI
I2C Address (depends on ALT ADDR)
0x1D, 0x53
Interface Speed
100kHz, 400kHz / 5MHz
Resolution (2g to 16g)
10 ~ 13  bits
Noise in LSB (400Hz, 2V5) [1] [2]
<1.7LSB (typ)
Shock survival
10,000g
Active current (ODR >=100Hz)
~150uA (170uA max)     
Active current (ODR <100Hz) ~30uA
Standby mode (leakage current)
0.1uA
Operating temperature
-40°C ~ 85°C
           [1] - Noise performance improves with higher supply voltage.
              [2] - Datasheet figure 51.

ADXL345 Block Diagram

ADXL345 block diagram
From Analog devices ADXL345 datasheet.

Breakout board Specification

As above except for the power supply. The breakout board has a LDO 3V3 regulator so the input voltage to VCC must be:

 4.0V to 6V.

Note: This is only for the breakout board - the actual chip Vsmin = 2V0.

Also:

  • CS is pulled high by a 10k resistor (active). Can be 4k7 on some boards.
  • ADDR(SDO) is pulled low by a 4k7 resistor.
  • SCL is pulled high to 3V3 by a 4k7 resistor.
  • SDA is pulled high to 3V3 by a 4k7 resistor.
  • VDDIO is connected to VS and both connect to 3V3.

Since the I2C interface uses open drain outputs, the interface is compatible with a 5V system. This is true as the Arduino never drives out 5V it only tristates an output to let the voltage level pull to 3V3. To generate a zero it outputs a digital zero which is also compatible with the 3V3 interface.

Warning: If you use the chip in SPI mode then you will need level translators (when using a 5V controller) or a controller board that only uses 3V3 throughout.

Breakout Board ICs


ADXL345 breakout board Chips (ICs)
Note: Note: The addition of a 3V3 regulator means the board can be used in a 5V system if I2C mode is used.

ADXL345 Breakout board GY-291 Schematic

adxl345 breakout board schematic

ADXL345 Interface to Arduino

Using the ADXL345 with the Arduino is quite easy if you use the I2C interface, as this allows to controller to operate at 5V and the ADXL345 to operate at 3V3.

Note: In I2C mode the SDO pin is an input, selecting an address.

I2C Address

For I2C mode operation the chip has an address pin. On the breakout board this is pulled low giving an I2C address of:

State of ADDR/SDO pin
I2C Address (hex)
I2C Address (binary)
ADDR pulled high 0x1D
%0001-1101
ADDR pulled low 0x53 %0101-0011


Note: The ALT ADDRESS pin is an output in SPI mode, for SDO.

ADXL345 Output Data Rate

The ODR is 100Hz set by bits in the BW_RATE register. In the library use setRate() and the following defined values:


 ADXL345_RATE_3200 // Must use SPI mode
 ADXL345_RATE_1600 // Must use SPI mode
 ADXL345_RATE_800  // Must use I2C rate of >=400kHz
 ADXL345_RATE_400  // Must use I2C rate of >=200kHz
 ADXL345_RATE_200  // Must use I2C rate of >=100kHz
 ADXL345_RATE_100 (default)
 ADXL345_RATE_50
 ADXL345_RATE_25
 ADXL345_RATE_12P5
 ADXL345_RATE_6P25
 ADXL345_RATE_3P13
 ADXL345_RATE_1P56
 ADXL345_RATE_0P78
 ADXL345_RATE_0P39
 ADXL345_RATE_0P20


Changing the data rate is useful for power saving (by going slow).

At 100Hz and above the current consumption is 140uA (p14 of datasheet) - except for 1600Hz (90uA). The minimum you can get is at 0.39Hz (and below) with 23uA used.

ADXL345 Resolution

You can select the acceleration range from the following values:

   2g, 4g, 8g, 16g.

The same scaling factor is used in all resolutions because the internal DAC resolution changes from 10bits to 13 bits as each resolution is selected. So each bit is always worth 3.9mg:

Calculation g/LSB
Result
2/pow(2,10-1)
0.003906
4/pow(2,11-1) 0.003906
8/pow(2,12-1)
0.003906
16/pow(2,13-1)
0.003906

Note the '-1' allows for the sign bit to give positive or negative results.  

Using 16g mode gives maximum dynamic range.

However an extract from the datasheet shows the actual deviation from the above ideal values (Sensitivity deviation from ideal):

Sensitivity deviation from ideal mg per lsb
If you restrict the range of the adxl345 to 10bits then it becomes less sensitive (fairly obvious). For full range you get the typical sensitivity of 3.9mg/LSB and 3.5 ~ 4.3mg/LSB min ~ max deviation (this is set using the FULL_RES bit). Why you would restrict to 10bits is something I don't understand - seems pointless.

Why not ±90°?

Why can't the ADXL345 accelerometer indicate ±90°?

If you run the program below you will see that the accelerometer is extremely good at indicating tilt at low angles (on a flat surface) to about 70°, but does not reach ±90°. In fact it can't seem to reach that magic number ±90°!

At first, I thought there was something wrong with the accelerometer since no one seems to talk about this. Why should it not be possible to measure all angles?

If you think about holding a piece of wood - the maximum force on it is when you hold it horizontally. The more you point it upwards, to the vertical position, the less gravity can affect it, hence - the accelerometer becomes less sensitive.

So it turns out that the sensitivity of the accelerometer decreases with increasing angle and follows a near sinusoidal response, and accuracy decreases the closer you get to ±90° of tilt.

"The sensor is a polysilicon surface-micromachined structure built on top of a silicon wafer. Polysilicon springs suspend the structure over the surface of the wafer and provide a resistance against forces due to applied acceleration."  [datasheet]

You can imagine the sensor as a beam suspended on springs, with the capacitance between the beam and the support base giving the acceleration measurement. When the beam is perpendicular to the acceleration field small tilt changes will have a large effect as gravity acts over a larger area.

As the beam tilts vertically, the sensor becomes less sensitive (interacting with the gravity vector less) until, at full vertical orientation, it can not return a reading since the gravity vector has no effect on the beam.

There is a very good discussion of accelerometer sensitivity in Analogue Devices Application note AN-1057 by C Fisher here. The following diagram is from that application note:

accelerometer tilt angle sensitivity 1 degree step

The diagram shows that around 0° ~ 23° tilt you only need a sensitivity of 16mg/LSB to resolve 1° steps, and around 60° tilt you need 8mg, and around 80° you need 4mg.  This is why the ADXl345 can not display 90° as its sensitivity is too coarse to resolve tilt at that angle (3.8mg). So the ADXL345 is good for tilt up to about ±80°. This is for the case where you want to resolve tilt measurement to within 1°.

TIP: Increase resolution by oversampling to resolve smaller angles.

Why can't I measure yaw?

Yaw is the rotation about the z axis - or compass heading. You may think that the ADXL345 z axis is not working but it is!

When you rotate the accelerometer about the x or y axis, sensors experience more or less of the gravity vector and therefore output different values of acceleration.

When you rotate the accelerometer about the z axis the sensor experiences the same value of gravity vector so you can't measure yaw.

This is why accelerometers are used in conjunction with a gyroscope or magnetometer for measuring yaw values.

What is the z accelerometer useful for?

So then the question becomes why do you need a z-accelerometer measurement?

The z axis accelerometer indicates deviation from vertical. In conjunction with either x or y accelerometer values you can calculate pitch and roll (deviation from vertical in the x or y directions).

ADXL345 Datasheet

Download here.

Hardware Connections:

You can use an Arduino Uno or Nano (the Nano is convenient for solderless breadboard use as it fits into one quite nicely). Here the ADXL345 is used on the Arduino in I2C mode so just connect SDA, SCL, power and ground:

Arduino Uno/Nano Connection
ADXL345 Connection
A4
SDA
A5
SCL
GND
GND
5V
Vcc

ADXL345 interfacing with the Arduino is simply to connecting the I2C interface as above.

Software

I2Cdev library

The I2Cdevlib has ADXL345 library code as well as code for many other devices.

https://github.com/jrowberg/i2cdevlib

This library has a lot of features, supported chips, and can operate on multiple processors but it is a little more involved to install and you can not use the automatic Arduino zip file installer.

Unzip the file (ic2devlib-master) then navigate to the Arduino directory within ic2devlib-master. Copy the directories ADXL345 and I2Cdev to the Arduino libraries directory, usually found here (on windows):

    C:\Users\<User name>\Documents\Arduino\libraries

ADXL345 Self Test Sketch

Because the device is a mechanical system the ADXL345 includes a self test circuit that imposes an electrostatic field on the physical measuring elements. These has a similar effect to gravitational acceleration adding to the existing acceleration element.

Note: Datasheet page 31 has more on using the self-test.

The following program tests the chip at 3.3V (the supply voltage changes the test parameters). The program will output the results to the serial monitor and gives you a PASS/FAIL indication. You can also observe raw g values, roll and pitch.


Use this sketch if you thing that the AXL345 is not working.

// By John Main © best-microcontroller-projects.com
// ADXL345 Self Test, show g and roll pitch values
#include "math.h"
#include "Wire.h"
#include "I2Cdev.h" // Has MIT license.
#include "ADXL345.h"

ADXL345 accel;

#define LED_PIN LED_BUILTIN // (Arduino is 13, Teensy is 6, nano? so use LED_BUILTIN)
bool toggleLED = false;
static byte showAngles = 0; // Display operation

void setup() {
    Wire.begin();
    Serial.begin(115200);

    // initialize device
    Serial.println( F("Starting I2C devices...") );
    accel.initialize();

    Serial.println( F("Checking ADXL345...") );
    if(accel.testConnection())
      Serial.println( F("ADXL345 comms Ok.") );
    else {
       Serial.println( F("ADXL345 comms failed.") );
       while(1); // Hang on error.
    }
    Serial.println(F("Enter t - Self test, s - Show angles"));

    accel.setFullResolution(1); // 0 => 10 bit mode.
    accel.setRate(ADXL345_RATE_100);  // This is default but shows the value.
    accel.setLowPowerEnabled(0);
    accel.setRange(3); // 0 => 2g, 3 => 16g

    pinMode(LED_PIN, OUTPUT);
}

// Get readings for self test - A set of n averaged results.
void ADXL345GetAvgN(int16_t *xi,int16_t *yi,int16_t *zi,uint8_t n) {
int16_t x,y,z;
int32_t ax,ay,az;
uint8_t i;

    ax=0; ay=0; az=0;
    for(i=0;i<n;i++) {
      accel.getAcceleration(&x, &y, &z);
      ax += x; ay += y; az +=z;
      delay(100); // Don't read faster than 100Hz: (10*100ms=1s 1s/10=100ms)
    }
    *xi = ax/n; *yi = ay/n; *zi = az/n;
}

void print_minmax(int vmin,int vmax) {
  Serial.print('(');Serial.print(vmin);Serial.print(" ~ ");Serial.print(vmax);Serial.print(')');
}

// This function setsup the relevant parameters while preserving them e.g. range
void ADXL345SelfTest(void) {
uint16_t fr,st,rt,lp,ra;
    fr = accel.getFullResolution();
    rt = accel.getRate();
    lp = accel.getLowPowerEnabled();
    ra = accel.getRange();

    int16_t avg_x=0, avg_y=0, avg_z=0;
    int16_t avg_stx=0, avg_sty=0, avg_stz=0;
    int16_t ax,ay,az;
    int16_t x,y,z;
    uint8_t i;

    // Setup operating mode required for self test.
    accel.setFullResolution(0); // 0 => 10 bit mode.
    accel.setRate(ADXL345_RATE_100);
    accel.setLowPowerEnabled(0);
    accel.setRange(3); // 3 => 16g

    ADXL345GetAvgN(&avg_x, &avg_y, &avg_z, 10);

    accel.setSelfTestEnabled(1);
    ADXL345GetAvgN(&avg_stx,&avg_sty,&avg_stz,5);    // Let device settle for >=4 samples.
    ADXL345GetAvgN(&avg_stx, &avg_sty, &avg_stz, 10);
    accel.setSelfTestEnabled(0);

    // Display results
    x = avg_stx-avg_x; y = avg_sty-avg_y; z = avg_stz-avg_z;
    Serial.print("\nstx-x\t");Serial.print(x);Serial.print("\t");
    Serial.print("sty-y\t");Serial.print(y);Serial.print("\t");
    Serial.print("stz-z\t");Serial.print(z);Serial.print("\t");

    // 3V3 error limit scale factors.
    #define scale_factor_xy  1.77;
    #define scale_factor_z   1.47;
    float vmin,vmax;

    Serial.print('\n');

    //Value limits are from ADXL345 datasheet - Tables 14 and 18.
    vmin = 6 * scale_factor_xy; vmax = 67 * scale_factor_xy;
    Serial.print( (x>=vmin && x <vmax) ? "X PASS " : "X FAIL " );
    print_minmax(vmin,vmax); Serial.print('\n');

    vmin = -67 * scale_factor_xy; vmax = -6 * scale_factor_xy;
    Serial.print( (y>=vmin && y <vmax) ? "Y PASS " : "Y FAIL " );
    print_minmax(vmin,vmax); Serial.print('\n');

    vmin = 10 * scale_factor_z; vmax = 110 * scale_factor_z;
    Serial.print( (z>=vmin && z <vmax) ? "Z PASS " : "Z FAIL " );
    print_minmax(vmin,vmax);Serial.print('\n');

    accel.setFullResolution(fr);
    accel.setRate(rt);
    accel.setLowPowerEnabled(lp);
    accel.setRange(ra);
}

void showAccelAngles(void) {
  float x,y,z;
  int16_t ax,ay,az;
   // Show angles
   // Datasheet: OPERATION AT VOLTAGES OTHER THAN 2.5 V
   // 3v3 X,Y 25mg too high, z 20mg too low
   // 3V3 lsb value 265/g   (g/265)=0.03698
   // 2V5 lsb value 256/g   (g/256)=0.03828 z axis unaffected by voltage supply.
   #define ADXL345_LSBVAL_3V3 3.698E-3
   #define ADXL345_LSBVAL_2V5 3.828E-3

   accel.getAcceleration(&ax, &ay, &az);
   x = ax*ADXL345_LSBVAL_3V3 - 25E-3;
   y = ay*ADXL345_LSBVAL_3V3 - 25E-3;
   z = az*ADXL345_LSBVAL_2V5 + 20e-3;

   float fNum;
   Serial.print(F("g values: "));
   fNum=x;Serial.print(fNum); Serial.print("\t");
   fNum=y;Serial.print(fNum); Serial.print("\t");
   fNum=z;Serial.print(fNum); Serial.print("\t");

   float r = sqrt(x*x + y*y + z*z);

   // Angle from x,y axis to gravity vector.
   int roll   = 180/M_PI * ( M_PI/2 - (acos(y/r) ) );
   int pitch  = 180/M_PI * ( M_PI/2 - (acos(x/r) ) );

   Serial.print(F("Roll ")); Serial.print(roll); Serial.print(' ');
   Serial.print(F("Pitch "));Serial.print(pitch);Serial.print(' ');
   Serial.print('\n');
}

void loop() {
float x,y,z;
static byte ch;

   if(Serial.available()) {
     ch=Serial.read();
   }

   if(ch=='t') {
      Serial.println(F("Testing..."));
      ADXL345SelfTest();
      showAngles = 0;
   }
   else if(ch=='s')
      showAngles = 1;

   if (showAngles) showAccelAngles();

   toggleLED = !toggleLED;
   digitalWrite(LED_PIN, toggleLED);
   delay(200);
}


[File:adxl345_self_test.ino]

Self Test Program Output

Type 't' to start a self-test and 's to output g, roll and pitch values.

The following shows typical output from the program:


Checking ADXL345...
ADXL345 comms Ok.
Enter t - Self test, s - Show angles
Testing...

stx-x    59    sty-y   
-18    stz-z    88   
X PASS (10 ~ 118)
Y PASS (-118 ~ -10)
Z PASS (14 ~ 161)
g values: -0.17    -0.02    1.05    Roll 0 Pitch -9
g values: -0.21    0.00    1.08    Roll 0 Pitch -11
g values: -0.18    -0.01    1.03    Roll 0 Pitch -9
g values: -0.17    -0.02    1.07    Roll -1 Pitch -9
g values: -0.17    -0.02    1.07    Roll 0 Pitch -8
g values: -0.01    -0.03    1.00    Roll -1 Pitch 0
g values: -0.69    -0.06    0.77    Roll -3 Pitch -41
g values: -1.30    -0.15    0.36    Roll -6 Pitch -73
g values: 0.24    -0.21    0.95    Roll -12 Pitch 13
g values: 0.22    -0.42    0.96    Roll -23 Pitch 11
g values: -0.06    -0.82    0.87    Roll -43 Pitch -2
g values: -0.01    0.05    1.01    Roll 2 Pitch 0

Sketch Example 2

This program outputs only roll and pitch values - these are used to show a processing simulation of the orientation of the accelerometer. This is the type of view produced by the processing code for the Arduino sketch below:

adxl345 processing view on PC



// By John Main © best-microcontroller-projects.com
// This sketch outputs serial data as 2 parameters (roll and pitch)
// for display in processing code on PC.

#include "math.h"
#include "Wire.h"
#include "I2Cdev.h"
#include "ADXL345.h"

#define LED_PIN LED_BUILTIN
// Macros to allow 0 ~ 180 mapped to -90 to 90

ADXL345 accel;

bool blinkState = false;

void setup() {

    Wire.begin();
    Serial.begin(115200);

    accel.initialize();

    accel.setRate(ADXL345_RATE_100);  // This is default but shows the value.
    accel.setFullResolution(1); // 0 => 10 bit mode.
    accel.setLowPowerEnabled(0);
    accel.setRange(0); // 0 => 2g, 3 => 16g

    pinMode(LED_PIN, OUTPUT);
}

void loop() {
float r,x,y,z;
int16_t ax, ay, az;

  // Datasheet: OPERATION AT VOLTAGES OTHER THAN 2.5 V
  // 3v3 X,Y 25mg too high, z 20mg too low
  // 3V3 lsb value 265/g c  (g/265)=0.03698
  // 2V5 lsb value 256/g   (g/256)=0.03828 z axis unaffected by voltage supply.
  #define ADXL345_LSBVAL_3V3 3.698E-3
  #define ADXL345_LSBVAL_2V5 3.828E-3

  accel.getAcceleration(&ax, &ay, &az);
  x = ax*ADXL345_LSBVAL_3V3 - 25E-3;
  y = ay*ADXL345_LSBVAL_3V3 - 25E-3;
  z = az*ADXL345_LSBVAL_2V5 + 20e-3;

  r = sqrt(x*x+y*y+z*z);
  // Angle from x,y axis to gravity vector.
  int roll   = 180/M_PI * ( M_PI/2 - (acos(y/r) ) );
  int pitch  = 180/M_PI * ( M_PI/2 - (acos(x/r) ) );

  Serial.print(roll); Serial.print(' ');
  Serial.print(pitch);Serial.print(' ');
  Serial.print('\n');

  // blink LED to indicate activity
  blinkState = !blinkState;
  digitalWrite(LED_PIN, blinkState);
  delay(50);
}


[File:degrees-roll-pitch.ino]

Processing Sketch

This shows the orientation of the accelerometer as a 3d image on screen - it mimics the orientation of the accelerometer in real time.

Download processing here.

Note: Change the Serial port to your own one. When you run the program below the output text screen will show the available serial ports. Change COM19 to match yours.


// By John Main © best-microcontroller-projects.com
// This sketch accepts serial data as 2 parameters (roll and pitch)
// and displays a 3d representation of the accelerometer.
import processing.serial.*;

Serial myPort;
String inString;  // Input string from serial port
int lf = 10;      // ASCII linefeed
float rx,ry,rz;
float mouseZoom;

float RAD_TO_DEGREE = 180/PI;
float DEGREE_TO_RAD = PI/180;


void setup() {
  size(400,400,P3D);

  print("-----------------------------");
  print("START");
  print("--------------------\n");
  // List all the available serial ports:
  printArray(Serial.list());

  // Open the port you are using at the rate you want:
  // Replace COMP19 with whatever port you want to use.
  myPort = new Serial(this, "COM19", 115200);
  myPort.bufferUntil(ENTER);

  mouseZoom= width/2;
}

void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  mouseZoom += e*10;
}

void draw() {
  int x,y=70,ysep=20;
  background(80);
  fill(#ffffff);
  translate(100,180);

  if (inString!=null) {
    inString =  trim(inString);
    text("received: " + inString, 10,y);y+=ysep;//50
    int[] nums = int( split(inString,' ') );
    text("roll " + nums[0],10,y);y+=ysep; // 70
    text("pitch " + nums[1],10,y);y+=ysep;
    rx = nums[0];
    ry = nums[1];
  }

  pushMatrix();
  // FOV START
  float fov = 0.6086836; float aspect=1; float cameraZ=636.74;
  perspective(fov, aspect, cameraZ/10.0, cameraZ*10.0);
  // END FOV

  // Angle camera view to left and above (1st 2 params).
  camera((width/2)*0.4, height/2*0.05, (height/2) / tan(PI/6), width/2, height/2, 0.5, 0, 1, 0);

  translate(200,200);

  stroke(#F4F52F); // Axes
  box(170,1,1);
  box(1,1,60);
  stroke(0);
  // Axes - processing    : y down x right z out of page
  // Axes - accelerometer : y - into page, x right, z up.
  rotateX(-rx * DEGREE_TO_RAD);
  rotateZ(-ry * DEGREE_TO_RAD); // Different z axis for accelerometer.

  fill(#6598C9,100);
  box(150,10,40);
  fill(#F52FF2,100);
  translate(0,10,0);
  box(150,10,40);

  popMatrix();

}

void serialEvent(Serial myPort) {
      inString = myPort.readString();
}


[File:roll_pitch.pde]

What's next:

How to make a digital compass. This requires an accelerometer to ensure accuracy - the magnetomer must be compensated as it can only generate accurate compass headings if it is level. Using the Accelerometer allows tilt compensation.

Deep Dive into MEMS

This a cutting edge university level tutorial on mems and micromachined structures with specific coverage of oscillator applications.

This provides a fascinating look at the world of micromachining from structures created between 1989 and the present day (Video released in Feb 2019). The tutorial is from 2018.

It starts off showing how structures are created using transistor etching techniques. The talk is given Professor Clark Nguyen - He was Bill Tang's undergraduate researcher when MEMS was being invented.

This is a great video to see cutting edge techniques - it is very advanced but starts off slowly, describing early designs - very good to dip into.



Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.




Privacy Policy | Contact | About Me

Site Map | Terms of Use