ADXL345 Interfacing with ESP32 (Single and Double Tap Detection) using NuttX RTOS

Welcome to EmbeTronicX, Embedded Tutorials World. This article is a continuation of the series on NuttX RTOS tutorials using the ESP32 Dev board and carries the discussion on NuttX RTOS and their usage. The aim of this series is to provide easy and practical examples that anyone can understand. In our previous tutorial, we saw how to read the X, Y, Z-axis data from the ADXL345 Accelerometer. In this tutorial, we are going to see ADXL345 Interfacing with ESP32 using the NuttX RTOS I2C and detect the Single tap and Double tap.

ADXL345 Interfacing with ESP32 using the NuttX RTOS I2C

Prerequisites

To continue with this tutorial, you must have configured the ESP32 toolchain and compiled the NuttX RTOS. If you aren’t configured anything yet, we suggest you to set up the esp32 using the below-given tutorial.

Hardware Required

  • ESP32 Dev board
  • Laptop machine (Host System)
  • ADXL345 Accelerometer sensor

We have covered the basics of the ADXL345 accelerometer in our previous tutorial itself. So, I just don’t want to waste your time by explaining it again. Let’s straightaway getting into the topic.

In this tutorial, we are mainly focusing on Single tap and Double-tap detection using the ADXL345 accelerometer.

Single Tap and Double Tap

We know already about the single tap and double-tap. Basically, we are using these features on our mobile and other electronics gadgets. Normally, I do double tap to wake up the screen in my mobile phone and I do a single tap to wake up my screen in my Smartwatch. So, we can create many use cases by using the single tap and double-tap. All are detecting the single and double-tap using the acceleration and vibration.

ADXL345 has an inbuilt single tap and double-tap detection. In our next upcoming chapter, we will see how to enable and use that.

How to enable Single tap and Double-tap in ADXL345?

In order to enable the Single tap and Double-tap, you have to, Configure the ADXL345 first. Then enable the Single tap and double-tap interrupt.

Configure the ADXL345

Set Threshold

The THRESH_TAP (0x1D) register is eight bits and holds the threshold value for tap interrupts.  We need to set the value 0 to 255 based on our needs. The scale factor is 62.5 mg/LSB which means, each value increments by 1 in this register, adds 62.5mg. For example, If you set this register to 10, the threshold value is 625mg. If you set 255 (0xFF), then the threshold value is 16g.

The magnitude of the tap event is compared with the value in THRESH_TAP for normal tap detection.

In this example, we are going to set the threshold as 16g. So, the value has to be 255. This means that you need at least 16g acceleration in X, Y, Z-AXIS in order for the tap to qualify as TAP.

Example
ETX_I2C_Write( 0x1D, 255);

Set Duration

The DUR (0x21) register is eight bits and contains an unsigned time value representing the maximum time that an event must be above the THRESH_TAP threshold to qualify as a tap event. The scale factor is 625 µs/LSB. A value of 0 disables the single tap/double-tap functions.

In this example, we are going to set the threshold as 150ms. So, the value has to be 240. (240 * 625µs = 150ms).

Example
ETX_I2C_Write( 0x21, 240);

Set Latency

The LATENT (0x22) register is eight bits and contains an unsigned time value representing the wait time from the detection of a tap event to the start of the time window (defined by the window register) during which a possible second tap event can be detected. The scale factor is 1.25 ms/LSB. A value of 0 disables the double-tap function.

In this example, we are going to set the threshold as 175ms. So, the value has to be 140. (140 * 1.25ms = 175ms).

Example
ETX_I2C_Write( 0x22, 140);

Set Window

The WINDOW (0x23) register is eight bits and contains an unsigned time value representing the amount of time after the expiration of the latency time (determined by the latent register) during which a second valid tap can begin. The scale factor is 1.25 ms/LSB. A value of 0 disables the double-tap function.

In this example, we are going to set the threshold as 318ms. So, the value has to be 255. (255 * 1.25ms = 318ms).

Example
ETX_I2C_Write( 0x23, 255);

Enable the Single tap and Double-tap

Till now, we have configured the ADXL345 Tap parameters. Now let’s enable the tap feature.

  • Enable Tap functionality in X, Y, Z-axis
  • Enable the Tap interrupts

Enable Tap functionality in X, Y, Z-axis

The TAP_AXES (0x2A) register is defined like below.

D7 D6 D5 D4 D3 D2 D1 D0
0 0 0 0 Suppress TAP_X enable TAP_Y enable TAP_Z enable

Where,

Suppress Bit – Setting the suppress bit, suppresses double-tap detection if acceleration greater than the value in THRESH_TAP is present between taps.

TAP_x Enable Bits – A setting of 1 in the TAP_X enable, TAP_Y enable, or TAP_Z enable bit enables x-, y-, or z-axis participation in tap detection. A setting of 0 excludes the selected axis from participation in tap detection.

In this example, we are going to enable tap detection in all the X, Y, Z-axis. So, we are setting it to 0x07.

Example
ETX_I2C_Write( 0x2A, 0x07);

Enable the Tap interrupts

We are at the last step. We have enabled everything. Finally, we have to enable the Tap interrupts. To do that, we have to use the INT_ENABLE (0x2E) register.

D7 D6 D5 D4 D3 D2 D1 D0
DATA_READY SINGLE_TAP DOUBLE_TAP Activity Inactivity FREE_FALL Watermark Overrun

Setting bits in this register to a value of 1 enables their respective functions to generate interrupts, whereas a value of 0 prevents the functions from generating interrupts.

In this example, we are interested in  SINGLE_TAP and DOUBLE_TAP. So, write 0x60 to this register.

Example
ETX_I2C_Write( 0x2E, 0x60);
GPIO Interrupt

Note: If you enable the interrupt in INT_ENABLE (0x2E) register, that won’t toggle you the INT1 or INT2 pin. If you want the pin to toggle, then you have to use the INT_MAP (0x2F) register. That register defined like below.

D7 D6 D5 D4 D3 D2 D1 D0
DATA_READY SINGLE_TAP DOUBLE_TAP Activity Inactivity FREE_FALL Watermark Overrun

Any bits set to 0 in this register send their respective interrupts to the INT1 pin, whereas bits set to 1 send their respective interrupts to the INT2 pin. All selected interrupts for a given pin are OR’ed.

Read the single tap and double tap interrupts

In this example, we are not going to use the GPIO interrupt (INT1 or INT2 pin). Instead, we are going to read the INT_SOURCE (0x30) register. That register defined like below.

D7 D6 D5 D4 D3 D2 D1 D0
DATA_READY SINGLE_TAP DOUBLE_TAP Activity Inactivity FREE_FALL Watermark Overrun

Bits set to 1 in this register indicate that their respective functions have triggered an event, whereas a value of 0 indicates that the corresponding event has not occurred. The DATA_READY, watermark, and overrun bits are always set if the corresponding events occur, regardless of the INT_ENABLE register settings, and are cleared by reading data from the DATAX, DATAY, and DATAZ registers. The DATA_READY and watermark bits may require multiple reads. Other bits, and the corresponding interrupts, are cleared by reading the INT_SOURCE register.

In this example, we are going to read this register and check whether the 5th bit or 6th bit is set or not.

Example
//Read the interrupt source
ret = ETX_I2C_Read( 0x30, &intr, 1 );

if( intr & DT_INTERRUPT )
{
  printf("Double Tap Detected!!!\r\n");
}
else if( intr & ST_INTERRUPT )
{
  printf("Single Tap Detected!!!\r\n");
}

That’s all. We have enough food on our plates. Let’s start eating it.

We are just reusing the previous application code and adding the single tap and double tap functionalities.

Connection Diagram – ADXL345 Interfacing with ESP32

In this example, we have used the I2C1. You can also use the I2C0. But make sure you are connecting to the proper SCL and SDA. Refer to the below connection diagram.

  • SDA – Pin 25
  • SCL –  Pin 26
  • Vcc –  3.3V
  • GND – Ground

ESP32 ADXL345 Connection

Concept of the Driver and Application

In this ADXL345 Interfacing with ESP32 tutorial, we are not going to write any I2C driver as we did for the GPIO driver. Instead, we are going to utilize the existing, well-written I2C driver. So, we are only going to write the I2C application.

The concept is very simple. We are configuring the ADXL345 and enable the single tap and double-tap. Once it is enabled, then we are reading the interrupt source register to check whether the single or double-tap interrupt bit is set or not.

Writing I2C Application

In application, I am going to create a task called “etx_i2c_accel_task” using the task_create() function. Then in that task,

  • Open the I2C driver (/dev/i2c1).
  • Initialize the ADXL345 (Put the ADXL345 into measurement mode, then turn off the interrupts).
  • Configure the Single Tap and Double-tap.
  • Read the INT_SOURCE register and check any tap events.
  • Close the driver if any error.

I am creating a new file called etx_i2c_accel_tap_app.c under apps/examples/etx_i2c (This directory has been created in the previous tutorial).

etx_i2c_accel_tap_app.c

[Get this code from GitHub]

/****************************************************************************
 * examples/etx_i2c/etx_i2c_accel_tap_app.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <nuttx/i2c/i2c_master.h>

#include "etx_i2c_accel_app.h"

#ifdef CONFIG_EXAMPLES_ETX_I2C_ACCEL_TAP


/****************************************************************************
 * Private Data
 ****************************************************************************/
static int fd;

/****************************************************************************
 * Private Functions
 ****************************************************************************/
/****************************************************************************
 * Name: ETX_I2C_Write
 *
 * Details : This function writes the 1byte data to the slave device
 ****************************************************************************/
static int16_t ETX_I2C_Write( uint8_t reg, uint8_t value )
{
  int16_t ex;  
  struct  i2c_msg_s       i2c_msg;
  struct  i2c_transfer_s  i2c_transfer;  
  uint8_t txbuffer[2];

  /* Setup to the data to be transferred.  Two bytes:  The ADXL345 register
   * address followed by one byte of data.
   */

  txbuffer[0] = reg;
  txbuffer[1] = value;

  /* Setup 8-bit ADXL345 address write message */

  i2c_msg.addr   = ADXL345_ADDRESS;
  i2c_msg.flags  = 0;
  i2c_msg.buffer = txbuffer;
  i2c_msg.length = 2;
  i2c_msg.frequency = 400000;  /* 400K bsp */
  
  i2c_transfer.msgv = &i2c_msg;
  i2c_transfer.msgc = 1;
  
  ex = ioctl(fd, I2CIOC_TRANSFER, (unsigned long)(uintptr_t)&i2c_transfer);
  
  if( ex < 0 )
  {
      printf("ETX_I2C:Write Error : %d\n", ex);
  }
  
  return( ex );
}

/****************************************************************************
 * Name: ETX_I2C_Read
 *
 * Details : This function reads the data from the slave device
 ****************************************************************************/
static int16_t ETX_I2C_Read( uint8_t reg, uint8_t* value, int16_t len )
{
  int16_t ex;
  struct  i2c_msg_s       i2c_msg[2];
  struct  i2c_transfer_s  i2c_transfer;
  
  // Write the register address first
  i2c_msg[0].addr   = ADXL345_ADDRESS;
  i2c_msg[0].flags  = 0;
  i2c_msg[0].buffer = &reg;
  i2c_msg[0].length = 1;
  i2c_msg[0].frequency = 400000;  /* 400K bsp */

  // Read the data
  i2c_msg[1].addr   = ADXL345_ADDRESS;
  i2c_msg[1].flags  = I2C_M_READ;
  i2c_msg[1].buffer = value;
  i2c_msg[1].length = len;
  i2c_msg[1].frequency = 400000;  /* 400K bsp */
  
  i2c_transfer.msgv = i2c_msg;
  i2c_transfer.msgc = 2;
  
  ex = ioctl(fd, I2CIOC_TRANSFER, (unsigned long)(uintptr_t)&i2c_transfer);
  
  if( ex < 0 )
  {
      printf("ETX_I2C:Read Error : %d\n", ex);
  }
  
  return( ex );
}

/****************************************************************************
 * Name: ETX_ADXL345_Init
 *
 * Details : This function Initializes the ADXL345
 ****************************************************************************/
static int16_t ETX_ADXL345_Init( void )
{
  int16_t ex;
  uint8_t dev_id;
  
  // Reset all power settings
  ex = ETX_I2C_Write( ADXL345_RA_POWER_CTL, 0x00);
  
  if( ex >= 0 )
  {
    // Read the devId
    ex = ETX_I2C_Read( ADXL345_RA_DEVID, &dev_id, 1);
  }
  
  // Check the devId
  if( dev_id == 0xE5 )
  {
    printf("Device ID : 0x%X (Success!!!)\r\n", dev_id);
    
    //Set Output Rate to 100 Hz
    ex = ETX_I2C_Write( ADXL345_RA_BW_RATE, 0x0A);
    
    if( ex >= 0 )
    {
      // ADXL345 into measurement mode
      ex = ETX_I2C_Write( ADXL345_RA_POWER_CTL, 0x08);
    }
    
    if( ex >= 0 )
    {
      // Disable Interrupts
      ex = ETX_I2C_Write( ADXL345_RA_INT_ENABLE, 0x00 );
    }
  }
  
  return( ex );
}

/****************************************************************************
 * Name: ETX_ADXL345_SingleDoubleTapEn
 *
 * Details : This function enables the single and double tap functionality
 ****************************************************************************/
static int16_t ETX_ADXL345_SingleDoubleTapEn( void )
{
  int16_t ex;
  
  // Enables the double tap in X, Y, Z axis (0b00000111)
  ex = ETX_I2C_Write( ADXL345_RA_TAP_AXES, 0x07);
  
  if( ex >= 0 )
  {
    // Setting threshold
    ex = ETX_I2C_Write( ADXL345_RA_THRESH_TAP, DT_THRESHOLD);
  }
  
  if( ex >= 0 )
  {
    // Setting duration
    ex = ETX_I2C_Write( ADXL345_RA_DUR, DT_DURATION);
  }
  
  if( ex >= 0 )
  {
    // Setting latency
    ex = ETX_I2C_Write( ADXL345_RA_LATENT, DT_LATENT);
  }
  
  if( ex >= 0 )
  {
    // Setting window
    ex = ETX_I2C_Write( ADXL345_RA_WINDOW, DT_WINDOW);
  }
  
  if( ex >= 0 )
  {
    // Enable Single and Double Tap Interrupt
    ex = ETX_I2C_Write( ADXL345_RA_INT_ENABLE, DT_INTERRUPT | ST_INTERRUPT );
  }
  
  return( ex );
}

/****************************************************************************
 * Name: etx_i2c_accel_task
 ****************************************************************************/

static int etx_i2c_accel_task(int argc, char *argv[])
{
  int     ret = 0;
  uint8_t intr;
    
  printf("ETX_I2C_ACCEL: Task Starting\n");
  
  do
  {
    fd = open( ETX_I2C_DRIVER_PATH, O_WRONLY);
    if( fd < 0 )
    {
      printf("ETX_I2C_ACCEL:ERROR - Failed to open %s: %d\n",
                                                     ETX_I2C_DRIVER_PATH, errno);
      ret = -1;
      break;
    }
    
    ret = ETX_ADXL345_Init();
    if( ret < 0 )
    {
      break;
    }
    
    ret = ETX_ADXL345_SingleDoubleTapEn();
    if( ret < 0 )
    {
      break;
    }
    
    while( ret >= 0 )
    {
      //Read the interrupt source
      ret = ETX_I2C_Read( ADXL345_RA_INT_SOURCE, &intr, 1);
      
      if( intr & DT_INTERRUPT )
      {
        printf("Double Tap Detected!!!\r\n");
      }
      else if( intr & ST_INTERRUPT )
      {
        printf("Single Tap Detected!!!\r\n");
      }
      
      usleep(1);
    }
    
  }while( false );

  close(fd);
  printf("ETX_I2C_ACCEL: Task finishing\n");
  return EXIT_FAILURE;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  int ret;
  
  printf("ETX_I2C_ACCEL: Starting the Application\n");

  
  ret = task_create( "ETX_I2C_ACCEL",                         // Task Name
                     CONFIG_EXAMPLES_ETX_I2C_ACCEL_PRIORITY,  // Task priority
                     CONFIG_EXAMPLES_ETX_I2C_ACCEL_STACKSIZE, // Task Stack size
                     etx_i2c_accel_task,                      // Task function
                     NULL
                   );
  if (ret < 0)
  {
    int errcode = errno;
    printf("ETX_I2C_ACCEL: ERROR: Failed to start ETX I2C Accel task: %d\n",
                                                                       errcode);
    return EXIT_FAILURE;
  }
  
  return EXIT_SUCCESS;
}

#endif //#ifdef CONFIG_EXAMPLES_ETX_I2C_ACCEL_TAP

We have modified the Makefile and Kconfig also. Please check this commit or GitHub.

Building the driver and app

Let’s build our app and the I2C driver together. I have created the build.sh shell script to combine all the required steps into one file.

build.sh

This build.sh has been added to the nuttx/ directory. You can get the build.sh from GitHub.

# !/bin/bash

# A simple bash script to build the NuttX RTOS

#export ESP32 env
. $HOME/esp/esp-idf/export.sh


if [ $1 -eq 1 ]
then
  #distclean
  echo "Dist clean"
  make distclean
  
  #ESP32 configure
  echo "configuring ESP32 with NuttX RTOS"
  ./tools/configure.sh esp32-devkitc:nsh
fi

#menuconfig
make menuconfig

#make and flash
make download ESPTOOL_PORT=/dev/ttyUSB0

The above script has one argument. If you pass 1, then it does distclean and initializes the board configs. If you pass other than 1, then it does only the make menuconfig and make.

I don’t want to do the distclean. So, just building the NuttX. Run the below command in NuttX/nuttx directory.

. ./build.sh 0

When you run the above command, you should get the menuconfig window. In that menuconfig, we have to enable the i2cdriver and the etx_i2c_accel_appapplication. Let’s do that.

Just navigate to System Type —> ESP32 Peripheral Selection —> and check (enable) the I2C1 like the below image.

I2C enable NuttX RTOS

After that, let’s enable the I2C driver by navigating to Device drivers —>enable the I2C Driver Support. And go to that menu and enable the I2C Character Driver like the below image.

I2C driver Enable NuttX RTOS

That’s from the driver. Let’s enable the Application that we have written earlier in this tutorial. Navigate to Application Configuration —> Examples —> check (enable) the EmbeTronicX I2C app examples (ADX345 Accelerometer) —> and also check (enable) the EmbeTronicX ADXL345 Accelerometer Tap detection like the below image.

ADXL345 Interfacing with ESP32

That’s all. Let’s build the NuttX now.

Save and Exit the menuconfig and let it build. Once it is trying to connect, hold the BOOT button. Then it will start flashing.

Once you build and flashed it, you connect the device using the picocom using the picocom -b 115200 /dev/ttyUSB0 command.

Now you will get the NuttShell (nsh). Enter the ? or help command. You will get the etx_i2c_adxl345_tap app. Now run the etx_i2c_adxl345_tap app.

Output – ADXL345 Interfacing with ESP32

Once the system is booted, then run the etx_i2c_adxl345_tap application. Then do the single tap and double-tap. You should get the output like below.

nsh> etx_i2c_adxl345_tap
ETX_I2C_ACCEL: Starting the Application
ETX_I2C_ACCEL: Task Starting
nsh> Device ID : 0xE5 (Success!!!)
Single Tap Detected!!!
Single Tap Detected!!!
Double Tap Detected!!!

Refer to the below video for a demonstration.

In this ADXL345 Interfacing with ESP32 tutorial, we just focused on Single tap and double-tap. You can explore the other events like Activity, Inactivity, and Freefall by yourself or comment below if you want us to post those tutorials.

In our next tutorial, we will interface the SSD1306 SPI OLED Display with ESP32 using NuttX RTOS.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials

Reference

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Table of Contents