ESP32 Snake Game with MPU6050 and OLED Display

Learn how to build a classic Snake game using an ESP32, an MPU6050 motion sensor and a 0.96-inch SSD1306 OLED Display.

Instead of pressing buttons, you control the snake by tilting the MPU6050 sensor, making this a fun project for learning ESP32 graphics, sensors and game programming.

In this tutorial you’ll learn:

  • ESP32 Snake Game wiring
  • MPU6050 tilt controls
  • OLED display connections
  • Required libraries
  • Complete Arduino code
  • GitHub source code
  • Project improvements

Project Overview

This project combines three popular ESP32 modules:

The MPU6050 detects the board’s orientation and changes the snake’s direction.

The OLED Display shows:

  • Snake
  • Food
  • Score
  • Game Over screen

Required Hardware

Component Link
ESP32 Development Board Recommended ESP32 Board
MPU6050 Module MPU6050 Motion Sensor
SSD1306 OLED Display 0.96” OLED Display
Breadboard Breadboard Kit
Jumper Wires Jumper Wire Kit

ESP32 Snake Game Wiring

ESP32 Snake Wiring

ESP32 Connections

ESP32 MPU6050 OLED SSD1306
3.3V VCC VCC
GND GND GND
GPIO21 SDA SDA
GPIO22 SCL SCL

Since both devices use I2C communication, they can share the same SDA and SCL pins.


How the Game Works

The snake moves continuously across the OLED display.

Tilting the MPU6050 changes its direction:

  • Tilt Left → Move Left
  • Tilt Right → Move Right
  • Tilt Forward → Move Up
  • Tilt Backward → Move Down

Every time the snake eats food:

  • Score increases
  • Snake becomes longer
  • Game speed gradually increases

If the snake hits:

  • A wall
  • Itself

The game ends and displays the final score.


Install Required Libraries

Open Arduino IDE.

Go to:

Sketch
→ Include Library
→ Manage Libraries

Install the following libraries:

  • Adafruit SSD1306
  • Adafruit GFX
  • MPU6050
  • Wire

ESP32 Snake Game Code

The complete project is available on GitHub.

// =====================================================
// Embedded Nerd
// ESP32 Snake Game
// MPU6050 + OLED Display
// https://embeddednerd.com
// =====================================================

// ==========================================
// Libraries
// ==========================================

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <MPU6050_tockn.h>

// ==========================================
// OLED Display Configuration
// ==========================================

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(
  SCREEN_WIDTH,
  SCREEN_HEIGHT,
  &Wire,
  -1
);

// ==========================================
// MPU6050 Motion Sensor
// ==========================================

MPU6050 mpu6050(Wire);

// ==========================================
// Game Configuration
// ==========================================

const int grid = 4;

// Snake body coordinates

int snakeX[200];
int snakeY[200];

// Initial snake length

int snakeLength = 5;

// Current movement direction

int dirX = 1;
int dirY = 0;

// Buffered next direction
// Prevents instant reverse movement

int nextDirX = 1;
int nextDirY = 0;

// Food position

int foodX;
int foodY;

// Game timing

unsigned long lastMove = 0;

// Dynamic game speed

int gameSpeed = 120;

// Game states

bool gameOver = false;
bool gameStarted = false;

// Score system

int score = 0;
int bestScore = 0;

// Tilt sensitivity

float threshold = 0.35;

// ==========================================
// Spawn Food
// ==========================================

void spawnFood() {

  foodX = random(0, SCREEN_WIDTH / grid) * grid;
  foodY = random(0, SCREEN_HEIGHT / grid) * grid;
}

// ==========================================
// Reset Game State
// ==========================================

void resetGame() {

  snakeLength = 5;

  score = 0;

  dirX = 1;
  dirY = 0;

  nextDirX = 1;
  nextDirY = 0;

  gameOver = false;

  // Create initial snake body

  for (int i = 0; i < snakeLength; i++) {

    snakeX[i] = 40 - i * grid;
    snakeY[i] = 20;
  }

  spawnFood();
}

// ==========================================
// Setup
// ==========================================

void setup() {

  Serial.begin(115200);

  // Initialize I2C

  Wire.begin();

  // Lower I2C speed for stability

  Wire.setClock(100000);

  // Initialize OLED display

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.clearDisplay();
  display.display();

  // Initialize MPU6050

  mpu6050.begin();

  // Gyroscope calibration

  mpu6050.calcGyroOffsets(true);

  // Random seed for food spawning

  randomSeed(millis());

  resetGame();
}

// ==========================================
// Main Loop
// ==========================================

void loop() {

  // ==========================================
  // Start Screen
  // ==========================================

  if (!gameStarted) {

    mpu6050.update();

    float x = mpu6050.getAccX();
    float y = mpu6050.getAccY();

    display.clearDisplay();

    display.setTextSize(2);
    display.setTextColor(WHITE);

    display.setCursor(30, 5);
    display.println("SNAKE");

    display.setTextSize(1);

    display.setCursor(10, 28);
    display.println("Tilt to play");

    display.display();

    // Start game when tilt is detected

    if (abs(x) > 0.5 || abs(y) > 0.5) {

      gameStarted = true;

      delay(300);
    }

    return;
  }

  // ==========================================
  // Game Over Screen
  // ==========================================

  if (gameOver) {

    display.clearDisplay();

    display.setTextSize(2);
    display.setTextColor(WHITE);

    display.setCursor(10, 5);
    display.println("GAME");

    display.setCursor(15, 25);
    display.println("OVER");

    display.setTextSize(1);

    display.setCursor(30, 50);
    display.print("SCORE: ");
    display.println(score);

    display.display();

    delay(2500);

    gameStarted = false;

    resetGame();

    return;
  }

  // ==========================================
  // Read MPU6050 Sensor
  // ==========================================

  mpu6050.update();

  // Use accelerometer values
  // Better for responsive tilt controls

  float x = mpu6050.getAccX();
  float y = mpu6050.getAccY();

  // ==========================================
  // Dynamic Speed System
  // ==========================================

  float tilt = max(abs(x), abs(y));

  if (tilt > threshold) {

    gameSpeed = map(
      constrain(tilt * 100, 35, 100),
      35,
      100,
      150,
      60
    );

  } else {

    gameSpeed = 150;
  }

  // ==========================================
  // Tilt Controls
  // ==========================================

  if (abs(x) > abs(y)) {

    // Move Up

    if (x > threshold) {

      if (!(dirX == 0 && dirY == -1)) {

        nextDirX = 0;
        nextDirY = 1;
      }
    }

    // Move Down

    else if (x < -threshold) {

      if (!(dirX == 0 && dirY == 1)) {

        nextDirX = 0;
        nextDirY = -1;
      }
    }

  } else {

    // Move Right

    if (y > threshold) {

      if (!(dirX == -1 && dirY == 0)) {

        nextDirX = 1;
        nextDirY = 0;
      }
    }

    // Move Left

    else if (y < -threshold) {

      if (!(dirX == 1 && dirY == 0)) {

        nextDirX = -1;
        nextDirY = 0;
      }
    }
  }

  // ==========================================
  // Snake Movement
  // ==========================================

  if (millis() - lastMove > gameSpeed) {

    lastMove = millis();

    // Apply next direction

    dirX = nextDirX;
    dirY = nextDirY;

    // Move snake body

    for (int i = snakeLength; i > 0; i--) {

      snakeX[i] = snakeX[i - 1];
      snakeY[i] = snakeY[i - 1];
    }

    // Move snake head

    snakeX[0] += dirX * grid;
    snakeY[0] += dirY * grid;

    // ==========================================
    // Screen Wrapping
    // ==========================================

    if (snakeX[0] < 0)
      snakeX[0] = SCREEN_WIDTH - grid;

    if (snakeX[0] >= SCREEN_WIDTH)
      snakeX[0] = 0;

    if (snakeY[0] < 0)
      snakeY[0] = SCREEN_HEIGHT - grid;

    if (snakeY[0] >= SCREEN_HEIGHT)
      snakeY[0] = 0;

    // ==========================================
    // Self Collision Detection
    // ==========================================

    for (int i = 1; i < snakeLength; i++) {

      if (snakeX[0] == snakeX[i] &&
          snakeY[0] == snakeY[i]) {

        gameOver = true;
      }
    }

    // ==========================================
    // Food Collection
    // ==========================================

    if (snakeX[0] == foodX &&
        snakeY[0] == foodY) {

      snakeLength++;

      score++;

      // Save best score

      if (score > bestScore) {
        bestScore = score;
      }

      spawnFood();
    }
  }

  // ==========================================
  // Render Game
  // ==========================================

  display.clearDisplay();

  // Draw food

  display.fillRect(
    foodX,
    foodY,
    grid,
    grid,
    WHITE
  );

  // Draw snake

  for (int i = 0; i < snakeLength; i++) {

    display.fillRect(
      snakeX[i],
      snakeY[i],
      grid,
      grid,
      WHITE
    );
  }

  // Draw score

  display.setTextSize(1);

  display.setCursor(0, 0);

  display.print(score);

  display.display();
}

The complete sketch includes:

  • Snake movement
  • Food generation
  • Collision detection
  • Score counter
  • MPU6050 controls
  • OLED graphics

Reading the MPU6050

The MPU6050 continuously measures acceleration on three axes.

Typical logic:

if(ax > 8000)
    direction = RIGHT;

if(ax < -8000)
    direction = LEFT;

if(ay > 8000)
    direction = DOWN;

if(ay < -8000)
    direction = UP;

Threshold values help prevent accidental direction changes caused by small movements.


Drawing the Snake

Each snake segment is represented as a small square.

The OLED is refreshed every frame by:

  • Clearing the display
  • Drawing the snake
  • Drawing food
  • Updating the score

This creates smooth movement while keeping memory usage low.


Game Features

Current features include:

  • Tilt controls
  • Score counter
  • Random food generation
  • Snake growth
  • Collision detection
  • Game Over screen

Possible Improvements

You can easily expand this project by adding:

  • High score memory
  • Sound effects with a buzzer
  • Difficulty levels
  • Animated splash screen
  • RGB LED effects
  • Wi-Fi leaderboard
  • Bluetooth multiplayer
  • Touch button controls

Troubleshooting

OLED Not Displaying

Check:

  • SDA and SCL connections
  • OLED I2C address
  • Display library installation

MPU6050 Not Detected

Verify:

  • Wiring
  • I2C address (0x68)
  • Power supply

Run an I2C scanner if necessary.


Snake Moves Randomly

Possible causes:

  • MPU6050 not calibrated
  • Sensor vibration
  • Threshold values too low

Increasing the tilt threshold usually improves stability.


Why Use an MPU6050?

Compared to push buttons, the MPU6050 provides a much more interactive gaming experience.

Advantages include:

  • No mechanical buttons
  • Natural motion control
  • Fast response
  • Great introduction to accelerometers
  • Easy integration with ESP32

GitHub Source Code

The complete ESP32 Snake Game source code is available in the GitHub repository.

The repository includes:

  • Complete Arduino sketch
  • Wiring diagram
  • Required libraries
  • Images
  • Project files

Related Tutorials


Conclusion

Building a Snake game with an ESP32, MPU6050 and OLED display is an excellent way to learn embedded programming while creating a fun interactive project.

This project introduces several important concepts including I2C communication, motion sensing, OLED graphics, game logic and real-time input handling.

Once completed, you can extend it with additional features such as sound effects, wireless scoreboards or even multiplayer gameplay, making it a great foundation for more advanced ESP32 game development.