Using ADXL345 with ESP32: Digital Spirit Level Project

A digital spirit level measures roll and pitch angles in real time using an accelerometer or gyroscope. In this project, we will use an ESP32 microcontroller and an ADXL345 sensor to make it.

Watch the video tutorial below:

How does this spirit level work?

The ADXL345 accelerometer measures real-time roll and pitch angles. It also detects orientation changes and provides this data to the ESP32.

The ESP32 uses this data and displays the angles on an OLED screen, along with a moving circle to graphically indicate the object’s orientation. This smart bubble level simplifies tasks like object leveling by providing accurate angle measurements.

Components required

  • ESP32
  • ADXL345 accelerometer sensor
  • 0.96-inch OLED display
  • Breadboard

Introduction to ADXL345 accelerometer sensor

The ADXL345 is a flexible 3-axis accelerometer sensor that measures acceleration in three dimensions precisely. It has high resolution (up to 13-bit), a wide range of sensitivity options, and low power consumption, making it perfect for motion, tilt, and vibration detection in a variety of applications.

This sensor can communicate through I2C or SPI so it is suitable for use in microcontroller-based projects.

Pinout of ADXL345

The pin diagram of the ADXL345 sensor is given below.

Pin namePin description
GNDGround pin
VCCPower supply (3V to 6V)
CSChip select pin
INT1Interrupt 1 output pin
INT2Interrupt 2 output pin
SDOSerial data output pin
SDASerial data input & output
SDLSerial communication clock
Pin description of an ADXL345 accelerometer sensor

Circuit diagram

The figure below shows the circuit diagram of the project.

Circuit diagram of a spirit level
Circuit diagram of the spirit level
OLED PinESP32 Pin
VCC3V3 Pin
GNDGND of ESP32
SDAGPIO21
SCLGPIO22
OLED connections With ESP32

ADXL PinESP32 Pin
VCC3V3 Pin
GNDGND of ESP32
SDAGPIO21
SCLGPIO22
ADXL345 Connections With ESP32

Physical connections

The project is assembled on a breadboard, this is how it looks:

Spirit-level circuit assembled on a breadboard

Program

Copy and paste the program given below to your Arduino IDE. Before uploading the program, select the correct board and port.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_ADXL345_U.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_ADXL345_Unified accelerometer = Adafruit_ADXL345_Unified(12345);

void setup() {
  Serial.begin(115200);

  if (!accelerometer.begin()) {
    Serial.println("Could not find a valid ADXL345 sensor, check wiring!");
    while (1);
  }

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;) ;
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Spirit Level");
  display.display();
  delay(2000);
}

void loop() {
  sensors_event_t event;
  accelerometer.getEvent(&event);

  float roll = atan2(event.acceleration.x, event.acceleration.z) * 180.0 / PI;
  float pitch = atan2(event.acceleration.y, event.acceleration.z) * 180.0 / PI;

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Roll: ");
  display.println(roll);
  display.print("Pitch: ");
  display.println(pitch);

  display.drawLine(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2, SSD1306_WHITE);
  display.drawLine(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, SSD1306_WHITE);

  int bubbleX = map(roll, -90, 90, 0, SCREEN_WIDTH);
  int bubbleY = map(pitch, -90, 90, 0, SCREEN_HEIGHT);
  display.drawCircle(bubbleX, bubbleY, 3, SSD1306_WHITE);

  display.display();
  delay(100);
}

Program explanation

Library includes:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_ADXL345_U.h>

The code includes several libraries required for the project:

Wire.h: I2C communication library.

Adafruit_GFX.h: Graphics library for drawing on the display.

Adafruit_SSD1306.h: OLED display control library.

Adafruit_ADXL345_U.h: Library for interfacing with the ADXL345 accelerometer.

Constant definitions:

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Constants are defined for the width, height, and reset pin of the OLED display. These values are used to configure the display.

Display and accelerometer object initialization:

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_ADXL345_Unified accelerometer = Adafruit_ADXL345_Unified(12345);

Instances of the Adafruit_SSD1306 and Adafruit_ADXL345_Unified classes are generated. The display object is used to control the OLED display, while the accelerometer object is used to communicate with the ADXL345 accelerometer.

Setup function:

void setup() {
  Serial.begin(115200);

  if (!accelerometer.begin()) {
    Serial.println("Could not find a valid ADXL345 sensor, check wiring!");
    while (1);
  }

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;) ;
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Spirit Level");
  display.display();
  delay(2000);
}
  • For debugging purposes, serial communication is enabled.
  • The code checks if the accelerometer is detected and ready to use. If not, it prints an error message and enters an infinite loop.
  • The OLED display is initialized, and if initialization fails, it enters an infinite loop.
  • The OLED display gets cleared, and some initial text (“Spirit Level”) is displayed. Then, there’s a 2-second delay.

Loop function:

void loop() {
  sensors_event_t event;
  accelerometer.getEvent(&event);

  float roll = atan2(event.acceleration.x, event.acceleration.z) * 180.0 / PI;
  float pitch = atan2(event.acceleration.y, event.acceleration.z) * 180.0 / PI;

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Roll: ");
  display.println(roll);
  display.print("Pitch: ");
  display.println(pitch);

  display.drawLine(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2, SSD1306_WHITE);
  display.drawLine(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, SSD1306_WHITE);

  int bubbleX = map(roll, -90, 90, 0, SCREEN_WIDTH);
  int bubbleY = map(pitch, -90, 90, 0, SCREEN_HEIGHT);
  display.drawCircle(bubbleX, bubbleY, 3, SSD1306_WHITE);

  display.display();
  delay(100);
}
  • The loop reads accelerometer data continuously.
  • It computes the roll and pitch angles based on accelerometer data using trigonometric functions.
  • The OLED display is cleared, and the roll and pitch values are displayed.
  • On the display, horizontal and vertical reference lines are drawn.
  • Based on the roll and pitch angles, a tiny circle (bubble) showing the orientation is drawn on the display.
  • The display is updated, and there’s a 100-millisecond delay before the loop repeats.

Conclusion

In this project, we used an ESP32, an ADXL345 accelerometer, and an OLED display and made a digital spirit level. Whether you’re fixing a picture or playing with a robot, this digital spirit level is like a smart, modern bubble level. It helps you make things straight and balanced easily.

Photo of author

Tejas Chavan

I am an electronics engineer with experience in firmware development and embedded systems. My expertise involves creating embedded solutions, utilizing communication protocols like UART, I2C, SPIetc. I have a proven track record in national-level competitions, showcasing my proficiency in embedded system design.

Leave a Comment