Arduino Game Project
Pong Game on Arduino UNO with OLED Display
A classic arcade Pong game running on Arduino UNO with a 0.96″ SSD1306 OLED — featuring real-time ball physics, a CPU opponent, push-button controls and sound effects from a piezo buzzer.
01 — Bill of Materials
Components Required
Everything you need to build this project. The buzzer is optional but adds great sound feedback.
02 — Concept Overview
How It Works
The Arduino drives a 128×64 OLED screen over I2C. Every 16 ms the ball moves one pixel; every 64 ms the paddles update. The CPU opponent tracks the ball automatically, while the player uses UP/DOWN buttons. A buzzer fires different tones for paddle hits, wall bounces and scoring events.
⚡ Timing (millis)
- BALL_RATE = 16 ms
- PADDLE_RATE = 64 ms
- No blocking delays in game loop
🎯 Collision Detection
- Ball vs top/bottom walls
- Ball vs left/right walls (score)
- Ball vs player paddle
- Ball vs CPU paddle
🖼️ Graphics Functions
- drawPixel() — ball
- drawFastVLine() — paddles
- drawRect() — court
- setCursor/print() — score
🔊 Sound Events
- Player paddle hit (250 Hz)
- CPU paddle hit (225 Hz)
- Wall bounce (200 Hz)
- Score jingle (two-tone)
03 — Visual Schematic
Circuit Diagram
Arduino UNO + SSD1306 OLED + Push Buttons + Buzzer
04 — Pin Connections
Wiring Connections
SSD1306 OLED → Arduino UNO
| OLED Pin | Arduino Pin | Wire | Note |
|---|---|---|---|
| VCC | 5V | Red | Power supply |
| GND | GND | Black | Ground |
| SDA | A4 | Yellow | I2C Data |
| SCL | A5 | Blue | I2C Clock |
Push Buttons → Arduino UNO
| Button | Arduino Pin | Wire | Note |
|---|---|---|---|
| UP Button (one leg) | D2 | Purple | INPUT_PULLUP — reads LOW when pressed |
| UP Button (other leg) | GND | Black | No external resistor needed |
| DOWN Button (one leg) | D3 | Teal | INPUT_PULLUP — reads LOW when pressed |
| DOWN Button (other leg) | GND | Black | Shared GND bus |
Piezo Buzzer → Arduino UNO
| Buzzer Pin | Arduino Pin | Wire | Note |
|---|---|---|---|
| Positive (+) | D11 | Orange | tone() signal output |
| Negative (−) | GND | Black | Ground |
05 — Sketch
Full Arduino Code
Install Adafruit_SSD1306 and Adafruit_GFX via the Arduino IDE Library Manager before uploading.
/* Pong Game — Arduino UNO + SSD1306 OLED MakeMindz.com | makemindz.com/2026/02/pong-game-with-arduino-uno-and-oled.html Based on Arduino Pong by eholk (github.com/eholk/Arduino-Pong) Original Wokwi version: notabug.org/Maverick/WokwiPong */ #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, 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.display(); // Adafruit splash screen 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); // 2-second intro display.display(); ball_update = paddle_update = millis(); } void loop() { bool update_needed = false; unsigned long time = millis(); static bool up_state = false, down_state = false; up_state |= (digitalRead(UP_BUTTON) == LOW); down_state |= (digitalRead(DOWN_BUTTON) == LOW); /* ── BALL UPDATE ── */ if(time > ball_update) { uint8_t new_x = ball_x + ball_dir_x; uint8_t new_y = ball_y + ball_dir_y; // Vertical walls → scoring 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; } } // Horizontal walls → bounce if(new_y == 0 || new_y == 53) { wallTone(); ball_dir_y = -ball_dir_y; new_y += ball_dir_y + ball_dir_y; } // CPU paddle collision 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; } // Player paddle collision 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; } /* ── PADDLE UPDATE ── */ if(time > paddle_update) { paddle_update += PADDLE_RATE; const uint8_t hp = PADDLE_HEIGHT >> 1; // CPU AI tracks ball display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, BLACK); if(mcu_y + hp > ball_y) mcu_y += (ball_x > MCU_X ? -1 : 1); if(mcu_y + hp < ball_y) mcu_y += (ball_x > MCU_X ? 1 : -1); mcu_y = constrain(mcu_y, 1, 53 - PADDLE_HEIGHT); display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, WHITE); // Player movement 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; player_y = constrain(player_y, 1, 53 - PADDLE_HEIGHT); display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, WHITE); update_needed = true; } /* ── DISPLAY ── */ 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); // Reset everything ball_x = 53; ball_y = 26; ball_dir_x = 1; ball_dir_y = 1; mcu_y = player_y = 16; mcu_score = player_score = 0; game_over = false; display.clearDisplay(); drawCourt(); } display.setTextColor(WHITE, BLACK); display.setCursor(0, 56); display.print(mcu_score); display.setCursor(122, 56); display.print(player_score); display.display(); } } /* ── SOUND FUNCTIONS ── */ 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); }
06 — Build Guide
Step-by-Step Instructions
Install Required Libraries
Open Arduino IDE → Sketch → Include Library → Manage Libraries. Search for and install Adafruit SSD1306 and Adafruit GFX Library. Both are required. Wire.h and SPI.h come built-in.
Wire the SSD1306 OLED Display
Connect OLED VCC → 5V, GND → GND, SDA → A4, SCL → A5. Most 0.96″ breakout modules have these four pins in order.
0x3C. If your display doesn't initialise, try 0x3D and update the display.begin() call accordingly.Connect the UP and DOWN Buttons
Wire one leg of the UP button to D2 and the other to GND. Repeat for the DOWN button on D3. The code uses INPUT_PULLUP, so no external resistors are needed.
Wire the Piezo Buzzer
Connect the buzzer's positive pin to D11 and negative to GND. The tone() function will generate frequencies automatically for each game event.
Upload the Sketch
Copy the full code above into Arduino IDE. Select Board: Arduino UNO and the correct COM port. Click Upload. The OLED will show the Adafruit splash for 2 seconds, then draw the Pong court.
Play!
Press the UP and DOWN buttons to move your right-side paddle. The CPU controls the left paddle automatically. First to 9 points wins. After 5 seconds the game resets automatically.
map(analogRead(A0), 0, 1023, 1, 41) to set player_y.08 — Try It Online
Simulation Links
Wokwi Online Simulator
Wokwi is the recommended simulator for this project — it supports SSD1306 OLED, push buttons and the tone() function natively. No hardware needed.
Cirkit Designer Visual Simulation
Load the diagram.json above to get a fully-wired schematic view, then run the simulation inside Cirkit Designer.
▶ Open Cirkit Designer09 — What You Gain
Learning Outcomes & Applications
🎓 Skills Learned
- I2C communication protocol
- SSD1306 OLED interfacing
- Embedded graphics (Adafruit GFX)
- Non-blocking timing with millis()
- Collision detection logic
- Digital INPUT_PULLUP buttons
- tone() / noTone() sound output
🚀 Applications
- Embedded game development
- Graphics programming basics
- OLED display learning projects
- STEM / robotics education
- Game physics simulation
10 — Keep Building
More MakeMindz Projects
Fun & Interactive
Arduino UNO Bluetooth-Controlled Servo Motor
Fun & Interactive
Interactive Touch & Distance Sensing with LED & Servo
IoT & Smart Systems
Environmental Monitoring with OLED & LCD
Display Projects
Digital Clock with DS3231 RTC & TM1637 Display
Robotics
Smart Obstacle Avoiding Robot (Ultrasonic + IR)
LED & Display
Lighting System with Gesture & Sound Interaction
.png)
Comments
Post a Comment