Build a tilt maze game for your computer

Tilt a motion sensor like a wooden labyrinth and a ball rolls through a maze on your screen. The UNO combines the accelerometer and the gyroscope into smooth, fast tilt angles, the same trick that keeps drones level.
  • Level:Advanced
  • Duration: 1 h 30 min
  • Ages 12+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a tilt maze game for your computer – EEMENTOR STEM™ lesson

Do you know those wooden labyrinth games where you tilt the board to roll a steel ball past the holes? In this capstone the board is a GY-521 motion sensor in your hand, and the maze is on your screen. The UNO reads the MPU-6050's accelerometer and its gyroscope, and combines them into two tilt angles that are both steady and quick. It sends them to the Tilt maze in the Arduino Playground, where a ball rolls the way you tilt. Get it to the green flag without falling into a hole! The same sensor fusion keeps drones, self-balancing scooters and phone screens the right way up.

What you'll learn

  • What an accelerometer and a gyroscope measure, and the weakness of each one
  • How a complementary filter combines them into one good angle
  • How to read all six motion values from the MPU-6050 over I2C
  • How to calibrate a sensor: gyro offset and a "level" button

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • GY-521 MPU-6050 module (45-in-1 Sensor Kit)
  • 1 push button (UNO R3 Starter Kit)
  • 4 long jumper wires, so you can hold the sensor comfortably
  • Optional: a small piece of card to glue the sensor onto, as a "maze board" to hold
  • A computer with Chrome or Edge
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Breadboard wiring diagram: Build a tilt maze game for your computer
Breadboard view. Check each connection against the wiring table below.
From To
MPU-6050 VCC 5V
MPU-6050 GND GND
MPU-6050 SCL UNO A5
MPU-6050 SDA UNO A4
Push button, one leg UNO pin 2
Push button, the diagonally opposite leg GND

Glue or tape the sensor flat onto a piece of card. Holding the card feels like holding the maze, and the sensor can't twist on its wires.

Example screen for: Build a tilt maze game for your computer
Example screen with sample values. Yours may look different.

The sketch

Wire comes with the Arduino IDE. The sketch talks to the MPU-6050 directly, so no extra library is needed.

#include <Wire.h>

const int MPU = 0x68;                    // I2C address of the MPU-6050
const int BUTTON_PIN = 2, LED_PIN = 13;
const float GYRO_SHARE = 0.96;           // how much to trust the gyroscope (0.9 to 0.99)

float pitch = 0, roll = 0;               // fused angles in degrees
float pitchZero = 0, rollZero = 0;       // the angles that count as "level"
float gyroBiasX = 0, gyroBiasY = 0;      // what the gyroscope reads when still
unsigned long lastMicros, lastSend = 0;

void readSensor(float& ax, float& ay, float& az, float& gx, float& gy) {
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);                      // start at ACCEL_XOUT_H
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 14);             // 6 accel bytes, 2 temperature, 6 gyro
  int16_t v[7];
  for (int i = 0; i < 7; i++) {
    v[i] = Wire.read() << 8;             // high byte first
    v[i] |= Wire.read();
  }
  ax = v[0] / 16384.0;                   // in g (±2 g range)
  ay = v[1] / 16384.0;
  az = v[2] / 16384.0;
  gx = v[4] / 131.0;                     // in degrees per second (±250 °/s range)
  gy = v[5] / 131.0;
}

void accelAngles(float ax, float ay, float az, float& p, float& r) {
  p = atan2(-ax, sqrt(ay * ay + az * az)) * 180 / PI;
  r = atan2(ay, az) * 180 / PI;
}

void calibrate() {                       // keep the sensor still and level
  digitalWrite(LED_PIN, HIGH);
  float sumX = 0, sumY = 0, sumP = 0, sumR = 0, ax, ay, az, gx, gy, p, r;
  for (int i = 0; i < 200; i++) {
    readSensor(ax, ay, az, gx, gy);
    accelAngles(ax, ay, az, p, r);
    sumX += gx; sumY += gy; sumP += p; sumR += r;
    delay(3);
  }
  gyroBiasX = sumX / 200;
  gyroBiasY = sumY / 200;
  pitch = pitchZero = sumP / 200;
  roll = rollZero = sumR / 200;
  digitalWrite(LED_PIN, LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Wire.begin();
  Wire.setClock(400000);                 // fast I2C
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);                      // power management: wake up
  Wire.write(0);
  Wire.endTransmission();
  delay(100);
  calibrate();
  lastMicros = micros();
}

void loop() {
  if (digitalRead(BUTTON_PIN) == LOW) {  // hold the maze level and press: new "level"
    calibrate();
    delay(300);
  }

  float ax, ay, az, gx, gy, accPitch, accRoll;
  readSensor(ax, ay, az, gx, gy);
  accelAngles(ax, ay, az, accPitch, accRoll);
  unsigned long now = micros();
  float dt = (now - lastMicros) / 1000000.0;
  lastMicros = now;

  // Complementary filter: follow the fast gyroscope, but let the
  // accelerometer slowly pull the angle back so it can't drift away.
  pitch = GYRO_SHARE * (pitch + (gy - gyroBiasY) * dt) + (1 - GYRO_SHARE) * accPitch;
  roll = GYRO_SHARE * (roll + (gx - gyroBiasX) * dt) + (1 - GYRO_SHARE) * accRoll;

  if (millis() - lastSend >= 20) {       // 50 lines a second, e.g. p:3.2,r:-8.5
    lastSend = millis();
    Serial.print("p:");
    Serial.print(pitch - pitchZero, 1);
    Serial.print(",r:");
    Serial.println(roll - rollZero, 1);
  }
}

Play it

  1. Upload the sketch. Open the Serial Monitor at 115200 baud for a moment to check that the numbers change when you tilt the sensor, then close the Serial Monitor: only one program can use the USB port at a time.
  2. Open the Arduino Playground in Chrome or Edge on a computer. These browsers can talk to a USB board through a feature called Web Serial.
  3. Click Connect your board, choose the port of your UNO (often called "USB Serial" or "CH340") and click Connect.
  4. Play! The status line says "Connected", and "Connection help" under the game shows the last line your UNO sent.

No board at hand? Click Play with the keyboard on the Playground page to try the game first.

When you start, hold the sensor level and press the button: that position becomes "flat". If the ball rolls the wrong way, tick the options under the game: Flip left and right, Flip up and down or Swap the two axes, until it rolls the way you tilt.

How it works

The accelerometer feels gravity, so it knows which way is down and therefore how much it is tilted. But it also feels every shake of your hand, so its angle is jittery. The gyroscope measures how fast the sensor is turning, in degrees per second. Add up those small turns (turn speed × time) and you get a smooth, fast angle, but every tiny error is added up too, and after a minute the angle has drifted far away.

The complementary filter uses the best of both. In every pass of loop(), the new angle is 96 % "old angle plus the gyroscope's turn" and 4 % "what the accelerometer says". Fast movements come from the gyroscope, and the accelerometer slowly corrects the drift. With GYRO_SHARE you choose the balance: closer to 1 is smoother but corrects more slowly.

Calibration fixes two more problems. A still gyroscope never reads exactly 0, so the sketch measures its bias at the start and subtracts it. And your hand never holds the sensor perfectly level, so the button stores the current angles as "zero". The game then turns the angles into the ball's acceleration: more tilt, faster roll, just like gravity on a real wooden board.

Try this next

  • Change GYRO_SHARE to 0.5 and to 0.995 and feel the difference in the game.
  • Draw the accelerometer-only angle and the fused angle together in the Serial Plotter, and shake the sensor: which line stays calmer?
  • Add a buzzer that beeps when the tilt is more than 25 degrees: "Careful, the ball is going fast!"

Troubleshooting

  • The ball slowly rolls by itself when you hold still: press the button while you hold the sensor level, to set a new zero.
  • The angles stay at 0 or jump wildly: check SDA on A4 and SCL on A5. If it still fails, try VCC on 3V3.
  • The ball rolls the wrong way: use the three options under the game; the sensor can be held in many directions.
  • "Could not connect" or "Failed to open serial port": another program is using the port. Close the Serial Monitor and the Serial Plotter in the Arduino IDE, then click Connect again.
  • Connected, but nothing moves: open "Connection help" on the Playground page. If no lines arrive, check that the sketch uses Serial.begin(115200) and that you picked the UNO's port.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit