Wednesday, 10 February 2010

DIY Games Console

Another PIC project... this one using a 14-pin 16F688 and playing a version of "Breakout". I might see if I can get a convincing version of "Space Invaders" to run on that 8x8 matrix too....



Usual setup of 74HC595 shift registers (x3) and ULN2803 NPN transistor arrays (x2). Columns are driven directly thru 100R resistors from one of the 595's... rows alternate red LEDs/green LEDs and are driven via 2 x chained 595's (data out from one goes to data in on the other) which in turn drive the NPN arrays, so only 5 I/O's from the MCU are needed to drive the display..

- Data in for the 595 driving the columns
- Shift clock for the 595 driving the columns
- Data in for the first of chained 595's driving the rows
- Shift clock for the pair of 595's driving the columns
- Store clock line for all 3 x 595's

There are 4 buttons: 3 are connected to PIC I/O for controlling game (only 2 used for Breakout game) and other is MCU reset (grounds MCLR#). 12k pull up resistors on all 4 lines.

The piezo buzzer is connected to the remaining I/O via a 0.1uF capacitor.

The LED matrix was from Sure Electronics (on eBay).. I got 10 of them for about £10. It is red/green but by driving both you get orange.

Here is the PIC code


#include <system.h>
#include <memory.h>

// Config bits
#pragma DATA _CONFIG, _WDT_OFF & _INTRC_OSC_NOCLKOUT 

// Clock freq (for SourceBoost delayt functions)
#pragma CLOCK_FREQ 8000000

typedef unsigned char byte;

// define IO ports
#define P_STORE     porta.2
#define P_DT_ROW    porta.4
#define P_SH_ROW    porta.5
#define P_DT_COL    portc.0
#define P_SH_COL    portc.1
#define P_BUTTON3 portc.5
#define P_SPEAKER portc.3
#define P_BUTTON1 portc.4
#define P_BUTTON2 portc.2

// define IO port direction
#define P_TRISA 0b00000000
#define P_TRISC 0b00110100

// macro defs
#define SET_RED(x,y) disp[y]|=1<<(7-(x))
#define SET_GREEN(x,y) disp[8+(y)]|=1<<(7-(x))
#define SET_ORANGE(x,y) SET_RED(x,y); SET_GREEN(x,y)    
#define BUTTON_DEBOUNCE 2

// info used by the interrupt handler
byte soundPhase = 0;
byte soundPeriod =  100;
byte soundDur = 0;

// display buffer (rows 0-7 for red, 8-15 for green)
byte disp[16];

//////////////////////////////////////////////////////
//
// interrupt handler 
//
// timer0 interrupt is used to drive the piezo speaker
//
void interrupt( void )
{
// check if this is timer0 overflow event
if( intcon.2 )
{
// drive the piezo sounder
soundPhase=!soundPhase;
P_SPEAKER = soundPhase?1:0;

// still sounding?
if(!soundDur)
{
// stop the interrupt.. killing sound
intcon.5 = 0;
}
else
{
// still sounding
--soundDur;
}

// setup the next timer interrupt
tmr0=soundPeriod;

// clear interrupt flag
intcon.2 = 0;

}
}

//////////////////////////////////////////////////////
//
// beep
// 
// start a sound playing
//
void beep(byte pitch, byte dur)
{
soundPeriod = 255-pitch;
soundDur = dur;
intcon.5 = 1;
}

//////////////////////////////////////////////////////
//
// refresh
// 
// update the LED matrix based on content of the 
// disp[] array
//
void refresh()
{
  int i;
  
  // clear vertical shift register and load a logic 1 at 
  // bit position 0. This bit will be shifted along to 
  // drive each row of the LED matrix in turn
  for(i=0;i<16;++i)
  {
    P_SH_ROW = 0;
    P_DT_ROW = (i==15)?1:0;
    P_SH_ROW = 1;
  }
  P_DT_ROW = 0;

  // for each row of data (8 x red, 8 x green)
  for(i=0;i<16;++i)
  {
// this cross reference of vertical bit position to row of the 
// disp[] array is used since the matrix is connected for wiring
// convenience and the order of rows is different
byte ix[16] = { 15, 7, 14, 6, 13, 5, 12, 4, 0, 8, 1, 9, 2, 10, 3, 11 };

// look up the row data byte
    byte d=disp[ix[i]];
    
    // store clock low
    P_STORE = 0;

    // load the 8 bits of data    
    for(int j=0;j<8;++j)
    {
      // shift a column bit
      P_SH_COL = 0;
      P_DT_COL = d&1;
      P_SH_COL = 1;
      d>>=1;
    }
    
    // store clock high.. row data is clocked to 
    // the output of shift registers, simultaneously
    // with the clocking in of a new scan row in the
    // vertical shift registers
    P_STORE = 1;

    // set pins low again and add a "display delay"
    // while the row data is shown, before it is 
    // hidden again
    P_SH_ROW = 0;
    P_DT_COL = 0;
    delay_ms(1);  
    P_SH_ROW = 1;
    P_STORE = 0;
  }
  P_SH_COL = 0;  
  P_SH_ROW = 0;  
}    
   
void breakout()
{
int i;
byte bricks[8];
byte rowsOfBricks=3;
byte speed = 250;
byte lives=3;

// loop for each level
for(;;)
{
// setup the wall
memset(bricks,0,sizeof(bricks));
memset(bricks,255,rowsOfBricks);
    
// init variables
char x=3; // position of bat
char bx=4; // position of ball
char by=6;
char dx=0; // direction of ball
char dy=-1;

// ball movement counter. Set to a value to
// give a short delay at the start of a level
byte bc = 100;  

// counter used to debounce the movement buttons
byte buttonDebounce = 0;

// loop until level is complete
for(;;)
{
// do we need to move the ball?
if(++bc == 0)
{
// reset the counter
bc = speed;

// calc next ball position
char nx = bx + dx;
char ny = by + dy;
if(nx<0||nx>7) // off screen left or right
{
dx=-dx;
nx=bx;
}
if(ny<0||ny>7) // off screen top or bottom
{
dy=-dy;
ny=by;
}
if(ny==7) // on the bottom row?
{
if(bx==x) // flat hit left side
{
if(dx>0) dx=0; else dx=-1;
beep(200,50);
}
else if(bx==x+1) // hit right side
{
if(dx<0) dx=0; else dx=1;
beep(200,50);
}
else if(nx==x) // hit left end
{
dx=-1;
beep(100,50);
}
else if(nx==x+1) // hit right end
{
dx=1;
beep(100,50);
}
else
{
// ball has dropped off bottom of screen
for(i=0;i<3;++i)
{
// death routine
refresh();
beep(50,100);
delay_ms(100);
refresh();
beep(150,100);
delay_ms(100);
}

// lose a life
if(lives-- <= 0)
{
// all lives gone
for(;;)
{
for(i=0;i<50;++i)
refresh();
delay_ms(500);
}
}
else
{
// start of next round
x=3;
bx=4;
by=6;
dx=0;  
bc=100;
}
}

// common stuff
nx=bx;
ny=by;
dy=-1;
}

// move the ball
bx = nx;
by = ny;

// hit a brick?
if(bricks[by]&(1<<(7-bx)))
{
// remove the brick and bounce
bricks[by]&=~(1<<(7-bx));
dy=-dy;
beep(100,200);

// any bricks left?
byte allGone = 1;
for(i=0;i<sizeof(bricks);++i)
{
if(bricks[i])
{
allGone=0;
break;
}
}

// end of level
if(allGone)
{
// beep
for(i=0;i<10;++i)
{
beep(50,50);
delay_ms(100);
}

// add more bricks
if(rowsOfBricks<5)
{
rowsOfBricks++;
}
else
{
// or make it faster
if(speed < 254)
++speed;
}
break;
}
}
}

// prepare screen buffer
memcpy(&disp[0], bricks, sizeof(bricks));
memcpy(&disp[8], bricks, sizeof(bricks));

// show ball
SET_RED(bx,by);

// show bat
SET_GREEN(x,7);
SET_GREEN(x+1,7);

// still in debounce period?
if(buttonDebounce)
{
// wait for button release
if(P_BUTTON1 && P_BUTTON2 && P_BUTTON3)
buttonDebounce--;
}
else 
{
// left?
if(!P_BUTTON1)
{
if(x>0) x--;
buttonDebounce = BUTTON_DEBOUNCE;
}
// right?
else if(!P_BUTTON2)
{
if(x<6) x++;
buttonDebounce = BUTTON_DEBOUNCE;
}
}

// and refresh the display
refresh();
}
}
}

void main()


osccon = 0b01110001; // osc control / 8MHz / internal
cmcon0 = 7; // comparator off
ansel=0; // digital IO

trisa = P_TRISA; // port A I/O direction
trisc = P_TRISC; // port C I/O direction
  
porta = 0; // clear port A
portc = 0; // clear port C

option_reg = 0b10000011; // timer0... configure source and prescaler
intcon.7 = 1; // GIE - enable interrupts
intcon.6 = 1; // PEIE - enable interrupts
intcon.5 = 0; // T0IE - timer 0 interrupts diabled for now
intcon.2 = 0; // T0IF - clear timer 0 interrupt flag

breakout();
}

No comments:

Post a Comment