Full Version : Weisz & Felber 4414 VT100 Pong (ASM)
avr >>GAME & VIDEO PROJECTS >>Weisz & Felber 4414 VT100 Pong (ASM)


Admin5- 04-22-2006
Pong on a VT100 Emulator over a serial port
Gabriel Weisz and Garrin Felber


Introduction

We wanted to show that it is possible to implement a (faitly simple) game on the ATS4414 board. Pong was an obvious choice, because it was the first video game in history. We chose to use a VT100 emulator because we couldn't get our LCD to work (we decided that the data input was fried), and this was an available alternative. It also supported the other main design decision that we made: rather than redraw the entire screen between frames, just draw the parts of the screen that change.


Design

Besides the game itself, the major design goal was to only update as little of the screen as possible between frames. The use of VT100 text mode facilitated this - characters couldn't overlap, and things moving could be erased by drawing a space where they were before moving. We don't have the microcontroller wait between frames, but rather draw them as fast as possible, because this makes the game more interesting. Besides the serial communication routines (taken from the rs232 sample code) we had two main functions: initscreen and updatescreen, as well as some utility functions.


Program Design

The program has two main functions: InitScreen and UpdateScreen.

Initscreen sets the initial paddle and ball positions and directions, and draws the paddles (they do need to be fully drawn once), and the scores.
Updatescreen computes the new ball and paddle positions (from previous positions and keypresses indicating paddle motion), decides if one of the players has scored a point, and then redraws the paddle and ball. It does change the score and call initscreen if a point was scored, and prints a message and enters a spin loop if one of the players has won the game.
We also have several utility functions. We have one function to start printing a string in memory to the serial port.
There's a function to convert numbers to ascii.
Finally (and most importantly> there's a function that will send the escape sequence to move the VT100 cursor to a specified point on the screen (this function is heavily used in updates).


Results

The game works! The speed of the game is reasonable. It does slow down when the paddles move far (we have both slow and fast paddle motion) but by raising the communication speed we have determined that this is due to the slowness of HyperTerminal's VT100 emulator rather than due to communication overhead (we determined this because when the paddles don't move, the ball moves very fast, but when they do move, the ball moves at the same speed that it moved at with a slower communication speed). This shows that our decision not to pause the processor between frames was justified, since this would only make the fast updates slower and not help the slow updates. We also think that this makes the game more interesting (it provides the oppertunity for some strategy since you can change the speed of updates when the ball is near the opponent's side).


What we would do differently

The main thing that we would do differently is make sure that our LCD works before trying to use it. We had originally planned to use a graphics mode LCD (along with modifications to the code to allow the ball to not be exactly on an bit boundary) and wrote a lot of code to use the LCD before we realized that it just didn't work and that we didn't have time to get another one. Everything else works well, and we're happy with it.

Link: http://instruct1.cit.cornell.edu/courses/e...eisz/VTPONG.HTM

CODE



;;***************************************
;;***** RS-232 test on 4114 *************
;;***************************************
;Fully interrupt driven
;Strings can come from flash or RAM.

.include "c:\users\g&g\4414def.inc"

.device AT90S4414

.def    temp    =r16   ;temporary register
.def    count   =r17   ;a counter to use in message
.def    savSREG =r18   ;save the status register
.def    TXbusy  =r19   ;transmit busy flag
.def    RXchar  =r20   ;a received character
.def    xpos    =r21
.def    ypos    =r22
.def    ballx=r24
.def    bally=r25
.def    bdx=r2
.def    bdy=r3
.def    ly=r26
.def    ry=r27
.def    ldy=r28
.def    rdy=r29
.def return=r23
.def drawchar=r20

.def rightscore=r6
.def leftscore=r5
.equ RIGHTPOINT=1
.equ LEFTPOINT=2
.equ    baud  =25    ;9600 baud constant for 4Mhz crystal
.equ    miny=2
.equ    maxy=25
.equ    minby=2
.equ    maxby=19
.equ    minx=2
.equ    maxx=79

;**************************************
.dseg

;define variable strings to be tranmitted from RAM
ss: .byte   128      ;a two digit count + a zero terminate

;**************************************
.cseg

.org $0000
       rjmp    RESET  ;reset entry vector
       reti            
       reti
       reti
       reti
       reti
       reti
       reti
       reti            
       reti
       rjmp    TXempty;UART buffer empty
       reti
       reti

;define fixed strings to be tranmitted from flash- zero terminated
crlf:   .db     0x0d, 0x0a, 0x00       ;carrage return/line feed
clrscr: .db     27,'c',0x00

RESET:  ldi     temp, LOW(RAMEND);setup stack pointer
       out     SPL, temp
       ldi     temp, HIGH(RAMEND)
       out     SPH, temp

      ;initial conditions
       clr     TXbusy         ;start out not busy on TX

      ;setup UART -- enable TXempty & RXdone int, and RX, TX pins
       ldi     temp, 0b00101000
       out     UCR, temp
      ;set baud rate to 9600
       ldi     temp, baud
       out     UBRR, temp
       ldi temp,1
       out tccr0,temp
      ;intialize text pointer BEFORE turning on interrupts
      ;because RESET causes the TX empty flag to be SET
       set
       ldi     ZL, LOW(clrscr<<1);do shift to convert word-addr to byte
       ldi     ZH, HIGH(clrscr<<1)    
       sei
       sbi     UCR, UDRIE     ;enable the TXempty interrupt
       clr temp
       out ddrc,temp
      ; clear global variables
       clr leftscore
       clr rightscore
      ; initialize the screen and loop on the screen update function
       rcall initscreen
mainloop:rcall updatescreen
       rjmp mainloop


      ; this is a utility function that starts sending data in ram
senddata:
       ldi ZL,LOW(ss)
       ldi zh,HIGH(ss)
       clt
       sbi     UCR, UDRIE     ;enable the TXempty interrupt
       ser txbusy      
       rjmp   TXwait

      ; this is a utility function to move the cursor to
      ; xpos,ypos
      ; it actually puts the data in ram (which allows movement and
      ; screen data to be overlapped in a bulk transfer)
movecursor:
       push temp
      ; send the begining of the escape code for "move cursor"
       ldi     temp,27
       st      Z+,temp
       ldi     temp,'['
       st      Z+,temp
       mov     temp,ypos
      ; send the row (in ascii, not hex)
       rcall   conv_ascii
      ; send the row - column separator
       ldi     temp,59
       st      Z+,temp
      ; send the column
       mov     temp,xpos
       rcall   conv_ascii
      ; send the move cursor terminator and null terminate the string in ram
      ; (in case there is no data after this data)
       ldi     temp,'H'
       st      Z+,temp
       ldi     temp,0
       st      Z,temp
       pop temp
       ret
   
  ; this utilility function will put the ascii representation of
      ; a two digit number in ram
conv_ascii:
       push temp
       push count
       cpi temp,10
      ; no leading '0' because that messes up the vt100 stuff
   brlt notens
       ldi count,'0'-1
      ; get the tens digit
ten_loop:
       inc count
       subi temp,10
       brmi donetens
       rjmp ten_loop
donetens:
       subi temp,-10
       st z+,count
      ; store the tens digit
notens:
      ; get the ones digit, store it, and null terminate
       subi temp,-'0'
       st Z+,temp
       clr temp
       st z,temp
       pop count
       pop temp
       ret

updatescreen:
      ; erase the old ball (move the cursor to the old position,
      ; store a ' ', and null terminate
       ldi Zl,LOW(ss)
       ldi ZH,HIGH(ss)
       mov xpos,ballx
       mov ypos,bally
       rcall movecursor
       ldi temp,' '
       st Z+,temp
       clr temp
       st Z,temp
       
  ; update the ball position first
      ; check for someone getting a point
      ; note that bounds checking is not completely symmetrical
      ; because of the available conditional branches
       clr return
      ; check the ball position with the left paddle position
       cpi ballx,minx+1
       brge checkrightpoint
       ldi ballx,minx
      ;  see if this is a bounce or a point
      ; check left edge
       cp bally,ly
       brge checkleftbottom
       ldi return,RIGHTPOINT
       rjmp donedraw
checkleftbottom:
       mov temp,ly
       subi temp,-6
       cp temp,bally
       brge bounceballx
       ldi return,RIGHTPOINT
       rjmp donedraw

      ;  check right edge
checkrightpoint:        
       cpi ballx,maxx
       brlt checktopandbottom
       ldi ballx,maxx
       cp bally,ry
       brge checkrightbottom
       ldi return,LEFTPOINT
       rjmp donedraw
checkrightbottom:
       mov temp,ry
       subi temp,-6
       cp temp,bally
       brge bounceballx
       ldi return,LEFTPOINT
       rjmp donedraw
bounceballx:
      ; the ball is bouncing in the x direction - negate it's velocity
       neg bdx

  ; check top and bottom boundary to see if the ball has hit a wall
  ; negate the y component of the ball's velocity if necessary
checktopandbottom:
       cpi bally,MAXY
       brlt checktopball
       ldi bally,MAXY
       neg bdy
       rjmp donebally
checktopball:  
       cpi bally,MINY+1
       brge donebally
       ldi bally,MINY
       neg bdy
donebally:
       add ballx,bdx
       add bally,bdy
       mov xpos,ballx
       mov ypos,bally
       rcall movecursor
       ldi temp,'*'
       st Z+,temp
       clr temp
       st z,temp      
       
      ; now move the paddles if a key is pressed
  ; the keys are as follows: the buttons on the board are
      ; connected to port c. each player has four buttons:
  ; fast and slow motion in both the up and down directions
  ; with combinations of fast and slow motion possible
  ; for really fast motion
       
       clr ldy
       sbis PINC,7
       subi ldy,-2
       sbis PINC,6
       subi ldy,-1
       sbis PINC,5
       subi ldy,1
       sbis PINC,4
       subi ldy,2
       clr rdy
       sbis PINC,3
       subi rdy,-2
       sbis PINC,2
       subi rdy,-1
       sbis PINC,1
       subi rdy,1
       sbis PINC,0
       subi rdy,2

      ; move the left bar if buttons are pressed and
  ; it's not touching a boundary
  ; the bar is moved by erasing '|' characters at the
  ; old position and drawing them in the new position
       add ly,ldy
       set
       tst ldy
       brge nonegldy
       neg ldy
       clt
nonegldy:
       breq startrightbar
       cpi ly,MAXBY
       brlt checktoply
  ; fix the motion of the paddle if it's past a wall boundary
       subi ly,MAXBY
       sub ldy,ly
       ldi ly,MAXBY
       rjmp donefixly
checktoply:
       cpi ly,MINby
       brge donefixly
       ldi temp,MINby
       sub temp,ly
       sub ldy,temp
       ldi ly,MINby
donefixly:
       tst ldy
       breq startrightbar
       mov count,ldy
       ldi xpos,1
       mov ypos,ly
       ldi drawchar,'|'
       clr temp
       brtc leftwriteloop
       ldi drawchar,' '
       sub ypos,ldy
  ; this loop draws pipes if moving up and
      ; spaces to erase if moving down
leftwriteloop:
       rcall movecursor
       st Z+,drawchar
       st Z,temp
       inc ypos        
       dec count
       brne leftwriteloop
       ldi xpos,1
       mov ypos,ly
       subi ypos,-6
       ldi drawchar,' '
       brtc leftclearloop
       ldi drawchar,'|'
       sub ypos,ldy
  ; this loop erases characters if moving up and draws them if moving down
leftclearloop:  
       rcall movecursor
       st Z+,drawchar
       st Z,temp
       inc ypos
       dec ldy
       brne leftclearloop


      ;; do the right bar - this is exactly symmetrical with the left bar
startrightbar:
       add ry,rdy
       set
       tst rdy
       brge nonegrdy
       neg rdy
       clt
nonegrdy:
       breq donedraw
       cpi ry,MAXBY
       brlt checktopry
       subi ry,MAXBY
       sub rdy,ry
       ldi ry,MAXBY
       rjmp donefixry
checktopry:
       cpi ry,MINby
       brge donefixry
       ldi temp,MINby
       sub temp,ry
       sub rdy,temp
       ldi ry,MINby
donefixry:
       tst rdy
       breq donedraw
       mov count,rdy
       ldi xpos,80
       mov ypos,ry
       clr temp
       ldi drawchar,'|'
       brtc rightwriteloop
       ldi drawchar,' '
       sub ypos,rdy
rightwriteloop:
       rcall movecursor
       st Z+,drawchar
       st Z,temp
       inc ypos
       dec count
       brne rightwriteloop

       mov ypos,ry
       subi ypos,-6
       ldi drawchar,' '
       brtC rightclearloop
       ldi drawchar,'|'
       sub ypos,rdy
rightclearloop:
       rcall movecursor
       st Z+,drawchar
       st Z,temp
       inc ypos
       dec rdy
       brne rightclearloop

donedraw:
       tst return
       brne point
       rjmp senddata
  ; if a side has gotten a point, update the score and reinitialize the
  ; screen.  If a side has won (gotten 10 points) print a message
  ; and spin loop
point:  ldi temp,10
       cpi return,RIGHTPOINT
       brne norightpoint
       inc rightscore
       cp rightscore,temp
       breq rightwins
       rjmp initscreen
norightpoint:
       inc leftscore
       cp leftscore,temp
       breq leftwins
       rjmp initscreen
       
rightwins:
       ldi ypos,12
       ldi xpos,34
       rcall movecursor
       rcall senddata
       ldi ZL,LOW(rws<<1)
       ldi zh,high(rws<<1)
       set
       sbi     UCR, UDRIE     ;enable the TXempty interrupt
       ser txbusy      
       rcall   TXwait
rwl:    rjmp rwl

leftwins:
       ldi ypos,12
       ldi xpos,34
       rcall movecursor
       rcall senddata
       ldi ZL,LOW(lws<<1)
       ldi zh,high(lws<<1)
       set
       sbi     UCR, UDRIE     ;enable the TXempty interrupt
       ser txbusy      
       rcall   TXwait
lwl:    rjmp lwl


      ; this utility function will print out initial stuff
      ; including the bars and the scores
initscreen:
      ; clear the screen first
       set
       ldi     ZL, LOW(clrscr<<1);do shift to convert word-addr to byte
       ldi     ZH, HIGH(clrscr<<1)    
       ser txbusy
       sbi     UCR, UDRIE     ;enable the TXempty interrupt
       ser txbusy      
       rcall   TXwait
       clt
      ; ss is a string to be printed - use it to store
  ; the scores and the initial bars
   
  ; first print the left score
   ldi Zl,LOW(ss)
       ldi ZH,HIGH(ss)
       ldi xpos,1
       ldi ypos,1
       rcall movecursor
       mov temp,leftscore
       rcall conv_ascii
       
  ; then print the right score
   ldi xpos,80
       rcall movecursor
       mov temp,rightscore
       rcall conv_ascii
      ;draw stuff
       ldi ly,10
       ldi ry,10      
       ldi xpos,1
       mov ypos,ly
       ldi count,6
  ; loop to create the bars
leftcreateloop:
       rcall movecursor
       ldi temp,'|'
       st Z+,temp
       clr temp
       st Z,temp
       inc ypos
       dec count
       brne leftcreateloop
       ldi xpos,80
       mov ypos,ry
       ldi count,6
rightcreateloop:
       rcall movecursor
       ldi temp,'|'
       st Z+,temp
       clr temp
       st Z,temp
       inc ypos
       dec count
       brne rightcreateloop
       rcall senddata

      ;; pseudorandomly choose ball direction
  ; based on the lsb of a free-running timer
       ldi bally,12
       ldi temp,-1
       mov bdy,temp
       in temp,tcnt0
       ror temp
       brcs goright
       ldi ballx,41
       ldi temp,-1
       mov bdx,temp
       ret
goright:
       ldi ballx,40
       ldi temp,1
       mov bdx,temp
       ret

rws: .db "right wins!",0x00
lws: .db "left wins!",0x00      











;*****************************
;interrupt routines

; UART needs a character
TXempty:in      savSREG, SREG  ;save processor status
       brtc    TXram          ;t bit=0:message in flash.t=1: message in ram        
       lpm                    ;and put it in r0
       inc     ZL             ;get the next char from flash                  
       rjmp    TXfls
TXram:  ld      r0,Z+          ;get char from mem
TXfls:  tst     r0             ;if char is zero then exit
       breq    TXend          
       out     UDR, r0        ;otherwise transmit it
       rjmp    TXexit         ;exit until next char
TXend:  clr     TXbusy         ;no more chars
       cbi     UCR, UDRIE     ;clear the TXempty interrupt
TXexit: out     SREG, savSREG  ;restore proc status
       reti                   ;back to pgm

; TX done -- buffer is empty  -- unused here
TXdone: in      savSREG, SREG  ;save processor status
       out     SREG, savSREG  ;restore proc status
       reti                   ;back to pgm

; UART read a character
RXdone: in      savSREG, SREG  ;save processor status  
       in      RXchar, UDR    ;get the character
       out     SREG, savSREG  ;restore proc status
       reti                   ;back to pgm

;*****************************
;subroutine

TXwait: tst     TXbusy         ;now wait for the tranmission to finish
       brne    TXwait
       ret



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