text.skipToContent text.skipToNavigation



Pong

Download Pong project resource Arduino Pong Game.zip

Create your own version of the classic Pong game with one of our super bright LED matrix boards and an Arduino Nano. Make a stylish talking point for your wall or coffee table, or just a fun game to play. Easy to assemble with Duinotech parts and no soldering, this retro project is something all ages can enjoy.

Components

XC4414 duinotech Nano
XC4622 White LED Dot Matrix Display or XC4621 Red LED Dot Matrix Display
XC4422 Duinotech Joystick Modules x2
XC4424 Buzzer Module
PB8817 Mini Breadboard
WC6028 Plug to Socket Linker Pack

Connections

Nano

DMD

Buzzer

Joystick 1 Joystick 2 Function

5V

5V

 

+5V +5V Power

GND

GND (x2)

 

GND GND Ground

2

 

-

    Ground for Buzzer

3

 

(middle)

    Not connected
4   S     Output for buzzer
6 2       Row selector A
7 4       Row selector B
8 10       Latch
9 1      

Output Enable

11 12       Serial Data
13 8       Serial Clock
A0     VRx   Player 1 position
A1     SW   Player 1 button
A2       VRx Player 2 position
A3       SW Player 2 button

 

The 5V connection to the DMD panel can go the pin 2 of the ICSP header, which will let you use plug-socket jumpers for all the connectors.

This is the pinout of the connector on the panel – the panel should be facing up (the arrows on the back of the panel will face up and right), and the connections are made to the left hand connector.

Pinout

Assembly

The first step will be to plug the Nano into the breadboard- note the picture below where the Nano is offset to allow extra connections on one side. The most difficult part of the assembly is the connections between the breadboard and the dot matrix panel. Ensure that you connect to the left hand connector (closest to VCC) looking at the back of the display. The older XC4250 and XC4251 have the same pinout, and can be connected the same way. Double check the connections, and make sure that no wires are in the wrong place. Note also the two wires running to the power screw terminals towards the middle of the display panel. The buzzer module is straightforward- it just connects to pins 2, 3 and 4. The joystick modules each have four wires. Power can be supplied through the USB port- even though it is recommended to run the display from a 3A supply, the panel should not have more than 50 LED’s on at a time (out of 512), shouldn’t need more than 300mA under normal use. You could use a 6xAA battery holder and feed power into the VIN and GND pins if you don’t want to be tethered to a USB cable. The joystick wires could be extended by plugging multiples plug-socket cables end to end.

General Layout
General Layout

JoystickDMD panel
Arrangement of Joystick (handle removed) and DMD panel

Nano and breadboard
Arrangement of Nano and breadboard

Code

The sketch for this project relies on a library called DMD to drive the panel, which in turn requires another library called TimerOne to automate the scanning of the display. These can be downloaded from https://github.com/freetronics/DMD/archive/master.zip and https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/arduino-timerone/TimerOne-r11.zip respectively. Make sure the libraries are installed, and then compile the code, making sure the Nano board is selected. If Pong does not come to life, there is probably a wiring error with the display. Try pressing down the joysticks- you should get sounds from the buzzer as the ball moves around.

Gameplay

The ball starts with Player 1 on the left, and is served by clicking down on the joystick. The bats can be moved up and down with the joysticks, and when a player misses, the other player scores a point and gets to serve. After one player gets to seven points, the game ends and a short tune plays, after which, the game returns to its starting state. The angle that the ball bounces off the bats depends on where it hits the bats, and also a little bit of randomness, just to stop the game from getting predictable.

Improvements

You’ll probably find it gets a bit awkward trying to play with the display panel floating around and the joysticks having such short leads, so the first step would be to mount the panel and install longer leads for the joysticks- you could even design a little 3d-printed box for the joysticks to make them easier to hold. If you don’t like the joysticks, an old-school paddle controller could be made from a small enclosure, a potentiometer and a pushbutton. Or make things really tricky and use an analog distance sensor like XC4585.

To tweak the skill levels, the delay between screen updates (currently 30ms) can be increased to make the ball move slower or increase to make it faster. The bat size is also a variable that can be changed.

The DMD library can support multiple panels, so there’s no reason that you can’t make a bigger display- you might just need to change the sketch to suit.

Sketch

#include <SPI.h>        //SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <DMD.h>        //
#include <TimerOne.h>   //

#define STICK1 A0
#define STICK2 A2
#define BUTTON1 A1
#define BUTTON2 A3
#define BATSIZE 4
#define SPEAKERPIN 4

DMD dmd(1, 1);

int nbm[]={32319,31744,24253,32437,31879,30391,30399,31777,32447,32439};
int score1=0;
int score2=0;
int ballx=0;
int bally=0;
int ballu=0;
int ballv=0;

void ScanDMD()
{
dmd.scanDisplayBySPI();
}

void setup(){
Timer1.initialize( 2000 );           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
Timer1.attachInterrupt( ScanDMD );   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
pinMode(BUTTON1,INPUT_PULLUP);
pinMode(BUTTON2,INPUT_PULLUP);
Serial.begin(9600);
pinMode(2,OUTPUT);
digitalWrite(2,LOW);    //ground for speaker
}

void loop(){
int speakertone=0;  //default to no tone playing
int p1,p2;
//read and display player 1 bat
p1=analogRead(STICK1);
p1=p1/60-1;
if(p1<0){p1=0;}
if(p1+BATSIZE>16){p1=16-BATSIZE;}
//read and display player 2 bat
p2=analogRead(STICK2);
p2=p2/60-1;
if(p2<0){p2=0;}
if(p2+BATSIZE>16){p2=16-BATSIZE;}
//display ball
if((ballx==0)&&(ballu==0)){
bally=p1+BATSIZE/2-1;
if(!digitalRead(BUTTON1)){ballu=1;ballv=0;      speakertone=256;}  //serve
}
if((ballx==30)&&(ballu==0)){
bally=p2+BATSIZE/2-1;
if(!digitalRead(BUTTON2)){ballu=-1;ballv=0;      speakertone=256;}  //serve
}
ballx=ballx+ballu;
if(ballx>30){ballx=0;ballu=0;ballv=0;score1=score1+1;if(score1==7){p1victory();}}  //P2 has missed, P1 wins
if(ballx<0){ballx=30;ballu=0;ballv=0;score2=score2+1;if(score2==7){p2victory();}}     //P1 has missed, P2 wins
if(ballx==29){//ball is in player 2's court
if(abs(bally-p2-1)<3){//ball is within p2's bat
ballu=-1; //goes back left
ballv=bally-p2-1; //change ball angle
if(ballv==0){ballv=random(-1,2);}  //mix it up a bit
speakertone=512;  //hit bat
}   
}
if(ballx==1){//ball is in player 1's court
if(abs(bally-p1-1)<3){//ball is within p1's bat
ballu=1; //goes back right
ballv=bally-p1-1; //change ball angle
if(ballv==0){ballv=random(-1,2);}  //mix it up a bit
speakertone=512;  //hit bat
}

int ballvtemp;    //to work out half steps
if(ballx&1){      //on odd steps, only step if 2
ballvtemp=ballv/2;
}else{            //on even steps, step if 1 or 2
ballvtemp=0;
if(ballv>0){ballvtemp=1;}
if(ballv<0){ballvtemp=-1;}
}
bally=bally+ballvtemp;
if(bally>13){bally=13;ballv=-1;speakertone=128;}  //hit wall
if(bally<1){bally=1;ballv=1;speakertone=128;}  //hit wall
//redraw screen from scratch every frame
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
net();
num(11,0,score1);   num(18,0,score2);
ball(ballx,bally);
paddle(0,p1,BATSIZE);
paddle(31,p2,BATSIZE);

  if(speakertone){tone(SPEAKERPIN,speakertone);}else{noTone(SPEAKERPIN);}   //play tone until next time
delay(30);  
}

void net(){
for(int i=0;i<16;i++){dmd.writePixel(15+(i%2),i,GRAPHICS_NORMAL,1);}
}

void num(int x,int y,int n){
for(int i=0;i<15;i++){
if(nbm[n%10]&(1<<i)){
dmd.writePixel(x+(i/5),y+(i%5),GRAPHICS_NORMAL,1);
}
}
}

void ball(int x,int y){            //draw 2x2 ball at x,y
dmd.writePixel(x,y,GRAPHICS_NORMAL,1);
dmd.writePixel(x+1,y,GRAPHICS_NORMAL,1);
dmd.writePixel(x,y+1,GRAPHICS_NORMAL,1);
dmd.writePixel(x+1,y+1,GRAPHICS_NORMAL,1);
}

void paddle(int x,int y,int s){      //draw paddle starting at x,y, extending s down
for(int i=0;i<s;i++){
dmd.writePixel(x,y+i,GRAPHICS_NORMAL,1);
}

void p2victory(){
int i;
for(i=0;i<8;i++){
tone(SPEAKERPIN,i*128);
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
net();
num(11,0,score1);   num(18,0,score2);
paddle(0,8,BATSIZE);
paddle(31,8,BATSIZE);
delay(300);
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
net();
num(11,0,score1);   //flash P2 score
paddle(0,8,BATSIZE);
paddle(31,8,BATSIZE);
delay(300);
}
//reset game state
score1=0;
score2=0;
ballx=0;
bally=0;
ballu=0;
ballv=0;
}

 

void p1victory(){
int i;
for(i=0;i<8;i++){
tone(SPEAKERPIN,i*128);
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
net();
num(11,0,score1);   num(18,0,score2);
paddle(0,8,BATSIZE);
paddle(31,8,BATSIZE);
delay(300);
dmd.clearScreen( true );     //clear/init the DMD pixels held in RAM
net();
num(18,0,score2);      //flash P1 score
paddle(0,8,BATSIZE);
paddle(31,8,BATSIZE);
delay(300);
}
//reset game state
score1=0;
score2=0;
ballx=0;
bally=0;
ballu=0;
ballv=0;
}

 

 



3D Printable Bracket:

See attached file: XC4622 bracket.zip.