Appendix A
SIMON Project Source Code
Note: To save this code as an .asm file, click on the hyperlink and save the file to disk; otherwise, it opens in Notepad.
; The game of Simon .include "c:\users\geiger\4414def.inc" ; Will likely have to change ; this file name .device AT90S4414 ; Specify chip to the assembler .def save =r1 ; SREG save location .def maxnum =r2 ; Maximum wait time for mode 2 ; Corresponds to level of difficulty .def minnum =r3 ; Minimum wait time for mode 2 ; Corresponds to level of difficulty .def waitval =r4 ; Current number of T1 interrupts ; for which I want to wait .def level =r5 ; Level of difficulty (not actually ; used in Mode 2, but used in Mode 1) .def r30save =r6 ; Save registers used in outstr .def r31save =r7 ; subroutine to avoid writing over ; registers .def lcdstat =r8 ; LCD status .def Temp =r16 ; Temporary register .def numtones=r17 ; Register to keep track ; of the number of tones stored in RAM ; (for Mode 1) .def reload =r18 ; Holds Timer0 reload value .def toggle =r19 ; Register used to toggle output bit (A6) .def outreg =r20 ; Register used to hold output values to Port A .def count =r21 ; Interrupt count .def wcount =r22 ; Timer1 interrupt count--T1 will be used to ; count wait times (Mode 2) .def tcount =r23 ; Count of number of tones played--used both when ; playing tones and when counting ; user button presses .def rand =r24 ; Pseudorandom value taken from T0 .def charcnt =r25 ; Count of characters sent to LCD .def rtemp1 =r26 ; Temporary reload register used to test ; user button presses .def rndnum =r27 ; The number of tones to play each time, based ; on the level .equ PreScal0=3 ; Set timer0 prescale to 64 (use CK/64 ; for counter/timer0) .equ PreScal1=2 ; Set timer1 prescale to 8 ; Counter reload values corresponding to the frequencies to be played ; for each button .equ reload3 =0xb0 ; 392 Hz (G4, switch3) .equ reload2 =0xb9 ; 440 Hz (A4, switch2) .equ reload1 =0xc1 ; 494 Hz (B4, switch1) .equ reload0 =0xc4 ; 523 Hz (C5, switch0) ; Interrupt count values corresponding to the frequencies to be ; played for each button .equ count3 =196 ; 392 Hz (G4, switch3) .equ count2 =220 ; 440 Hz (A4, switch2) .equ count1 =247 ; 494 Hz (B4, switch1) .equ count0 =255 ; 523 Hz (C5, switch0) ; Note: count0 should actually be ; 261, but in order to only use ; one register, we have to sacrifice ; a few counts so that a tone produced ; using that button is slightly shorter ; than the others--.2448 seconds rather ; than .25 seconds .equ maxL1 =30 ; Maximum and minimum numbers of T1 interrupts .equ maxL2 =25 ; for which the program will wait for a user .equ maxL3 =20 ; to press a button, depending on the level of .equ minL1 =10 ; difficulty .equ minL2 =5 .equ minL3 =2 ;***** Other equates .equ lcdrs =6 ; LCD rs pin connected to PC6 .equ lcdrw =5 ; LCD r/w pin connected to PC5 .equ lcde =4 ; LCD enable pin connected to PC4 ; Timer/Counter prescaler values .equ TSTOP =0 ; Stop Timer/Counter .equ TCK8 =2 ; Timer/Counter runs from CK / 8 .equ TCK256 =4 ; Timer/Counter runs from CK / 256 .equ TCK1024 =5 ; Timer/Counter runs from CK / 1024 .dseg tones: .byte 100 ; Tone list to hold up to 100 tones in RAM ; There is no code to handle an overflow of ; this array because finding a person who can ; remember 100 tones is a highly unlikely ; occurrence ; ***** Initialization ***** .cseg .org $0000 rjmp RESET ; reset entry vector reti ; external interrupt 0 not used reti ; external interrupt 1 not used reti ; timer1 capture not used reti ; timer1 compare match A not used reti ; timer1 compare match B not used rjmp T1ovfl ; timer1 overflow not used rjmp T0ovfl ; timer0 overflow vector reti ; SPI transfer complete not used reti ; Rx complete vector not used reti ; Tx empty not used reti ; Tx complete not used reti ; analog comparator not used ; NOTE: All leading spaces on the following strings are included for the ; purpose of centering the strings on the LCD simon: .db " SIMON", 0x00 ; opening string simon2: .db " EE 476 Project", 0x00 ; sequence simon3: .db " by Mike Geiger", 0x00 ; modsel: .db " Select mode", 0x00 ; mode select modsel2:.db " S1: Memory mode", 0x00; sequence modsel3:.db " S2: React mode", 0x00 level1: .db " Choose level", 0x00 ; Level select sequence level2a:.db " S1: L1, S2: L2",0x00 level2b:.db "S1:1, S2:2, S3:3",0x00 ready: .db " Get ready ...", 0x00 ; String used to fill the pause ; at the start of each game mode1s: .db " Mode 1", 0x00 ; Mode 1 string ; Displayed while playing in Mode 1 mode2s: .db " Mode 2", 0x00 ; Mode 2 string ; Displayed while playing in Mode 2 end: .db " Game Over", 0x00 ; Game over plagain:.db " Play again?", 0x00 ; Play again plagn2: .db " S1: Y, S2: N", 0x00 ; sequence bye: .db " Goodbye", 0x00 ; To be shown if user decides ; not to play again RESET: ldi Temp, low(RAMEND) ; set stack pointer out SPL, Temp ldi Temp, high(RAMEND) out SPH, Temp ; LCD setup ldi Temp, TSTOP ; Timer 0 off (just in case) out TCCR0, Temp ; Stop timer ldi Temp, 0b00000010 ; Enable Timer 0 interrupt out TIMSK, Temp sei ; Enable interrupts ; power down the LCD ldi Temp, 0xff ; LCD power connection out DDRD, Temp ; power it down after reset ldi Temp, 0x00 out PORTD, Temp ldi Temp, 100 ; then Wait 1.5 second with mov count, Temp ; LCD power off clt ldi Temp, TCK256 out TCCR0, Temp offwait:brtc offwait ; wait for interrupt to set clt ; T bit tst Count ; Count == 0? breq pwrup ; dec Count ; Not needed here--Count is rjmp offwait ; decremented in T0ovfl ISR ; now power up LCD pwrup: ldi Temp, 0xff out PORTD, Temp ldi Temp, 100 ; Wait 1.5 second for LCD mov count, Temp ; power up puwait: brtc puwait clt tst Count breq init ; dec Count ; As above--dec not needed here rjmp puwait ; since T0ovfl ISR takes care of it ; hopefully by now the LCD has rebooted init: rcall lcdinit ; Initialize LCD module rcall lcdclr ; clear lcd ; Output opening string sequence ldi ZH, high(simon*2) ldi ZL, low(simon*2) rcall outstr rcall wait2s ldi ZH, high(simon2*2) ldi ZL, low(simon2*2) rcall outstr rcall wait2s ldi ZH, high(simon3*2) ldi ZL, low(simon3*2) rcall outstr rcall wait2s ser Temp ; set Port A to be out DDRA, Temp ; all outputs out DDRB, Temp ; set Port B to be all outputs out PORTB, Temp ; and clear LEDs out PORTD, Temp ; Turn on Port D pullups ldi Temp, 0b10000000 ; Set Port D to all inputs except out DDRD, Temp ; for bit 7--LCD power clr reload ; clear reload register out TCNT0, reload ; and preset counter to 0 ldi Temp, 0b10000010 ; enable timer0 and timer1 out TIMSK, Temp ; interrupts ldi Temp, PreScal0 ; prescale timer0 by 64 out TCCR0, Temp ; turn on timer0 ldi toggle, 0x40 ; set bit 6 of toggle register main: clr outreg ; clear output register clr numtones ; clear number of tones clr count ; clear T0 interrupt count clr wcount ; clear T1 interrupt count clr tcount ; clear tone count ldi ZL, low(tones) ; set Z register to point to ldi ZH, high(tones) ; RAM string ; Save the address of tones mov r30save, r30 mov r31save, r31 ; Display mode select sequence ldi ZH, high(modsel*2) ; Display "Select mode" ldi ZL, low(modsel*2) rcall outstr rcall wait2s ldi ZH, high(modsel2*2) ; Display "S1: Memory mode" ldi ZL, low(modsel2*2) rcall outstr rcall wait2s ldi ZH, high(modsel3*2) ; Display "S2: React mode" ldi ZL, low(modsel3*2) rcall outstr rcall wait2s ldi ZH, high(modsel*2) ; Now return display to ldi ZL, low(modsel*2) ; "Select mode" and rcall outstr ; wait for button press mpoll: sbis PIND, 0 ; Pick mode rjmp mode1 sbis PIND, 1 rjmp mode2 rjmp mpoll mode1: ldi Temp, 0x01 ; Set T1 to CK out TCCR1B, Temp ldi rndnum, 1 sbis PIND, 0 ; First, wait for switch 0 to rjmp mode1 ; be released ldi ZH, high(level1*2) ; Display "Choose level" ldi ZL, low(level1*2) rcall outstr rcall wait2s ldi ZH, high(level2a*2) ; Display "S1: L1, S2: L2" ldi ZL, low(level2a*2) rcall outstr mode1a: clr Temp ; Set the level of difficulty sbis PIND, 0 ; Switch 0 => level 1 ldi Temp, 1 ; (Only adds one new tone ; to the sequence each time) sbis PIND, 1 ; Switch 1 => level 2 ldi Temp, 2 ; (Adds a random number ; (between 1 and 4) of new ; tones to the sequence each time) tst Temp breq mode1a subi Temp, 1 ; reduce level by 1 (represent ; level 1 as a 0 in the mov level, Temp ; level register so that the ; level can be determined ; by a simple tst operation, ; rather than a set of ; cpi operations) ; Wait ldi ZH, high(ready*2) ; Display "Get ldi ZL, low(ready*2) ; ready ..." rcall outstr rcall wait2s ; Wait for two seconds before ; starting game ; Now display "Mode 1" ldi ZH, high(mode1s*2) ldi ZL, low(mode1s*2) rcall outstr mov r30, r30save ; Restore Z reg.--holds address mov r31, r31save ; of tones array mode1b: ; First, wait .25 seconds clr reload ldi Count, 63 pause: tst Count brne pause ldi YL, low(tones) ; set Y register to point to ldi YH, high(tones) ; RAM string mode1c: andi rand, 0x03 ; Convert rand to 2-bit value ; (in range 0-3) cpi rand, 0 ; Check possible values for rand-- breq tone0 ; set tone accordingly cpi rand, 1 breq tone1 cpi rand, 2 breq tone2 rjmp tone3 tone0: ldi reload, reload0 ; Load appropriate rjmp st_tone ; reload values tone1: ldi reload, reload1 rjmp st_tone tone2: ldi reload, reload2 rjmp st_tone tone3: ldi reload, reload3 st_tone:st Z+, reload ; and store them in tones buffer inc numtones ; increment number of stored tones clr reload ; clear the reload value to prevent ; unintended tones dec rndnum tst rndnum breq next in rand, TCNT1L ; Get another random value rjmp mode1c next: clr tcount ; Clear tcount before entering into loop ; where tones are played pltone: ld reload, Y+ inc tcount cpi reload, reload0 ; Another jump table--compare reload values breq pl0 ; loaded from array of saved notes cpi reload, reload1 ; and jump to code which loads appropriate breq pl1 ; interrupt count value cpi reload, reload2 breq pl2 cpi reload, reload3 breq pl3 pl0: ldi Count, count0 ; Preload interrupt count values cbi PORTB, 0 rjmp plback pl1: ldi Count, count1 cbi PORTB, 1 rjmp plback pl2: ldi Count, count2 cbi PORTB, 2 rjmp plback pl3: ldi Count, count3 cbi PORTB, 3 plback: tst Count brne plback ; spin until Count == 0 ser Temp ; Turn off LEDs out PORTB, Temp ; Now wait .25 seconds clr reload ldi Count, 63 wloop: tst Count brne wloop ; Check to see what tones have been played cp tcount, numtones ; Have all tones in RAM ; been played? brne pltone ; if not, play next tone ; Now, wait and see if the user can match the buttons pressed clr tcount clr reload ldi YL, low(tones) ldi YH, high(tones) tstlp: ld rtemp1, Y+ ; Get tone from RAM inc tcount poll: sbis PIND, 0 ; Test for user ldi reload, reload0 ; button press sbis PIND, 1 ldi reload, reload1 sbis PIND, 2 ldi reload, reload2 sbis PIND, 3 ldi reload, reload3 tst reload breq poll cp reload, rtemp1 brne error ; Light appropriate LED corresponding to button pressed sbis PIND, 0 cbi PORTB, 0 sbis PIND, 1 cbi PORTB, 1 sbis PIND, 2 cbi PORTB, 2 sbis PIND, 3 cbi PORTB, 3 ; Have the program play the tone for as long as you press the button waitA: sbis PIND, 0 rjmp waitA sbis PIND, 1 rjmp waitA sbis PIND, 2 rjmp waitA sbis PIND, 3 rjmp waitA ser Temp ; Turn off LEDs out PORTB, Temp in rand, TCNT0 ; Get next random value tst level ; if level==0 breq m1L1 ; We're in level 1 mov rndnum, rand andi rndnum, 0x0C ; Actually take the random lsr rndnum ; counter off of the 3rd lsr rndnum ; and 4th bits of TCNT0 subi rndnum, -1 rjmp stopton m1L1: ldi rndnum, 1 ; Add only one tone to the ; sequence stopton:clr reload ; Stop the current tone ldi Count, 63 waitlp: tst Count ; Play silence for .25 sec brne waitlp cp tcount, numtones ; Has the whole sequence ; been replicated? brlo tstlp ; If not, check next tone rjmp mode1b error: ldi reload, 10 ; Make some awful tone ldi ZH, high(end*2) ; Display "Game over" ldi ZL, low(end*2) rcall outstr ldi Count, 255 ; Play the tone for a somewhat ; unspecified amount of time errlp: tst Count brne errlp clr reload ; Now the game is over--display game over message, and ask if player ; wants to play again gmover: ldi Temp, 0xF0 out PORTB, Temp ldi ZH, high(plagain*2) ; Display "Play again?" ldi ZL, low(plagain*2) rcall outstr rcall wait2s ldi ZH, high(plagn2*2) ; Display "S1: Y, S2: N" ldi ZL, low(plagn2*2) rcall outstr gmover2:sbis PIND, 0 ; if Port D, pin 0 is clear, play again rjmp replay sbis PIND, 1 ; if Port D, pin 1 clear, game's over rjmp endg rjmp gmover2 replay: ser Temp ; Clear LEDs out PORTB, Temp sbic PIND, 0 ; and wait for switch 0 to rjmp main ; be released before returning rjmp replay ; to main menu endg: ldi ZH, high(bye*2) ; Display "Goodbye" ldi ZL, low(bye*2) rcall outstr ser Temp ; Turn off LEDs since blue LED out PORTB, Temp ; is a little too bright to ; leave on for any long amount ; of time without hurting ; someone's eyes endg2: rjmp endg2 ; Reaction mode--mode 2 mode2: sbis PIND, 1 ; first, must wait for switch 1 to rjmp mode2 ; be released ldi ZH, high(level1*2) ; Display "Choose level" ldi ZL, low(level1*2) rcall outstr rcall wait2s ldi ZH, high(level2b*2) ; Display "S1:1, S2:2, S3:3" ldi ZL, low(level2b*2) rcall outstr mode2b: clr Temp ; Test for user button press sbis PIND, 0 ; and set level accordingly ldi Temp, maxL1 sbis PIND, 1 ldi Temp, maxL2 sbis PIND, 2 ldi Temp, maxL3 tst Temp breq mode2b mov maxnum, Temp cpi Temp, maxL1 brne L2 ldi Temp, minL1 mov minnum, Temp rjmp setT1 L2: cpi Temp, maxL2 brne L3 ldi Temp, minL2 mov minnum, Temp rjmp setT1 L3: ldi Temp, minL3 mov minnum, Temp setT1: ldi Temp, PreScal1 ; Set Timer1 to CK/8 out TCCR1B, Temp mov waitval, maxnum ; Start waitval at whatever the ; maximum number of interrupts is ; for that level ; Wait ldi ZH, high(ready*2) ; Display "Get ready ... " ldi ZL, low(ready*2) rcall outstr rcall wait2s ldi ZH, high(mode2s*2) ; Display "Mode 2" ldi ZL, low(mode2s*2) rcall outstr mode2a: ; First, wait .25 seconds clr reload ldi Count, 63 pause2: tst Count brne pause2 andi rand, 0x03 cpi rand, 0 ; Check possible values for rand & 0x03 breq tone02 ; set tone accordingly cpi rand, 1 breq tone12 cpi rand, 2 breq tone22 rjmp tone32 tone02: ldi reload, reload0 ; Load appropriate reload values ldi Count, count0 cbi PORTB, 0 rjmp plbck2 tone12: ldi reload, reload1 ldi Count, count1 cbi PORTB, 1 rjmp plbck2 tone22: ldi reload, reload2 ldi Count, count2 cbi PORTB, 2 rjmp plbck2 tone32: ldi reload, reload3 ldi Count, count3 cbi PORTB, 3 plbck2: tst Count ; spin until Count == 0 brne plbck2 mov rtemp1, reload ; store reload value in reload ; temp register ser Temp ; Turn off LEDs out PORTB, Temp mov wcount, waitval clr reload poll2: tst wcount ; Has time run out? breq gerror ; If so, game over sbis PIND, 0 ; Test for user button press ldi reload, reload0 sbis PIND, 1 ldi reload, reload1 sbis PIND, 2 ldi reload, reload2 sbis PIND, 3 ldi reload, reload3 tst reload breq poll2 cp reload, rtemp1 ; Did user replicate correct tone? brne gerror ; If not, game over rjmp light gerror: rjmp error ; Light appropriate LED corresponding to button pressed light: sbis PIND, 0 cbi PORTB, 0 sbis PIND, 1 cbi PORTB, 1 sbis PIND, 2 cbi PORTB, 2 sbis PIND, 3 cbi PORTB, 3 ; Have the program play the tone for as long as you press the button waitA2: sbis PIND, 0 rjmp waitA2 sbis PIND, 1 rjmp waitA2 sbis PIND, 2 rjmp waitA2 sbis PIND, 3 rjmp waitA2 ser Temp ; Turn off LEDs out PORTB, Temp in rand, TCNT0 ; Get next random value clr reload ; Turn off speaker ldi Count, 63 ; and wait .25 sec in silence wtlp2: tst Count brne wtlp2 dec waitval ; wait for 1 less interrupt cp waitval, minnum brsh gmode2a mov waitval, minnum ; ensure that we never go below ; the minimum number of interrupts ; for a given level of difficulty gmode2a:rjmp mode2a ; ================================================ ; Clear entire LCD and delay for a bit lcdclr: ldi Temp, 1 ; Clear LCD command rcall lcdcmd ldi Temp, TCK256 ; Wait for 15 ms out TCCR0, Temp cwait: brtc cwait clt ldi Temp, PreScal0 out TCCR0, Temp clr Temp out TCNT0, Temp ret ;================================================ ; Initialize LCD module lcdinit:ldi Temp,0 ; Setup port pins out PORTC,Temp ; Pull all pins low ldi Temp, 0xff ; All pins are outputs out DDRC, Temp ldi Temp, TCK256 ; Wait for 15 ms out TCCR0, Temp iwait: brtc iwait clt ; LCD specs call for 3 repetitions as follows ldi Temp, 3 ; Function set out PORTC, Temp ; to 8-bit mode nop ; nop is data setup time sbi PORTC, lcde ; Toggle enable line cbi PORTC,lcde clr Temp out TCNT0, Temp iwait2: brtc iwait2 clt ldi Temp, 3 ; Function set out PORTC, Temp ; to 8-bit mode nop ; nop is data setup time sbi PORTC, lcde ; Toggle enable line cbi PORTC,lcde clr Temp out TCNT0, Temp iwait3: brtc iwait3 clt ldi Temp, 3 ; Function set out PORTC, Temp ; to 8-bit mode nop ; nop is data setup time sbi PORTC, lcde ; Toggle enable line cbi PORTC,lcde clr Temp out TCNT0, Temp iwait4: brtc iwait4 clt ldi Temp, 0b11110000 ; Make 4 data lines inputs out DDRC, Temp ldi Temp, TSTOP ; Clear timer0 and turn it off out TCCR0, Temp out TCNT0, Temp ; Finally, ; At this point, the normal 4 wire command routine can be used ldi Temp, 0b00100000 ; Function set, 4 wire, rcall lcdcmd ; 1 line, 5x7 font ldi Temp, 0b00001100 ; Display on, no cursor, rcall lcdcmd ; no blink ldi Temp, 0b00000110 ; Address increment, no rcall lcdcmd ; scrolling ret ; ============================================ ; Wait for LCD to go unbusy lcdwait:ldi Temp, 0xF0 ; Make 4 data lines inputs out DDRC, Temp sbi PORTC, lcdrw ; Set r/w pin to read cbi PORTC, lcdrs ; Set register select to command waitloop: sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde in lcdstat, PINC ; Read busy flag ; Read, and ignore lower nibble sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde sbrc lcdstat, 3 ; Loop until done rjmp waitloop ret ; ============================================= ; Send command in Temp to LCD lcdcmd: push Temp ; Save character rcall lcdwait ; Wait for LCD to be ready ldi Temp, 0xFF ; Make all port D pins outputs out DDRC, Temp pop Temp ; Get character back push Temp ; Save another copy swap Temp ; Get upper nibble andi Temp, 0x0F ; Strip off upper bits out PORTC, Temp ; Put on port nop ; wait for data setup time sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde pop Temp ; Recall character andi Temp, 0x0F ; Strip off upper bits out PORTC, Temp ; Put on port nop sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde ldi Temp, 0xF0 ; Make 4 data lines inputs out DDRC, Temp ret ; ============================================= ; Send character data in Temp to LCD lcdput: push Temp ; Save character rcall lcdwait ; Wait for LCD to be ready ldi Temp, 0xFF ; Make all port D pins outputs out DDRC, Temp pop Temp ; Get character back push Temp ; Save another copy swap Temp ; Get upper nibble andi Temp, 0x0F ; Strip off upper bits out PORTC, Temp ; Put on port sbi PORTC, lcdrs ; Register select set for data nop sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde pop Temp ; Recall character andi Temp, 0x0F ; Strip off upper bits out PORTC, Temp ; Put on port sbi PORTC, lcdrs ; Register select set for data nop sbi PORTC, lcde ; Toggle enable line cbi PORTC, lcde ldi Temp, 0xF0 ; Make 4 data lines inputs out DDRC, Temp ret ; Subroutine to output a string to the LCD ; The address of the first character in the string should be in ; the Z register (R30-31) when the subroutine is called outstr: clr charcnt rcall lcdclr strlp: lpm tst R0 breq strend mov Temp, R0 rcall lcdput adiw ZL, 1 inc charcnt cpi charcnt, 8 brne strlp ldi Temp, 0xC0 ; Set address to last 8 chars rcall lcdcmd rjmp strlp strend: ret ; Cheesy way of waiting about 2 seconds (2.097152 seconds, to be precise) wait2s: ldi Count, 255 wait1: tst Count brne wait1 ldi Count, 255 wait2: tst Count brne wait2 ret ; Timer1 overflow ISR T1ovfl: in save, SREG dec wcount ; decrement wait count out SREG, save reti ; Timer0 overflow ISR T0ovfl: in save, SREG dec Count ; decrement interrupt count tst reload ; if reload = 0, breq no_tone eor outreg, toggle ; toggle output bit out TCNT0, reload ; reload counter out PORTA, outreg ; send output to PORTA rjmp t0_end no_tone:clr outreg ; clear output and ; play nothing out PORTA, outreg ; this is the case ; where no button has ; been pushed yet t0_end: out SREG, save set reti
Back to top of page.
Back to SIMON Project Home Page.