Programming the Game Simon

Cornell University
Electrical Engineering 476
Microcontroller Design
Final Project


Alberto Cid 
acid@ee.cornell.edu
May 5th, 1999 Nicole Banash
nb24@cornell.edu

Index:


Introduction

Many of the simpler electronic games of the past decade can be easily programmed on the AVR microcontrollers we are using this semester, using only the lights and switches available on the evaluation boards. For our final project we programmed the game Simon using the AT90S8535 microcontroller chip and corresponding evaluation board. This is a simple computer game involving remembering a sequence of lights which are generated by the program, and repeating them back to the program in the correct order by pressing the correct sequence of buttons. Each time the player correctly repeats back the sequence, the sequence is increased by one element.

This project, as well as giving us a chance to design a fun game, gave us the opportunity to use several pieces of programs which we had developed for previous labs throughout the semester. In particular, many features of the musical tone generator which we developed for lab 3 were very useful for this project. Using the 8535 also gave us an opportunity to use a new evaluation board, which we had not used in previous labs.

User Interface: Playing the Game

After the program is started (either by turning on the evaluation board or pressing reset), the LED's all flash on and off, notifying the player that the game is starting, and a short starting melody is played. The game then turns on LED's 1 and 2, and waits until the player selects a level before starting the game. Level one has a constant playing speed while level two plays back the sequence of notes at an increasing rate as play continues. The game then randomly selects a number which is mapped to an LED which lights up while the corresponding tone is sounded. After playing the note, the game waits for the user to press the matching button. If the person presses the correct button, the program plays the previous string of lights and tones, selects the next random number, lights up the new LED while playing the corresponding tone, and waits for the player to press the correct sequence of buttons. As long as the player continues to press the correct sequence of switches without running out of time, the game will continue. Play stops when the playback sequence contains 40 numbers, which is the defined RAM storage size for the array of numbers, or the person looses. The player looses by either pressing the wrong button or taking longer then 2.1 seconds to press the correct button. When either of those things occur, the player looses the game, the level that the player was on, that is, the length of the stored sequence, is shown in binary on the LED's, and a short 'game over' song is played. A flow chart describing how our program works is provided below.
Flow Chart
Figure 1.
Simon Flow Chart
.

How the Game Actually Works

The Random Number Generator

Timer1 is used to as a random number generator and is prescaled to 1 at the start of the program. When the next number in the series is needed, the last three bits of timer1 are read into a register and then mapped, using a table, to a corresponding light and tone. As the time between successive reads on timer1 depends on the speed at which the player presses the buttons, this method was deemed adequate for generating random numbers, particularly as timer1 changes once every 0.25 mSec, whereas the player can press the buttons no faster then once every 20 msec.

Lights

Although we originally wished to use colored LED's with our program we were unable to make the external LED's work with our program.  As a result, we chose to use only the LED's located on the evaluation board. Lookup tables are used to map the random number to an LED. The LED's are controlled through port B.
Flow Chart 2
Figure 2.
Steps for reading the buttons

Switches

Switches, or buttons, are located on the evaluation board and are controlled through port D. Lookup tables are used to map each switch to an LED and a musical tone which are activated when a person presses a button. A polling loop is used to determine which button was pressed and hence which LED to activate and which tone to play. The switches are debounced by calling a 0.46 second delay loop after reading the buttons.

Notes

The notes which are played when the buttons are pressed were taken from lab 3 and correspond to the octave which starts with middle C at 262 Hz (switch 7) and ends with the next highest C at 523Hz (switch 0). The note frequencies were declared as constants at the beginning of the program and the constant labels were used throughout the program. All tones were played through a speaker attached to port C. The notes are controlled using the timer0 overflow interrupt, sending the speaker signal out every time the interrupt is called. Commands in the main program turn on the timer0 overflow interrupt enable bit when a note is to be played and turn the bit off when the note is finished. A 0.66 second delay loop runs in the main program while the note is being played, with a 0.07 second delay loop running afterward to ensure that there is silence time between the notes. As well as the eight tones needed for the game, we also implemented two small melodies, one for starting the game, and one for when the game is over. A 'play song' subroutine is implemented for this part of the program, which loads the frequencies for each note in the song individually, and then calls a second 'play note' subroutine. The 'play note' subroutine then turns on and off the timer0 overflow interrupt enable bit and implements delay loops as in the main program for both the playing time and the silence time between notes. The timer0 overflow interrupt service routine controls the sending of the speaker output.

Figure 3.
Formula to compute the delay time in the delay-subroutines.

Storing the Sequence

The sequence of three bit random numbers which map to the lights and tones, are stored in the chip SDRAM starting with location 0x060. We decided to limit the size of the sequence stored in memory to 40 numbers as we needed to exactly define the available memory space and assumed that no player will make it past 40 levels. The routine for storing data in RAM that was used in Lab 4 is used as a guideline for this part of our program: register 31 is used as a pointer to the current RAM location that is being accessed. A register (memlo) is used to keep track of how many numbers are in the stored sequence and is also used to determine if the game has filled up the allocated memory, that is, if the fortieth level has been reached. Additionally, this register is used to output the level of the game the player is on when they loose.

Hardware

The program is designed to work on the 8535 microcontroller and corresponding evaluation board. As mentioned earlier, the LED's are located on a breadboard and controlled through port B, the switches are controlled through port D, and port C controls the speaker. We chose not to implement other hardware features because hardware is very difficult to debug in the simulator programs, and most of what we needed was available on the evaluation boards.

Results

Our program worked rather well overall. During testing we established that the game does not play too fast and adequate time is allowed for the user to press the buttons without the player running out of time and loosing the game. We chose to debounce the buttons by implementing a 0.46 second delay loop between successive reads of the switches, instead of checking to verify that the switches had been depressed before starting the next read. If the player holds down a switch for a long time the switch will be read more then once and the player will loose the game. As a result, we had to be careful when calculating the timing of our program so that enough time is allowed for the player to press the buttons, but the game still runs fast enough that the player does not have to wait a long time between each action

We did not have to worry about timing precision in our project. The times that we decided on when programming our delay loops were based on delay loops calculated for previous projects or on our experiences of playing the game during testing. The only timing that we did carefully calculate was the decrease in note playing time for level 2. We were more careful with this calculation because we needed to be able to decrease the note time in 40 equal steps, one step for each level, but the time that the notes are played had to remain long enough that the player was still able to hear the individual tones and see the LED's light up. We chose to decrease the note playing time by 0.75 m Sec for each level, so that there is a noticeable change in speed after some levels, but the game does not become too fast to be played. The game starts with a note playing time of 0.66 seconds and will finish with a note playing time of 0.35 seconds on the fortieth level. The amount of time that the game waits for the player to press a button before they loose the game remains constant.

Calculation of the delay loops was carried out by counting the number of instruction cycles within each loop and multiplying this by the instruction cycle time of 0.25 m Sec .

Although we originally wished to use colored LED's with our program we were unable to supply external LED's with enough power for them to work properly.  We were unable to determine why our LED's were not working properly and only the first five LED's would light up.  When we measured the voltages being supplied to the LED's, enough voltage was present on all pins, however, the last three LED's remained extremely dim.  This problem could be due to either the external LED's not receiving enough current, or the evaluation board being damaged.  Enough power is present to run all of the LED's on the evaluation board.

Future Improvements

Although the program worked well in general, having all red LED's made it difficult to remember which LED was lit when the sequence was long.  To fix this problem we attempted to use colored LED's, however, we were unable to make them work properly, possibly due to them not receiving enough current.  A possible solution to this problem would involve using two different ports to drive the LED's, with each port driving only 4 of them.  As this would get very complicated, we chose not to try this solution, and to simply use the LED's on the evaluation board.

Another problem we noticed while testing the external LED's is that pressing the buttons when the LED's were on the breadboard was a bit awkward. It would probably be easier to play the game if the buttons and switches were a bit more spread out. As a result, a future improvement would be to either use external switches which could be positioned near the LED's on the breadboard, or to use LED's which are also switches themselves. Either of these features would make the program more user friendly.

A second improvement would be to change the method that is used to let the player select a level. Unless the player is familiar with the format of our game, although LED's 1 and 2 are turned on until a level is selected, there is no way the player will know that it is necessary to select a level to start the game. As a result, it would be a good idea to implement a separate switch which could be permanently set on a level and read only at the beginning of the game. Alternatively, it would be possible to attach a small LCD screen which would display a message telling the player to select a level of play, however, as this would require a lot of extra code, the first possibility would probably be better.

Other, small improvements which we could have made to our program were to add a winning song or a scoring method, however, we decided that these features were not necessary for our program.

A final improvement that we could make to this program is to change our method of debouncing the buttons. We chose to use delay loops between successive reads of the buttons, but it would speed up the game if we instead checked to see that the button was released before beginning another read of the switches. This would allow the player more control over the speed of the game, and would make the game more fun for people with faster reflexes.


Appendix 1. Program Code

.include "c:\users\acnb\8535def.inc"

.device AT90S8535

.def    memlo   =r9
.def    cntstr3 =r10
.def    cntstr1 =r12
.def    cntstr2 =r13
.def    time    =r14
.def    count1  =r16
.def    count2  =r17
.def    savSREG =r18
.def    wreg    =r19
.def    count3  =r20
.def    butime  =r21
.def    reload  =r22
.def    lights  =r23
.def    spk     =r24
.def    tmer    =r26
.def    temp1   =r27
.def    temp2   =r28
.def    temp3   =r29

.equ    PreScale=1

.equ    do=0xEE         ;freq. for the notes
.equ    re=0xD4
.equ    mi=0xBD
.equ    fa=0xB3
.equ    sol=0x9F
.equ    la=0x8E
.equ    si=0x7E
.equ    do2=0x77

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

;define variable strings to be tranmitted from RAM
;this defines a maximum storage length of 40
cntstr: .byte   41      ;a 40 digit count + a zero terminate

;**************************************

.cseg
.org $0000
rjmp    RESET   ;reset entry vector
        reti
        reti
        reti
        reti
        reti
        reti
        reti
        reti
        rjmp    timer
        reti
        reti
        reti
        reti
        reti
        reti
        reti

;begin program
;attach LEDS to port B
;attach buttons to port D
;attach speaker to port C

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

;set up portB as output for LED's
        ser     wreg                    ;Set portB to all outputs
        out     DDRB,wreg

        ldi     temp1, PreScale         ;Prescale timer by 1
        out     TCCR1B, temp1

;set up counters
        ldi     count1, 0xFF
        ldi     count2, 0xF0
        mov     cntstr1, count1
        mov     cntstr2, count2
        ldi     count1, 0x0A
        mov     cntstr3, count1

;set up and turn on timer1
        ldi     wreg, 0x41
        out     TCCR1B, wreg

;set up speaker on Port C
        ser     wreg
        out     DDRC, wreg
        ldi     spk, 0

;set up port D buttons as input
        ldi     wreg, 0x00
        out     DDRD, wreg

;set up possible note time
        ldi     wreg, 200
        mov     time, wreg

;set up pointer to highest RAM array location
        ldi     wreg, 0x60
        mov     memlo, wreg

;prescale timer 0
        ldi     wreg, 3         ;prescale timer by clk/64
        out     TCCR0, wreg

;initialize text pointer before turning on interrupts
        ldi     ZL, LOW(cntstr)         ;ptr to RAM
        ldi     ZH, HIGH(cntstr)
        sei                             ;turn on interrupts

;flash all LEDS tentatively assigned to portb
main:   ldi     wreg, 0x00
        out     PORTB, wreg             ;flash all LED's on
        rcall   song1
        rcall   delay                   ;delay subroutine so player can see lights
        ldi     wreg, 0xF9
        out     PORTB, wreg             ;flash all LED's off, leaving on LED's 1 and 2
                                                                                ;until player selects a level
 
 

loop1:  ldi     wreg, 0xFF
        sbis    PIND, 1 ;check for level 1
        ldi     wreg, 1
        sbis    PIND, 2 ;check for level 2
        ldi     wreg, 2
        cpi     wreg, 0xFF
        breq    loop1           ;loop until level pressed

        cpi     wreg, 2
        brne    sta1
        set                     ;set T bit to indicate level=2.

sta1:   ldi     wreg, 0xFF      ;turn of all LED's
        out     PORTB, wreg

;random number generator
start:  in      wreg, TCNT1L            ;keep
        in      temp2, TCNT1H           ;discard
        andi    wreg, 0x07              ;keep last three bits of this register to map to lights and tones
        mov     temp1, memlo                         ;set up extra array pointer
        cpi     temp1, 0x88
        brne    noreset
win:    rjmp    win                     ;Spin loop when player wins
 

; load next digit in RAM
noreset:mov     ZL, memlo
        ldi     ZH, 0x00
        st      Z, wreg                 ;load the button digit into RAM
        inc     memlo

        inc     ZL
        ser     wreg                    ;zero string terminator to RAM - we chose to use FF as 00 maps to a switch
        st      Z, wreg

;start at beginning of RAM and play all the data
;now the pointer to the variable message in RAM
        ldi     ZL, LOW(cntstr)  ;ptr to RAM
        ldi     ZH, HIGH(cntstr)
plystr: ld      r0,Z            ;button is in r0
        inc     ZL
        mov     wreg,r0
        cpi     wreg, 0xFF      ;see if at end of message
        brne    here
        rjmp    end1            ;If so, jump to next part of program

; otherwise play note and set up lights
;poll for which note should be played
here:   cpi     wreg, 7
        brne    one
        ldi     tmer, do
        ldi     lights, 0x7F
        rjmp    outn
one:    cpi     wreg, 6
        brne    two
        ldi     tmer, re
        ldi     lights, 0xBF
        rjmp    outn
two:    cpi     wreg, 5
        brne    three
        ldi     lights, 0xDF
        ldi     tmer, mi
        rjmp    outn
three:  cpi     wreg, 4
        brne    four
        ldi     lights, 0xEF
        ldi     tmer, fa
        rjmp    outn
four:   cpi     wreg, 3
        brne    five
        ldi     lights, 0xF7
        ldi     tmer, sol
        rjmp    outn
five:   cpi     wreg, 2
        brne    six
        ldi     lights, 0xFB
        ldi     tmer, la
        rjmp    outn
six:    cpi     wreg, 1
        brne    seven
        ldi     tmer, si
        ldi     lights, 0xFD
        rjmp    outn
seven:  cpi     wreg, 0
        ldi     lights, 0xFE
        ldi     tmer, do2
        rjmp    outn

;output LEDs
outn:
        out     PORTB, lights

;play note

;turn on timer ovfl interrupt timer0

        ldi     Reload, 0xFF            ;load first freq into timer0
        sub     Reload, tmer
        out     TCNT0, Reload

        ldi     wreg,0x01       ;enable timer interrupt
        out     TIMSK, wreg

wait2:  mov     temp1, cntstr1          ; wait while note is played
        mov     wreg, cntstr2
        mov     temp2, cntstr3
one2:   nop
        dec     temp1
        brne    one2
        dec     wreg
        brne    one2
        dec     temp2
        brne    one2

;turn off timer0 ovfl interrupt
        ldi     wreg, 0x00
        out     TIMSK, wreg
;turn off lights
        ser     lights
        out     PORTB, lights

;silence time
        ldi     temp1, 0xFF
        ldi     temp2, 0xFF
one3:   nop
        dec     temp1
        brne    one3
        dec     temp2
        brne    one3
 

;loop back to get next note out of RAM
        rjmp    plystr

;if done playing all notes
;wait for button pressed
end1:   ldi     ZL, LOW(cntstr)  ;ptr to RAM
        ldi     ZH, HIGH(cntstr)
end4:   ld      r0,Z
        inc     ZL
        mov     butime, r0      ;button is in r0
        cpi     butime, 0xFF            ;See if at end of RAM array
        brne    next
        rjmp    end2
 

next:   ldi     temp1, 0xFF             ;load a counter with the time allowed for player
        ldi     temp3, 0xFF             ;to press button before losing
        ldi     count1, 0x03
end3:   ldi     temp2, 0xFF
pressed.
        in      wreg, PIND
        cpi     wreg, 0x7F
        brne    six1
        ldi     temp2, 0x07
        ldi     lights, 0x7F
        ldi     tmer, do
        rjmp    out1
six1:   cpi     wreg, 0xBF
        brne    five1
        ldi     temp2, 0x06
        ldi     lights, 0xBF
        ldi     tmer, re
        rjmp    out1
five1:  cpi     wreg, 0xDF
        brne    four1
        ldi     temp2, 0x05
        ldi     lights, 0xDF
        ldi     tmer, mi
        rjmp    out1
four1:  cpi     wreg, 0xEF
        brne    three1
        ldi     temp2, 0x04
        ldi     tmer, fa
        ldi     lights, 0xEF
        rjmp    out1
three1: cpi     wreg, 0xF7
        brne    two1
        ldi     temp2, 0x03
        ldi     tmer, sol
        ldi     lights, 0xF7
        rjmp    out1
two1:   cpi     wreg, 0xFB
        brne    one1
        ldi     temp2, 0x02
        ldi     lights, 0xFB
        ldi     tmer, la
        rjmp    out1
one1:   cpi     wreg, 0xFD
        brne    zero1
        ldi     temp2, 0x01
        ldi     lights, 0xFD
        ldi     tmer, si
        rjmp    out1
zero1:  cpi     wreg, 0xFE
        brne    out1
        ldi     temp2, 0x00
        ldi     tmer, do2
        ldi     lights, 0xFE
        rjmp    out1
out1:   cpi     temp2, 0xFF             ;if don't have button pressed check if out of time

        brne    gotit           ;jump if have a valid number from RAM array

;updating timer to see if user is out of time
        dec     temp1                   ;else decrease counter
        cpi     temp1, 0xFF
        brne    down1
        dec     temp3
        cpi     temp3, 0xFF
        brne    down1
        dec     count1
        cpi     count1, 0x00
        breq    error                                     ;jump to game over if player is out of time
down1:  rjmp    end3
 

;compare if same value from button as from RAM
gotit:  cp      butime, temp2   ;If equal, get next value
        brne    error           ;if not equal go to end of game
                                ;LED should not turn on if wrong button was pressed
        out     PORTB, lights       ;else output lights

;play note

;turn on timer ovfl interrupt timer0

        ldi     Reload, 0xFF            ;load first freq into timer0
        sub     Reload, tmer
        out     TCNT0, Reload
        ldi     wreg,0x01       ;enable timer0 overflow interrupt
        out     TIMSK, wreg

        rcall   delay           ;delay subroutine before reading next button

;turn off timer0 ovfl interrupt
        ldi     wreg, 0x00
        out     TIMSK, wreg

        ldi     temp2, 0xFF     ;turn off lights
        out     PORTB, temp2
rjmp    end4                    ;if match between RAM and pressed button repeat procedure.

end2:   rcall   delay           ;call delay subroutine before beginning again
        brts    chnge
        rjmp    start           ;compare if level=2 so variable clock
chnge:  ldi     wreg, 0x10      ;if level=2,  decrease count
        sub     cntstr2, wreg
        ldi     wreg, 0x00
        sbc     cntstr3, wreg
rjmp    start           ;and go back to start of game, generating next random number

;error procedure
error:  mov     wreg, memlo
        subi    wreg, 0x60      ;display level player lost on
        com     wreg
        out     PORTB, wreg
        rcall   song2           ;jump to play game over song
display level
        rcall   delay
        rjmp    reset           ;reset game

;******** Timer0 overflow interrupt service routine**********

timer:  in      savSREG, SREG      ;save the status reg

        com     spk
        out     PORTC, spk                     ;send out speaker value
        ldi     Reload, 0xFF
        sub     Reload, tmer
        out     TCNT0, Reload
        out     SREG, savSREG      ;restore status reg
        reti

;**********************************************************

;***** Delay subroutine used throughout program

delay:  ldi     temp1, 0xF
        ldi     wreg, 0x00
        ldi     temp3, 0x07
oned2:  nop
        dec     temp1
        brne    oned2
        dec     wreg
        brne    oned2
        dec     temp3
        brne    oned2
        ret
;*******************************************************************************

song1:  ldi     temp3,0x02      ;start song subroutine
        ldi     tmer, do
        rcall   play
        ldi     temp3,0x02
        ldi     tmer, re
        rcall   play
        ldi     temp3,0x02
        ldi     tmer, mi
        rcall   play
        ldi     temp3,0x03
        ldi     tmer, fa
        rcall   play
        ldi     temp3,0x03
        ldi     tmer, mi
        rcall   play
        ldi     temp3,0x03
        ldi     tmer, fa
        rcall   play
        reti

song2:  ldi     temp3,0x01      ;Game Over song subroutine
        ldi     tmer, do2
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, si
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, la
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, sol
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, fa
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, mi
        rcall   play
        ldi     temp3,0x01
        ldi     tmer, re
        rcall   play
        ldi     temp3,0x07
        ldi     tmer, do
        rcall   play
        reti
 

;*** subroutine to play a note ***************************
play:   ldi     Reload, 0xFF            ;load first freq into timer0
        sub     Reload, tmer
        out     TCNT0, Reload
        ldi     wreg,0x01               ;enable timer interrupt
        out     TIMSK, wreg

        ldi     temp1, 0x00
        ldi     temp2, 0x00

dly1:   nop                     ;time given by temp3 to play the note
        dec     temp1
        brne    dly1
        dec     temp2
        brne    dly1
        dec     temp3
        brne    dly1

        ldi     wreg, 0x00      ;turn off timer0 ovfl interrupt
        out     TIMSK, wreg

dly2:   nop                     ;small silence time for between notes
        dec     temp1
        brne    dly2
        dec     temp2
        brne    dly2

        ret