;------------------------------------------------------------------------------------------------------ ; Simon.asm program ; Written by Andrew D. Vassallo (snurple@hotmail.com) ; copyright 2000, 2001 ; ; This code may not be used for any commercial purposes. If reproduced in any form, the original ; author's credits must be included. Users are free to distribute this code in any form, as long as ; it is done so free of charge. Modifications are welcome, as long as the original author's credits ; remain intact in the header of this file. ;------------------------------------------------------------------------------------------------------ ; Program Abstract ; ; This is based on an old handheld electronic game "Simon." Through 4 pushbutton switches, ; the user attempts to mirror an increasingly difficult sequence of blinking LEDs and speaker ; tones. Upon successful game completion (of 63 moves), the game sounds a cycling series of tones. ; Upon failed input at any point in the game, the correct move will be displayed, the error tone ; will sound, and then the maximum move number reached is sounded out on the speaker: first, low ; tones will sound the "tens" and then a higher tone will sound the "ones." So, for 12 moves reached, ; one single low tone will sound followed by two higher tones. ; ; Each move is stored in memory and matched against 4 user input switches. ; The "moves" generated by the program are obtained from the TMR0 register at the time of ; the last user input, preventing the user from intentionally pushing a switch at a specific ; time to generate a predictable LED turn-on. ; ; Each "move" is recorded in EEPROM data memory, 1 "move" per memory register (since location 0x00 isn't ; used, we really only have 63 moves possible). Upon ; mismatch of a switch input from the user and a recorded (expected) "move," the correct LED ; will turn on while a buzzer sounds the error tone. Also, if the user takes more time ; than allowed (~5 seconds), the timeout will indicate failed match. Note that if the user waits ; 4.9 seconds to press the button, then he has another 4 seconds to release it, as TMR_Overflow is ; reset. This gives a total of 10 seconds time to think between moves if the user is really careful. ; ; (Note: It is possible to fit 4 "moves" per register (shifted in) to maximize the storage capacity at 252 ; moves, but at this point, 63 moves are enough. If anyone wants to try to increase this to 126 moves (swap ; the moves into the storage registers) or more (up to 252 moves by shifting the 2 LED bits), go right ; ahead. I wrote the core of this in a day, and added some options later, so there's probably some room for ; improvement. On the other hand, if you can remember more than 63 moves in sequence, you probably ; shouldn't be playing this game :) ; ; Note the use of TMR0 interrupts to generate the different tones. This effectively allows continuous ; asynchronous control of switch input polling and speaker oscillation. ; I tried to comment this as completely as possible to help out those new to PIC programming. If ; there's something you don't understand, drop me an e-mail and I'll try to help. ; ;------------------------------------------------------------------------------------------------------ ; Options: ; A reset button (SW5) is provided for the user to restart a new game (pulls MCLR down to Vss for reset). ; This button may be omitted if a power On/Off switch is installed. ; ; A switch (SW6) is provided for choosing high or low difficulty (fast or slow game play). ; ; Holding down the Red button (SW1) during power-up will initiate a "demonstration mode," where the program ; creates the move sequence and displays it without accepting user input. ; ; Holding down the Yellow button (SW2) during power-up will initiate a "reverse mode," where the sequence ; of moves is displayed in reverse order (the latest move is displayed first). ; ; Holding down the Green button (SW3) during power-up will initiate a "silent mode," where the tones are ; not sounded along with the LED illumination. ; ; Holding down the Blue button (SW4) during power-up will initate a "double mode," where two moves are ; generated each time around. ;------------------------------------------------------------------------------------------------------ list p=16F84 ; list directive to define processor #include ; processor specific variable definitions __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _RC_OSC ;------ All timing in this program is set for RC operation using a 4.7K and 18 pF RC circuit. This really ;------ doesn't operate properly at the calculated frequency, so I had to change the timing for the delay ;------ loops and buzzer tones through experimentation. Each Tcy is approximately 1.8us rather than 1.0us ;------ (at 4MHz). If desired, a 4MHz crystal could be used with a TMR0 prescale of 1:2 and probably achieve ;------ the same overall game speed. Maybe some of the Delay subroutine timing would have to be fixed, though, ;------ as it counts based on incrementing W, not via TMR0. ;***** VARIABLE DEFINITIONS CBLOCK 0x0C w_temp status_temp ; for context saving during interrupt routines rnd_hold ; register to hold temporary value of rnd number TMR_Div ; divisor for TMR0 overflow - every 64 overflows equals ONE TMR_Overflow increment TMR_Overflow ; counts how many times TMR0 overflows Delay_Count ; counter for delay loop LED_Number ; holds which LED should be active User_Number ; holds which button was pressed by user Move_Number ; register to hold address of EEPROM current move Recall_Addr ; current move number - call address for EEPROM recall routine speed_value ; holds the current tone delay cycle Tone ; register to hold flags to determine which tone to sound: ; (if all clear, do not sound any tones) ; bit0: 1=sound Red_Tone ; 0=don't sound Red_Tone ; bit1: 1=sound Yellow_Tone ; 0=don't sound Yellow_Tone ; bit2: 1=sound Green_Tone ; 0=don't sound Green_Tone ; bit3: 1=sound Blue_Tone ; 0=don't sound Blue_Tone ; bit4: 1=sound Error_Tone ; 0=don't sound Error_Tone Tone_Count ; register to hold the number of TMR0 overflows desired for proper tone flags ; register to hold generic flags ; bit0: 1=tone output wave currently high ; 0=tone output wave currently low ; bit1: 1=demo mode enabled ; 0=demo mode off - normal program operation ; bit2: 1=reverse mode enabled - newest move displayed first ; 0=normal program operation - newest move displayed last ; bit3: 1=difficult mode - two moves per round ; 0=easy mode - normal operation ; bit4: 1=second time through loop for difficult mode ; 0=first time through loop - ok to repeat for second move in this round ; bit5: 1=silent mode enabled ; 0=not silent mode - OK to output tones Hold_Number ; register to hold the random number seed for next randomized number (used for demo mode) ENDC ; 15 RAM registers allocated ;------ Initialize literal values Timeout EQU 0xC8 ; timeout delay limit (Timeout * TMR0 overflows*64 = ~5 seconds) easy_speed EQU 0x14 ; starting speed value - 20 loops is a nice starting speed ; increasing this will slow the game play down by increasing the buzzer ON time, and vice versa ; Using this value, it will take 26 moves to get down to the base_speed. difficult_speed EQU 0x0D ; using this value, it will take 12 moves to get to the base speed base_speed EQU 0x07 ; upper limit for speed, be careful (0x07 is not very fast, but fast enough) Error_Tone EQU 0x08 ; 8 loops @ 256*1.8us = 135 Hz Red_Tone EQU 0x05 ; 5 loops @ 256*1.8us = 2.30ms per high and low bit (217Hz) Yellow_Tone EQU 0x04 ; 4 loops @ 256*1.8us = 1.84ms per high and low bit (271Hz) Green_Tone EQU 0x03 ; 3 loops @ 256*1.8us = 1.38ms per high and low bit (361Hz) Blue_Tone EQU 0x02 ; 2 loops @ 256*1.8us = .920ms per high and low bit (542Hz) OPTIONVAL EQU 0x98 ; PORTB P/U off, TMR0 internal, RB0 falling edge, WDT prescale (1:1 for TMR0) INTCONVAL EQU 0xA8 ; GIE & T0IE enabled, RB0 int. disabled, RBIE (port change) enabled for Tone generation only TRISAVAL EQU 0xFF ; direction: PORTA all input ;-- PORTA<0:3> are Red, Yellow, Green, Blue pushbuttons, respectively TRISBVAL EQU 0x00 ; direction: PORTB all output ;-- RB1 is connected to speaker output. ;-- PORTB<2:5> are Red, Yellow, Green, Blue LEDs, respectively ;-- Note that RB0 is reserved in case RB0 interrupt is desired. ;********************************************************************** ORG 0x000 ; processor reset vector goto Start ; go to beginning of program ;------ Interrupts below ORG 0x004 ; interrupt vector location movwf w_temp ; save off W and STATUS registers swapf STATUS, 0 movwf status_temp clrf STATUS ; select Bank0 btfsc flags, 5 ; if silent mode... clrf Tone ; ...then prevent any tones from sounding btfsc INTCON, T0IF goto Timer_Int btfsc INTCON, RBIF ; PORTB change flag (only set to generate tone) goto Generate_Tone movf INTCON, 0 andlw 0xF8 ; clear all flags and ignore the interrupt if unknown movwf INTCON goto Reset_Interrupts Timer_Int ;------ Note the use of TMR_Div - this achieves a dual rate for TMR0 overflow. The straight 1:1 rate is used for ;------ the tone generation frequency count, and the 1:64 rate is used for the game speed. bcf INTCON, T0IF ; clear TMR0 overflow-interrupt flag decfsz TMR_Div goto Continue_Tone ; always find out if a tone is sounding incf TMR_Overflow ; this increments once every 64 TMR0 overflows movlw 0x40 ; reset TMR_Div to 64 cycles through movwf TMR_Div goto Continue_Tone ; always check tone sound if TMR0 overflows Generate_Tone ;------ Find out which Tone should be sounded, then begin the tone on the NEXT TMR0 interrupt btfsc Tone, 0 movlw Red_Tone btfsc Tone, 1 movlw Yellow_Tone btfsc Tone, 2 movlw Green_Tone btfsc Tone, 3 movlw Blue_Tone btfsc Tone, 4 movlw Error_Tone movwf Tone_Count goto Reset_Interrupts Continue_Tone movf Tone, 1 btfss STATUS, Z ; if Tone register is all zeros, do not sound or continue any tones decfsz Tone_Count goto Reset_Interrupts goto Switch_Wave Switch_Wave btfss flags, 0 ; if we're currently outputting a high wave, switch to low goto Set_High bcf PORTB, 1 bcf flags, 0 ; we're outputting a low wave now goto Generate_Tone ; reset Tone_Count for next time Set_High bsf PORTB, 1 ; otherwise set the wave high bsf flags, 0 ; we're outputting a high wave now goto Generate_Tone Reset_Interrupts bcf INTCON, RBIF ; always keep this flag cleared swapf status_temp, 0 ; restore all registers movwf STATUS swapf w_temp, 1 swapf w_temp, 0 retfie ;------ Done with interrupts. Start bsf STATUS, RP0 ; select bank 1 movlw OPTIONVAL movwf OPTION_REG ; set options in PIC movlw TRISAVAL movwf TRISA ; set port A direction bits movlw TRISBVAL ; set PORTB for all output movwf TRISB bcf STATUS, RP0 ; select bank 0 clrf Move_Number clrf Tone clrf flags movlw 0x3C movwf PORTB ; set all LEDs to turn on for power-up indication btfss PORTA, 0 ; Red button normally pulled high - if low, then enable demo mode bsf flags, 1 btfss PORTA, 1 ; Yellow button normally pulled high - if low, then enable reverse mode bsf flags, 2 btfss PORTA, 2 ; Green button normally pulled high - if low, then enable silent mode bsf flags, 5 btfss PORTA, 3 ; Blue button normally pulled high - if low, then enable double mode bsf flags, 3 movlw base_speed ; base speed - absolute upper speed limit movwf speed_value movlw easy_speed ; default is easy speed btfss PORTA, 4 movlw difficult_speed ; difficult switch selected (normally pulled high) addwf speed_value ; add the constant to the base speed ;------ How speed_value works: ; LED will light and correct tone will sound *when generated by the program*. (When user input generates the tone, it ; will shut off when the button is released, or if the timeout is exceeded) A flag is set to initiate an interrupt ; to begin the tone sounding. After TMR_Overflow increments enough to equal the speed_value, the sound will shut off. ; TMR_Overflow will increment once per (256 TMR0 counts * 64 TMR_Div) instructions @ ~1.8us each. So, the total time for ; each TMR_Overflow is ~0.0295 seconds. With a speed_value of 0x10, for example, this will be 16*.0295=0.47 seconds per ; move. Note that this will keep getting faster until the base_speed is reached. ;------ ;------ Delay for ~4 seconds (3 + 1 for Main delay) for user to get set up clrw clrf Delay_Count ; set for 256 loops (~.45 seconds total) call Delay clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay movlw INTCONVAL movwf INTCON ; set interrupts movf TMR0, 0 movwf Hold_Number ; used for demo mode clrf PORTB ; turn off all LEDs after power-up sequence Main ;------ Delay for ~1 second between sequences clrw clrf Delay_Count ; set for 256 loops call Delay clrw clrf Delay_Count ; set for 256 loops call Delay rrf Move_Number, 0 ; decrement once every 2 moves to increase game speed btfss STATUS, C goto No_Reduction movf speed_value, 0 sublw base_speed btfss STATUS, C ; if speed_value >= base speed, speed_value-- every 2 moves decf speed_value No_Reduction ;------ This section reads the current value of TMR0 (unpredictable because of user input on switches) and loads ;------ it into LED_Number to be randomized. If demo mode is enabled, don't use TMR0 each time, only use the first ;------ one as a seed, then recycle each randomized value as a seed for the next move. ;------ Once the move is randomized, it's stored in EEPROM memory, then the recall address is set (to the first move ;------ if normal operation, or to the last move if reverse mode is enabled) and each move is recalled and displayed ;------ at a speed determined by the speed_value. movf TMR0, 0 ; use unknown state of TMR0 as seed for random number btfsc flags, 1 ; if demo mode, load random number movf Hold_Number, 0 movwf LED_Number call Randomize ; returns LED number to illuminate next incf Move_Number ; create next move - do not use location 0x00 (start at 0x01) call Store_Number ; add this number to stored values clrf Recall_Addr incf Recall_Addr ; begin at 0x01 for move recall for normal operation mode movf Move_Number, 0 btfsc flags, 2 ; if reverse mode, begin recall address at maximum move number movwf Recall_Addr ; start recalling moves at the current max. move number ; This register is decreased to zero to provide the move sequence ; while the Move_Number register holds the current latest move number sublw 0x40 btfsc STATUS, Z ; 64 moves max. (really 63 since we started at 0x01) goto Game_Finished ; if we get to this point, game is over btfsc flags, 3 ; if we're in difficult mode, then check flag bit 4 btfsc flags, 4 ; if this is the first time through (bit=clear)... goto Recall_Loop ; if bit3 clear or if bit4 set, skip the second move bsf flags, 4 goto Main ; ...then set flag and create another move for difficult mode Recall_Loop bcf flags, 4 ; as default, clear flag movlw 0xAA ; set for 170 loops to add a small break between moves movwf Delay_Count clrw call Delay call Recall_Number ; read the current Recall_Addr and return the move into LED_Number movlw LED_Number ; treat as literal to load address pointer into FSR movwf FSR ; load FSR with LED_Number address pointer call Switch_LED ; determines which LED to illuminate, does so and selects tone call SoundTone ; begin sounding correct tone btfsc flags, 2 ; if reverse mode is set, skip to proper display method goto Reverse_Method movf Move_Number, 0 subwf Recall_Addr, 0 btfsc STATUS, C goto Test_Mode ; if equal or greater, we're done recalling moves incf Recall_Addr ; otherwise display next move goto Recall_Loop Reverse_Method decfsz Recall_Addr ; output next move (note that 0x01 is the lowest move) goto Recall_Loop Test_Mode btfsc flags, 1 ; if demo mode, skip user input... goto Main ; ...and loop directly back to generate the next move ;------ This next section of the program recalls each move and waits for user input before comparing them. After ;------ each move is recalled, we wait for user input by polling the switches. Once one is pressed, the corresponding ;------ tone is sounded as long as the button is depressed. Once released (if within the timeout period), the ;------ user input move is compared with the expected (recalled) move. If they match, the next move is recalled ;------ until all moves are finished. Then we loop back again to generate the next move. Once we hit 63 moves, ;------ we're done. clrf Recall_Addr incf Recall_Addr ; begin at 0x01 for move recall for normal operation mode movf Move_Number, 0 btfsc flags, 2 ; if reverse mode, start at maximum move number movwf Recall_Addr ; reset address register to top position GetUserInput call Recall_Number ; get current "move" to match with user input movlw 0x40 movwf TMR_Div ; reset divisor and overflow registers clrf TMR_Overflow ; holds total timeout counts for user input call WaitForButton ; waits for user input, W register holds button pressed on return movwf User_Number ; dump off which button was pressed movlw 0x0C movwf Delay_Count clrw call Delay ; debounce switch to 20ms movlw User_Number ; treat as literal to load address pointer into FSR movwf FSR ; load FSR with LED_Number address pointer call Switch_LED ; indicate which LED the user pressed ;------ Wait for button to be released - user can't cheat by holding down the button due to timeout. ;------ We will use User_Number to determine which button to wait for, rather than check ALL buttons, since ;------ the user could press another button and, due to the bounce, effectively "release" a button, thus throwing ;------ off the timing. movlw 0x40 movwf TMR_Div clrf TMR_Overflow bsf INTCON, RBIF ; use flag to begin generating tone call WaitForRelease ; if timeout occurs, Failed_Input will be called from WaitForRelease clrf PORTB ; turn off LEDs (and buzzer if still on) clrf Tone ; reset the tone register to stop generating tone movlw 0x0C movwf Delay_Count clrw call Delay ; debounce switch to 20ms movf LED_Number, 0 ; compare LED and User numbers subwf User_Number, 0 btfss STATUS, Z goto Failed_Input ; if current move and User inputs don't match, abort btfsc flags, 2 ; if reverse mode is set, skip to proper display method goto Reverse_Compare movf Move_Number, 0 subwf Recall_Addr, 0 btfsc STATUS, C goto Main ; if equal or greater, we're done recalling moves incf Recall_Addr ; otherwise display next move goto GetUserInput Reverse_Compare decfsz Recall_Addr ; output next move (note that 0x01 is the lowest move) goto GetUserInput goto Main ; continuous loop - find the next number in the cycle ;------------------------------------------- Win/Loss Endgame routines listed below -------------------------------- Failed_Input ;------ Turn on LED that was SUPPOSED to be entered and sound error tone. clrf PORTB ; reset LEDs and buzzer in preparation for Error indication movlw LED_Number ; treat as literal to load address pointer into FSR movwf FSR ; load FSR with LED_Number address pointer call Switch_LED ; will sound correct tone with LED in addition to the error_tone movlw 0x40 ; long tone delay movwf speed_value clrf Tone bsf Tone, 4 ; error tone to be sounded call SoundTone clrw clrf Delay_Count call Delay clrw clrf Delay_Count ; set for 256 loops call Delay ; wait for 1 second before sounding completed move count ;------ Divide Move_Number by 10 to identify to the user the maximum move number achieved. Store multiplier in Recall_Addr ;------ and remainder in Move_Number. Sound out total count with 10s as long low tones and 1s as short high tones. ;------ Also, light up Red LED for 10s and Yellow LED for 1s. ;------ We're not doing anything fancy here - we're just using successive subtraction for the division since it ;------ would be such a small number. Alternately, some real division bit-shifting code could be substituted. clrf Recall_Addr Div_By_10 movlw 0x0A ; successive subtraction by 10 subwf Move_Number, 0 btfss STATUS, C ; if carry=0, we overflowed, so begin sounding tones goto Sound_Tens incf Recall_Addr movwf Move_Number ; move remainder into Move_Number register for next subtraction goto Div_By_10 Sound_Tens movf Recall_Addr, 1 ; move file to itself to test for zero btfsc STATUS, Z goto Sound_Ones ; if Recall_Addr = 0, skip to Ones movlw 0x20 ; longer time duration for 10s movwf speed_value clrf Tone bsf PORTB, 2 ; turn Red LED on bsf Tone, 0 ; Red tone counts number of 10s call SoundTone ; shuts off buzzer and LED clrw clrf Delay_Count ; set for 256 loops call Delay ; wait for 0.5 seconds between tones decfsz Recall_Addr goto Sound_Tens Sound_Ones movf Move_Number, 1 btfsc STATUS, Z sleep ; if no Ones, then end game movlw 0x10 ; shorter tone duration for 1s movwf speed_value clrf Tone bsf PORTB, 3 ; turn Yellow LED on bsf Tone, 3 ; Blue tone counts number of 1s call SoundTone ; shuts off buzzer and LED clrw clrf Delay_Count ; set for 256 loops call Delay ; wait for 0.5 seconds between tones decfsz Move_Number goto Sound_Ones sleep ; power-down to save battery after game over Game_Finished ;------ Cycle speaker tones to indicate game won. movlw 0x07 movwf speed_value ; preset quick cycle time for tones in this routine movlw 0x05 movwf Delay_Count ; just re-use Delay_Count as a loop counter clrf Tone GF_Loop bsf Tone, 0 call SoundTone bsf Tone, 1 call SoundTone bsf Tone, 2 call SoundTone bsf Tone, 3 call SoundTone decfsz Delay_Count ; 4 times through this routine goto GF_Loop sleep ;------------------------------------------- Subroutines listed below ---------------------------------------------- Store_Number ;------ Stores value of LED_Number into EEPROM address "Move_Number" movf Move_Number, 0 movwf EEADR movf LED_Number, 0 movwf EEDATA bsf STATUS, RP0 ; select Bank1 for EECON access bcf INTCON, GIE ; disable interrupts temporarily bsf EECON1, WREN ; enable EEPROM write movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1, WR ; write values to EE bsf INTCON, GIE ; reenable interrupts bcf EECON1, WREN ; disable EEPROM write Waitforflag btfss EECON1, EEIF ; poll flag bit for write cycle completion goto Waitforflag bcf EECON1, EEIF ; reset flag - write cycle complete bcf STATUS, RP0 ; leave Bank0 active by default return Recall_Number ;------ Recalls the value located at Recall_Addr from EEPROM into LED_Number movf Recall_Addr, 0 movwf EEADR bsf STATUS, RP0 ; select Bank1 for EECON access bsf EECON1, RD ; enable read bcf STATUS, RP0 ; select Bank0 for EEDATA access movf EEDATA, 0 movwf LED_Number return Randomize ;Rnew = Rold * 221 + 53 ;221 = 256 - 32 - 4 + 1 ;256 can be eliminated ;so we need to calculate Rnew = Rold * (1 - 32 - 4) + 53 using ;truncating arithmetic ;or Rnew = Rold * (-32 - 3) + 53 clrc rlf LED_Number, 1 swapf LED_Number, 0 andlw 0xE0 rrf LED_Number, 1 addwf LED_Number, 0 addwf LED_Number, 0 addwf LED_Number, 0 sublw 0x35 movwf LED_Number movwf Hold_Number ; used for demo mode only ;Then divide by 64 to get a result from 0-3 (4 values): ;shift LED_Number right 6 times rlf LED_Number, 1 clrf rnd_hold rlf rnd_hold, 1 rlf LED_Number, 1 rlf rnd_hold, 1 movf rnd_hold, 0 movwf LED_Number return Switch_LED ;------ Find out which LED should be turned on, then do it and select appropriate tone ;------ Indirect addressing is used due to 2 different sources to switch (user or program) ;------ Leave LED on when finished - must clear PORTB after this routine to turn off LEDs ;------ (This is done so when button is pressed by user input, the LED remains on) btfsc INDF, 1 ; test bit 1 goto GT_2 ; number is either 2 or 3 btfsc INDF, 0 ; test bit 0 goto Yellow bsf PORTB, 2 ; must be zero (Red) bsf Tone, 0 ; set flag to turn on Low tone return Yellow bsf PORTB, 3 ; number is 1 (Yellow) bsf Tone, 1 return GT_2 btfsc INDF, 0 goto Blue bsf PORTB, 4 ; must be 2 (Green) bsf Tone, 2 return Blue bsf PORTB, 5 ; number is 3 (Blue) bsf Tone, 3 return WaitForButton ;------ Continually poll PORTA switches to determine which was pressed (grounded). Rather than using ;------ PORTB pin interrupt on change, polling is just as quick and I don't have to worry about reading ;------ other PORTB pins causing trouble or disabling/enabling interrupts at the proper times, etc. ;------ If TMR_Overflow equals allowed delay time (Timeout register), then abort due to timeout. btfss PORTA, 0 ; normally pulled-up retlw 0x00 ; Red button depressed (RA0) btfss PORTA, 1 retlw 0x01 ; Yellow (RA1) btfss PORTA, 2 retlw 0x02 ; Green (RA2) btfss PORTA, 3 retlw 0x03 ; Blue (RA3) movf TMR_Overflow, 0 sublw Timeout btfsc STATUS, Z goto Failed_Input goto WaitForButton WaitForRelease ;------ Continually poll PORTA switches to determine if User_Number pin was released. ;------ If TMR_Overflow equals allowed delay time (Timeout register), then abort due to timeout. btfsc User_Number, 1 ; test bit 1 goto Pin2_3 ; number is either 2 or 3 btfsc User_Number, 0 ; test bit 0 goto Pin1Loop Pin0Loop btfsc PORTA, 0 ; when pin is pulled up again, return return movf TMR_Overflow, 0 sublw Timeout btfsc STATUS, Z goto Failed_Input ; exceeded timeout allowance goto Pin0Loop Pin1Loop btfsc PORTA, 1 return movf TMR_Overflow, 0 sublw Timeout btfsc STATUS, Z goto Failed_Input goto Pin1Loop Pin2_3 btfsc User_Number, 0 goto Pin3Loop Pin2Loop btfsc PORTA, 2 return movf TMR_Overflow, 0 sublw Timeout btfsc STATUS, Z goto Failed_Input goto Pin2Loop Pin3Loop btfsc PORTA, 3 return movf TMR_Overflow, 0 sublw Timeout btfsc STATUS, Z goto Failed_Input goto Pin3Loop SoundTone ;---Set flag to start interrupt moving which starts tone sounding, clear flag inside interrupt. ;---At every TMR0 overflow, we will check to see if tone is still sounding and also to see if we have ;---to change the wave bit from high to low. The "Tone" register bits being set will determine if ;---the PORTB, 1 output continues, since the flag is cleared after the first time through. movlw 0x40 movwf TMR_Div ; reset divisor and overflow registers clrf TMR_Overflow bsf INTCON, RBIF ; use flag to begin generating tone SoundToneLoop movf speed_value, 0 subwf TMR_Overflow, 0 btfss STATUS, Z goto SoundToneLoop clrf PORTB ; turn off LEDs (and buzzer if still on) clrf Tone ; reset the tone register to stop generating tone return Delay ;------ Note that W register must be cleared before calling this routine to obtain the proper delay. ;------ Delay_Count is predefined with the multiplier (Delay_Count*256 loops*4Tcy) gives total delay. ;------ Delay is approximately .45 seconds with Delay_Count=0xFF. ;------ Note that if W register is preloaded with higher values, the total delay time can be fine tuned. ;------ Alternately, another constant can be defined and preloaded prior to entering this routine, then ;------ copying it to W and proceding normally. Another inner loop would need to be defined, with the ;------ constant value being held outside the inner loop. addlw 0x01 btfss STATUS, Z goto Delay decfsz Delay_Count goto Delay return END ; directive 'end of program'