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

ADXL345_WE myAcc(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;

ADXL345_WE myAcc(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;

ADXL345_WE myAcc(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;

ADXL345_WE myAcc(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;

ADXL345_WE myAcc(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;

ADXL345_WE myAcc(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. 

Leave a Reply

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