ADXL345 – The Universal Accelerometer – Part 2

About this post

This is part 2 of my article about the ADXL345 accelerometer and my associated library. It builds on the first part, in which I discussed the general features and the first seven sketches.

This part focuses on:

  • Activity / Inactivity
    • here I also catch up with the Auto Sleep function
  • Single and Double Tap
  • FIFO functions

Further notes:

  • The circuit used for the example sketches is the same as that of the first part.
  • Again, you’ll only find the shortened sample sketches. You get the original sketches with the library. They contain considerably more comments, especially the options for the function parameters.
  • I found a few bugs after I posted the first part. So install the updates via the Arduino IDE or keep an eye out for the latest version on GitHub!

Activity – Inactivity – Auto Sleep

The Activity interrupt is triggered when a certain acceleration value is exceeded on the specified axes. The Inactivity interrupt is triggered when the values for the axes involved fall below a certain acceleration value for a certain time.

Both interrupts can be set up independently of each other. Because of their relationship, I treat both in one example sketch. In addition, the effect of the link bit can be best shown this way.

In the last post I covered the interrupts of the ADXL345 in general.

Example sketch 8: ADXL345_activity_inactivity_interrupt.ino

Here I use the following new functions:

  • setActivityParameters(mode, axes, g-threshold)
    • mode: is DC or AC
      • DC: the limit (threshold) applies absolutely.
      • AC: the limit is the delta to the starting value.
    • axes: the participating axes.
    • g-threshold: the limit in g.
  • setInactivityParameters(mode, axes, g-threshold, time-threshold)
    • mode, axes, g-threshold: as above.
    • time-threshold: the time limit in seconds (max. 255).
  • setLinkBit(true/false)
    • I’ll explain that below.
  • getActTapStatusAsString()
    • Returns as a string which axis/axes was/were responsible for the Activity interrupt. Also used for the Tap functions.
  • getActTapStatus()
    • Returns the content of the Act Tap Status register as byte. The meaning of the returned value can be understood from the data sheet and the library files. For advanced users.

In the sketch you will find the permissible parameters. In addition, you need to activate the interrupts with setInterrupt().

For Activity and Inactivity interrupt, I set the DC mode, selected the x and y axis, 0.5 g as the g limit, and chose 10 seconds as the time limit for inactivity.

Since an interrupt can be both an Activity and an Inactivity interrupt, the content of the interrupt register is read with intSource = readAndClearInterrupt() and then checkInterrupt(intSource, interruptType) checks if interruptType was the trigger.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53
const int int2Pin = 2;
volatile bool in_activity = false; // in_activity: either activity or inactivity interrupt occured

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Activity and Inactivity Interrupts");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);


  myAcc.setDataRate(ADXL345_DATA_RATE_25);
  myAcc.setRange(ADXL345_RANGE_4G);
  
  attachInterrupt(digitalPinToInterrupt(int2Pin), in_activityISR, RISING);

/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.5);
  
/* Four parameters have to be set for in activity:
    1. DC / AC Mode:
        see activity parameters
    2. Axes, that are considered:
        see activity parameters
    3. Threshold in g
    4. Inactivity period threshold in seconds (max 255)
*/  
  myAcc.setInactivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.5, 10.0);
  
/* Setting the link bit only makes sense if both activity and inactivity are used
 *  If link bit is not set:  
 *   - activity interrupt can be triggered any time and multiple times
 *   - inactivity can be triggered if inactivity param. are met, independent of activity param. (if used) 
 *  If link bit is set:
 *   - activity interrupt can only be triggered after inactivity interrupt 
 *   - only one activity interrupt can be triggered until the next inactivity interrupt occurs
 */
   // myAcc.setLinkBit(true);

/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the thresold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_2) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);
  myAcc.setInterrupt(ADXL345_INACTIVITY, INT_PIN_2);
  
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); return the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/

void loop() {
  if ((millis() % 1000) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(in_activity == true) {
      //byte actTapSource = myAcc.getActTapStatus();
      //Serial.println(actTapSource, BIN);
      String axes = myAcc.getActTapStatusAsString();
      byte intSource = myAcc.readAndClearInterrupts();
      if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("Activity at: ");
        Serial.println(axes);
      }
     
     if(myAcc.checkInterrupt(intSource, ADXL345_INACTIVITY)){
        Serial.println("Inactivity!");
      }
      
    delay(1000);
    myAcc.readAndClearInterrupts();
    in_activity = false;
  }
}

void in_activityISR() {
  in_activity = true;
}

 

Output of the Activity / Inactivity Sketch

If the criterion for an activity interrupt is met, it is triggered. Since I have set the limits for Activity and Inactivity equal, an Activity interrupt sets the timer for the Inactivity interrupt back to zero.

ADXL345 example sketch:
output of ADXL345_activity_inactivity.ino
Output of ADXL345 activity_inactivity.ino

The Link Bit

Uncomment line 63 (setLinkBit(true)) and restart the sketch. Activity and Inactivity are now coupled. An Activity interrupt – one at a time – can only be triggered if an Inactivity interrupt has been triggered before. Try it best.

Example sketch 9: ADXL345_auto_sleep.ino

Here we now catch up with Auto Sleep mode. When enabled, the ADXL345 goes into Sleep mode when an Inactivity interrupt has been triggered. An Activity interrupt, on the other hand, wakes up the ADXL345. As in normal Sleep mode, the ADXL345 periodically wakes up briefly at a selectable frequency of 1 to 8 Hz even without an Activity interrupt.

For Auto Sleep mode, the link bit must be set. This is done automatically by the library in the background. Accordingly, Activity and Inactivity interrupt are interdependent.

The following features are new:

  • setAutoSleep(true/false, frequency)
    • true/false is the on/off switch.
    • frequency sets the wake-up frequency.
  • setAutoSleep(true/false)
    • Turn the Auto Sleep function on/off without changing the wake-up frequency.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool in_activity = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Auto Sleep");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }
/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_25);
  myAcc.setRange(ADXL345_RANGE_4G);
 
  attachInterrupt(digitalPinToInterrupt(int2Pin), in_activityISR, RISING);

/* The following settings are similar to the settings in ADXL345_activity_inactivity_interrupt.ino */

/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold 
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/  
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.8);

/* Four parameters have to be set for in activity:
    1. DC / AC Mode:
        see activity parameters
    2. Axes, that are considered:
        see activity parameters
    3. Threshold in g
    4. Inactivity period threshold in seconds (max 255)
*/    
  myAcc.setInactivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.8, 10);
  
/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the threshold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_1) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/ 
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);
  myAcc.setInterrupt(ADXL345_INACTIVITY, INT_PIN_2);
 
/* Auto sleep is connected with activity and inactivity. The device goes in sleep when inactivity is 
    detected. The link bit must be set, if you want to use auto sleep. The library sets the link bit 
    automatically. When the ADXL345 goes into sleep mode it wakes up periodically (default is 8 Hz).
    
    Choose the wake up frequency:
    ADXL345_WUP_FQ_1  =  1 Hz
    ADXL345_WUP_FQ_2  =  2 Hz
    ADXL345_WUP_FQ_4  =  4 Hz 
    ADXL345_WUP_FQ_8  =  8 Hz
    
*/
  myAcc.setAutoSleep(true, ADXL345_WUP_FQ_1);
  // alternative: myAcc.setAutoSleep(true/false) without changing the wake up frequency.
}

void loop() {
  if ((millis() % 300) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(in_activity == true) {
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
      Serial.println("Activity!");
      if(!myAcc.isAsleep()){        //check the asleep bit
        Serial.println("I am awake!");
      }
    }
     
    if(myAcc.checkInterrupt(intSource, ADXL345_INACTIVITY)){
      Serial.println("Inactivity!");
      if(myAcc.isAsleep()){
        Serial.println("I am sleeping...");
      }
    }
      
    myAcc.readAndClearInterrupts();
    in_activity = false;
  }
}

void in_activityISR() {
  in_activity = true;
}

Output of the Auto Sleep sketch

Below you can see the excerpt of an output. I have moved the ADXL345 so that the acceleration values were below the activity limit. You can see that the ADXL345 is really asleep by the fact that the measured values are only updated once per second in Sleep mode (as with the Sleep example sketch). Since the values are retrieved every 300 ms, 3-4 consecutive values are identical.

ADXL345 example sketch:
Output of ADXL345_auto_sleep.ino
Output of ADXL345 auto_sleep.ino

Single and Double Tap

A Single Tap event is basically an activity event, but with a defined maximum duration (DUR). If the event is longer than DUR, it is not considered a single tap. For example, vibrations can be distinguished from a longer acceleration. The next tap can only be detected after a latency period (latent) has elapsed. The latency starts after falling below the threshold.

A Double Tap consists of two Single Taps with same parameters. The additional condition is that the second tap must take place in a time window that starts after the latency of the first tap has expired. Sounds complicated, but it’s not when you look at it graphically:

ADXL345 Single and Double Tap Detection
ADXL345 Single and Double Tap Detection

For Single and Double tap detection you need relatively high measuring frequencies. For example, it would make no sense to specify a value for DUR in the millisecond range, but record measured values in the seconds range. That has to fit together.

Example sketch 10: ADXL345_single_tap.ino

You can set various parameters for the Single and Double Tap. The parameters that apply to both the Single and the Double Tap are defined by the following function:

  • setGeneralTapParameters(axes, g-threshold, duration, latent)
    • Axes are again the axes involved.
    • g-threshold is the limit in g (this time without DC/AC mode).
      • Vibrations usually cause high acceleration values and produce “echoes”, so the data sheet recommends 3 g as a starting value.
    • duration is the maximum duration of the tap in milliseconds. The datasheet recommends values greater than 10 ms. The maximum is 159 ms.
    • latent is the latency in milliseconds. It should be larger than 20 ms. The maximum is 318 ms.

You already know the function getActTapStatusAsString() from ADXL345_activity_inactivity.ino. Here it now returns the axis responsible for the tap interrupt or, if necessary, several.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool tap = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Single Tap Interrupt");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_200);
  myAcc.setRange(ADXL345_RANGE_8G);

  attachInterrupt(digitalPinToInterrupt(int2Pin), tapISR, RISING);
  
/* The following four parameters have to be set for tap application (single and double):
    1. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    2. Threshold in g
        It is recommended to not choose the value to low. 3g is a good starting point. 
    3. Duration in milliseconds (max 159 ms): 
        maximum time that the acceleration must be over g threshold to be regarded as a single tap. If 
        the acceleration drops below the g threshold before the duration is exceeded an interrupt will be 
        triggered. If also double tap is active an interrupt will only be triggered after the double tap 
        conditions have been checked. Duration should be greater than 10. 
    4. Latency time in milliseconds (maximum: 318 ms): minimum time before the next tap can be detected.
        Starts at the end of duration or when the interrupt was triggered. Should be greater than 20 ms.  
*/
  myAcc.setGeneralTapParameters(ADXL345_XY0, 3.0, 30, 100.0);
  myAcc.setInterrupt(ADXL345_SINGLE_TAP, INT_PIN_2); 
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are resposible activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); return the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/
void loop() {
  if ((millis() % 1000) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(tap == true) {
      //byte actTapSource = myAcc.getActTapStatus();
      //Serial.println(actTapSource, BIN);
      String axes = myAcc.getActTapStatusAsString();
      byte intSource = myAcc.readAndClearInterrupts();
      if(myAcc.checkInterrupt(intSource, ADXL345_SINGLE_TAP)){
        Serial.print("TAP at: ");
        Serial.println(axes);
      }
    
    delay(1000);
    myAcc.readAndClearInterrupts();
    tap = false;
  }
}

void tapISR() {
  tap = true;
}

Output of the Single Tap sketch

For the output below, I first tapped the module several times in the direction of the x-axis and then in the direction of the y-axis. By the way, the measurement output has no deeper meaning. That’s just to make it not so boring in between.

ADXL345 example sketch:
Output of ADXL345_single_tap.ino
Edition of ADXL345 single_tap.ino

Example sketch 11: ADXL345_double_tap.ino

For Double Tap detection, you first define the general parameters as for the Singles Taps. Then you set two more parameters for the Double Taps:

  • setAdditionalDoubleTapParameters(suppress, window)
    • suppress is true or false and determines whether the suppress bit is set. If the suppress bit is set, then a second tap within the latency of the first tap invalidates the Double Tap condition. All right? If not, then go to the data sheet. There is a graphic on page 29 which should make things clearer.
    • window sets the time window in which the second tap must take place to be considered a double tap. The maximum is 318 ms.

In the example sketch I activated both the Single and the Double Tap Interrupt. Since there can be two different interrupts, it is necessary to check which interrupt was triggered.

The purpose of Single and Double tap detection is to look for characteristic shocks or vibrations. However, this requires a lot of trying out. Just play with the parameters.

Output of the Double Tap sketch

This is what an output might look like:

ADXL345 example sketch:
Output of ADXL345_double_tap.ino
Output of ADXL345 double_tap.ino

In this example, I tapped the ADXL345 twice in quick succession in the direction of the x-axis.

FIFO Functions

The FIFO buffer is a kind of recording device for measured values. This allows you to track very fast events at a measurement frequency of 3200 Hz, for example. Or you can analyze what happens before, around or after a particular event (activity or tap).

The FIFO has space for up to 32 readings, i.e. 32 full x,y,z data sets. You can select the number. FIFO stands for “first in, first out”, i.e. the measured values that are stored first are read first. Reading of the FIFO works like normal reading of measured values. However, the data registers are not supplied with fresh readings, but from the FIFO.

There are four modes of operation:

  • Bypass: FIFO is disabled, which is the default. The measured values are provided in the data register and new measurements overwrite the old ones.
  • FIFO: you give the starting signal to fill the FIFO. If the buffer contains the number of values you have defined, the recording ends. The ADXL345 triggers the Watermark interrupt. It is a bit confusing that one of the FIFO modes is also called FIFO.
  • Stream: as the name suggests, values are permanently written to the buffer. When the FIFO is full, new values are pushed in and the oldest ones fall out. You define the end of the measured value recording.
  • Trigger: Measured values are permanently recorded as in Stream mode. If a selected event (= trigger, interrupt) occurs, only the number of values you have defined remains in the FIFO. Then, however, the FIFO is filled up with new readings up to 32. Then the process ends. So, you can set the middle of a measured value recording in the Trigger mode.

Example sketch 12: ADXL345_fifo_fifo.ino

The following new functions are used:

  • setFifoParameters(TriggerIntPin, number)
    • The TriggerIntPin only comes into play for the trigger mode. In this example, it doesn’t matter which parameter you choose.
    • number sets the maximum number of measured values in the FIFO.
  • setFifoModes(mode)
    • With mode, you set – guess what – the FIFO mode.

The setting of the FIFO mode is simple. The other settings that make sure that the whole thing works are a bit more complex. In particular, interrupts offer many possibilities for errors. It is important to follow the correct sequence.

A few notes for example:

  • I set the measuring frequency at 6.25 Hz quite low. With 32 measured values it takes about 5 seconds until the FIFO is full. Enough time to tilt the ADXL345 slowly, for example.
  • I deactivated all measurements until the beginning of the FIFO recording.
  • The measurements are started with a countdown. Of course, you can also take something different as a “starting shot”, for example an interrupt.
  • The Watermark interrupt is assigned to INT2. It signals the end of the measurement.
  • After this I output 34 x,y,z value triple. This is more than the FIFO buffer contains. I just want to show that nothing changes after the 32nd value.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Fifo Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_6_25);
  myAcc.setRange(ADXL345_RANGE_4G);

/* Stop the measure mode - no new data will be obtained */
  myAcc.setMeasureMode(false);
  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);

/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the threshold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_1) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/ 
  myAcc.setInterrupt(ADXL345_WATERMARK, INT_PIN_2); // Interrupt when FIFO is full
  
/* The following two FIFO parameters need to be set:
    1. Trigger Bit: not relevant for this FIFO mode.
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_1, 32);

/* You can choose the following FIFO modes:
    ADXL345_FIFO     -  you choose the start, ends when FIFO is full (at defined limit)
    ADXL345_STREAM   -  FIFO always filled with new data, old data replaced if FIFO is full; you choose the stop
    ADXL345_TRIGGER  -  FIFO always filled up to 32 samples; when the trigger event occurs only defined number of samples
                        is kept in the FIFO and further samples are taken after the event until FIFO is full again. 
    ADXL345_BYPASS   -  no FIFO
*/   
  myAcc.setFifoMode(ADXL345_FIFO);
}

void loop() {
  event = false; 
  
  countDown(); // as an alternative you could define any other event to start to fill the FIFO
  myAcc.readAndClearInterrupts();
  myAcc.setMeasureMode(true); // this is the actual start
  while(!event){}  // event = true means WATERMARK interrupt triggered -> means FIFO is full
  myAcc.setMeasureMode(false);
  Serial.println("FiFo full");
  
  for(int i=0; i<34; i++){ // this is > 32 samples, but I want to show that the values do not change when FIFO is full
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
  Serial.println("For another series of measurements, enter any key and send");
  
  while(!(Serial.available())){}
  Serial.read();
  Serial.println();
}

void eventISR() {
  event = true;
}

void countDown(){
  Serial.println("Move your ADXL345 to obtain interesting data");
  Serial.println();
  delay(1000);
  Serial.print("Fifo collection begins in 3, "); 
  delay(1000);
  Serial.print("2, "); 
  delay(1000);
  Serial.print("1, "); 
  delay(1000);
  Serial.println("Now!");
}

Output of the FIFO FIFO Sketch

For the output shown below, I slowly rotated the ADXL345 around the y-axis (i.e. the x and z values vary).

Output of ADXL345 fifo_fifo.ino
Output of ADXL345 fifo_fifo.ino

Example sketch 13: ADXL345_fifo_stream.ino

The example sketch does not use any new functions. But a few comments:

  • setFifoMode(ADXL345_STREAM) sets the stream mode.
  • As the event that ends the measurement recording, I have selected an Activity interrupt: 0.6 g on the x- or y-axis.
  • The 32 measured values before the event occurs are stored in the FIFO.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53  // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Stream Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

  myAcc.setDataRate(ADXL345_DATA_RATE_12_5);
  myAcc.setRange(ADXL345_RANGE_4G);

  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);

 /* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.6);
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);

/* The following two FIFO parameters need to be set:
    1. Trigger Bit: not relevant for this FIFO mode.
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_1, 32);
  myAcc.setFifoMode(ADXL345_STREAM);

  Serial.println("Waiting for Activity Event - I propose to slowly turn the ADXL345");
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible for activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); returns the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/

void loop() {
  if(event){
    myAcc.setMeasureMode(false);
    //byte actTapSource = myAcc.getActTapStatus();
    //Serial.println(actTapSource, BIN);
    String axes = myAcc.getActTapStatusAsString();
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("ACT at: ");
        Serial.println(axes);
    }
    printFifo();
    Serial.println("For another series of measurements, enter any key and send");
    while(!(Serial.available())){}
    Serial.read();
    Serial.println();
    Serial.println("Waiting for Activity Event");
  
    event = false;
    myAcc.readAndClearInterrupts();
    myAcc.setMeasureMode(true);
  }
}

void eventISR() {
  event = true;
}

void printFifo(){
  for(int i=0; i<32; i++){
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
}

Output of THE FIFO Stream Sketch

Again, I slowly turned the ADXL345 around the y-axis.

Issue of ADXL345 fifo_stream.ino
Issue of ADXL345 fifo_stream.ino

Example sketch 14: ADXL345_fifo_trigger.ino

And now to the last example sketch. Here, the recording of FIFO data is controlled by a trigger. For this, I come back to the following function:

  • setFifoParameters(TriggerIntPin, number)
    • TriggerIntPin is set to INT1 or INT2. An interrupt on the selected pin is the trigger for the FIFO recording. You have to select an interrupt that you assign to this pin. Again, I have selected the Activity interrupt.
    • number should be somewhere between 1 and 32. If you choose 32, that would be similar to Stream mode. If you choose 1, you might as well choose the FIFO FIFO mode. For this example, I chose 15.

The question arises as to how to detect the completion of the measurements. Basically, this would work with the Watermark interrupt. However, if we assign this to INT2, we may get confused with the Activity interrupt. And if we assign it to INT1, then there may be problems with the Data Ready interrupt, which, stupidly, cannot be turned off. A pragmatic solution would be a simple delay which length would have to be adapted to the measuring frequency. Fortunately, the number of values present in the FIFO can be queried. The getFifoStatus() function returns the content of the FIFO status register. Its bits 0 to 5 contain the number of values in the FIFO. If the bit 5 is set, 25= 32 readings are in the FIFO. This results in:

FIFO is full when (getFifoStatus() & 32) == true.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53  // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Trigger Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

  myAcc.setDataRate(ADXL345_DATA_RATE_12_5);
  myAcc.setRange(ADXL345_RANGE_4G);
 
  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);
  
/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.6);
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);

/* The following two FIFO parameters need to be set:
    1. Trigger Bit: the trigger is an interrupt at INT1 or INT2
        ADXL345_TRIGGER_INT_1 - Trigger is an interrupt at INT1
        ADXL345_TRIGGER_INT_2 - Trigger is an interrupt at INT2
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_2, 15);
  myAcc.setFifoMode(ADXL345_TRIGGER);

  myAcc.readAndClearInterrupts();
  event = false;

  Serial.println("Waiting for Activity Event");
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible for activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); returns the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/
void loop() {
  
  if(event){
    String axes = myAcc.getActTapStatusAsString();
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("Activity at: ");
        Serial.println(axes);
    }
    while(!(myAcc.getFifoStatus() & 32)){}
    printFifo();
    
    Serial.println("For another series of measurements, enter any key and send");
    while(!(Serial.available())){}
    Serial.read();
    Serial.println();
    myAcc.readAndClearInterrupts();
    myAcc.resetTrigger();
    Serial.println("Waiting for Activity Event");
    event = false;
  }
}

void eventISR() {
  event = true;
}

void printFifo(){
  for(int i=0; i<32; i++){
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
}

 

Output of the FIFO Trigger Sketch

For the output below, I rotated the ADXL345 around the y-axis until the activity interrupt was triggered and then turned it back. You can see that the measured value responsible for the Activity interrupt is in the middle of the FIFO values.

Output of ADXL345 fifo_trigger.ino
Output of ADXL345 fifo_trigger.ino

At this measurement frequency, this is just a gimmick. It becomes interesting if you change to high measuring frequencies and analyze a tap event in more detail, for example.  

Final words

Creating the library and example sketches was a pretty tough job due to the versatility of this component. I hope it was worth it and some people use the library. Bugs are not only not excluded in such a project, but almost inevitable. If you discover something or if you miss features, please get in touch. 

35 thoughts on “ADXL345 – The Universal Accelerometer – Part 2

  1. Thanks for the awesome library.

    I’ll explain my setup 1st.

    I need to configure the tap threshold. And then I am interested in sensing tap from 8 different adxl345 connected to esp32-s3.
    No additional info is needed on tap event.

    My hopefull approach is

    1. Configure set GeneralTapParameters of all 8 adxl345 via i2c

    2. Connect vcc, gnd and int2 to esp32-s3 (without connecting sda and scl) (vcc and gnd will be common but int2 will be connected to 8 different gpio pins of esp32-s3)

    3. Detect where tap came from based on which gpio has interrupt event.

    Is my approach right?

    1. Hi, if you don’t connect SDA and SCL, you won’t be able to communicate to the ADXL345 modules. So, how should you initiate the modules and set up the top interrupts. You can’t do this separate because the modules forget their settings when diconneczed from power. But even if you solve this issue, you stimmt need to clean the interrupts on the ADXL345 otherwise you won’t be able to detect new interrupts. With other words: all ADXL345 modules need to be connected via I2C. Which leads to the next question how to address 8 modules when only have two I2C-addresses. For this you can use an I2C multiplexer:

      https://wolles-elektronikkiste.de/en/tca9548a-i2c-multiplexer

      Regards, Wolfgang

  2. Hi Wolfgang, I’m really thankful for your very well documented post. The example sketch files are well explained and really easy to understand and configure.
    I’m using Lolin D1 mini board with ADXL345 to generate activity interrupts to wake-up the D1 mini from DeepSleep. I’m getting the changing g-values on the serial monitor as I move the ADXL but it’s not firing any interrupt. The pin connections are: 3v3–Vcc; Gnd–Gnd; D1–SCL; D2–SDA; D3–INT2. I used ICACHE_RAM_ATTR to load ISR in RAM as it was showing an error: “ISR not in IRAM”, which solved later. What more changes would I need to make the whole setup work?

    #include
    #include
    #define ADXL345_I2CADDR 0x53

    const int int2Pin = 3; //to receive interrupt on D3 pin
    volatile bool in_activity = false;
    ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

    void ICACHE_RAM_ATTR in_activityISR()
    {
    in_activity = true;
    }

    void setup()
    {
    Serial.begin(115200);
    Wire.begin(D2, D1);
    pinMode(int2Pin, INPUT);
    Serial.println(“ADXL345_Sketch – Activity and Inactivity Interrupts”);
    Serial.println();

    if (!myAcc.init())
    {
    Serial.println(“ADXL345 not connected!”);
    }

    myAcc.setActivityParameters(ADXL345_AC_MODE, ADXL345_XYZ, 0.5);

    myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);

    attachInterrupt(digitalPinToInterrupt(int2Pin), in_activityISR, RISING);
    }
    void loop()
    {
    if ((millis() % 1000) == 1) \
    {
    xyzFloat g = myAcc.getGValues();
    Serial.print(“x = “);
    Serial.print(g.x);
    Serial.print(” | y = “);
    Serial.print(g.y);
    Serial.print(” | z = “);
    Serial.println(g.z);
    }
    if(in_activity == true)
    {
    Serial.println(“ISR executed”);

    delay(1000);
    myAcc.readAndClearInterrupts();
    in_activity = false;
    }
    }

    1. Hi Rohitashva,
      first of all, thanks for the feedback. D3 is not Pin 3. If you wanted to use D3 as interrupt pin, you would need to write:
      const int int2Pin = D3; and not:
      const int int2Pin = 3;
      But D3 is not a good choice. It can cause problems when the board is reset.
      Take for example D5. Then it works.

      1. Thank you for your quick response. I tried it with D5 and even lowered the threshold to 0.2 but it’s still not triggering the interrupt 🙁

        1. That is strange. I took your code and only changed the int2pin to D5. I only had to add the libraries in the include statement (they go lost in the comment field, because it’s interpreted as HTML). I took a WEMOS D1 Mini board, and I chose “LOLIN(WEMOS) D1 R1 & mini” in the Arduino IDE. It’s only the interrupt that is not working? The acceleration values on the serial monitor are displayed correctly? Maybe try a pull down resistor on the Int2 to D5 line. But it should work without. Can measure the voltage on this line with a multimeter – is it low without interrupt as it should be?
          Maybe try to change the interrupt logic with myAcc.setInterruptPolarity(ADXL345_ACT_LOW);? Of course then you also have to change the attachInterrupt parameter from RISING to FALLING.
          I am a bit clueless…

          1. Yes, the acceleration values are displayed correctly. Its only the interrupt that is not working. Though strange enough, yesterday the interrupt worked and the ISR was triggered (for some angle/orientation) but now again its not working. I don’t know what’s going on, I tried the same code and even tried changing some threshold value and interrupt polarity as suggested but it doesn’t seem to work again.

            1. Maybe another myAcc.readAndClearInterrupts(); at the end of the setup could help. Just a gut feel. And I would try to attach a pull-down resistor (10 kilohms) to the interrupt line. I am not very optimistic, but you could try that quickly. And as proposed before, you could try to change the interrupt logic. In that case, I would attach a pull-up resistor to the interrupt line.

              1. Sorry for the late reply. I tried attaching a resistor as pull-down and pull-up both ways with myAcc.readAndClearInterrupts(); in the setup as suggested but it didn’t worked. Also tried changing the interrupt logic but no success so far. When connecting the D5 with GND/3v3, It does fires an interrupt but not when INT2/INT1 pin is connected to GND/3v3.
                I wanted to ask you if you also tried this code with D1 mini and is it working? Thank you!

                1. Strange, as I wrote before I took your code, only changed the int2pin to D5, added the libraries and it worked on a Wemos D1 mini board. I will think about potential causes. But I don’t have an idea right now.

                2. I have tried again this evening to see if this triggers any new ideas. It’s still working fine on my side. I am very motivated to identify the issue. There are not so many parameters and therefor it should be possible to identy what’s different on your and my side. But since this goes into the details I suggest we change the communication to e-mail. I’ll contact you.

    2. For all people who read this: The issue is solved. It was simply a hardware issue. With a new ADXL345 module, it worked!

  3. Thank you for this library. You have simplified everything for a beginner like me. I got the ADXL345 today, and I have interfaced it with the ESP32 D1 mini.

    Happy to say your code worked well. I have edited it to have a high sampling rate.

    I selected an ODR of 3200Hz. Does it mean I will have 3200 samples per second?

    I will be glad if you can throw more light or, most importantly, a code to show the number of samples per second.

    /***************************************************************************
    * Example sketch for the ADXL345_WE library
    *
    * This sketch shows how use SPI for the basic data sketch. Please apply the
    * same steps in the other sketches if you want to use SPI.
    *
    * Further information can be found on:
    * https://wolles-elektronikkiste.de/adxl345-teil-1 (German)
    * https://wolles-elektronikkiste.de/en/adxl345-the-universal-accelerometer-part-1 (English)
    *
    ***************************************************************************/

    #include
    #include
    #define CS_PIN 5 // Chip Select Pin
    bool spi = true; // flag that SPI shall be used

    /* There are two ways to create your ADXL345 object in SPI mode:
    * ADXL345_WE myAcc = ADXL345_WE(CS_PIN, spi) -> uses SPI, spi is just a flag, see SPI example
    * ADXL345_WE myAcc = ADXL345_WE(&SPI, CS_PIN, spi) -> uses SPI / passes the SPI object, spi is just a flag, see SPI example
    */
    ADXL345_WE myAcc = ADXL345_WE(CS_PIN, spi);

    void setup(){
    Serial.begin(2000000);
    //Serial.println(“ADXL345_Sketch – Basic Data”);
    if(!myAcc.init()){
    Serial.println(“ADXL345 not connected!”);
    }

    /* You can set the SPI clock speed. Default is 8 MHz. */
    myAcc.setSPIClockSpeed(4000000);

    /* Choose the data rate Hz
    ADXL345_DATA_RATE_3200 3200
    ADXL345_DATA_RATE_1600 1600
    ADXL345_DATA_RATE_800 800
    ADXL345_DATA_RATE_400 400
    ADXL345_DATA_RATE_200 200
    ADXL345_DATA_RATE_100 100
    ADXL345_DATA_RATE_50 50
    ADXL345_DATA_RATE_25 25
    ADXL345_DATA_RATE_12_5 12.5
    ADXL345_DATA_RATE_6_25 6.25
    ADXL345_DATA_RATE_3_13 3.13
    ADXL345_DATA_RATE_1_56 1.56
    ADXL345_DATA_RATE_0_78 0.78
    ADXL345_DATA_RATE_0_39 0.39
    ADXL345_DATA_RATE_0_20 0.20
    ADXL345_DATA_RATE_0_10 0.10
    */
    myAcc.setDataRate(ADXL345_DATA_RATE_3200);
    // delay(100);
    //Serial.print(“Data rate: “);
    //Serial.print(myAcc.getDataRateAsString());

    /* In full resolution the size of the raw values depend on the
    range: 2g = 10 bit; 4g = 11 bit; 8g = 12 bit; 16g =13 bit;
    uncomment to change to 10 bit for all ranges.
    */
    // myAcc.setFullRes(false);

    /* Choose the measurement range
    ADXL345_RANGE_16G 16g
    ADXL345_RANGE_8G 8g
    ADXL345_RANGE_4G 4g
    ADXL345_RANGE_2G 2g
    */
    myAcc.setRange(ADXL345_RANGE_2G);
    // Serial.print(” / g-Range: “);
    //Serial.println(myAcc.getRangeAsString());
    // Serial.println();

    /* Uncomment to enable Low Power Mode. It saves power but slightly
    increases noise. LowPower only affetcs Data Rates 12.5 Hz to 400 Hz.
    */
    // myAcc.setLowPower(true);
    }

    /* The LSB of the Data registers is 3.9 mg (milli-g, not milligramm).
    This value is used calculating g from raw. However, this is an ideal
    value which you might want to calibrate.
    */

    void loop() {
    //xyzFloat raw = myAcc.getRawValues();
    xyzFloat g = myAcc.getGValues();

    /* Serial.print(“Raw-x = “);
    Serial.print(raw.x);
    Serial.print(” | Raw-y = “);
    Serial.print(raw.y);
    Serial.print(” | Raw-z = “);
    Serial.println(raw.z);*/

    //Serial.print(“g-x = “);
    Serial.print((g.x)/0.10197);
    Serial.print(“,”);
    Serial.print(g.y/0.10197);
    Serial.print(“,”);
    Serial.println(g.z/0.10197);
    //Serial.println();

    // Wait for 1/3200 seconds (sampling period)
    delayMicroseconds(313);

    }

    1. Hi, yes, 3200 is the data rate in samples per second. But with the sketch above you won’t be able to display 3200 measured values in a second. The bottleneck is the slow Serial.print() function. You can test this: forget the ADXL345 for a moment and just write a short sketch in which repeat e.g. Serial.println(“Hi ilook”) in for-loop let’s say 1000 times. Measure the time via millis() before and after. And in your sketch you are even using 12 Serial.print() commands for one measured value. And also the SPI communication takes time. Maybe it’s possible to store 3200 samples per second on the flash of the ESP32, but I haven’t tried. But again: displaying 3200 samples per second – not possible.
      And finally, if you want to achieve a high data rate, then I would not apply a delay in loop(), but use the data ready interrupt.

      1. Thanks for your responses and suggestion.

        How do I use the data ready interrupt with the millis() to show the ? I really want to achieve high data rate. A code would be appreciated.

        Serial.print((g.x)/0.10197); is this right? i did that because I wanted the values to be between +/- 9.8 m/s^2

        1. Sorry, I have seen your code on data ready interrupt. Can I use this with SPI? I am using the sensor for vibration analysis.

          1. You can use all features with both I2C and SPI. Some lines at the top of the sketch need to be modified. There is one SPI example sketch. If you compare it with the corresponding I2C sketch you will see what needs to be changed and you can apply the same to the data ready interrupt example.

        2. Just determine the current time with millis() like this:
          unsigned long startingTime = millis();
          Then you read values let’s say 1000 times using a for construct:
          for(int i=0; i<1000; i++){
          Read values, print them, or whatever you want to do }
          And then:
          unsigned long timeNeeded = millis() – startingTime;
          That’s basically all.

          The other question: yes, if you want to turn g-values into m/s^2 then divide by 0.1097 or multiply by 9,81.

  4. Super great job!

    Just 2 questions…

    According datasheet, use of the 3200 Hz and 1600 Hz output data rates is only recommended with SPI communication rates greater than or equal to 2 MHz (datasheet->page15) Did you take this into account?

    I’m trying to guess which is the maximum number of readings that I can get with this module in one second…Could you shed some light on this?

    Thank you!

    1. Hi, first of all thanks for the feedback. You are right, you won’t be able to get the highest sample rates with I2C. Maybe I should mention this clearly. Not sure where the exact limit is. You can increase it by changing from 100 to 400 kHz using Wire.setClock(400000). I’ve implemented SPI, so you can change if you need. With SPI you should achieve the maximum as long as you don’t apply slow commands like Serial.print between the sample queries.

      1. Wolfgang, do you mean you can increase the I2C clock speed from 100 to 400 kHz using Wire.setClock(400000)?

          1. Wofgang,

            I find your website to be a very useful resource, regardless of minor issues such as the one I pointed out. I hope you continue contributing to the knowledge and understanding of the international electronics community with your educational postings.

            Chip
            Arizona, USA

  5. Do you have a simple sketch to wake a sleeping arduino with the adxl345. I have tried it a few different ways but keep coming up short.

    Wonderful job on documenting and explaining your libraries! this has been incredibly helpful!!

    1. Thanks for your kind feedback!

      And to answer your question: you just need to set up an interrupt on the ADXL side (activity, inactivity, freefall, etc) and an interrupt on the Arduino side. Connect the interrupt pin of the ADXL345 with the interrupt pin of the Arduino. Don’t forget to pull it down (or up for active low setting). Interrupts are a source of many potential bugs! In this specific example it’s very important to clear the interrupt before the Arduino is sent to sleep.

      Hope this helps. If you have questions regarding the code please ask!

      #include <avr/sleep.h>
      #include<Wire.h>
      #include<ADXL345_WE.h>
      #define ADXL345_I2CADDR 0x53
      const int int2Pin = 2;

      ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

      void setup(){
      Wire.begin();
      Serial.begin(9600);
      delay(100);
      if (!myAcc.init()) {
      Serial.println("ADXL345 not connected!");
      }
      pinMode(int2Pin, INPUT);
      attachInterrupt(digitalPinToInterrupt(int2Pin), activityISR, RISING);
      myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.5);
      myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);
      }

      void loop(){
      Serial.println("Going to sleep in 2 seconds...");
      delay(2000);
      myAcc.readAndClearInterrupts();
      set_sleep_mode(SLEEP_MODE_PWR_DOWN);
      sleep_mode();
      // sleeping now and waiting for interrupt
      Serial.println("I woke up!");
      }

      void activityISR(){

      }

      1. WOW! THANKS!

        I was definitely over complicating it! haha!

        This will be a wonderful place for me to start with.

        If I have more questions I will reach out.

        THANK YOU SO MUCH!!!

  6. Hello,
    How do I get the .h and .c files (library?):
    #include
    #include

    Thank you,
    Traian Morar

    1. Dear Traian,
      the easiest ways to get the library are:
      1) If you use the Arduino IDE, go to the library manager. There you search for ADXL345_WE. Then click on install.
      or:
      2) Go to GitHub with this link:
      https://github.com/wollewald/ADXL345_WE
      There is a green button (“Code”). Click on it and choose download as ZIP. Extract the Zip file in your library folder.

      If you still have problems, then google “install Arduino libraries ” or similar – There are lots of tutorials.
      Good luck and have fun!
      Best wishes, Wolfgang

  7. Thank you for a well written article. The explanation off the interrupts was very good and made me switch from the 5060 to this module.
    Which sample should I use to make the detection in one direction possible like the detection of braking and acceleration in combination with the interupts?

    Thank you,
    Peter

    1. Hi Peter,
      thanks for the feedback. Acceleration interrupts are covered in the example ADXL345_activity_inactivity_interrupt.ino. The key functions are setActivityParameters() and setInterrupt(). Activity interrupt is maybe not the ideal name, acceleration interrupt would be better, but I wanted to use the nomenclature of the data sheet. Have fun with your project!
      Wolfgang

  8. Hi Wolfgang, thank you so much for this post. Very informative and very clear. I have been trying to read the ADXL345 data sheet or should I say sheets for a long time now and I’ve looked at so many videos, instruction sites and forums that tell you exactly how easy it is but then do not quite show you how it’s done. Your style of instruction is very good and has helped me understand the ADXL345 a lot better. Thank you once again.

    1. Thank you so much – this comment is very motivating for me!

Leave a Reply

Your email address will not be published. Required fields are marked *