Tilt meter on an Arduino Uno
An MPU6050 over I2C and three lamps. Drag the board to tip it: the accelerometer reads gravity, and the gyroscope reads how fast you moved. The whole build, an Arduino Uno and 7 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $33. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino MPU6050: Accelerometer and Tilt
What you'll learn
- Reading an MPU6050 over I2C on A4 and A5
- Why the chip must be woken from sleep first
- How gravity gives a tilt angle
- Telling the accelerometer from the gyroscope
- Using a threshold to pick one of three lamps
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 9
How it works
An MPU6050 sensor measures acceleration. At rest, that is gravity. Flat, all of gravity is on the Z axis. Tip the board and gravity shifts onto X and Y, and the sketch turns that into a roll angle in degrees.
Three LEDs show the tilt. Red on pin 7 means tipped left, green on pin 8 means flat, and blue on pin 9 means tipped right. Flat means within 12 degrees. The Serial Monitor prints the readings twice a second.
Parts list
9 parts, plus the jumper wires. They retail for about $33; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Accelerometer and gyroscope, MPU6050In Desk Buddy Kit, Laser Sword Kit, Escape Box Kit.
- 3 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, blueIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
Kits that can build it
These 2 starter kits have every part in the circuit. Each kit's page lists what else it can build.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Resistor, 220 Ω (3) pin 1 | Arduino Uno R3 pin 9 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 8 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 7 |
| Accelerometer and gyroscope, MPU6050 pin VCC | Arduino Uno R3 pin 5V |
| Accelerometer and gyroscope, MPU6050 pin GND | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| LED, green pin C | Arduino Uno R3 pin GND |
| LED, blue pin C | Arduino Uno R3 pin GND |
| Accelerometer and gyroscope, MPU6050 pin SDA | Arduino Uno R3 pin A4 |
| Accelerometer and gyroscope, MPU6050 pin SCL | Arduino Uno R3 pin A5 |
| LED, red pin A | Resistor, 220 Ω (1) pin 2 |
| LED, green pin A | Resistor, 220 Ω (2) pin 2 |
| LED, blue pin A | Resistor, 220 Ω (3) pin 2 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Tilt meter: an MPU6050 over I2C, three lamps and a serial readout.
//
// A5 -> SCL A4 -> SDA the GY-521 board carries its own pull-ups
// pin 7 the red lamp: tipped left
// pin 8 the green lamp: flat
// pin 9 the blue lamp: tipped right
//
// Drag the board on the canvas to tip it. Three things worth knowing, and all
// three are in the first twenty lines of every driver ever written for this
// chip.
//
// # It powers up asleep
//
// PWR_MGMT_1 reads 0x40 after reset, the SLEEP bit is set, and until it is
// cleared every measurement register reads zero. A sketch that forgets gets a
// perfectly steady nothing and no error, which is worse than a crash. The
// driver's begin() writes 0x00 there, and this sketch prints whether that
// worked.
//
// # WHO_AM_I is 0x68 whatever the address is
//
// The register holds bits 6:1 of the address and AD0 is bit 0, so a board
// strapped to 0x69 still identifies as 0x68. Comparing WHO_AM_I against the
// address a driver is using is wrong on exactly the board somebody had to
// strap, and this prints both so the difference is visible.
//
// # An accelerometer at rest measures gravity
//
// That is the whole trick. Flat, all of gravity is on Z and the reading is
// (0, 0, +1 g). Tip it and gravity moves onto X and Y, and the angle is the
// arctangent of the two. There is no floating point on this board, so the
// driver's tiltDegrees() does the arctangent from a table, good to about a
// degree, which is far better than a real MPU6050 is before it is calibrated.
//
// The gyroscope reads how *fast* the board is being moved rather than where it
// is, so it sits at zero until you drag it and falls back the moment you stop.
#include "mokxi_i2c.h"
#include "mokxi_mpu6050.h"
const int SCL_PIN = A5;
const int SDA_PIN = A4;
const int LAMP_LEFT = 7;
const int LAMP_FLAT = 8;
const int LAMP_RIGHT = 9;
// Past this much roll, one of the outer lamps takes over.
const int FLAT_DEGREES = 12;
SoftI2c bus(SCL_PIN, SDA_PIN);
Mpu6050 imu(bus);
unsigned long printedAt = 0;
void setup() {
pinMode(LAMP_LEFT, OUTPUT);
pinMode(LAMP_FLAT, OUTPUT);
pinMode(LAMP_RIGHT, OUTPUT);
Serial.begin(115200);
Serial.println("Mokxi Uno: MPU6050 tilt meter");
bus.begin();
Serial.print("WHO_AM_I: 0x");
Serial.println((long)imu.whoAmI(), HEX);
if (!imu.begin()) {
Serial.println("no answer at 0x68; check the wiring");
return;
}
Serial.println("awake (SLEEP cleared)");
// The most sensitive ranges, which is what a tilt meter wants.
imu.setRanges(0, 0);
}
void loop() {
Mpu6050Reading r;
if (!imu.read(r)) {
Serial.println("read failed");
delay(500);
return;
}
// Thousandths of a g. Flat on the bench is (0, 0, 1000).
long ax = imu.milliG(r.ax);
long ay = imu.milliG(r.ay);
long az = imu.milliG(r.az);
int roll = Mpu6050::tiltDegrees(ay, az);
int pitch = Mpu6050::tiltDegrees(-ax, az);
digitalWrite(LAMP_LEFT, roll < -FLAT_DEGREES ? HIGH : LOW);
digitalWrite(LAMP_RIGHT, roll > FLAT_DEGREES ? HIGH : LOW);
digitalWrite(LAMP_FLAT, (roll >= -FLAT_DEGREES && roll <= FLAT_DEGREES) ? HIGH : LOW);
if (millis() - printedAt >= 500) {
printedAt = millis();
Serial.print("a ");
Serial.print(ax);
Serial.print(",");
Serial.print(ay);
Serial.print(",");
Serial.print(az);
Serial.print(" mg roll ");
Serial.print(roll);
Serial.print(" pitch ");
Serial.print(pitch);
Serial.print(" gyro ");
Serial.print(imu.degreesPerSecond(r.gx));
Serial.print(",");
Serial.print(imu.degreesPerSecond(r.gy));
Serial.print(" deg/s chip ");
Serial.print(Mpu6050::temperatureTenths(r.temperature) / 10);
Serial.println(" C");
}
delay(40);
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Sets the three lamp pins as outputs and starts the Serial Monitor and the I2C bus. It prints the WHO_AM_I value, then imu.begin() wakes the chip, which powers up asleep. If it does not answer, the sketch prints a wiring hint. Otherwise it sets the most sensitive ranges.
void setup() {
pinMode(LAMP_LEFT, OUTPUT);
pinMode(LAMP_FLAT, OUTPUT);
pinMode(LAMP_RIGHT, OUTPUT);
Serial.begin(115200);
Serial.println("Mokxi Uno: MPU6050 tilt meter");
bus.begin();
Serial.print("WHO_AM_I: 0x");
Serial.println((long)imu.whoAmI(), HEX);
if (!imu.begin()) {
Serial.println("no answer at 0x68; check the wiring");
return;
}
Serial.println("awake (SLEEP cleared)");
// The most sensitive ranges, which is what a tilt meter wants.
imu.setRanges(0, 0);
}loop sketch.ino
Reads the sensor and converts acceleration to thousandths of a g. Roll and pitch come from tiltDegrees. The three lamps are set by comparing roll with FLAT_DEGREES. Every 500 ms it prints the acceleration, angles, gyro rates and chip temperature.
void loop() {
Mpu6050Reading r;
if (!imu.read(r)) {
Serial.println("read failed");
delay(500);
return;
}
// Thousandths of a g. Flat on the bench is (0, 0, 1000).
long ax = imu.milliG(r.ax);
long ay = imu.milliG(r.ay);
long az = imu.milliG(r.az);
int roll = Mpu6050::tiltDegrees(ay, az);
int pitch = Mpu6050::tiltDegrees(-ax, az);
digitalWrite(LAMP_LEFT, roll < -FLAT_DEGREES ? HIGH : LOW);
digitalWrite(LAMP_RIGHT, roll > FLAT_DEGREES ? HIGH : LOW);
digitalWrite(LAMP_FLAT, (roll >= -FLAT_DEGREES && roll <= FLAT_DEGREES) ? HIGH : LOW);
if (millis() - printedAt >= 500) {
printedAt = millis();
Serial.print("a ");
Serial.print(ax);
Serial.print(",");
Serial.print(ay);
Serial.print(",");
Serial.print(az);
Serial.print(" mg roll ");
// ... 13 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- Every reading is zero and there is no error This project
The MPU6050 powers up asleep.imu.begin()must wake it, so check the Serial Monitor for the awake message. - The Serial Monitor says no answer at 0x68 This project
Check that SDA goes to A4, SCL goes to A5, and the board has 5 V and ground. - The green lamp stays on for small tilts This project
That is the flat zone. LowerFLAT_DEGREESto make the outer lamps switch on sooner. - The sensor is not found or reads all zeros. Accelerometer and gyroscope
Check SDA and SCL (A4 and A5 on an Uno), power, and ground. The I2C address is 0x68, or 0x69 if AD0 is high. The chip starts in sleep mode, so write 0 to the power register PWR_MGMT_1. - The values look wrong or the angles drift. Accelerometer and gyroscope
Raw readings are counts, not g or degrees per second. Divide by the scale for your range (16384 per g at 2 g). Gyro angles drift, so blend with the accelerometer using a filter. - The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure. - A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground. - The LED does not light, or it lights once and then dies. LED
LEDs have polarity. The longer leg (anode) goes toward the positive side. Always add a series resistor, about 220 ohms to 1 k ohm on a 5 V pin.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.