;-------------------------------------------------------------------------;
; Darkroom Timer ;
; April '99 Stan Ockers (ockers@anl.gov) ;
; Circuit diagram in CNTDN.PCX ;
; Further description in CNTDN.TXT ;
; ;
; Modified March 2002 by Chris Deceuninck (turbokeu@turbokeu.com) ;
; Circuit diagrams in *.JPG ;
; Further description in CNTDN3.TXT ;
; ;
; Counts down from 0-99 min and 0-59 sec giving an alarm at 00:00 ;
; Initial counts are held in data EEPROM settable with one button. ;
; ;
; RBO-RB3 to bases of PNP transistors to drive common anode of displays. ;
; RA0-RA3 to 1,2,4,8 BCD inputs of CD4543 7-segment latch and driver. ;
; RB7 goes to START pushbutton to start countdown and silence alarm. ;
; RB6 goes to SET pushbutton to successively set the digits. ;
; RA4 goes to SEL pushbutton to select from 15 starting counts. ;
; RB4 goes to buzzer. ;
; RB5 goes to relay. ;
;-------------------------------------------------------------------------;
LIST P=16F84A
#INCLUDE "p16f84a.inc"
__CONFIG _CP_OFF & _WDT_ON & _PWRTE_ON & _HS_OSC
;-------------------------------------------------------------------------;
; 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, 20msec 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
TEMP EQU H'1B' ; temporarily holds value of W
;-------------------------------------------------------------------------;
; 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 BUZZER D'4'
#DEFINE RELAY D'5'
;-------------------------------------------------------------------------;
; We set the start of code to originate a location zero ;
;-------------------------------------------------------------------------;
ORG H'0000'
GOTO MAIN ; jump to the main routine
ORG H'0004'
GOTO INTERRUPT ; jump to the 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 PNP 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
BSF STATUS,RP0 ;
MOVWF TRISB ;
MOVLW H'10' ; RA4 input, others outputs
MOVWF TRISA ;
MOVLW H'03' ; prescaler on TMR0 and 1:16
MOVWF OPTION_REG ; write to OPTION register
BCF STATUS,RP0 ;
MOVLW H'A0' ; GIE & T0IE set T0IF cleared
MOVWF INTCON ; write to INTCON register
MOVLW D'244' ; initialize INTCNT
MOVWF INTCNT ;
MOVLW H'06' ; initialize FUDGE
MOVWF FUDGE ;
CLRF OFFSET ; initialize OFFSET
CLRF PORTB ; initialize PORTB
CLRF RUNFLG ; initialize RUNFLG
CLRF ALARM ; initialize ALARM
RETURN ;
;-------------------------------------------------------------------------;
; This is the interrupt routine that is jumped to when TMR0 overflows ;
; (every 4.096 msec) ;
;-------------------------------------------------------------------------;
INTERRUPT MOVWF W_TEMP ; save W
SWAPF STATUS,W ; save status
MOVWF STATUS_TEMP ; without changing flags
CLRWDT ; Clear Watchdog timer
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 CD4543
MOVF PORTB,W ; read PORTB bits
ANDLW B'00110000' ; mask bit 4 & 5
MOVWF TEMP ; save it for later
MOVF DIGIT,W ; recall digit #
CALL BITPAT ; get bit pattern
XORWF TEMP,W ; exclusive OR with TEMP
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 D'244' ; 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' ; reset SEC10 to 5
MOVWF SEC10 ;
DECF MIN,F ; decrement MIN
INCFSZ MIN,W ; check underflow
GOTO CKZERO ;
MOVLW H'09' ; reset MIN to 9
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 routine reads a byte from 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 BEEP ; sound buzzer
CALL GETEE ; put eeprom in display regs.
BCF PORTB,RELAY ; relay off
BCF PORTB,BUZZER ; buzzer off
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 BEEP ; sound buzzer
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
BSF PORTB,RELAY ; turn relay on
;-------------------------------------------------------------------------;
; Once started just loop looking for cancel or reaching 00:00 ;
;-------------------------------------------------------------------------;
MAINLOOP BTFSS PORTB,START_PB ; countdown in progress, check start
GOTO EE2D ; start over again if pressed
BTFSC ALARM,0 ; reached 00:00 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 BSF PORTB,BUZZER ; buzzer on - For some unknown
BSF PORTB,BUZZER ; reason I need to set it twice...
BCF PORTB,RELAY ; relay off
WAIT MOVLW 4 ; delay 4 milliseconds
CALL NMSEC ;
BTFSC PORTB,START_PB ; start button pressed?
GOTO WAIT ; not yet
CALL DLY20 ; debounce just to make sure
BTFSC PORTB,START_PB ; second look
GOTO WAIT ; not yet
CALL WAITSTARTUP ; now wait for the switch up
GOTO EE2D ; start all over again
;-------------------------------------------------------------------------;
; Keyboard Beep ;
;-------------------------------------------------------------------------;
BEEP BSF PORTB,BUZZER ; buzzer on
MOVLW 50 ; delay 50 milliseconds
CALL NMSEC ;
BCF PORTB,BUZZER ; buzzer off
RETURN ;
;-------------------------------------------------------------------------;
; 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 that follows sets the countdown time digit by digit ;
;-------------------------------------------------------------------------;
SETDISP CALL BEEP ; sound buzzer
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 20msec
MOVWF SECNT ; into counting register
WAIT1 CALL DLY20 ; 20msec delay
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 BEEP ; sound buzzer
CALL WAITSETUP ; wait for release of set key
STARTMIN CLRF MIN ; 0 into MIN
MOREMIN MOVLW H'32' ; 50 delays of 20msec
MOVWF SECNT ; into counting register
WAIT2 CALL DLY20 ; 20msec delay
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 BEEP ; sound buzzer
CALL WAITSETUP ; wait release
STARTSEC10 CLRF SEC10 ; 0 into SEC10
MORESEC10 MOVLW H'32' ; 50 delays of 20msec
MOVWF SECNT ; into counting register
WAIT3 CALL DLY20 ; 20msec delay
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 BEEP ; sound buzzer
CALL WAITSETUP ; wait for release
STARTSEC CLRF SEC ; 0 into SEC
MORESEC MOVLW H'32' ; 50 delays of 20msec
MOVWF SECNT ; into counting register
WAIT4 CALL DLY20 ; 20msec delay
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 CALL BEEP ; sound buzzer
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 CALL BEEP ; sound buzzer
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 10 ; 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 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