← All projects
Sensors Intermediate

MPU6050 Digital Level: Tilt Angles from the Accelerometer

At rest an accelerometer measures only gravity, and its projections on the axes give the tilt angles. We compute roll and pitch with atan2, show them on an LCD, and see why a gyroscope on its own drifts.

Difficulty
Intermediate
Time
40 min
Category
Sensors
Updated

About the project

The MPU6050 is a cheap module (GY-521) with a 3-axis accelerometer and a 3-axis gyroscope. We turn it into a spirit level: two tilt angles, roll (sideways) and pitch (front-back), on an LCD 1602.

What you need

  • ESP32 DevKit
  • MPU6050 (GY-521) module
  • LCD 1602 with an I2C backpack
  • The Adafruit MPU6050 library (pulls in Adafruit Unified Sensor) and LiquidCrystal_I2C

Wiring

Both modules share one I2C bus:

Module VCC GND SDA SCL Address
MPU6050 3V3 GND GPIO21 GPIO22 0x68 (AD0 → GND)
LCD 1602 3V3 GND GPIO21 GPIO22 0x27

For two MPU6050s on one bus, tie AD0 of the second one to VCC: its address becomes 0x69.

Where the angles come from

A still accelerometer measures the gravity vector, g ≈ 9.81 m/s². Lying flat, the module sees all of it on the Z axis. Tilt it, and part moves over to X and Y. The ratio of the projections gives the angles:

  • roll = atan2(ay, az)
  • pitch = atan2(−ax, √(ay² + az²))

Unlike atan, atan2 takes the signs of both arguments into account and works in all four quadrants. RAD_TO_DEG converts radians to degrees.

The angle around the vertical (yaw, the heading) cannot be found this way: gravity does not depend on it. A heading needs a magnetometer.

Code

// Digital level: MPU6050 → roll/pitch angles → LCD 1602
#include <Adafruit_MPU6050.h>
#include <LiquidCrystal_I2C.h>

Adafruit_MPU6050 mpu;
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
  if (!mpu.begin()) {
    lcd.print("MPU6050 missing");
    while (true) delay(1000);
  }
  mpu.setAccelerometerRange(MPU6050_RANGE_2_G);   // highest sensitivity
}

void loop() {
  sensors_event_t a, g, t;
  mpu.getEvent(&a, &g, &t);
  float ax = a.acceleration.x, ay = a.acceleration.y, az = a.acceleration.z;
  float roll = atan2(ay, az) * RAD_TO_DEG;
  float pitch = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG;
  char line[17];
  snprintf(line, sizeof line, "Roll  %6.1f", roll);
  lcd.setCursor(0, 0);
  lcd.print(line);
  lcd.write(223);   // the HD44780 degree sign
  snprintf(line, sizeof line, "Pitch %6.1f", pitch);
  lcd.setCursor(0, 1);
  lcd.print(line);
  lcd.write(223);
  delay(100);
}

Accelerometer or gyroscope

  • The accelerometer gives an absolute angle, but it is noisy and wanders with any motion: a shake looks like a tilt.
  • The gyroscope measures the rotation rate (°/s). Integrating it gives an angle that is smooth and fast but drifts: the error accumulates, and after a minute you are degrees off.
  • A complementary filter combines both: angle = 0.98 * (angle + gyro * dt) + 0.02 * accAngle. It is the next step for drones and balancing robots.

What next

Beep when both angles are within ±0.5°, and you have a proper shelf level. Or draw a "bubble" on an OLED instead of numbers.