Pong Game with Arduino UNO and OLED Display – Project Explanation

 Project Overview

The Pong Game using Arduino UNO and OLED Display is a simple embedded systems project where a classic arcade-style Pong game is implemented using a microcontroller and a small display. The circuit design and simulation were created using Cirkit Designer.

In this project, the Arduino Uno controls an OLED display and reads player input (push buttons or potentiometer) to move the paddle and bounce the ball on screen.


 Objective

  • To develop a basic game using Arduino

  • To interface an OLED display with Arduino

  • To understand graphics rendering in embedded systems

  • To learn input-output handling


 Components Used



  • Arduino UNO

  • OLED Display (0.96” I2C – SSD1306)

  • Push Buttons or Potentiometer

  • Breadboard

  • Jumper Wires

  • USB Cable


Working Principle

Code:
/*
  A simple Pong game.
  https://notabug.org/Maverick/WokwiPong
 
  Based on Arduino Pong by eholk
  https://github.com/eholk/Arduino-Pong
*/

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

#define UP_BUTTON 2
#define DOWN_BUTTON 3

const unsigned long PADDLE_RATE = 64;
const unsigned long BALL_RATE = 16;
const uint8_t PADDLE_HEIGHT = 12;
const uint8_t SCORE_LIMIT = 9;

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);

bool game_over, win;

uint8_t player_score, mcu_score;
uint8_t ball_x = 53, ball_y = 26;
uint8_t ball_dir_x = 1, ball_dir_y = 1;

unsigned long ball_update;
unsigned long paddle_update;

const uint8_t MCU_X = 12;
uint8_t mcu_y = 16;

const uint8_t PLAYER_X = 115;
uint8_t player_y = 16;

void setup()
{
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

    // Display the splash screen (we're legally required to do so)
    display.display();
    unsigned long start = millis();

    pinMode(UP_BUTTON, INPUT_PULLUP);
    pinMode(DOWN_BUTTON, INPUT_PULLUP);

    pinMode(7, OUTPUT);
    digitalWrite(7, LOW);

    display.clearDisplay();
    drawCourt();

    while(millis() - start < 2000);

    display.display();

    ball_update = millis();
    paddle_update = ball_update;
}

void loop()
{
    bool update_needed = false;
    unsigned long time = millis();

    static bool up_state = false;
    static bool down_state = false;
   
    up_state |= (digitalRead(UP_BUTTON) == LOW);
    down_state |= (digitalRead(DOWN_BUTTON) == LOW);

    if(time > ball_update)
    {
        uint8_t new_x = ball_x + ball_dir_x;
        uint8_t new_y = ball_y + ball_dir_y;

        // Check if we hit the vertical walls
        if(new_x == 0 || new_x == 127)
        {
            ball_dir_x = -ball_dir_x;
            new_x += ball_dir_x + ball_dir_x;

            if (new_x < 64)
            {
                player_scoreTone();
                player_score++;
            }
            else
            {
                mcu_scoreTone();
                mcu_score++;
            }

            if (player_score == SCORE_LIMIT || mcu_score == SCORE_LIMIT)
            {
                win = player_score > mcu_score;
                game_over = true;
            }
        }

        // Check if we hit the horizontal walls.
        if(new_y == 0 || new_y == 53)
        {
            wallTone();
            ball_dir_y = -ball_dir_y;
            new_y += ball_dir_y + ball_dir_y;
        }

        // Check if we hit the CPU paddle
        if(new_x == MCU_X && new_y >= mcu_y && new_y <= mcu_y + PADDLE_HEIGHT)
        {
            mcuPaddleTone();
            ball_dir_x = -ball_dir_x;
            new_x += ball_dir_x + ball_dir_x;
        }

        // Check if we hit the player paddle
        if(new_x == PLAYER_X && new_y >= player_y && new_y <= player_y + PADDLE_HEIGHT)
        {
            playerPaddleTone();
            ball_dir_x = -ball_dir_x;
            new_x += ball_dir_x + ball_dir_x;
        }

        display.drawPixel(ball_x, ball_y, BLACK);
        display.drawPixel(new_x, new_y, WHITE);
        ball_x = new_x;
        ball_y = new_y;

        ball_update += BALL_RATE;

        update_needed = true;
    }

    if(time > paddle_update)
    {
        paddle_update += PADDLE_RATE;

        // CPU paddle
        display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, BLACK);
        const uint8_t half_paddle = PADDLE_HEIGHT >> 1;

        if(mcu_y + half_paddle > ball_y)
        {
            int8_t dir = ball_x > MCU_X ? -1 : 1;
            mcu_y += dir;
        }

        if(mcu_y + half_paddle < ball_y)
        {
            int8_t dir = ball_x > MCU_X ? 1 : -1;
            mcu_y += dir;
        }

        if(mcu_y < 1)
        {
            mcu_y = 1;
        }

        if(mcu_y + PADDLE_HEIGHT > 53)
        {
            mcu_y = 53 - PADDLE_HEIGHT;
        }

        // Player paddle
        display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, WHITE);
        display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, BLACK);

        if(up_state)
        {
            player_y -= 1;
        }

        if(down_state)
        {
            player_y += 1;
        }

        up_state = down_state = false;

        if(player_y < 1)
        {
            player_y = 1;
        }

        if(player_y + PADDLE_HEIGHT > 53)
        {
            player_y = 53 - PADDLE_HEIGHT;
        }

        display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, WHITE);

        update_needed = true;
    }

    if(update_needed)
    {
        if (game_over)
        {
            const char* text = win ? "YOU WIN!!" : "YOU LOSE!";
            display.clearDisplay();
            display.setCursor(40, 28);
            display.print(text);
            display.display();

            delay(5000);

            display.clearDisplay();
            ball_x = 53;
            ball_y = 26;
            ball_dir_x = 1;
            ball_dir_y = 1;
            mcu_y = 16;
            player_y = 16;
            mcu_score = 0;
            player_score = 0;
            game_over = false;
            drawCourt();
        }

        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 56);
        display.print(mcu_score);
        display.setCursor(122, 56);
        display.print(player_score);
        display.display();
    }
}

void playerPaddleTone()
{
    tone(11, 250, 25);
    delay(25);
    noTone(11);
}

void mcuPaddleTone()
{
    tone(11, 225, 25);
    delay(25);
    noTone(11);
}

void wallTone()
{
    tone(11, 200, 25);
    delay(25);
    noTone(11);
}

void player_scoreTone()
{
    tone(11, 200, 25);
    delay(50);
    noTone(11);
    delay(25);
    tone(11, 250, 25);
    delay(25);
    noTone(11);
}

void mcu_scoreTone()
{
    tone(11, 250, 25);
    delay(25);
    noTone(11);
    delay(25);
    tone(11, 200, 25);
    delay(25);
    noTone(11);
}

void drawCourt()
{
    display.drawRect(0, 0, 128, 54, WHITE);
}

  1. Display Initialization
    The OLED (I2C communication) is initialized using SDA and SCL pins.

  2. Game Logic Execution

    • A ball moves across the screen with X and Y coordinates.

    • A paddle is controlled by button or potentiometer input.

    • Collision detection checks:

      • Ball hitting paddle

      • Ball hitting top/bottom walls

    • Score increases when ball successfully bounces.

  3. Input Reading

    • Push button → Move paddle up/down

    • Potentiometer → Analog position control

  4. Screen Refresh
    The Arduino continuously updates:

    • Ball position

    • Paddle position

    • Score display


 Circuit Connections (I2C OLED)

OLED PinArduino UNO Pin
VCC5V
GNDGND
SDAA4
SCLA5

Buttons connected to digital pins with pull-down or pull-up resistors.


 Features

  • Real-time paddle control

  • Ball physics simulation

  • Collision detection

  • Score counter

  • Compact embedded gaming system


Applications

  • Learning embedded graphics

  • Understanding I2C communication

  • Beginner game development

  • Arduino display interfacing practice


Conclusion

The Pong Game project demonstrates how Arduino can be used not only for automation but also for interactive graphical applications. Using Cirkit Designer allows safe testing and visualization before hardware implementation. This project improves understanding of embedded programming, display interfacing, and game logic.

Comments