Pages: [1] 2 3 ... 5   Go Down
Author Topic: Arduino Pong  (Read 37465 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello all,

I have been playing with pal signal generation, and decided to make pong as an example.

I based my video code on work by Benoît ROUSSEAU, found at
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1176757335.
But it didn't work for me, so I rewrote it using information found in.
http://www.amazon.com/Black-Video-Game-Console-Design/dp/0672328208

This code works for both of my (Australian) pal tvs.

All the source on one page
http://alastair.parker.googlepages.com/arduinopong



Title screen


during play


schematic

Code:
//Arduino Tv framebuffer
//Alastair Parker
//2007

// Video out voltage levels
#define _SYNC 0x00
#define _BLACK  0x01
#define _GRAY  0x02
#define _WHITE  0x03

// dimensions of the screen
#define WIDTH 38
#define HEIGHT 14

//number of lines to display
#define DISPLAY_LINES 240

// update speed for the main loop of the game
#define UPDATE_INTERVAL 1

// positions of the player paddles
#define PLAYER_RIGHT_X 35
#define PLAYER_LEFT_X 2

// maximum velocity of the ball
#define MAX_VEL 10

// locations of the top and bottom virtual bars
#define SCORE_BAR 3
#define BASE_BAR 13

// time to wait while paused
#define DONE_WAITING 30
#define SCORE_WAITING 30

// max player life
#define MAX_LIFE 6

//positions of player life bars
#define PLAYER_LEFT_LIFE 2
#define PLAYER_RIGHT_LIFE 23

// input pins for the player pots
#define PLAYER_LEFT_PIN 2
// currently the same pin
#define PLAYER_RIGHT_PIN 2

//video pins
#define DATA_PIN 9
#define SYNC_PIN 8

// the video frameBuffer
byte frameBuffer[WIDTH][HEIGHT];

// loop indices
byte index, index2;

// pal video line loop
byte line;
// current drawing line in framebuffer
byte newLine;

// flag used in wait loop to indicate that player just scored
boolean justScored=false;

// positions for the left paddle
byte playerLeft;
byte playerLeftOld=-1;
// life for the left player
byte playerLeftLife = MAX_LIFE;

// positions of the right paddle
byte playerRight;
byte playerRightOld=-1;
// life for the right player
byte playerRightLife = MAX_LIFE;

// positions and velocity of the ball
int ballXVel = -1;
int ballYVel = -5;
byte ballXPos = 14;
byte ballYPos = 7;

// loop counters to control the velocity of the ball
byte ballXLoop =MAX_VEL;
byte ballYLoop =MAX_VEL;

// loop counter to for the main loop delay
int waitingCount = 0;

// start postion of the loading bar
byte loadingBar = 2;

// if the left player should be updated or the right
boolean playerLeftTurn=true;

// if we should be waiting for something to happen
boolean waiting=true;
// if displaying the title
boolean showingTitle = true;

// value of the counter controlling the freq of updates
byte updateCounter=0;

// init the variables
void initGame()
{
  playerLeftOld=-1;
  playerLeftLife = MAX_LIFE;
  playerRightOld=-1;
  playerRightLife = MAX_LIFE;
  ballXVel = -1;
  ballYVel = -5;
  ballXPos = 14;
  ballYPos = 7;
  ballXLoop =MAX_VEL;
  ballYLoop =MAX_VEL;
  waitingCount = 0;
  loadingBar = 2;
  playerLeftTurn=true;
  waiting=true;
  showingTitle = true;
  justScored=false;
  updateCounter=0;
}

// draw a pixel to the buffer
void setPixel(byte x,byte y)
{
  frameBuffer[x][y]= _WHITE;
}

void grayPixel(byte x, byte y)
{
   frameBuffer[x][y]= _GRAY;
}

// draw a black pixel to the buffer
void clearPixel(byte x,byte y)
{
  frameBuffer[x][y]= _BLACK;
}

// draw a paddle
void drawPaddle(byte x,byte y,byte col)
{
  frameBuffer[x][y]= col;
  frameBuffer[x][y+1]= col;
}


//draw the title message
void drawArduinoPong()
{
  //arduino
  setPixel(7,3);
  setPixel(8,3);
  setPixel(15,3);
  setPixel(21,3);
  setPixel(6,4);
  setPixel(8,4);
  setPixel(14,4);
  setPixel(15,4);
  setPixel(28,4);
  setPixel(6,5);
  setPixel(7,5);
  setPixel(8,5);
  setPixel(10,5);
  setPixel(11,5);
  setPixel(13,5);
  setPixel(15,5);
  setPixel(17,5);
  setPixel(19,5);
  setPixel(21,5);
  setPixel(23,5);
  setPixel(24,5);
  setPixel(27,5);
  setPixel(29,5);
  setPixel(6,6);
  setPixel(8,6);
  setPixel(10,6);
  setPixel(14,6);
  setPixel(15,6);
  setPixel(18,6);
  setPixel(19,6);
  setPixel(21,6);
  setPixel(23,6);
  setPixel(25,6);
  setPixel(28,6);

  //pong
  setPixel(10,8);
  setPixel(11,8);
  setPixel(15,8);
  setPixel(16,8);
  setPixel(19,8);
  setPixel(22,8);
  setPixel(24,8);
  setPixel(25,8);
  setPixel(26,8);
  setPixel(10,9);
  setPixel(12,9);
  setPixel(14,9);
  setPixel(17,9);
  setPixel(19,9);
  setPixel(20,9);
  setPixel(22,9);
  setPixel(24,9);
  setPixel(10,10);
  setPixel(11,10);
  setPixel(14,10);
  setPixel(17,10);
  setPixel(19,10);
  setPixel(21,10);
  setPixel(22,10);
  setPixel(24,10);
  setPixel(26,10);
  setPixel(10,11);
  setPixel(15,11);
  setPixel(16,11);
  setPixel(19,11);
  setPixel(22,11);
  setPixel(24,11);
  setPixel(25,11);
  setPixel(26,11);
}

« Last Edit: August 20, 2007, 09:52:50 pm by Alastair.Parker » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

next chunk o' code

Code:
// do the main game logic regarding the ball and collisions
void updateBall(boolean xEvent)
{
  // if checking the x coords of the ball
  if(xEvent)
  {
    /*
    TODO
    
    have the yvel start 1t 1
    
    */
    
     //check for collision with left player
     if(ballXPos <PLAYER_LEFT_X+2 & (playerLeft == ballYPos or playerLeft+1 == ballYPos))
     {
       ballXVel = -1 * ballXVel;
       ballXPos=PLAYER_LEFT_X+1;
     }
     //check for collision with right player
     if(ballXPos > PLAYER_RIGHT_X-2  & (playerRight == ballYPos or playerRight+1 == ballYPos))
     {
       ballXVel = -1 * ballXVel;
       ballXPos=PLAYER_RIGHT_X-1;
     }
    
     //move the ball in the x direction
     if(ballXVel < 1)
     {
       ballXPos--;
     }
     else
     {
         ballXPos++;
     }
   }
  
  //if checking the y coords
  if(!xEvent)
  {
     //check for top of screen hit
     if( ballYPos <= SCORE_BAR )
     {
       ballYVel = -1 * ballYVel;
       ballYPos = SCORE_BAR;
     }
     //check for bottom of screen hit
     if( ballYPos>BASE_BAR )
     {
       ballYVel = -1 * ballYVel;
       ballYPos = BASE_BAR+1;
     }
    
     // move the ball in the y direction
     if(ballYVel < 1)
     {
       ballYPos--;
     }
     else
     {
       ballYPos++;
     }    
  }
  
  //always check for scoring of points
  
  //right player scores
  if(ballXPos==PLAYER_LEFT_X)//0
  {
    //reset the position of the ball
    ballXPos = PLAYER_RIGHT_X - 5;
    ballYPos=7;
    
    // change the other players life
    clearPixel(PLAYER_LEFT_LIFE+2*playerLeftLife-2,SCORE_BAR-1);
    playerLeftLife--;
    
    // indicate that someone scored so the wait doesn't start a new game
    justScored=true;
    // indicate that we should wait next loop
    waiting=true;

    // check if game is over
    if(playerLeftLife==0)
      justScored=false;      
   }
 
 //left player scores
 if(ballXPos==PLAYER_RIGHT_X)//38
 {
    //reset position of ball
    ballXPos = PLAYER_LEFT_X + 5;
    ballYPos=7;
    
    //update other players score
    clearPixel(PLAYER_RIGHT_LIFE+2*(MAX_LIFE-playerRightLife)+2,SCORE_BAR-1);
    playerRightLife--;
    
    // indicate that someone scored so the wain doesn't start a new game
    justScored=true;
    // indicate that we should wait next loop
    waiting=true;

    // check for game over
    if(playerRightLife==0)
      justScored=false;
 }
 
}

// draw the player life bars
void initScreen()
{
  for(index=1;index<=MAX_LIFE;++index)
  {
    grayPixel(index*2,SCORE_BAR-1);
    grayPixel(index*2+PLAYER_RIGHT_LIFE,SCORE_BAR-1);
  }
}


// draw the ball
void drawBall(byte col)
{
 frameBuffer[ballXPos][ballYPos]=col;
}

// clear the screen
void clearScreen()
{
    for (index = 0; index < WIDTH; index++)
      for (index2=0;index2<=HEIGHT;++index2)
        {
         frameBuffer[index][index2] = _BLACK;
        }
}

// the setup routine
void setup()
{
  pinMode (SYNC_PIN, OUTPUT);
  pinMode (DATA_PIN, OUTPUT);
  digitalWrite (SYNC_PIN, HIGH);
  digitalWrite (DATA_PIN, HIGH);
  clearScreen();
  drawArduinoPong();
}

void loop()
{
  // iterate over the lines on the tv
  for ( line =0;line< DISPLAY_LINES;++line)
    {
    
      // HSync
      // front porch (1.5 us)
      PORTB = _BLACK;
      delayMicroseconds(1.5);
      //sync (4.7 us)
      PORTB = _SYNC;
      delayMicroseconds(4.7);
      // breezeway (.6us) + burst (2.5us) + colour back borch (1.6 us)
      PORTB = _BLACK;
      delayMicroseconds(0.6+2.5+1.6);


      //calculate which line to draw to
      newLine = line >>4;
      delayMicroseconds(1);      
      
      //display the array for this line
      // a loop would have been smaller, but it messes the timing up
      PORTB = frameBuffer[0][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[1][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[2][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[3][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[4][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[5][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[6][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[7][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[8][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[9][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[10][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[11][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[12][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[13][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[14][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[15][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[16][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[17][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[18][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[19][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[20][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[21][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[22][newLine];
      delayMicroseconds(1);      
 
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

last chunk o' code

Code:
     PORTB = frameBuffer[23][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[24][newLine];
      delayMicroseconds(1);      
      PORTB = frameBuffer[25][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[26][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[27][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[28][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[29][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[30][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[31][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[32][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[33][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[34][newLine];
      delayMicroseconds(1);
      PORTB = frameBuffer[35][newLine];
      delayMicroseconds(1);
      
      // klugdge to correct timings
      PORTB = frameBuffer[36][newLine];      
      PORTB=PORTB;
      PORTB=PORTB;
      PORTB=PORTB;
      delayMicroseconds(3);
     }
    
 
  //vsync    
  PORTB = _SYNC;

  // if delaying for some reason
  if(waiting)
  {
      // increment the waiting counter
      waitingCount++;
      
      // if waiting is over for a new game
      if(waitingCount==DONE_WAITING & !justScored)
      {
        waitingCount=0;
        // if on title screen
        if(showingTitle)
        {
          // draw the loading bar
          grayPixel(loadingBar,BASE_BAR);
          loadingBar+=2;
          
          // done loading
          if (loadingBar > 37)
          {
            showingTitle=false;
            clearScreen();
            initGame();
            initScreen();
            waiting=false;
          }
        }
      }
      
      // waiting because someone scored
      if(waitingCount==SCORE_WAITING & justScored)
      {
         justScored=false;
         waitingCount=0;
         waiting=false;
      }
      // wait for the remainder of the sync period
      delayMicroseconds(305);
  }
  // not waiting
  else
  {
    // increment the update delay counter
    updateCounter++;
    if( updateCounter >= UPDATE_INTERVAL)
    {
      //player movement
      if(playerLeftTurn)
      {
        drawPaddle(PLAYER_LEFT_X,playerLeftOld,_BLACK);
        playerLeft = analogRead(PLAYER_LEFT_PIN)>>6;
        if(playerLeft<SCORE_BAR)
          playerLeft=SCORE_BAR;
        if(playerLeft>BASE_BAR)
          playerLeft=BASE_BAR;
        drawPaddle(PLAYER_LEFT_X,playerLeft,_WHITE);
        playerLeftOld=playerLeft;
        playerLeftTurn=false;
      }
      else
      {
        drawPaddle(PLAYER_RIGHT_X,playerRightOld,_BLACK);
        playerRight = analogRead(PLAYER_RIGHT_PIN)>>6;
        if(playerRight<SCORE_BAR)
          playerRight=SCORE_BAR;
        if(playerRight>BASE_BAR)
          playerRight=BASE_BAR;
        drawPaddle(PLAYER_RIGHT_X,playerRight,_WHITE);
        playerRightOld=playerRight;    
        playerLeftTurn=true;
      }
      // remainder of sync period
      delayMicroseconds(10);    
      updateCounter=0;
    }
    else
      // remainder of sync period
      delayMicroseconds(250);    

      // perform ball x velocity delay
      ballXLoop--;
      if(ballXLoop <= abs(ballXVel) and ballXVel != 0)
      {
        //ball movement;
        drawBall(_BLACK);
        updateBall(true);
        ballXLoop=MAX_VEL;
      }
      
      // perform ball y velocity delay      
      ballYLoop--;
      if(ballYLoop <= abs(ballYVel) and ballYVel != 0)
      {
        //ball movement;
        drawBall(_BLACK);
        updateBall(false);
        ballYLoop=MAX_VEL;
      }
      // draw new ball
      drawBall(_GRAY);      
  }
}

The pong code needs some work to make it better, but at least it works (for me anyway) as a proof of concept.

Alastair
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 11
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

That's awesome!   smiley
Logged

Poitiers (France)
Offline Offline
Full Member
***
Karma: 0
Posts: 136
Ca va j'vais le faire !
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

WOOOH ! GREAT JOB !
« Last Edit: August 22, 2007, 05:22:04 am by Benoit » Logged

Cordialement,
Benoît ROUSSEAU

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the comments, especially Benoit, as I based this on your code!

For a bit of a write up on the process, have a look at
http://alastair.parker.googlepages.com/arduinopong

Enjoy playing pong!

Alastair
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 18
M0OOPHONE
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

WOAh !!! Awsome job !! I ve built it too based on your source, and will post some pics later today possibly, but I m having some sync issues, how should I edit it in order to make it work on NTSC ?

Thanks for sharing and good job !  ;D
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Heres some info that I forgot about, which is important if you guys are actually going to run pong!

In order to get the timings exact, you will need to disable the timer interrupt on the atmega168

Edit the file lib/targets/arduino/wiring.c
and block comment out the following

    // enable timer 0 overflow interrupt
#if defined(__AVR_ATmega168__)
    sbi(TIMSK0, TOIE0);
#else
    sbi(TIMSK, TOIE0);
#endif


ie:

make it look like this

/*
    // enable timer 0 overflow interrupt
#if defined(__AVR_ATmega168__)
    sbi(TIMSK0, TOIE0);
#else
    sbi(TIMSK, TOIE0);
#endif
*/



Getting nstc to work.

According to http://www.amazon.com/Black-Video-Game-Console-Design/dp/0672328208
pal has 625 total lines (about 575 visible) and ntsc has 525(about 480 visible), because arduino pong ignores interlacing, the effective number of lines is cut in half.
If someone were to try to get it to work on ntsc, I would start by decreasing the number of displayed lines, which will probably mess up the timing, so some experimentation would be needed with the remainder of the delays after vsync.

Perhaps someone with an ntsc tv can have a go at hacking the code and then sharing the results so other ntsc hackers can enjoy pong aswell.

Sorry I can't be more help right now, I will try to look into it, but without an ntsc tv, testing is hard smiley

Alastair
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 18
M0OOPHONE
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

thanks alistair. I m gonna dedicate tonight 's .. night. at it. Will try to post my results !

 :smiley
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 18
M0OOPHONE
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

DAMMNNNN I don t have any more 75() and 900() !!!!!  :'(
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The 75ohm is required, but the 450 and 900 arent so critical, more common values like 430 and 1k could probably be used instead, just remember to check the volatges before you plug anthing into the tv !

have a read of http://www.rickard.gunee.com/projects/video/pic/howto.php, he explains the overall process quite well, including voltage info

Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 56
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Very nice! I tried it tonight but with not much luck. BUT I don't have a 75 Ohm resistor so I tried a 68 and 85 resistor. I can see that the program is running, but it's jumpy and not very much in sync. Is it possible that this happens because I don't have 75 ohm resistor ? Also, you mentioned something about disabling the timer interrupt? Is this only for the atmega168 ? I'm running it on an atmega8.

We have PAL tv's here in South Africa so thats not the problem. Any information would be greatly appreciated. Once I get your example going I would like to try and get something like tetris or even snake going smiley
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 40
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Try putting
Code:
cli();
in the setup routine. That should disable the interrupts.  
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

disabling the timer is quite important, each scan line in the signal must take 63( +/- 0.5)us, if the interrupt fires during the line generation, it will blow out the timing significantly, I will try to take a picture of what I get when the timer is NOT disabled, then you can compare what you're getting.

I'm not too sure how to disable it on the amtega8, but try using agent_orange's solution of 'cli();' somewhere in the setup() routine.

with regards to the 75ohm resistor, I can't be sure how critical it is, the most important thing is that you can generate 4 distinct voltages wrt the tv signal pin.

the voltages that you are aiming for are close to:
sync = 0.0v
black = 0.33v
gray = 0.66
white 1v

I was able to generate approx:
sync = 0v
black= 0.30
gray = 0.64
white = 0.95

Check your results with a multimeter first, to see what you get.

Good luck!

Oh, on a side note, I can't verify how correct the PAL signal is, (I don't have an oscilloscope) so I only know that this works on the 2 tvs that I have (which are both pal, but quite new).
If anyone has actually got this working, maybe they could post back with their resistor values and geographical location.

Al
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Heres what I get (on the 168) when I don't disable the interrupt




And this is what I get when i put 'cli();' as the first line in setup()


So it seems that cli(); works just as well as changing wiring.c, and it's easier!
Thanks agent_orange.

If your getting something similar to the first img, try the cli(); method.

Al
Logged

Pages: [1] 2 3 ... 5   Go Up
Jump to: