;-------------------------------------------------------------------------; ; Darkroom Timer ; ; April '99 Stan Ockers (ockers@anl.gov) ; ; circuit diagram in CNTDN.PCX ; ; further description in CNTDN.TXT ; ; ; ; Counts down from 0-99 min and 0-59 sec giving an alarm at 0 ; ; initial counts are held in data EEPROM setable with one button ; ; ; ; RBO-RB3 to bases of transistors connect to common cathode of displays. ; ; RA0-RA3 to 1,2,4,8 BCD inputs of CD4511 7 segment latch and driver. ; ; RB7 to start pushbutton used to start countdown and silence alarm. ; ; RB6 goes to time set pushbutton use to sucessively set the digits. ; ; RA4 with pull-up resistor goes to PB to select from 15 starting counts ; ; RB4 and RB5 go to speaker which gives an alarm. ; ;-------------------------------------------------------------------------; LIST P=16F84 #INCLUDE "p16f84.inc" ;-------------------------------------------------------------------------; ; Here we define our own personal registers and give them names ; ;-------------------------------------------------------------------------; SEC EQU H'0C' ; this register holds the value of seconds SEC10 EQU H'0D' ; holds value of 10's of seconds MIN EQU H'0E' ; holds value of minutes MIN10 EQU H'0F' ; holds value of 10's of minutes DIGCTR EQU H'10' ; 8 bit counter, only 2 lowest bits actually used DIGIT EQU H'11' ; hold digit number to access table INTCNT EQU H'12' ; counts # interrupts to determine when 1 sec up FUDGE EQU H'13' ; allows slight adjustment every 7 interrupts RUNFLG EQU H'14' ; bit 0 only, tells if countdown in progress W_TEMP EQU H'15' ; temporarily holds value of W STATUS_TEMP EQU H'16' ; temporarily holds value of STATUS SECNT EQU H'17' ; used in counting 50, 20 msec delays for 1 sec CNTMSEC EQU H'18' ; used in timing of milliseconds ALARM EQU H'19' ; bit 0 only, used as flag for when to alarm OFFSET EQU H'1A' ; hold offset of address in EEPROM ;-------------------------------------------------------------------------; ; Here we give names to some numbers to make their use more clear ; ;-------------------------------------------------------------------------; #DEFINE START_PB D'7' #DEFINE SET_PB D'6' #DEFINE SELECT_PB D'4' #DEFINE RB4 D'4' #DEFINE RB5 D'5' ;-------------------------------------------------------------------------; ; We set the start of code to orginate a location zero ; ;-------------------------------------------------------------------------; ORG 0 GOTO MAIN ; jump to the main routine NOP NOP NOP GOTO INTERRUPT ; interrupt routine ;-------------------------------------------------------------------------; ; This table is used to get a bit pattern that will turn on a digit ; ;-------------------------------------------------------------------------; BITPAT ADDWF PCL,f ; get bit pattern for transistors RETLW H'0E' ; a low, (0), turns the transistor on RETLW H'0D' RETLW H'0B' RETLW H'07' ;-------------------------------------------------------------------------; ; Initialization routine sets up ports and timer ; ;-------------------------------------------------------------------------; INIT MOVLW H'C0' ; PB6 & PB7 inputs all others outputs TRIS PORTB MOVLW H'10' ; Port RA4 input, others outputs TRIS PORTA MOVLW H'03' ; prescaler on TMR0 and 1:16 OPTION MOVLW H'A0' ; GIE & T0IE set T0IF cleared MOVWF INTCON MOVLW H'F4' ; initialize INTCNT MOVWF INTCNT MOVLW H'06' ; initialize FUDGE MOVWF FUDGE CLRF OFFSET ; initialize OFFSET RETURN ;-------------------------------------------------------------------------; ; This is the interrupt routine that is jumped to when TMR0 overflows ; ;-------------------------------------------------------------------------; INTERRUPT MOVWF W_TEMP ; save W SWAPF STATUS,W ; save status MOVWF STATUS_TEMP ; without changing flags INCF DIGCTR,f ; next digit # MOVF DIGCTR,W ; get it into W ANDLW H'03' ; mask off 2 lowest bits MOVWF DIGIT ; save it for later ADDLW H'0C' ; point at register to display MOVWF FSR ; use as pointer MOVF INDF,W ; get value of reg pointed to into W MOVWF PORTA ; output to CD4511 MOVF DIGIT,W ; recall digit # CALL BITPAT ; get bit pattern MOVWF PORTB ; select transistor DECFSZ INTCNT,f ; finished 1 sec ? GOTO RESTORE ; not yet, return and enable inter. CALL EVERYSEC ; go to every second routine MOVLW H'F4' ; reset INTCNT to normal value MOVWF INTCNT DECFSZ FUDGE,f ; time for fudge? GOTO RESTORE ; not yet, continue on MOVLW H'06' ; reset FUDGE to 6 MOVWF FUDGE INCF INTCNT,f ; INTCNT to 245 RESTORE SWAPF STATUS_TEMP,W ; get original status back MOVWF STATUS ; into status register SWAPF STATUS_TEMP,f ; old no flags trick again SWAPF STATUS_TEMP,W ; to restore W BCF INTCON,T0IF ; clear the TMR0 interrupt flag RETFIE ; finished ;-------------------------------------------------------------------------; ; This routine is called by the interrupt routine every second ; ;-------------------------------------------------------------------------; EVERYSEC BTFSS RUNFLG,0 ; return if runflg not set RETURN DECF SEC,f ; decrement seconds digit INCFSZ SEC,W ; test for underflow GOTO CKZERO MOVLW H'09' ; reset sec to 9 MOVWF SEC DECF SEC10,f ; decrement SEC10 INCFSZ SEC10,W ; check underflow GOTO CKZERO MOVLW H'05' MOVWF SEC10 DECF MIN,f INCFSZ MIN,W GOTO CKZERO MOVLW H'09' MOVWF MIN DECF MIN10,f CKZERO MOVF SEC,f ; test SEC for zero BTFSS STATUS,Z RETURN MOVF SEC10,f ; check SEC10 for zero BTFSS STATUS,Z RETURN MOVF MIN,f ; check MIN for zero BTFSS STATUS,Z RETURN MOVF MIN10,f ; check MIN10 for zero BTFSS STATUS,Z RETURN CLRF RUNFLG ; stop the countdown BSF ALARM, 0 ; set the alarm flag RETURN ;-------------------------------------------------------------------------; ; This is a routine to read a byte from the data EEPROM ; ;-------------------------------------------------------------------------; READEE MOVWF EEADR ; set up eeprom address from W BSF STATUS,RP0 ; change to page 1 BSF EECON1,RD ; set the read bit BCF STATUS,RP0 ; back to page 0 MOVF EEDATA,W ; return value in W RETURN ;-------------------------------------------------------------------------; ; This routine fills the display registers from data EEPROM ; ;-------------------------------------------------------------------------; GETEE MOVLW H'01' ; EEprom location 1 + ADDWF OFFSET,W ; offset from start CALL READEE ; into W MOVWF SEC ; into SEC register MOVLW H'02' ; location 2 + ADDWF OFFSET,W ; offset from start CALL READEE ; into W MOVWF SEC10 ; into SEC10 register MOVLW H'03' ; location 3 + ADDWF OFFSET,W ; offset from start CALL READEE ; into W MOVWF MIN ; into MIN register MOVLW H'04' ; location 4 + ADDWF OFFSET,W ; offset from start CALL READEE ; into W MOVWF MIN10 ; into MIN10 register RETURN ;-------------------------------------------------------------------------; ; This routine writes a byte to data EEPROM ; ;-------------------------------------------------------------------------; WRITEEE BSF STATUS,RP0 ; set up EEADR and EEDATA first CLRF EECON1 BSF EECON1,WREN ; enable write MOVLW H'55' ; magic sequence MOVWF EECON2 MOVLW H'AA' MOVWF EECON2 BSF EECON1,WR EELOOP BTFSC EECON1,WR ; wait for WR to go low GOTO EELOOP ; not yet BSF EECON1,WREN BCF EECON1,EEIF ; clear the interrupt flag BCF STATUS,RP0 ; return to page 0 RETURN ;-------------------------------------------------------------------------; ; This routine puts display registers into data EEPROM ; ;-------------------------------------------------------------------------; PUTEE MOVF SEC,W ; put digit registers into EEprom MOVWF EEDATA MOVLW H'01' ; EEPROM location 1 + ADDWF OFFSET,W ; offset from start MOVWF EEADR CALL WRITEEE MOVF SEC10,W MOVWF EEDATA MOVLW H'02' ; EEPROM location 2 + ADDWF OFFSET,W ; offset from start MOVWF EEADR CALL WRITEEE MOVF MIN,W MOVWF EEDATA MOVLW H'03' ; EEPROM location 3 + ADDWF OFFSET,W ; offset from start MOVWF EEADR CALL WRITEEE MOVF MIN10,W MOVWF EEDATA MOVLW H'04' ; EEPROM location 4 + ADDWF OFFSET,W ; offset from start MOVWF EEADR CALL WRITEEE RETURN ;-------------------------------------------------------------------------; ; This is the main routine, the program starts here ; ;-------------------------------------------------------------------------; MAIN CALL INIT ; set up ports etc. ;-------------------------------------------------------------------------; ; We will return to this point when alarm is shut off. ; ;-------------------------------------------------------------------------; EE2D CALL GETEE ; put eeprom in display regs. BCF RUNFLG, 0 ; clear run flag so no countdown BCF ALARM, 0 ; clear alarm flag CALL WAITSTARTUP ; wait till no switches pressed CALL WAITSETUP CALL WAITSELECT ;-------------------------------------------------------------------------; ; This loop checks for either pushbutton and acts accordingly ; ;-------------------------------------------------------------------------; KEYCHKLOOP BTFSS PORTB,START_PB ; check for start pressed GOTO STARTCNT ; yes, start count BTFSS PORTB,SET_PB ; check for set pressed GOTO SETDISP ; yes, set display BTFSS PORTA,SELECT_PB ; check select pushbutton pressed GOTO SETSELECT ; yes, select starting count GOTO KEYCHKLOOP ; loop to catch key press ;-------------------------------------------------------------------------; ; If start key has been pressed then start countdown process, ; ; I initially released this code with only the setting of the ; ; run flag included. If you think about it you must also reset ; ; TMR0 to zero. TMR0 is free running and could have any value ; ; 0-255 when the button in pressed. Also INTCNT has to be ; ; initialized because the previous count could have been cancelled. ; ;-------------------------------------------------------------------------; STARTCNT CALL WAITSTARTUP ; wait for release of start key MOVLW D'244' ; reset INTCNT MOVWF INTCNT CLRF TMR0 ; and clear timer 0 BSF RUNFLG, 0 ; start the countdown ;-------------------------------------------------------------------------; ; Once started just loop looking for cancel or reaching 0000 ; ;-------------------------------------------------------------------------; MAINLOOP BTFSS PORTB,START_PB ; countdown in progress, check start GOTO EE2D ; start over again if pressed BTFSC ALARM, 0 ; reached 0000 yet? GOTO SOUNDALARM ; yes, turn alarm on GOTO MAINLOOP ; no start switch, continue looping ;-------------------------------------------------------------------------; ; This code sounds the alarm and waits on start to be pressed ; ;-------------------------------------------------------------------------; SOUNDALARM FINALWAIT BCF PORTB,RB4 ; speaker leads set up BSF PORTB,RB5 ; opposite polarity MOVLW 2 ; delay 2 milliseconds CALL NMSEC BSF PORTB,RB4 ; flip the speaker leads BCF PORTB,RB5 MOVLW 2 ; another 2 msec delay CALL NMSEC BTFSC PORTB,START_PB ; start button pressed GOTO FINALWAIT ; not yet CALL DLY20 ; debounce just to make sure BTFSC PORTB,START_PB ; second look GOTO FINALWAIT ; nah, keep waiting BCF PORTB,RB4 ; speaker leads set to same polarity BCF PORTB,RB5 CALL WAITSTARTUP ; now wait for the switch up GOTO EE2D ; start all over again ;-------------------------------------------------------------------------; ; Wait for release of start button ; ;-------------------------------------------------------------------------; WAITSTARTUP BTFSS PORTB,START_PB ; wait for release GOTO WAITSTARTUP ; not released yet CALL DLY20 ; debounce release BTFSS PORTB,START_PB ; 2nd check, make sure released GOTO WAITSTARTUP ; keep checking RETURN ;-------------------------------------------------------------------------; ; Wait for release of set button ; ;-------------------------------------------------------------------------; WAITSETUP BTFSS PORTB,SET_PB ; wait for release GOTO WAITSETUP ; not yet CALL DLY20 ; debounce release BTFSS PORTB,SET_PB ; 2nd check, make sure released GOTO WAITSETUP ; keep checking RETURN ;-------------------------------------------------------------------------; ; Wait for release of select button ; ;-------------------------------------------------------------------------; WAITSELECT BTFSS PORTA,SELECT_PB ; wait for release GOTO WAITSELECT ; not yet CALL DLY20 ; debounce release BTFSS PORTA,SELECT_PB ; 2nd check, make sure released GOTO WAITSELECT ; keep checking RETURN ;-------------------------------------------------------------------------; ; Routine to follow sets the countdown time digit by digit ; ;-------------------------------------------------------------------------; SETDISP CALL WAITSETUP ; wait for set key to be released MOVLW H'0A' ; put A's in digits, (no display) MOVWF MIN10 ; 10's of minutes MOVWF MIN ; minutes MOVWF SEC10 ; 10's of seconds MOVWF SEC ; seconds STARTMIN10 CLRF MIN10 ; 0 now in MIN10 MOREMIN10 MOVLW H'32' ; 50 delays of 20 msec MOVWF SECNT ; into counting register WAIT1 CALL DLY20 BTFSS PORTB,SET_PB ; set key pressed? GOTO MINSET ; yes MIN10 now set DECFSZ SECNT,f ; finished 1 sec delay? GOTO WAIT1 ; continue wait INCF MIN10,f ; every second increment 10's MIN MOVLW H'0A' ; reached 10? SUBWF MIN10,W BTFSC STATUS,Z ; Z set if reached 10 GOTO STARTMIN10 ; start again with 0 GOTO MOREMIN10 ; set up another 1 sec delay MINSET CALL WAITSETUP ; wait for release of set key STARTMIN CLRF MIN ; 0 into MIN MOREMIN MOVLW H'32' ; 50 delays of 20 msec MOVWF SECNT ; into counting register WAIT2 CALL DLY20 BTFSS PORTB,SET_PB ; set pressed? GOTO SETSEC10 ; yes, finished with MIN DECFSZ SECNT,f ; finished 1 sec delay? GOTO WAIT2 ; continue wait INCF MIN,f ; every second increment MIN MOVLW H'0A' ; reached 10? SUBWF MIN,W BTFSC STATUS,Z ; Z set if reached 10 GOTO STARTMIN ; put zero in if Z set GOTO MOREMIN ; set up another 1 sec delay SETSEC10 CALL WAITSETUP ; wait release STARTSEC10 CLRF SEC10 ; 0 into SEC10 MORESEC10 MOVLW H'32' ; 50 delays of 20 msec MOVWF SECNT ; into counting register WAIT3 CALL DLY20 BTFSS PORTB,SET_PB ; set pressed? GOTO SETSEC ; yes quit incrementing DECFSZ SECNT,f ; finished 1 sec delay? GOTO WAIT3 ; continue wait INCF SEC10,f ; every second increment 10's SEC MOVLW H'06' ; reached 6? SUBWF SEC10,W BTFSC STATUS,Z ; Z set if reached 6 GOTO STARTSEC10 ; put zero in if Z set GOTO MORESEC10 ; set up another 1 sec delay SETSEC CALL WAITSETUP ; wait for release STARTSEC CLRF SEC ; 0 into SEC MORESEC MOVLW H'32' ; 50 delays of 20 msec MOVWF SECNT ; into counting register WAIT4 CALL DLY20 BTFSS PORTB,SET_PB ; set button pressed? GOTO FINSET ; yes finished setting digits DECFSZ SECNT,f ; finished 1 sec delay? GOTO WAIT4 ; continue wait INCF SEC,f ; every second increment SEC MOVLW H'0A' ; reached 10? SUBWF SEC,W BTFSC STATUS,Z ; Z set if reached 10 GOTO STARTSEC ; put zero in if Z set GOTO MORESEC ; set up another 1 sec delay FINSET BCF INTCON, GIE ; disable interrupts CALL PUTEE ; put new digits into EEPROM BSF INTCON, GIE ; re-enable interrupts CALL WAITSETUP ; make sure set switch up GOTO KEYCHKLOOP ; start checking buttons again ;-------------------------------------------------------------------------; ; Selects starting count by changing EEPROM location 0 ; ;-------------------------------------------------------------------------; SETSELECT MOVLW D'4' ; offset up 4 ADDWF OFFSET,F ; next offset position MOVLW D'60' ; reached 16th yet? SUBWF OFFSET,W ; will give zero if yes BTFSC STATUS,Z ; skip if not 64 CLRF OFFSET ; reset position to zero MOVLW 0 ; EEPROM location MOVWF EEADR ; set up address MOVF OFFSET,W ; offset # into W MOVWF EEDATA ; set up data BCF INTCON,GIE ; clear GIE, disable interrupts CALL WRITEEE ; save # in location 0 BSF INTCON,GIE ; re-enable interrupts CALL GETEE ; get new start count into display CALL WAITSELECT ; make sure select switch is up GOTO KEYCHKLOOP ; start checking buttons again ;-------------------------------------------------------------------------; ; The following are various delay routines based on instruction length. ; ; The instruction length is assumed to be 1 microsecond (4Mhz crystal). ; ;-------------------------------------------------------------------------; DLY20 MOVLW 20 ; delay for 20 milliseconds ;*** N millisecond delay routine *** NMSEC MOVWF CNTMSEC ; delay for N (in W) milliseconds MSECLOOP MOVLW D'248' ; load takes 1 microsec CALL MICRO4 ; by itself CALL takes ... ; 2 + 247 X 4 + 3 + 2 = 995 NOP ; 1 more microsec DECFSZ CNTMSEC,f ; 1 when skip not taken, else 2 GOTO MSECLOOP ; 2 here: total 1000 per msecloop RETURN ; final time through takes 999 to here ; overhead in and out ignored ;*** 1 millisecond delay routine *** ONEMSEC MOVLW D'249' ; 1 microsec for load W ; loops below take 248 X 4 + 3 = 995 MICRO4 ADDLW H'FF' ; subtract 1 from 'W' BTFSS STATUS,Z ; skip when you reach zero GOTO MICRO4 ; loops takes 4 microsec, 3 for last RETURN ; takes 2 microsec ; call + load W + loops + return = ; 2 + 1 + 995 + 2 = 1000 microsec ;-------------------------------------------------------------------------; ; Here we set up the initial values of the digits in data EEPROM ; ;-------------------------------------------------------------------------; ORG H'2100' DE 0, 1, 0, 0, 0 ; 1st starting # DE 2, 0, 0, 0 ; 2nd starting # DE 3, 0, 0, 0 ; 3rd starting # DE 4, 0, 0, 0 ; 4th starting # DE 5, 0, 0, 0 ; 5th starting # DE 6, 0, 0, 0 ; 6th starting # DE 7, 0, 0, 0 ; 7th starting # DE 8, 0, 0, 0 ; 8th starting # DE 9, 0, 0, 0 ; 9th starting # DE 0, 1, 0, 0 ; 10th starting # DE 1, 1, 0, 0 ; 11th starting # DE 2, 1, 0, 0 ; 12th starting # DE 3, 1, 0, 0 ; 13th starting # DE 4, 1, 0, 0 ; 14th starting # DE 5, 1, 0, 0 ; 15th starting # END 

See:

Interested: