
;-------------------------------------------------------------------------;
;                             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   
