← SHEET 02 · ASSEMBLIES EL-009

IMU Digital Spirit Level

ELSENSORS
LIVE DRAWING — HOVER OR DRAG TO CRANK · BUILT FROM THE REAL PLANT PARAMETERS
PART NOEL-009
MATL / SYSTEMESP32 · MPU6050 · SSD1306
TOOLSArduino IDE · Wokwi · C++

A pocket spirit level built on the same estimation math as the balance robots elsewhere on this site: the TWIP and ballbot state estimators fuse a noisy absolute sensor with a drifting rate sensor, and this project strips that problem down to its minimum working form. An ESP32 reads an MPU6050 over raw I2C, a complementary filter (tau = 1.0 s) blends accelerometer tilt with integrated gyro rate, and an SSD1306 draws a 2-axis bubble with numeric pitch/roll to 0.1°. A button tares the zero into flash; a buzzer holds a tone when both axes sit within ±0.2° of level.

OVERVIEW & MOTIVATION

Accelerometer-only tilt meters jitter; gyro-only ones drift. Every balancing robot solves this with some flavor of sensor fusion before its controller sees an angle, and the complementary filter is the smallest honest member of that family — one pole, one parameter, closed-form. The spirit level is the ideal carrier for it: the device is read mostly at rest, so the filter can be tuned slow (heavy vibration rejection) and the whole estimator fits in two lines of C++. Everything runs at a fixed 100 Hz so the discrete filter’s dt is exact, and calibration is a real tare (accel zero + gyro bias captured together, persisted in NVS) rather than a hardcoded offset.

COMPONENTS & BOM

REFCOMPONENTSPECROLE
U1ESP32 DevKit-C v4dual-core 240 MHz, 3.3 V I/Oruns the 100 Hz fusion loop, I2C master
U2MPU6050 module (GY-521)3-axis accel ±2 g + 3-axis gyro ±250 °/s, I2C 0x68tilt + rate sensing
U3SSD1306 OLED128×64, I2C 0x3C, 3.3 Vbubble display + numeric readout
SW1Pushbuttonmomentary, to GNDzero-calibration tare
BZ1Passive buzzerdriven via tone(), ~2.2 kHzlevel indication beep

WIRING

ESP32 DEVKIT-C GPIO21 GPIO22 GPIO4 GPIO26 3V3/GND MPU6050 (0x68) SDA SCL VCC GND SSD1306 (0x3C) SDA SCL VCC GND ZERO-CAL BTN SIG · GND BUZZER (PASSIVE) + · GND SDA SCL SIG (PULLUP) TONE + 3V3 RAIL GND RAIL SHARED I2C BUS — 0x68 + 0x3C, NO CONFLICT BTN: INPUT_PULLUP, ACTIVE LOW
ESP32 PINNETPERIPHERAL PIN
GPIO21I2C SDAMPU6050 SDA · SSD1306 SDA
GPIO22I2C SCLMPU6050 SCL · SSD1306 SCL
GPIO4CAL_BTN (INPUT_PULLUP, active low)Button leg 1 (leg 2 → GND)
GPIO26BUZZERBuzzer +
3V3powerMPU6050 VCC · SSD1306 VCC
GNDgroundMPU6050 · SSD1306 · button · buzzer −

Both I2C devices share the one bus — MPU6050 answers at 0x68 (AD0 low), SSD1306 at 0x3C, so no address conflict and no extra pins.

SENSOR FUSION

Two angle estimates exist every sample, each broken in a complementary way. The accelerometer gives an absolute tilt (atan2 of the gravity components) with zero drift — but it is noisy and reads wrong under any linear acceleration: bump the surface and the “angle” jumps. The gyro gives a clean, motion-immune rate, but integrating it accumulates bias into unbounded drift. The complementary filter low-passes the accel angle and high-passes the gyro-integrated angle with the same corner, so the weights sum to one at every frequency. Discretized at fixed dt this collapses to a one-pole blend:

const float TAU   = 1.0f;              // s — crossover time constant
const float DT    = 0.01f;             // 100 Hz fixed loop
const float ALPHA = TAU / (TAU + DT);  // = 0.9901

pitch = ALPHA * (pitch + gyDps * DT) + (1.0f - ALPHA) * pitchAcc;
roll  = ALPHA * (roll  + gxDps * DT) + (1.0f - ALPHA) * rollAcc;

Below ~tau seconds the estimate follows the gyro (smooth), beyond ~tau it settles to the accelerometer (absolute). tau = 1.0 s is deliberately slow for a device read at rest: strong rejection of hand tremor and surface vibration, while gyro bias still washes out within a second — faster than you can read the display. The same structure, with faster tau and a state-space dressing, is exactly what the TWIP/ballbot estimators run before their LQR/PPO loops.

Calibration is a two-part tare captured in one button press while the device sits still: 100 samples average the raw gyro rates into per-axis bias (fed back into the filter’s rate input) and the accel angles into pitch/roll offsets (subtracted only at display time, so integration stays in the sensor frame). Both persist in ESP32 NVS via Preferences:

gyroBiasY   = sumGy / N;          // deg/s — removed before integration
pitchOffset = sumPitch / N;       // deg   — tare, subtracted for display/beep
prefs.putFloat("pOff", pitchOffset);

The level beep uses hysteresis — trigger inside ±0.2°, release outside ±0.35° — so the tone doesn’t chatter on the boundary.

SIMULATION

The build runs unmodified in Wokwi — all five parts exist as native Wokwi components (board-esp32-devkit-c-v4, wokwi-mpu6050, wokwi-ssd1306, wokwi-pushbutton, wokwi-buzzer), no substitutions. Steps: new ESP32 project on wokwi.com, paste sketch.ino and diagram.json from sources/electronics/digital-level/, press Play. Click the MPU6050 part and drag its simulated accelerometer axes in the part inspector to tilt the virtual board — the bubble slides off center and the numeric pitch/roll follows. Return it to flat and the buzzer holds its 2.2 kHz level tone. The blue ZERO CAL button tares any pose to zero (two confirmation beeps).

STATUS

Design and simulation only. The firmware is complete and exercised in Wokwi (tilt response, tare, level beep); no physical unit has been assembled and no bench accuracy characterization exists. The ±0.2° level window and 0.1° display resolution are design intent, not verified accuracy — the MPU6050’s datasheet-typical offset drift and cross-axis terms put the realistic post-tare accuracy in the ±0.5° class, and mounting flatness of a real enclosure would dominate the error budget before the sensor does.

USE CASES & APPLICATIONS

Machine and equipment setup (leveling a lathe bed, printer gantry, or wash machine feet with a live numeric readout), camera gimbal and tripod horizon alignment, construction layout where a beeping level frees the eyes, and RV or trailer leveling. The filter itself is the wider payoff: the same accel-plus-gyro complementary structure is the entry-level attitude estimator in drones, self-balancing robots, and every hobby IMU stack — this project is that estimator isolated, instrumented, and made legible.

FILES

  • sources/electronics/digital-level/sketch.ino — firmware: raw MPU6050 I2C driver, complementary filter with derivation in the header, NVS-backed calibration, OLED bubble UI, beep hysteresis.
  • sources/electronics/digital-level/diagram.json — Wokwi wiring diagram, pin-identical to the sketch.
  • sources/electronics/digital-level/README.md — Wokwi run steps, library list, real-build and accuracy notes (datasheet-typical, labeled as such).

← BACK TO ASSEMBLIES

NAME ODILBEK MARIMOV
DWG NO. PF-2026
SHEET 01 / 07
DISCIPLINE ROBOTICS / MECHATRONICS
SCALE 1:1
REV A
THIRD-ANGLE PROJECTION
DATE 2026-07-11
UNITS mm