Programming the Game Simon
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.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.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