ADXL345 and Activity Interrupt

Hello everyone

I am currently trying to make use of an ADXL345 sensor to detect activity and I am also making use of the I2Cdev library for the device. I want the interrupt to take place on INT1. Below is some code:

// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
// is used in I2Cdev.h
#include "Wire.h"

// I2Cdev and ADXL345 must be installed as libraries, or else the .cpp/.h files
// for both classes must be in the include path of your project
#include "I2Cdev.h"
#include "ADXL345.h"

// class default I2C address is 0x53
// specific I2C addresses may be passed as a parameter here
// ALT low = 0x53 (default for SparkFun 6DOF board)
// ALT high = 0x1D
ADXL345 accel;

int16_t ax, ay, az;

#define LED_PIN 13 // (Arduino is 13, Teensy is 6)

void setup() {
    // join I2C bus (I2Cdev library doesn't do this automatically)
    Wire.begin();

    // initialize serial communication
    Serial.begin(9600);

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

    // verify connection
    Serial.println("Testing device connections...");
    Serial.println(accel.testConnection() ? "ADXL345 connection successful" : "ADXL345 connection failed");

    // configure LED for output
    pinMode(LED_PIN, OUTPUT);
    
    //Create an interrupt that will trigger when an activity is detected.
    attachInterrupt(0, activity, RISING);
    
    accel.setActivityAC(true);          // AC-Coupled
    accel.setInterruptMode(1);          // Set interrupts to active low
    accel.setActivityThreshold(20);     // Set the threshold
    accel.setIntActivityEnabled(true);  // Set Interrupt Activity as enabled
    accel.setActivityXEnabled(true);
    accel.setActivityYEnabled(true);
    accel.setActivityZEnabled(true);
    accel.setIntActivityPin(0);   // Create Activity interrupt on INT1
}

void activity(void){
    // Run this code if Activity Interrupt was triggered
    if(accel.getIntActivitySource() == 16){ // 16 is 0001 0000 which is D4 of INT_SOURCE Register
       digitalWrite(LED_PIN, HIGH);
    }
}

void loop() { 
    // read raw accel measurements from device
    accel.getAcceleration(&ax, &ay, &az);

    // display tab-separated accel x/y/z values
     Serial.print("accel:\t");
     Serial.print(ax); Serial.print("\t");
     Serial.print(ay); Serial.print("\t");
     Serial.println(az);
}

For some reason the Arduino freezes (when serial monitor open, can see it freeze up when you tap it or move it). Hope someone can point out what I am doing wrong.

Thank you

In the sketch you have INT0. INT0 or int.0 is digital pin 2 on a Arduino Uno board. http://arduino.cc/en/Reference/attachInterrupt Do you have the interrupt connected to digital pin 2 ?

Thanks for your reply. Oddly, I didn't get a notification to say I got a reply. Anyhow back to my problem....

Yes, I do have a jumper wire going from INT1 on the ADXL345 to Pin 2 of my Arduino Uno board. I know that Serial.print() make use of interrupts, but even if I comment that out and I shake the sensor around with its current settings, I expect the ADXL345 to generate an interrupt on INT1 and then my Arduino Uno should pick this up and turn on the LED on pin 13. But every time I shake/bump it and I make use of serial print (to see what is going on), it just freezes. And I think my settings are configured correctly.

I pretty much want to the ADXL345 sensor to detect when earthquake activity happen is happening, which in-turn should generate an interrupt and notify my Arduino.

The interrupt of the Serial Library and the interrupt of the Wire (I2C) Library and the interrupt of the ADXL345 should all work without problem.

I don't have an ADXL345 myself, so I can't test it. But I took a better look at your sketch, and I think I noticed something.

If you read the datasheet, the INT_SOURCE register does not only set the interrupt bit, but it could also set other bits. In the interrupt routine, you test for a value of '16', but perhaps you could test for that bit (regardless of other bits).

void activity(void){
    // Run this code if Activity Interrupt was triggered
    if( bitRead( accel.getIntActivitySource(), 4) { // bit 4 is D4 is Activity
       digitalWrite(LED_PIN, HIGH);
    }
}

I'm not sure about the interrupt. If you execute the interrupt service routine at the rising edge, I think the ADXL345 should be set in the default "active high" mode ? So why do you set it active low ?

I tried that just now and it still doesn't work :(

void activity(void){
    if(bitRead(accel.getIntActivitySource(), 4)  == 1){ // bit 4 is D4 is Activity
       digitalWrite(LED_PIN, HIGH);
    }
}

I thought I will add the "== 1" as bitRead will return either 0 or 1.

I had the Invert_Bit set as my understanding was, while the interrupt is not triggered, it will be at 0V, and when an interrupt occurs it will rise from 0V -> 3.3V and then back to 0V. Either way I tried it with the invert_bit set as active_high and active_low.

Here is the code for the .getIntActivitySource from the ADXL345.cpp file:

/** Get ACTIVITY interrupt source flag.
 * @return Interrupt source flag
 * @see ADXL345_RA_INT_SOURCE
 * @see ADXL345_INT_ACTIVITY_BIT
 */
uint8_t ADXL345::getIntActivitySource() {
    I2Cdev::readBit(devAddr, ADXL345_RA_INT_SOURCE, ADXL345_INT_ACTIVITY_BIT, buffer);
    return buffer[0];

And in the ADXL345.h file

#define ADXL345_INT_ACTIVITY_BIT    4

So my understanding from the above is, when I execute accel.getIntActivitySource() it will check only D4 and return an unit8 (range from 0 - 255), which is why I thought I will try and test for the value 16.

Also I noticed the datasheet says by INT_SOURCE register, "Other bits, and the corresponding interrupts, are cleared by reading the INT_SOURCE register". So how are you meant to test for what interrupt occurred if it clears the register when you read it?

I also noticed I didn't set the POWER_CTL register and the DATA_FORMAT register, so I added the code for that as well:

accel.setRange(0x01);               // Set into 4g range
accel.setMeasureEnabled(true);      // Set as measurement mode

But my biggest concern is how to know which interrupt occurred so I can execute different code for different interrupts.

Thank you for your help so far. If you need more info just let me know.

Could you try the example sketch that came with the library. If that is okay, perhaps you could make small steps towards your sketch.

Other bits are cleared, but with the first read, perhaps the data ready bit could be set. When I was reading the datasheet it was not clear if only D4 was set or perhaps also another bit. Since you only want to know D4, it is better to test only for that bit.

I did try writing it with the example sketch that came with the library, the example sketch only spits out x,y,z values, nothing too exciting.

Anyhow, after a few days, I think I might have found a solution. The following is code to distinguish between a single and double tap.

In setup() I did the following:

    // Set up single/double-tap
    accel.setTapThreshold(50);
    accel.setTapDuration(15);
    accel.setDoubleTapLatency(80);
    accel.setDoubleTapWindow(200);  
    accel.setTapAxisZEnabled(true);
    accel.setIntSingleTapEnabled(true);
    accel.setIntDoubleTapEnabled(true);
    accel.setIntSingleTapPin(0);
    accel.setIntDoubleTapPin(0);

    attachInterrupt(0, interrupt, RISING);

The following code was used to check what I actually got back and to see if any other pins were set.

void loop(){
int_singleTap = accel.getIntSingleTapSource();
Serial.print(int_singleTap, BIN); // Returns a value of 1000000 (0x40 or 64)

int_doubleTap = accel.getIntDoubleTapSource();
Serial.print(int_doubleTap, BIN); // Returns a value of 100000 (0x20 or 32)
}

Now that I know what I am getting back I changed the code in the main loop to:

void loop(){
int_singleTap = accel.getIntSingleTapSource();
    if (int_singleTap == 64){
      i = 1;
    }
    
    int_doubleTap = accel.getIntDoubleTapSource();
    if (int_doubleTap == 32){
      i = 2;
    }
}

Now as we can see from the above I set a different variable inside the if-statements, this variable will be used to help determine what code to execute within the actual interrupt code.

void interrupt(void){
  if (i == 1){
    Serial.println("Single Tap");
    i = 0;
  }
  
  if (i == 2){
    Serial.println("Double Tap");
    i = 0;
  }
}

Note that int_singleTap, int_doubleTap and i are global variables declared at the top of the sketch. The values just need to be fine tuned now.

I guess another way to distinguish between interrupts is to trigger INT1 and INT2 independently on the ADXL345 and make use of interrupt 0 and interrupt 1 of the UNO. Like in this sketch:

http://barrettsprojects.wordpress.com/2012/09/15/tap-and-double-tap/

I don't know the ADXL345 or the library that well, but I'm glad you have it working. If you got this far, I think you should also be able to use INT1 and INT2.

When using a variable in both the loop() and in an interrupt routine, you better make those variable 'volatile' and 8-bit. You can use 'byte' or 'char' or 'boolean'.

If you use more than 8-bit you have to disable the interrupts if that variable is changed in the loop() function. Writing a single byte can not be interrupted (read 'broken') by an interrupt. So a single byte makes it easier.

In you code you have the variable 'i' to pass data between the loop() code and the interrupt service routine. I think you better use something like: volatile byte tapID;

Thanks for the tip, I just changed those variables to volatile. They are all currently declared as uint8_t.

Thank you for all your help, it is much appreciated.