FluxBench.AIShared project · read-only

MPU6050 Bubble Level

ESP32 DevKit (WROOM-32)·Arduino C++·Updated Jul 27, 2026·by FluxBench TeamIncludes Lab + Schematic

A digital spirit level: raw accelerometer reads from an MPU6050 over I2C are turned into pitch and roll angles and drawn as a bubble inside a circle on a 128x64 OLED. Click the MPU6050 on the bench and drag its X / Y tilt sliders while the sketch runs - the bubble slides around, the numbers update and the green LED on GPIO 4 lights when you are level within 3 degrees. The sketch talks to the sensor with plain Wire register reads (wake 0x6B, burst-read 0x3B), so it runs on real hardware with no extra libraries.

sketch.ino
// MPU6050 Bubble Level - a digital spirit level on the OLED.
// Click the MPU6050 on the bench and drag its X / Y tilt sliders while the
// sketch runs - the bubble slides around the circle, the numbers update and
// the green LED lights when you are level (within 3 degrees).

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

#define MPU6050_ADDR 0x68
#define LEVEL_PIN 4

Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
  Serial.begin(115200);
  pinMode(LEVEL_PIN, OUTPUT);
  Wire.begin();
  // Wake the MPU6050 (clear the sleep bit in PWR_MGMT_1)
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display();
  Serial.println("Bubble level ready - drag the MPU6050 tilt sliders");
}

void loop() {
  // Read the 6 raw accelerometer bytes starting at ACCEL_XOUT_H (0x3B)
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 6);
  int16_t ax = (int16_t)(Wire.read() << 8 | Wire.read());
  int16_t ay = (int16_t)(Wire.read() << 8 | Wire.read());
  int16_t az = (int16_t)(Wire.read() << 8 | Wire.read());

  // Tilt angles from gravity (degrees)
  float pitch = atan2((float)ax, sqrt((float)ay * ay + (float)az * az)) * 180.0 / PI;
  float roll = atan2((float)ay, sqrt((float)ax * ax + (float)az * az)) * 180.0 / PI;
  bool level = fabs(pitch) < 3 && fabs(roll) < 3;
  digitalWrite(LEVEL_PIN, level ? HIGH : LOW);

  Serial.print("Pitch ");
  Serial.print(pitch, 1);
  Serial.print("  Roll ");
  Serial.print(roll, 1);
  Serial.println(level ? "  [LEVEL]" : "");

  // Draw the bubble level: fixed circle + crosshair, bubble offset by tilt
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("BUBBLE LEVEL");
  display.setCursor(0, 20);
  display.print("P ");
  display.print(pitch, 1);
  display.setCursor(0, 32);
  display.print("R ");
  display.print(roll, 1);
  display.setCursor(0, 52);
  display.print(level ? "LEVEL!" : "tilted");

  int cx = 96;
  int cy = 32;
  int r = 28;
  display.drawCircle(cx, cy, r, SSD1306_WHITE);
  display.drawLine(cx - r, cy, cx + r, cy, SSD1306_WHITE);
  display.drawLine(cx, cy - r, cx, cy + r, SSD1306_WHITE);
  // Bubble moves opposite the tilt, like a real spirit level
  int bx = cx - (int)constrain(roll, -45, 45) * (r - 5) / 45;
  int by = cy - (int)constrain(pitch, -45, 45) * (r - 5) / 45;
  display.fillCircle(bx, by, 4, SSD1306_WHITE);
  display.display();

  delay(150);
}

Built with FluxBench.AI — the AI workbench for embedded projects.