As a side effect of my electronics hobby I developed the addiction of online parts shopping. From time to time I browse the well known electronics shops looking for parts I don’t need. One of the brands with the highest wanna have factor is Adafruit. Adafruit has a wide verity of neat little components that are accompanied with clear documentation and nice code samples. One of my recent Adafruit purchases is an 0.96" monochrome 128x64 pixel OLED display. During my purchase, I had no idea what to use it for … until I got my hands on it!
The Adafruit OLED display can be used with either an SPI or I2C interface. Typically SPI is a bit faster, while I2C uses less wires. It can be used on both 3V3 as 5V and thus it’s Arduino ready. Just wire it up, and start coding …
Since I love the simplicity of I2C, I configured my OLED for this kind of usage, and started doing some tests. Soon I fell in love with this little bright screen, allowing me to built more useless stuff without the need of extra screen controllers or lots of wires.
The goal
One of the animating scripts I always tend to build, is a small ball (or in this case, pixel) bouncing against all sides of the screen. Only this time I took it a little bit further. With the help of a few extra components I set the goal to re-built the original PONG.
PONG is one of the first arcade video games. Or as Wikipedia states:
Pong is one of the earliest arcade video games; it is a tennis sports game featuring simple two-dimensional graphics. While other arcade video games suchas Computer Space came before it, Pong was one of the first video games to reach mainstream popularity. The aim is to defeat the opponent in a simulated table tennis game by earning a higher score. The game was originally manufactured by Atari Incorporated (Atari), who released it in 1972.
If they were able to build it back then. I should be able to built it now.
The hardware
Technically the hardware of my PONG game is very straight forward. The whole project consists of the following components:
- An Arduino. In this example I used the Uno
- A breadboard
- The Adafruit 128x64 OLED screen (In I2C mode!)
- 2 x 10K potentiometers
- A piezo buzzer
- A hand full of breadboard wires
Wiring these components only took me a few minutes. Eventually resulting in the following setup:
-
The Adafruit OLED screen
- DATA connects to Arduino's A4 pin
- CLK connects to Arduino's A5 pin
- RST connects to Arduino’s pin 4
- Vin connects to 5V (via the breadboard)
- GND connects to Ground (via the breadboard)
-
The piezo
- The negative lead connects to Ground (via the breadboard)
- The positive lead connects to Arduino’s pin 3
-
Potentiometer A
- The left pin connects to Ground (via the breadboard)
- The center pin connects to Arduino's A0 pin
- The right pin connects to 5V (via the breadboard)
-
Potentiometer B
- The left pin connects to 5V (via the breadboard)
- The center pin connects to Arduino's A1 pin
- The right pin connects to Ground (via the breadboard)
An that concludes all the wiring. Time for some coding!
The code
To use the screen, make sure both the Adafruit_GFX and the Adafruit_SSD1306 libraries are installed. As soon as this is done, we’re ready to go and start the PONG sketch …
External libraries have no use when they are not included. So first things first …
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
As a second step, I prefer to defines some macro’s. This allow me to easily modify settings in the future.
//Define Pins
#define OLED_RESET 4
#define BEEPER 3
#define CONTROL_A A0
#define CONTROL_B A1
//Define Visuals
#define FONT_SIZE 2
#define SCREEN_WIDTH 127 //real size minus 1, because coordinate system starts with 0
#define SCREEN_HEIGHT 63 //real size minus 1, because coordinate system starts with 0
#define PADDLE_WIDTH 4
#define PADDLE_HEIGHT 10
#define PADDLE_PADDING 10
#define BALL_SIZE 3
#define SCORE_PADDING 10
#define EFFECT_SPEED 0.5
#define MIN_Y_SPEED 0.5
#define MAX_Y_SPEED 2
Additionally I declare all necessary variables.
//Define Variables
Adafruit_SSD1306 display(OLED_RESET);
int paddleLocationA = 0;
int paddleLocationB = 0;
float ballX = SCREEN_WIDTH/2;
float ballY = SCREEN_HEIGHT/2;
float ballSpeedX = 2;
float ballSpeedY = 1;
int lastPaddleLocationA = 0;
int lastPaddleLocationB = 0;
int scoreA = 0;
int scoreB = 0;
It’s time to do some setup … the Arduino likes a good setup, so who am I to leave it out?
//Setup
void setup()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64)
display.clearDisplay(); // clears the screen and buffer
display.display();
display.setTextWrap(false);
splash();
display.setTextColor(WHITE);
display.setTextSize(FONT_SIZE);
display.clearDisplay();
}
As you can see, halfway I call the splash function. This function will show the splash screen, and will do so, until someone uses one of the controls.
//Splash Screen
void splash()
{
display.clearDisplay();
display.setTextColor(WHITE);
centerPrint("PONG",0,3);
centerPrint("By Allan Alcorn",24,1);
centerPrint("Ported by",33,1);
centerPrint("MichaelTeeuw.nl",42,1);
display.fillRect(0,SCREEN_HEIGHT-10,SCREEN_WIDTH,10,WHITE);
display.setTextColor(BLACK);
centerPrint("Move paddle to start!",SCREEN_HEIGHT-9,1);
display.display();
int controlA = analogRead(CONTROL_A);
int controlB = analogRead(CONTROL_B);
while (abs(controlA - analogRead(CONTROL_A) + controlB - analogRead(CONTROL_B)) < 10) {
// show as long as the total absolute change of
// both potmeters is smaler than 5
}
soundStart();
}
The splash function checks both control A and B and will do this until the total change of these potentiometers is more than 10. Additionally this function uses centerText()
and soundStart()
for some extra audiovisual candy. These functions are defined at the end of the sketch, so keep on reading!
As much as the Arduino loves the setup()
function, it also needs a loop.
//Loop
void loop()
{
calculateMovement();
draw();
}
As you can see I kept this one very simple. I call calculateMovement()
to update all variables anddraw()
to update the screen. First, let’s take a look at the calculateMovement()
function.
//Movement calculator
void calculateMovement()
{
int controlA = analogRead(CONTROL_A);
int controlB = analogRead(CONTROL_B);
paddleLocationA = map(controlA, 0, 1023, 0, SCREEN_HEIGHT - PADDLE_HEIGHT);
paddleLocationB = map(controlB, 0, 1023, 0, SCREEN_HEIGHT - PADDLE_HEIGHT);
int paddleSpeedA = paddleLocationA - lastPaddleLocationA;
int paddleSpeedB = paddleLocationB - lastPaddleLocationB;
ballX += ballSpeedX;
ballY += ballSpeedY;
//bounce from top and bottom
if (ballY >= SCREEN_HEIGHT - BALL_SIZE || ballY <= 0) {
ballSpeedY *= -1;
soundBounce();
}
//bounce from paddle A
if (ballX >= PADDLE_PADDING && ballX <= PADDLE_PADDING+BALL_SIZE && ballSpeedX < 0) {
if (ballY > paddleLocationA - BALL_SIZE && ballY < paddleLocationA + PADDLE_HEIGHT) {
soundBounce();
ballSpeedX *= -1;
addEffect(paddleSpeedA);
}
}
//bounce from paddle B
if (ballX >= SCREEN_WIDTH-PADDLE_WIDTH-PADDLE_PADDING-BALL_SIZE && ballX <= SCREEN_WIDTH-PADDLE_PADDING-BALL_SIZE && ballSpeedX > 0) {
if (ballY > paddleLocationB - BALL_SIZE && ballY < paddleLocationB + PADDLE_HEIGHT) {
soundBounce();
ballSpeedX *= -1;
addEffect(paddleSpeedB);
}
}
//score points if ball hits wall behind paddle
if (ballX >= SCREEN_WIDTH - BALL_SIZE || ballX <= 0) {
if (ballSpeedX > 0) {
scoreA++;
ballX = SCREEN_WIDTH / 4;
}
if (ballSpeedX < 0) {
scoreB++;
ballX = SCREEN_WIDTH / 4 * 3;
}
soundPoint();
}
//set last paddle locations
lastPaddleLocationA = paddleLocationA;
lastPaddleLocationB = paddleLocationB;
}
It looks like a LOT of code. But it’s mostly some calculations to check if the ball hits the wall or one of the paddles. Additionally it adds some sound using soundBounce()
and soundPoint()
, but more important, it adds effect to the ball when the ball hits one of the paddles by usingaddEffect(paddleSpeed)
. We get to this effect function soon. But first it’s time to do some drawing …
//draw the visuals
void draw()
{
display.clearDisplay();
//draw paddle A
display.fillRect(PADDLE_PADDING,paddleLocationA,PADDLE_WIDTH,PADDLE_HEIGHT,WHITE);
//draw paddle B
display.fillRect(SCREEN_WIDTH-PADDLE_WIDTH-PADDLE_PADDING,paddleLocationB,PADDLE_WIDTH,PADDLE_HEIGHT,WHITE);
//draw center line
for (int i=0; i<SCREEN_HEIGHT; i+=4) {
display.drawFastVLine(SCREEN_WIDTH/2, i, 2, WHITE);
}
//draw ball
display.fillRect(ballX,ballY,BALL_SIZE,BALL_SIZE,WHITE);
//print scores
//backwards indent score A. This is dirty, but it works ... ;)
int scoreAWidth = 5 * FONT_SIZE;
if (scoreA > 9) scoreAWidth += 6 * FONT_SIZE;
if (scoreA > 99) scoreAWidth += 6 * FONT_SIZE;
if (scoreA > 999) scoreAWidth += 6 * FONT_SIZE;
if (scoreA > 9999) scoreAWidth += 6 * FONT_SIZE;
display.setCursor(SCREEN_WIDTH/2 - SCORE_PADDING - scoreAWidth,0);
display.print(scoreA);
display.setCursor(SCREEN_WIDTH/2 + SCORE_PADDING+1,0); //+1 because of dotted line.
display.print(scoreB);
display.display();
}
As you can see, drawing is a small part of the sketch. Most of the magic was already done in thecalculateMovement()
function. But, as promised, we’ll add some effect to the ball in theaddEffect(paddleSpeed)
function:
//add affect to the ball
void addEffect(int paddleSpeed)
{
float oldBallSpeedY = ballSpeedY;
//add effect to ball when paddle is moving while bouncing.
//for every pixel of paddle movement, add or substact EFFECT_SPEED to ballspeed.
for (int effect = 0; effect < abs(paddleSpeed); effect++) {
if (paddleSpeed > 0) {
ballSpeedY += EFFECT_SPEED;
} else {
ballSpeedY -= EFFECT_SPEED;
}
}
//limit to minimum speed
if (ballSpeedY < MIN_Y_SPEED && ballSpeedY > -MIN_Y_SPEED) {
if (ballSpeedY > 0) ballSpeedY = MIN_Y_SPEED;
if (ballSpeedY < 0) ballSpeedY = -MIN_Y_SPEED;
if (ballSpeedY == 0) ballSpeedY = oldBallSpeedY;
}
//limit to maximum speed
if (ballSpeedY > MAX_Y_SPEED) ballSpeedY = MAX_Y_SPEED;
if (ballSpeedY < -MAX_Y_SPEED) ballSpeedY = -MAX_Y_SPEED;
}
And of course, we add the functions for the sound and central printing of the text.
//startSound - sound before the game starts including delay
void soundStart()
{
tone(BEEPER, 250);
delay(100);
tone(BEEPER, 500);
delay(100);
tone(BEEPER, 1000);
delay(100);
noTone(BEEPER);
}
//soundBounce
void soundBounce()
{
tone(BEEPER, 500, 50);
}
//soundPoint
void soundPoint()
{
tone(BEEPER, 150, 150);
}
//centerPrint
void centerPrint(char *text, int y, int size)
{
display.setTextSize(size);
display.setCursor(SCREEN_WIDTH/2 - ((strlen(text))*6*size)/2,y);
display.print(text);
}
And that’s all there is! After hitting the upload button, playing PONG is all that is left.
The result
The end result looked something like this:
Although this version did not yet include the Splash screen, and did not yet have the effect option built in.
If you want to built this yourself, feel free to grab the full sketch from my GitHub account. And don’t forget to post a picture of your PONG build in the comments.
Next project? Building a Play Station 4 clone using some matches and duct tape. Any suggestions where to start?
NOTE: This post was in no way sponsored by Adafruit … Although I wouldn’t mind if they did … ;)