Single and double tap detection with the ADXL345 accelerometer

I’ve had an Arduino Uno for almost a year now, but seldom had the time to work with it, and when I did have time, I had too little experience to build anything interesting. Recently however, I’ve been spending a little more time with it and have become proficient enough in the language to construct fun and interesting systems. This project uses the ADXL345 3-axis accelerometer to detect single and double taps, and blink a dual color (red and green) LED depending on whether an sigle or double tap has been detected.

I chose this project to learn how to use two things: Inter-integrated circuit protocol (I2C) and interrupts on the Arduino (see I2C and Interrupts respectively).

The Ariduino has two designated I2C bus pins (data and clock) and the Arduino IDE includes an I2C library (Wire.h, which includes other protocols as well). Also, pins 2 and 3 on the Arduino Uno can be configured as hardware interrupts. The I2C device I use is the ADXL345 3-axis accelerometer (mine is similar to this, but has SPI interface pins as well as I2C pins). Get the ADXL345 datasheet here.

The hardware:

Arduino Uno Rev. 3

ADXL345 3-axis Accelerometer

Breadboard

9v power supply cable

I2C wires (5v, SCL, SDA, 0v)

Interrupt wires

Jumper wires and two-color LED

The Build:

Ground the LED through a 150ohm resistor (the middle pin is ground)

Connect the LED to pins 4 and 5 on the Arduino

Power the breadboard rails with the Arduino and attach the power supply cable

Attach the I2C wires. Connect the power and ground wires to the breadboard power rails and the SDA (data) and SCL (clock) lines to the designated SDA and SCL pins on the Arduino

Attach interrupt wires to pins 2 and 3 on the Arduino

Attach the ADXL345

Finished with 9v power supply connected

The Code:

Because of the large number of functions on the ADXL345, writing a library was necessary to keep everything looking clean. The library is not very well commented, but as the functions are extremely simple it should be easy to follow (if you’ve read the datasheet).

The library can be found here.

The sketch is well commented so it should be relatively easy to follow, but if you have questions about the sketch or library feel free to ask in the comments below.

The sketch can be found here (click to view, right-click and “Save as” to download).

Advertisements

27 thoughts on “Single and double tap detection with the ADXL345 accelerometer

  1. I would like to write the code with interrupts without using the library of the ADXL345. This gives me a better understanding in programming registers plus in the future I would like to switch over to attiny85 to reduce the actual footprint (which has less memory on board so size is key).This sensor is attached to a wafer in the semiconductor industry and moved through the machine. Therefore, size and weight is very important since this highly influences the measurement results.

    My goal: i want a certain activity threshold, such that when the wafer starts moving the sensor is activated and I can log all this data to my openlogger (sparkfun). Therefore I dont have to shift every new measurement to correspond when overlapping in MATLAB. Furthermore, I would like to obtain a constant sampling rate since i am converting my time domain measurement to frequency domain (fast fourier transform) to do a frequency analysis. So when I configure my ADXL345 to 800 Hz (max with I2C) or 3200 Hz (max with ISP) using the BW_RATE register the sensor is sampling at this speed, however I have read using the DATA_READY intterupt you can synchronize the arduino with the sensor such that if you set the BW to whatever value you want the arduino will write with the same sampling speed to the serial.

    I have made several functions and a calibration method, and now I would like to have those interrupts implemented, can you help me out? I think the code is pretty straightforward, although it is messy with a lot of copy paste work hehe.

    #include
    #include

    #define DEVICE (0x53) //ADXL345 device address
    #define TO_READ (6) //num of bytes we are going to read each time (two bytes for each axis)

    /*********** COMMUNICATION SWITCHES ***********/

    int Calibrate = 0;

    /*********** DEFINE VARIABELS ***********/

    byte buff[TO_READ] ; //6 bytes buffer for saving data read from the device
    char data[512]; //string buffer to transform data before sending it to the serial port
    int regAddress = 0x32; //first axis-acceleration-data register on the ADXL345

    int16_t x, y, z; //three axis acceleration data
    int16_t average_x, average_y, average_z;
    int16_t offset_x, offset_y, offset_z;
    int axis[3] ; //6 bytes buffer for saving data read from the device

    //const byte INT1 = 2; // INT1XM tells us when accel data is ready

    #define SAMPLESIZE 3200

    //ADXL345 Register Addresses
    #define DEVID 0x00 //Device ID Register
    #define THRESH_TAP 0x1D //Tap Threshold
    #define OFSX 0x1E //X-axis offset
    #define OFSY 0x1F //Y-axis offset
    #define OFSZ 0x20 //Z-axis offset
    #define DURATION 0x21 //Tap Duration
    #define LATENT 0x22 //Tap latency
    #define WINDOW 0x23 //Tap window
    #define THRESH_ACT 0x24 //Activity Threshold
    #define THRESH_INACT 0x25 //Inactivity Threshold
    #define TIME_INACT 0x26 //Inactivity Time
    #define ACT_INACT_CTL 0x27 //Axis enable control for activity and inactivity detection
    #define THRESH_FF 0x28 //free-fall threshold
    #define TIME_FF 0x29 //Free-Fall Time
    #define TAP_AXES 0x2A //Axis control for tap/double tap
    #define ACT_TAP_STATUS 0x2B //Source of tap/double tap
    #define BW_RATE 0x2C //Data rate and power mode control
    #define POWER_CTL 0x2D //Power Control Register
    #define INT_ENABLE 0x2E //Interrupt Enable Control
    #define INT_MAP 0x2F //Interrupt Mapping Control
    #define INT_SOURCE 0x30 //Source of interrupts
    #define DATA_FORMAT 0x31 //Data format control
    #define DATAX0 0x32 //X-Axis Data 0
    #define DATAX1 0x33 //X-Axis Data 1
    #define DATAY0 0x34 //Y-Axis Data 0
    #define DATAY1 0x35 //Y-Axis Data 1
    #define DATAZ0 0x36 //Z-Axis Data 0
    #define DATAZ1 0x37 //Z-Axis Data 1
    #define FIFO_CTL 0x38 //FIFO control
    #define FIFO_STATUS 0x39 //FIFO status

    uint16_t lastUpdate = 0; // used to calculate integration interval
    uint16_t now = 0; // used to calculate integration interval

    unsigned long lastTime;

    /******************** SETUP ADXL345 ********************/
    void setup() {
    Wire.begin(); // join i2c bus (address optional for master)
    Wire.setClock(400000); //I2C at 400Khz
    Serial.begin(250000); // start serial for output

    // disable ADC
    ADCSRA = 0;
    delay(2);
    // Set up interrupt pins as inputs:
    pinMode(INT1, INPUT);

    // attachInterrupt(0, interrupt, RISING);

    //Turning on the ADXL345
    writeTo(DEVICE, POWER_CTL, 0); // Wakeup
    writeTo(DEVICE, POWER_CTL, 16); // Auto_Sleep

    //Set data parameters
    writeTo(DEVICE, BW_RATE, 0x0C); //set bandwidth
    writeTo(DEVICE, DATA_FORMAT, 0x0); //set measurement range 2GArnhem

    // //Configure Interrupts
    // writeTo(DEVICE, INT_MAP, 1000000); //128 map data ready interrupt
    writeTo(DEVICE, INT_ENABLE, 0);// disable interrupt

    writeTo(DEVICE, INT_MAP, 0xE0); //Enable the single and double taps

    //Configure FIFO
    writeTo(DEVICE, FIFO_CTL, 10111111); //10 = stream 1=triggerevent to INT1 11111=32=how many entries are needed to trigger watermark

    //Enable interrupts
    // writeRegister(INT_ENABLE, 1000000); //128 enable data ready interrupt
    writeTo(DEVICE, INT_ENABLE, 128); //128 enable data ready interrupt

    //activate device in measurement mode
    writeTo(DEVICE, POWER_CTL, 8); // Measure
    readFrom(DEVICE, INT_SOURCE, 1, data); //Clear the interrupts from the INT_SOURCE register.

    delay(10);
    /****************** Calibrate ******************/
    if (Calibrate == 1) {

    gatherAverage(&average_x, &average_y, &average_z);

    /* we have to divide by four because the offset sensitivity
    is 15.6mg in the register vs 3.9mg of the 2g sensitivity. The OFSX, OFSY, and OFSZ registers are each eight bits and
    offer user-set offset adjustments in twos complement format
    with a scale factor of 15.6 mg / LSB (that is, 0x7F = +2 g).*/
    offset_x = 0 – round((double)average_x / 4);
    offset_y = 0 – round((double)average_y / 4);
    offset_z = 0 – round((double)(average_z – 256) / 4);

    //Set new axis offset
    writeTo(DEVICE, 0x1E, byte(offset_x)); // x-axis offset
    writeTo(DEVICE, 0x1F, byte(offset_y)); // y-axis offset
    writeTo(DEVICE, 0x20, byte(offset_z)); // z-axis offset
    }

    /****************** CHECK SETTINGS ******************/

    //printAllRegister();
    }

    void loop() {

    readFrom(DEVICE, regAddress, TO_READ, buff); //read the acceleration data from the ADXL345
    //each axis reading comes in 10 bit resolution, ie 2 bytes. Least Significat Byte first!!
    //thus we are converting both bytes in to one int

    x = (((int)buff[1]) << 8) | buff[0];
    y = (((int)buff[3]) << 8) | buff[2];
    z = (((int)buff[5]) << 8) | buff[4];

    //readFrom(DEVICE, DATAX0, sizeof(axis), (byte*)axis); //read the acceleration data from the ADXL345

    // we send the x y z values as a string to the serial port
    sprintf(data, "%d, %d, %d", x, y, z);
    Serial.println(data);
    // Serial.flush(); //If you want your code to wait for a serial string to be finished sending, you need to make use of Serial.flush().
    }

    /****************** READING BITS ******************/

    void setRegisterBit(byte regAdress, int bitPos, bool state) {
    byte _b;
    readFrom(DEVICE, regAdress, 1, &_b);
    if (state) {
    _b |= (1 << bitPos); // Forces nth Bit of _b to 1. Other Bits Unchanged.
    }
    else {
    _b &= ~(1 <> bitPos) & 1);
    }

    //ISR function

    //void interrupt(void){
    //sensor_update=1;
    //}

    /****************** FUNCITONS ******************/

    //Writes val to address register on device
    void writeTo(int device, byte address, byte val) {
    Wire.beginTransmission(device); //start transmission to device
    Wire.write(address); // send register address
    Wire.write(val); // send value to write
    Wire.endTransmission(); //end transmission
    }

    //reads num bytes starting from address register on device in to buff array
    void readFrom(int device, byte address, int num, byte buff[]) {
    Wire.beginTransmission(device); //start transmission to device
    Wire.write(address); //sends address to read from
    Wire.endTransmission(); //end transmission

    Wire.beginTransmission(device); //start transmission to device
    Wire.requestFrom(device, num); // request 6 bytes from device

    int i = 0;
    while (Wire.available()) //device may send less than requested (abnormal)
    {
    buff[i] = Wire.read(); // receive a byte
    i++;
    }
    Wire.endTransmission(); //end transmission
    }

    /****************** GATHERAVERAGE ******************/

    int16_t gatherAverage(int16_t * average_x, int16_t * average_y, int16_t * average_z)
    {
    // clear any existing offsets
    writeTo(DEVICE, 0x1E, byte(0)); // x-axis offset
    writeTo(DEVICE, 0x1F, byte(0)); // y-axis offset
    writeTo(DEVICE, 0x20, byte(0)); // z-axis offset

    int32_t accum_x = 0, accum_y = 0, accum_z = 0;

    for (uint16_t i = 0; i < SAMPLESIZE; ++i)
    {
    int16_t x, y, z;

    readFrom(DEVICE, regAddress, TO_READ, buff); //read the acceleration data from the ADXL345
    //each axis reading comes in 10 bit resolution, ie 2 bytes. Least Significat Byte first!!
    //thus we are converting both bytes in to one int

    x = (((int)buff[1]) << 8) | buff[0];
    y = (((int)buff[3]) << 8) | buff[2];
    z = (((int)buff[5]) << 8) | buff[4];

    accum_x += x;
    accum_y += y;
    accum_z += z;

    // sensor runs at 3200 Hz wait atleast 1/100th of a second before attempting another read or we will read the same value
    delayMicroseconds(1250);
    }

    // average values
    *average_x = accum_x / SAMPLESIZE;
    *average_y = accum_y / SAMPLESIZE;
    *average_z = accum_z / SAMPLESIZE;

    }

  2. Thanks for letting me know. Apparently Dropbox has killed public links. I have added new links to the files through Mega.

  3. Hi

    Great tutorial! One small comment though: the link to your sketch does not work anymore. Do you still have the code?

    Regards,
    David

  4. Hi Sami,

    I never looked into that, but if you look in the data sheet there should be a register that allows you to select 1g, 8g or 19g. You would write to the register as you would any other (see the code for how to use the Wire library).

    Regards,

    Barrett

  5. good job , thanks alot , just one easy question . How can I change the working of ADXL345 from 1g to 8g or 16 g , because my sensors in 2g the z-axis does not work .

  6. Hello pawel,

    It has been a very long time since I have worked on this project, but I recall that I would have this problem if there was a loose connection between the Arduino and ADXL345, or between the ADXL345 and the battery/power supply. Either the ADXL345 is not updating the X-Y-Z registers correctly (which is unlikely if the code is running correctly), or a wire is loose and the Wire.h function is timing out because a connection is broken (which returns a -1. Much more likely). Try checking your wires, especially the power supply.

    Let me know if you have any further questions or figure anything out.

    Regards,

    Barrett

  7. same story, I’ve got returning “-1 -1 -1″ in serial monitor, made connection same as on your pictures but adxl345 isn’t work, no taps are detected, have you any what could be wrong if connection is same as your shown on your pictures?

  8. Oliver,

    Yes, I do think an accelerometer could be used, but you may want to combine it with a gyroscope for more accurate and precise movement measurements. If you are intending to build a head-tracking system, you will definitely need both an accelerometer and gyroscope as well as some type of software filter to integrate the sensors’ data (such as a Kalman or complementary filter).

  9. Hi Geetanjali,

    “-1 -1 -1” is what the code returns if it fails to retrieve data from the ADXL345. This could be due to two things. The first is that the Arduino is trying to read from a register on the ADXL345 that is empty. This could be because the ADXL345 was not configured correctly and is therefore not updating its x, y, z data registers. The other possibility is that you have a broken connection between the Arduino and the ADXL345. A symptom of this would be the data updating very slowly (because of timeouts). My code worked perfectly for me, so, besides what I have mentioned, I do not know what your problem could be. Are you sure you’re using an ADXL345 and not another similar variant? If you can provide me with more information I may be able to be of more assistance.

  10. Hello Miles,

    I do not currently, but I would be interested to hear a proposition. Please contact me at my email (banderies [at] gmail [dot] com) if you are still interested. I check this website as much as possible, but email is much more convenient (and I will likely respond much more quickly to email).

  11. Hello Sir,
    The above code is not working with Arduino Uno board.
    I did single tap and double tap of Accelerometer i.e. ADXL345 but at the output , the data is-1 -1 -1 continuosly.
    If I am changing its orientation than also data is not changing and the output is -1 -1 -1.
    Please help me and thanks for sharing code.
    Regards,
    Geetanjali

  12. Hi. Can you write some code so the arduino can read raw values from the accelerometer?

  13. i dont think you need library for an adc bro. check out example at ardunio site for analog read

  14. Do you think there is a library for converting analog into digital for arduino uno and this type of accelerometer?

  15. what i want to detect is the movement of the patient’s head. my 1st idea is to try to have the initial position of the head. and then if any movement that occur more than the threshold, it will trigger an alarm. but i have no idea how to detect the initial position. so i decided to try the interrupt function. your single tap actually detecting how much of G?

  16. Oliver,

    It’s possible, but depending on exactly what type of movement you wish to detect, you may want to try a different method. Are you just trying to detect the start of a movement, or would you like to determine how much movement has occurred (i.e. angle of head rotation)?

  17. just to get an opinion.do you think tap function can be use to detect movement? im going to detect head movement for a paralyze person. because as what i have read from the datasheet. the treshold can be set.

  18. I don’t quite understand your question. I am using breadboard jumper wires. If you’re asking whether or not you can use ONLY I2C and NOT the interrupts, then yes, you can. As I mention in the video, I only used the interrupts to learn how they worked. You can read the interrupt registers in the ADXL345 via I2C to achieve the exact same operation.

  19. Would this be possible with breadboard jumper wire? Or do you have to use the I2C cables and the interrupt wires?

  20. Let me make sure I understand you correctly. You would like me to write a short piece of code that will do exactly what I did in the video, except without the use of my library? If so, are you interested in using interrupts, or just reading the tap-detection registers? Unless you want to practice using interrupts, or really need them, I would suggest just reading the tap registers as it is much easier. Plus, you mention you are interested in understanding I2C protocol, which is completely unrelated to using interrupts.
    I’d be happy to write you some code, although I’m back in school so it may take me awhile to get it to you. Before I do though, have you made any progress? If you’ve already written something that kind of works, I may be able to help you debug it. There is a certain sequence you must request information from the ADXL345 in order to use the tap-detection feature. I think I wrote comments regarding this sequence in the code, but I’m not sure.
    Just let me know if you’ve written anything, and whether you’d like to use interrupts or just read the tap registers, and I can get started.

  21. Hello there, great tutorial!
    I got and ADXL 345 breakout board and I would like to do the same set up like you. However I would like to write the whole code without using any libraries so I can understand how the I2C works. Is it possible for you to rewrite the code for the arduino into a new one without the library?
    That would be awsome becuase I can’t really figure out how to do it by my own. I know how to read values from the accelerometer thanks to this guy and his tutorial http://codeyoung.blogspot.se/2009/11/adxl345-accelerometer-breakout-board.html
    Please help me and thank you for taking your time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s