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.