Learning Goals 5 min
The MPU6050 is the cheapest combo accelerometer + gyroscope — a 6-axis IMU on a tiny breakout. It tells your robot which way is up, how fast it's rotating, and (with a bit of maths) what its tilt angle is. By the end of this lesson you will:
- Wire an MPU6050 over I²C (4 pins) to an Arduino.
- Read raw accelerometer (3 axes) and gyroscope (3 axes) values, and convert them to physical units (g and °/s).
- Compute a tilt angle from the accelerometer alone — and identify why this naive approach is too noisy for balancing.
Warm-Up 10 min
Hardware: MPU6050 breakout (very common, ~£3). 4 wires: VCC, GND, SDA, SCL.
What an IMU measures
- Accelerometer: linear acceleration on 3 axes, in m/s² or "g" (1 g = 9.81 m/s²). Includes gravity: at rest, the "up" axis reads ~1 g.
- Gyroscope: angular velocity on 3 axes, in °/s. Tells you how fast you're spinning.
The MPU6050 has built-in temperature sensor too. 6 + 1 = 7 channels of data.
New Concept · Reading + processing 25 min
Wiring
| MPU6050 | UNO |
|---|---|
| VCC | 3.3 V or 5 V (breakout has a regulator) |
| GND | GND |
| SDA | A4 |
| SCL | A5 |
I²C address
Default 0x68. Some modules can be moved to 0x69 by tying the AD0 pin to VCC — useful if you also have a DS3231 RTC (also 0x68) on the same bus.
Library: Adafruit_MPU6050
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
Adafruit_MPU6050 mpu;
void setup() {
Serial.begin(115200);
Wire.begin();
if (!mpu.begin()) {
Serial.println("# no MPU6050 found");
while (true);
}
mpu.setAccelerometerRange(MPU6050_RANGE_2_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_5_HZ);
}
void loop() {
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
Serial.print("ax="); Serial.print(a.acceleration.x);
Serial.print(" ay="); Serial.print(a.acceleration.y);
Serial.print(" az="); Serial.print(a.acceleration.z);
Serial.print(" | gx="); Serial.print(g.gyro.x);
Serial.print(" gy="); Serial.print(g.gyro.y);
Serial.print(" gz="); Serial.println(g.gyro.z);
delay(100);
}At rest with the chip flat on the table:
ax,ay≈ 0 m/s²az≈ 9.81 m/s² (gravity straight up)gx,gy,gz≈ 0 (not rotating)
Tilt the chip on its side: az drops, ax or ay rises to ~9.81 (gravity now sideways).
Computing tilt angle from accelerometer
Pitch (rotation about the Y axis):
float pitchDeg = atan2(a.acceleration.x,
sqrt(a.acceleration.y * a.acceleration.y +
a.acceleration.z * a.acceleration.z))
* 180.0 / PI;Roll (rotation about the X axis):
float rollDeg = atan2(a.acceleration.y, a.acceleration.z) * 180.0 / PI;Both work at rest. The instant you move (shake, vibrate, accelerate), the "tilt angle" jumps — because the accelerometer can't tell apart gravity from any other acceleration. So the naive method is noisy under motion.
Why this matters for balancing
A self-balancing robot needs to know its tilt angle 100+ times per second. The accelerometer alone is too noisy. The gyroscope alone drifts (integration error compounds). Tomorrow you'll combine them with a complementary filter — the classic solution.
Worked Example · Tilt visualisation 25 min
Step 1 — wire MPU6050 to UNO
VCC → 3.3 V (or 5 V on a tolerant module), GND, SDA → A4, SCL → A5.
Step 2 — verify with the I²C scanner
Use the L03-18 I²C scanner to confirm 0x68 (or 0x69) appears on the bus.
Step 3 — read raw values
Upload the §3 sketch. Watch the values change as you tilt the breakout.
Step 4 — compute tilt
void loop() {
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
float pitch = atan2(a.acceleration.x,
sqrt(a.acceleration.y * a.acceleration.y +
a.acceleration.z * a.acceleration.z))
* 180.0 / PI;
float roll = atan2(a.acceleration.y, a.acceleration.z) * 180.0 / PI;
Serial.print("pitch="); Serial.print(pitch);
Serial.print(" roll="); Serial.println(roll);
delay(50);
}Step 5 — open the Serial Plotter
Watch pitch and roll graph in real time. Tilt the breakout — the lines move smoothly. Tap the breadboard — they spike (vibration = false acceleration).
Step 6 — calibrate the gyro bias
float gyroOffsetX = 0, gyroOffsetY = 0, gyroOffsetZ = 0;
void calibrateGyro() {
Serial.println("# leave chip still; calibrating...");
long sumX = 0, sumY = 0, sumZ = 0;
const int N = 500;
for (int i = 0; i < N; i++) {
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
sumX += g.gyro.x * 1000.0;
sumY += g.gyro.y * 1000.0;
sumZ += g.gyro.z * 1000.0;
delay(2);
}
gyroOffsetX = sumX / 1000.0 / N;
gyroOffsetY = sumY / 1000.0 / N;
gyroOffsetZ = sumZ / 1000.0 / N;
Serial.print("# offsets X="); Serial.print(gyroOffsetX);
Serial.print(" Y="); Serial.print(gyroOffsetY);
Serial.print(" Z="); Serial.println(gyroOffsetZ);
}Subtract the offsets from every reading after that. Now the gyro reads ~0 °/s at rest instead of ±2 °/s.
Try It Yourself 15 min
Goal: Light an LED when the breakout is tilted more than 30° (pitch or roll). The MPU6050 becomes a tilt switch.
Goal: Detect "tap" or "shake": if any acceleration magnitude exceeds 2 g for > 50 ms, fire an LED. Trial-and-error the threshold.
Goal: Integrate gyro Z over time to estimate "heading" (yaw). Drift will accumulate (~1°/min); confirm by yaw-ing 360° physically and seeing the integrated value.
Mini-Challenge · Spirit level 10 min
- Make a simple spirit-level using just the accelerometer.
- 3 LEDs: tilt left = left LED, level = middle, tilt right = right.
- Threshold ~5° each way.
- Bonus: drive a servo as the bubble.
Recap 5 min
MPU6050 = 6-axis IMU on I²C. Accelerometer measures gravity + motion (so noisy under motion). Gyroscope measures rotation rate (drifts over time). Bias-calibrate the gyro at rest. Tomorrow: combine accelerometer + gyroscope with a complementary filter to get a clean tilt angle suitable for balancing.
- IMU (Inertial Measurement Unit)
- Sensor that measures linear acceleration and angular velocity. 6-axis = accel + gyro; 9-axis adds a magnetometer.
- Accelerometer
- Measures linear acceleration on 3 axes. Detects gravity when at rest.
- Gyroscope
- Measures angular velocity (°/s) on 3 axes. Drifts over time when integrated.
- g (acceleration unit)
- 1 g = 9.81 m/s² = Earth's surface gravity.
- Pitch / Roll / Yaw
- The three rotation axes of an aircraft. Pitch = nose up/down; Roll = wing up/down; Yaw = nose left/right.
- Bias / offset
- Sensor's non-zero reading when there's no real input. Calibrated at rest.
- Drift
- Slow accumulation of error when integrating a noisy / biased signal. The gyro's nemesis.
- Adafruit_MPU6050
- Adafruit's Arduino library for the MPU6050. Friendly events API.
Homework 5 min
- Build the §4 tilt visualisation. Watch the Serial Plotter while shaking.
- Note how noisy the "pitch from accel" reading is under motion.
- Read ahead to ARD-L04-29 (Sensor Fusion Basics).