Full Version : Video Game Console (GCC)
avr >>GAME & VIDEO PROJECTS >>Video Game Console (GCC)


Admin5- 04-20-2006
Video Game Console

An Atmel AVR AT90S8535 at 8 MHz is used to generate video signals in software. A video game console framework is described, that can be used to build simple video games, similar to the early video games that was common in the 80's. The framework provides an easy way of creating black and white video signals with a resolution of 64x56 pixels and simple sound effects on any TV set, using almost no hardware except the AVR. A scart connector, 5 resistors and 4 switches are all that is required.

The resolution is limited by the memory and speed of the AVR. With 8 kB additional RAM it would be possible to increase the resolution to 256x256 pixels. To go beyond that it would be necessary to increase the clock speed to more than the current 8 MHz.


Link: http://hem.bredband.net/robinstridh/

CODE

// ###########################################################################
// ## AVR Video Game - Example on how to generate video signals
// ## with an Atmel AVR at90s8535 at 8 MHz.
// ## By: Robin Stridh 2002
// ##
// ## Software: See sample program at end of file.
// ##
// ## Hardware: The external hardware consists of only 5 resistors,
// ##           4 switches and a scart connector.
// ##
// ##
// ##   AVR:                                        SCART CONNECTOR:
// ##   ====                                        ================
// ##                   ___
// ##   B.0 -- UP ------- -----+
// ##                   ___    |
// ##   B.1 -- RIGHT ---- -----+
// ##                   ___    |
// ##   B.2 -- LEFT ----- -----+
// ##                   ___    |
// ##   B.3 -- DOWN ----- -----+-- GND    
// ##                                                     ___
// ##                                               +- 2 |- _|
// ##                                               |    |- _|
// ##   B.7 -- SOUND --[10k ]-----+-- AUDIO SIGNAL -+- 6 |- _|
// ##                             |                      |- _|
// ##            GND --[1k  ]-----+                      |- _|
// ##                                                    |- _|
// ##                                                    |- _|
// ##   C.0 -- SYNC  --[1.2k]-----+                      |- _|
// ##                             |                      |- _| 17
// ##   D.7 -- VIDEO --[560 ]-----+-- VIDEO SIGNAL -- 20 |- _|
// ##                             |                         \|
// ##            GND --[100 ]-----+
// ##
// ##  PINS C.1-C.7 and D.0-D.6 cannot be used by other i/o.
// ##  PINS A.0-A.7 and B.4-B.6 are free to use however.
// ###########################################################################

#define CLOCK_FREQUENCY 8
#include <io.h>
#include <interrupt.h>
#include <sig-avr.h>                    
#include <delay.h>

// ###########################################################################
// ## Screen settings
// ## Constraints:
// ## LVL - FVL = HEIGHT * (8 >> LINE_REPEAT)
// ## W * H = SCREEN_SIZE * 8
// ###########################################################################

// ## An almost full screen image of 64x56 pixels with time for sound at
// ## end of each line.
#define WIDTH 64  // Width of screen in pixels
#define HEIGHT 56  // Height of screen in pixels
#define SCREEN_SIZE 448  // Size of screen in bytes
#define LINE_DELAY delay_us(8) // Defines left edge
#define FIRST_VISIBLE_LINE 40 // Defines upper edge
#define LAST_VISIBLE_LINE 264 // Defines lower edge
#define PIXEL_DELAY { __asm__("nop"); __asm__("nop"); }
   // Defines pixel width
#define LINE_REPEAT 1  // Defines pixel height (1 means 4 lines,
   // 2 means 2 lines and 3 means 1 line)

// ## A nice full screen image of 64x56 pixels. Leaves very little time at
// ## end of line.
/*
 #define WIDTH 64
 #define HEIGHT 56
 #define SCREEN_SIZE 448
 #define LINE_DELAY  delay_1us_8
 #define FIRST_VISIBLE_LINE 40
 #define LAST_VISIBLE_LINE 264
 #define PIXEL_DELAY { __asm__("nop"); __asm__("nop"); __asm__("nop"); }
 #define LINE_REPEAT 1
*/

// ## A small window with highest possible resolution with 8 MHz.
/*
 #define WIDTH 64
 #define HEIGHT 56
 #define SCREEN_SIZE 448
 #define LINE_DELAY delay_us(12);
 #define FIRST_VISIBLE_LINE 120
 #define LAST_VISIBLE_LINE 176
 #define PIXEL_DELAY
 #define LINE_REPEAT 3
*/

// ###########################################################################
// ## Video settings
// ## These settings are adapted to PAL. For NTSC use the following:
// ## BEGIN_VSYNC = 248, END_VSYNC = 251, NEW_FRAME = 263 (Not tested!)
// ###########################################################################

#define BEGIN_VSYNC 298
#define END_VSYNC   301
#define NEW_FRAME   313

// Brute force macro for outputting a line of pixel data as
// fast as possible. If that is too fast a delay is used.
#define OUTPUT_LINE(r) { \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
 outp((r), PORTD); (r) = (r) << 1; PIXEL_DELAY; \
}

// Help macros
#define CLEAR_PIXEL(x, y) screen[((y)<<3)+((x)>>3)] &= ~(128 >> ((x)&0x07))
#define PUT_PIXEL(x, y) screen[((y)<<3)+((x)>>3)] |= 128 >> ((x)&0x07)
#define GET_PIXEL(x, y) (screen[((y)<<3)+((x)>>3)] & 128 >> ((x)&0x07))
#define KEYPAD_UP    (bit_is_clear(PINB, 0))
#define KEYPAD_RIGHT (bit_is_clear(PINB, 1))
#define KEYPAD_LEFT  (bit_is_clear(PINB, 2))
#define KEYPAD_DOWN  (bit_is_clear(PINB, 3))


// ###########################################################################
// ## Globals
// ###########################################################################

// Video
unsigned char sync, data;
volatile int scanline;
#define black 0x00
#define white 0xff
unsigned char screen[SCREEN_SIZE];

// Sound
#define SOUND_FREQ 4
unsigned char sound_active;
#define SOUND sound_active = 200 // Duration in ms
unsigned char sound = 1; // high

// Extern functions
extern void VID_PostLineRoutines(void);
extern void VID_PostFrameRoutines(void);

// ###########################################################################
// ## Interrupt routine called on every line to generate the horisontal
// ## sync pulse. Also genareates the vertical sync on every frame by
// ## inverting the horisontal sync.
// ###########################################################################

SIGNAL(SIG_OUTPUT_COMPARE1A)
{
 outp(sync, PORTC);    
 outp(black, PORTD);
 scanline ++;
 if (scanline == BEGIN_VSYNC) { sync  = 0xff; data = 0x00; }
 if (scanline == END_VSYNC)   { sync  = 0x00; data = 0xff; }
 if (scanline == NEW_FRAME)   { scanline = 0; }
 outp(data, PORTC);
}  


void VID_Init(void)
{
 //init timer 1 to generate sync
 outp(0x02, OCR1AH);
 outp(0x00, OCR1AL);

 outp(0x09, TCCR1B);   //full speed; clear-on-match
 outp(0x00, TCCR1A);  //turn off pwm and oc lines
 outp(0x10, TIMSK);  //enable interrupt
 
 //init ports
 outp(0xf0, DDRB);  //outputs/inputs
 outp(0x0f, PORTB);  //zeros/activate pull-up

 outp(0xff, DDRD);  //video out and switches
 outp(0xff, DDRC);
 //initialize synch constants
 scanline = 0;
 sync = 0x00;
 data = 0xff;  
 
 outp(0x40, MCUCR); // enable sleep
}

void VID_MainLoop(void)
{  
 register unsigned char v1, v2, v3, v4, v5, v6, v7, v8;
 int i;

 sei();             // enable interrupts
 while(1) {
   // Performed in visible part of screen
   if (scanline < LAST_VISIBLE_LINE && scanline >= FIRST_VISIBLE_LINE) {
     i = (scanline - FIRST_VISIBLE_LINE) << LINE_REPEAT & 0xfff8;
     // Wait until next line starts
     __asm__("sleep");
     // Load the pixels into registers
     v1 = screen[i];
     v2 = screen[i+1];
     v3 = screen[i+2];
     v4 = screen[i+3];
     v5 = screen[i+4];
     v6 = screen[i+5];
     v7 = screen[i+6];
     v8 = screen[i+7];
     // Wait until right position
     LINE_DELAY;
     // Output pixels
     OUTPUT_LINE(v1);
     OUTPUT_LINE(v2);
     OUTPUT_LINE(v3);
     OUTPUT_LINE(v4);
     OUTPUT_LINE(v5);
     OUTPUT_LINE(v6);
     OUTPUT_LINE(v7);
     OUTPUT_LINE(v8);
     // Return to black level
     outp(black, PORTD);
   }
   // Performed outside visible part of screen (about 5.6 ms)
   else {
     // Performed once every frame
     if (scanline == LAST_VISIBLE_LINE + 1) {
VID_PostFrameRoutines();
     }
     // Sleep until next interrupt
     __asm__("sleep");
   }
   // Performed after each line
   VID_PostLineRoutines();
   // Generate sound if active
   if ((scanline & 0x07) != 0 && sound_active) {
     if (sound == 1) { sbi(PORTB, 7); sound = 0; }
     else { cbi(PORTB, 7); sound = 1; }
     sound_active--;
   }
 }
}

// ##################################################################
// ## Bouncing Ball - Sample program to demonstrate the AVR Video
// ## Game console. Copy into a separate file and compile for
// ## AT90S8535.
// ## By: Robin Stridh 2002
// ##################################################################

/*
#include <video.h>

void init_screen(void);

int main(void)
{
 init_screen();
 VID_Init();
 VID_MainLoop();
 return 0;
}

void init_screen()
{
 int i;

 for (i = 0; i < SCREEN_SIZE; i++) screen[i] = 0x00;                
 //top line
 for (i = 0; i < 8; i++) screen[i] = 0xff;
 //bottom line
 for (i = SCREEN_SIZE - 9; i < SCREEN_SIZE; i++) screen[i] = 0xff;
 //left and right line
 for (i = 8; i < SCREEN_SIZE - 8; i += 8) {
   screen[i] = 0x80;
   screen[i+7] = 0x01;
 }
}

void VID_PostLineRoutines(void)
{}

void VID_PostFrameRoutines(void)
{
 static unsigned char x = WIDTH / 2, y = HEIGHT / 2, dx = 2, dy = 1;

 CLEAR_PIXEL(x, y);
 x += dx; if (x < 1 || x >= WIDTH)  { x -= dx; dx = -dx; SOUND; }
 y += dy; if (y < 1 || y >= HEIGHT - 1) { y -= dy; dy = -dy; SOUND; }
 PUT_PIXEL(x, y);
}
*/

#########################################
#include <video.h>

// Functions
void init_screen(void);
void move_ball(void);

// Globals
int i, j;
char x, y, dx, dy;
char px, py, pw, pdx;
unsigned char n_blocks;
unsigned char demo, game_over;
#define BALL_DELAY 1 // no of empty loops between ball movements
unsigned char delay_loops = BALL_DELAY;


int main(void)
{
 VID_Init();
 init_screen();
 VID_MainLoop();
 return 0;
}

void VID_PostLineRoutines(void)
{}

void VID_PostFrameRoutines(void)
{
 if (KEYPAD_DOWN) demo = 1;
 if (KEYPAD_UP) demo = 0;
 if (!game_over) {
   // Move paddle
   for (i = 0; i < pw; i++) CLEAR_PIXEL(px + i, py);
   if (!demo) {
     if (KEYPAD_RIGHT && px < WIDTH - pw - pdx) px += pdx;
     if (KEYPAD_LEFT  && px > pdx)              px -= pdx;
   }
   else {
     px = x - pw / 2;
     if (px < 1) px = 1;
     if (px > WIDTH - pw - 1) px = WIDTH - pw - 1;
   }
   for (i = 0; i < pw; i++) PUT_PIXEL(px + i, py);
   // Move ball
   if (delay_loops == 0) {
     move_ball();
     delay_loops = BALL_DELAY;
   }
   else
     delay_loops--;
   
   if (n_blocks == 0) game_over = 1;  // hit all blocks
   if (y > HEIGHT - 2) game_over = 1; // dropped ball
 }
 else { // Game over
   for (i = 0; i < SCREEN_SIZE; i++)
     screen[i] = white;
   // restart on up
   if (KEYPAD_UP) init_screen();
 }
}

void init_screen()
{
 int i;
 unsigned char r, c;

 x = WIDTH / 2; y = HEIGHT - 2; dx = 2; dy = -1;
 px = WIDTH / 2 - 3; py = HEIGHT - 2; pw = 12; pdx = 2;
 n_blocks = 36;
 demo = 1;
 game_over = 0;

 for (i = 0; i < SCREEN_SIZE; i++) {
   screen[i] = black;
 }                
 //top line
 for (i = 0; i < 8; i++) {
   screen[i] = white;
 }
 //bottom line
 //  for (i = 247; i < 256; i++) {
 //  for (i = SCREEN_SIZE - 9; i < SCREEN_SIZE; i++) {
 //    screen[i] = white;
 //  }
 //left and right line
 //  for (i = 8; i < 248; i += 8) {
 for (i = 8; i < SCREEN_SIZE - 8; i += 8) {
   screen[i] = 0x80;
   screen[i+7] = 0x01;
 }
 //blocks
 for (r = 5; r < 23; r += 3) {
   for (c = 1; c < 7; c++) {
     screen[(r<<3) + c] = 0x7e;
     screen[((r+1)<<3) + c] = 0x7e;
   }
 }
}

void move_ball()
{
 // Move ball
 CLEAR_PIXEL(x, y);
 // x += dx; if (x < 1 || x >= WIDTH) { x -= dx; dx = -dx; }
 // y += dy; if (y < 1 || y >= HEIGHT - 1) { y -= dy; dy = -dy; }
 x += dx;
 if (GET_PIXEL(x, y)) {
   if (x > 0 && x < WIDTH && y > 0 && y < HEIGHT - 2) {
     // clear block
     screen[((y-1)<<3) + (x>>3)] = black;
     screen[(y<<3)     + (x>>3)] = black;
     screen[((y+1)<<3) + (x>>3)] = black;
     n_blocks--;
     SOUND;
   }
   x -= dx; dx = -dx;
 }
 y += dy;
 if (GET_PIXEL(x, y)) {
   if (x > 0 && x < WIDTH && y > 0 && y < HEIGHT - 2) {
     // clear block
     screen[((y-1)<<3) + (x>>3)] = black;
     screen[(y<<3)     + (x>>3)] = black;
     screen[((y+1)<<3) + (x>>3)] = black;
     n_blocks--;
     SOUND;
   }
   y -= dy; dy = -dy;
 }
 PUT_PIXEL(x, y);
}
// ###########################################################################
// ## delay.h - Exact delay routines for Atmel AVR with 4 and 8 MHz crystals.
// ## If an 8 MHz crystal is used, define  the clock frequency as:
// ## #define CLOCK_FREQUENCY 8
// ## Observe that delay_us only is exact for times > 2 us with 8 Mhz and
// ## times > 4 us with 4 MHz. Use delay_1us for shorter delays.
// ##
// ## By: Robin Stridh 2002
// ###########################################################################

#ifndef __DELAY_H__
#define __DELAY_H__

// With an 8 MHz crystal his delay will be exact for any us > 2.
// (us == 1 takes 1.63 us and us == 2 takes 2.25 us.)  
void delay_us_8(unsigned int us)
{
 if (us > 1) { // The minimum overhead for us == 1
   us--;
   __asm__ ("nop"); __asm__ ("nop");
   // This while loop will take exactly 1 us per turn.
   while ( --us ) {
     __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop");
   }
 }
}

// With a 4 MHz crystal his delay will be exact for any us > 4.
// (us == 1, 2, 3 or 4 all takes 3.25 us.)  
void delay_us_4(unsigned int us)
{
 if (us > 4) { // The minimum overhead for us == 1  
   us-=4;
   __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop");
   // This while loop will take exactly 1 us per turn.
   while (--us);
 }
}

// Macro to use for delays less than 3 us.
#define delay_1us_8 { \
 __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); \
 __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); }

// Macro to use for delays less than 5 us.
#define delay_1us_4 { \
 __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); }


// With an 8 MHz crystal his delay will be exact for any ms.
void delay_ms_8(unsigned int ms)
{
 delay_us_8(997); // The function call and setup will take exactly 3 us.
 // This while loop will take exactly 1 ms per turn. Run ms - 1 times.
 while (--ms) {
   delay_us_8(999);
   __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop"); __asm__ ("nop");
 }
}

// With an 4 MHz crystal his delay will be exact for any ms.
void delay_ms_4(unsigned int ms)
{
 delay_us_4(994); // The function call and setup will take exactly 3 us.
 // This while loop will take exactly 1 ms per turn. Run ms - 1 times.
 while (--ms) delay_us_4(999);
}

#if CLOCK_FREQUENCY == 8
 #define delay_us delay_us_8
 #define delay_ms delay_ms_8
 #define delay_1us delay_1us_8
#else
 #define delay_us delay_us_4
 #define delay_ms delay_ms_4
 #define delay_1us delay_1us_4
#endif

#endif // __DELAY_H__





Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.