;----------------------------------------------------------------------;
; BINCLK4M.ASM          A clock that displays in bcd numbers           ;
;----------------------------------------------------------------------;

;                            .-----------.
;                           -|RA2     RA1|-       V+ = 4.5 or 5 Volts
;                           -|RA3     RA0|-       X = 4.096 MHz xtal
;                           -|RA4    OSC1|--|X|-||--- gnd  20 pf caps 
;   -{r}- = 470 ohm    V+ ---|MCLR   OSC2|--|X|-||--- gnd  
;   -|<- = LED        gnd ---|Vss     Vdd|--- V+
;            gnd ---|<--{r}--|RB0     RB7|-         
;            gnd ---|<--{r}--|RB1     RB6|-         
;            gnd ---|<--{r}--|RB2     RB5|---[PB]--- gnd  <- SET PB     
;            gnd ---|<--{r}--|RB3     RB4|---[PB]--- gnd  <- SHOW PB      
;                            '-----------'         
;                               PIC16F84      -[PB]- pushbutton

        LIST P=16F84           ;  tells which processor is used
        INCLUDE "p16f84.inc"   ;  defines various registers etc. Look 
        ERRORLEVEL -224        ;  supress annoying message from tris
        __CONFIG _PWRTE_ON & _XT_OSC & _WDT_OFF   ;  config. switches

            CBLOCK     0CH
                sec             ; seconds digit
                sec10           ; 10's of second digit
                mins            ; minutes digit
                min10           ; 10's of minutes digit
                hr              ; hours digit
                hr10            ; 10's of hours digit
                w_temp          ; holds W during interrupt
                status_temp     ; holds STATUS during interrupt
                fsr_temp        ; holds FSR during interrupt
                digit           ; holds digit position
                cntmsec         ; used to count milliseconds
                counter         ; used to stretch isr out to 1 sec
                flashcnt        ; counter for flashing LED
              ENDC
          
;----------------------------------------------------------------------;
;  Here are some DEFINEs which give 'names' to pushbutton port bits    ;
;----------------------------------------------------------------------;
            #DEFINE SHOWPB PORTB, 4
            #DEFINE SETPB PORTB, 5

            ORG 0               ; start at location 0

            goto main           ; jump over to main routine       

            ORG 4
             
            goto isr            ; jump to interrupt routine

;----------------------------------------------------------------------;
;                  High limit + 1 of digits at position W              ;
;----------------------------------------------------------------------;
sethi:
            addwf PCL, f
         dt H'A',H'6',H'A',H'6',H'A',H'3'

;----------------------------------------------------------------------;
;                        time delay routines                           ;
;----------------------------------------------------------------------;

micro4      addlw H'FF'                ;  subtract 1 from 'W'
            btfss STATUS,Z             ;  skip when you reach zero
            goto micro4                ;  more loops
            return                     

msec250:    movlw D'250'               ;  delay for 250 milliseconds
                ;*** N millisecond delay routine ***
nmsec:      movwf cntmsec              ;  delay for N (in W) millisec
msecloop:   movlw D'248'               ;  1 usec for load
            call micro4                ;  this instruction is 995 usec
            nop                        ;  1 usec 
            decfsz cntmsec,f           ;  1 usec, (2 if skip taken)
            goto msecloop              ;  2 usec, loop = 995+5 = 1 msec
            return 

;----------------------------------------------------------------------;
;                        Delay for one second                          ;
;----------------------------------------------------------------------;
onesecond:                     ; a subroutine that delays for 1 seconds
         call msec250
         call msec250
         call msec250
         call msec250
         return

;----------------------------------------------------------------------;
;                   Put value in W on LEDs for 1 second                ;
;----------------------------------------------------------------------;
sendnbr:
         btfsc STATUS, Z      ; skip if not zero
         movlw H'0F'          ; else all on for a zero
         movwf PORTB          ; light LEDs
         call onesecond       ; wait 1 second
         clrf PORTB           ; clear the LEDs
         movlw D'100'         ; pause for 0.1 sec
         call nmsec
         return 

;----------------------------------------------------------------------;
;                   Send the current time out LEDs                     ;
;----------------------------------------------------------------------;

disptime:              
             movf hr10, W
             call sendnbr
             movf hr, W
             call sendnbr
             movf min10, W
             call sendnbr
             movf mins, W
             call sendnbr
             return

;----------------------------------------------------------------------;
;                 Wait until set button is released                    ;
;----------------------------------------------------------------------;
waitsetup:                           ; wait for set pushbutton up
            btfss SETPB              ; skip if set button released
            goto waitsetup
            movlw D'10'
            call nmsec               ; wait 10 msec for debounce
            btfss SETPB              ; check again for release
            goto waitsetup           ; false alarm, start over
            return                   ; yes, finished

;----------------------------------------------------------------------;
;                       Initialization Subroutine                      ;
;----------------------------------------------------------------------;
init:
            movlw B'0000000'           ; all outputs port A
            tris PORTA                 
            movlw B'00110000'          ; RB4, RB5 inputs, others outputs
            tris PORTB                 ; on port B
            movlw H'0'                 ; all low (off)
            movwf PORTB
            movlw B'00000100'          ; pull-ups enabled                                    
                                       ; prescaler assigned to TMR0
                                       ; prescaler set to 1:16
                                       ; rolls over each 1/125 th second
            option                
            movlw 0
            movwf hr10
            movlw H'9'                 ; initialize hrs, mins and secs
            movwf hr                   ; Do this before interrupts are
            movlw H'5'                 ; turned on because isr also acts
            movwf min10                ; on these registers
            movlw H'0'
            movwf mins
            movwf sec10
            movwf sec
            movlw D'125'               ; initialize isr counter
            movwf counter
            movlw B'10100000'          ; GIE & T0IE set, T0IF cleared
            movwf INTCON
            return

;----------------------------------------------------------------------;
;  Interrupt routine, increments time by one second  (BCD)             ;
;----------------------------------------------------------------------;
isr:
            movwf w_temp             ; save W
            swapf STATUS,W           ; save status
            movwf status_temp        ; without changing flags
            swapf FSR,W              ; save FSR
            movwf fsr_temp           ; without changing flags


            decfsz counter, f        ; skip on 125th time thru
            goto restore             ; else exit
            movlw D'125'              ; reset interrupt counter
            movwf counter              
            movlw sec                ; point at sec register
            movwf FSR
newdigit:   incf INDF, f             ; current digit up one
            movlw sec                ; get difference sec and FSR
            subwf FSR, W
            call sethi               ; use to get high limit + 1
            subwf INDF, W            ; reached that number yet?
            btfss STATUS, Z          ; skip over if yes
            goto restore             ; else exit isr
            clrf INDF                ; set current digit to 0
            incf FSR, f               ; point at next digit
            btfss hr10, 1            ; has hr10 reached 2?
            goto newdigit            ; no, increment the next digit
            btfss hr, 2              ; has hr reached 4?
            goto newdigit            ; no
            clrf hr                  ; yes, set hour to 00
            clrf hr10                ; and hour 10
restore: 
            swapf status_temp,W      ; get original status back
            movwf STATUS             ; into status register
            swapf fsr_temp,W         ; get original fsr back
            movwf FSR                ; into status register
            swapf w_temp,f           ; old no flags trick again
            swapf w_temp,W           ; to restore W
            bcf INTCON,T0IF          ; clear the TMR0 interrupt flag
            retfie                   ; finished reset GIE

;----------------------------------------------------------------------;
;           Increment and display digit pointed to by FSR              ;
;----------------------------------------------------------------------;
updigit:
            incf INDF, f          ; selected digit up one
            movlw mins            ; set up to subtract mins address
            subwf FSR, W          ; from address of current digit
            call sethi            ; get maximum of digit + 1 into W
            subwf INDF, W         ; is it = to current digit value?
            btfsc STATUS, Z       ; gives zero if yes, skip if no
            clrf INDF             ; reset value of digit to zero
            movf INDF, W          ; get current value and ..
            movwf PORTB           ; display it
            call onesecond        ; pause for 1 second
            return

;----------------------------------------------------------------------;
;              flash  LED representing digit position                  ;
;----------------------------------------------------------------------;
setnext:
            movwf digit           ; save digit position
flashloop:
            clrf PORTB            ; all LEDs off
            movf digit, W         ; restore digit position
            btfss flashcnt, 0     ; leave off on odd #'s of counter
            movwf PORTB           ; flash digit on even #'s
            movlw 50              ; delay for 50 msec
            call nmsec
            incf flashcnt, f      ; flip LSB of flashcnt
            btfss SHOWPB          ; skip over if SHOW not pressed
            call updigit          ; else increment current digit
            btfsc SETPB           ; skip over if set pressed
            goto flashloop        ; continue until pressed again
            incf FSR, f           ; set up for next digit
            call waitsetup        ; wait for release
            return

;----------------------------------------------------------------------;
;                        Increment and set digits                      ;
;----------------------------------------------------------------------;
setdigits:
            bcf INTCON, GIE       ; no interrupts while setting time
            movlw mins            ; point at minutes register
            movwf FSR
            call waitsetup        ; wait on set pushbutton up
            movlw B'00000001'     ; light right LED (mins)
            call setnext
            movlw B'00000010'     ; light min10 LED
            call setnext
            movlw B'00000100'     ; light hr LED
            call setnext
            movlw B'00001000'     ; hr10 LED on
            call setnext
            clrf PORTB            ; clear LEDs
            bsf INTCON, GIE       ; enable interrupts again
            return

;----------------------------------------------------------------------;
;                          The main routine                            ;
;----------------------------------------------------------------------;

main:      
            call init             ; set up initial conditions
loop:       
            btfss SHOWPB          ; check for show pushbutton
            call disptime         ; display the time
            btfss SETPB           ; check for setting of time
            call setdigits
            goto loop             ; do forever

            end

;  Note:  A 4.096 MHz xtal is needed for accurate timing over long
;  periods.  You press the SHOW pushbutton to show the time, digit by
;  digit.  Zeros are shown as all lights on.  To set the time you press
;  SET.  The right most digit will blink indicating minutes are to
;  be set.  Pressing SET again chooses tens of minutes etc. After tens
;  of hours, the LEDs blank and the clock is timing again.  While any
;  LED is blinking, pressing SHOW will display that digit value and
;  increment it every second.  Release SHOW when the digit value is what
;  you want.