(http://img268.imageshack.us/img268/1126/ilker.gif) (http://img268.imageshack.us/i/ilker.gif/)
;-------------------------------------------------------------------------;
; 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