; 7 segment Clock
; By Hein Ragas
; http://home.publishnet.nl/~hragas
; Version 1.0
; Clock functionality ONLY
;****_***|***|**_*************_********************************************

     title "7 segment Clock"
     list P = 16F628A
     include "p16F628A.inc"
     ERRORLEVEL 0,-302
     ERRORLEVEL 0,-306
     __CONFIG 0x2101
                              ; _CP_OFF & _XT_OSC & _WDT_OFF &
                              ; _PWRTE_ON & _MCLRE_OFF &
                              ; _BODEN_OFF & _LVP_OFF

; Variables for delay loop
teller       EQU 070h
teller2      EQU 071h

; Variables for time-keeping
sece         EQU 072h         ; Second units
sect         EQU 073h         ; Second tens
mine         EQU 07Bh         ; Minute units
mint         EQU 07Ch         ; Minute tens
hre          EQU 07Dh         ; Hour units
hrt          EQU 07Eh         ; Hour tens

; Variables for the one second timer
bres_hi      EQU 076h         ; hi byte of our 24bit variable
bres_mid     EQU 077h         ; mid byte
bres_lo      EQU 078h         ; lo byte

; Variables to save register statusses during interrupt
temp_s       EQU 079h
temp_w       EQU 07Ah

; Variables for debouncing and inputs
bounce_timer EQU 07Fh         ; Counter for the debounce routine
csa          EQU 060h
cva          EQU 061h
count_A      EQU 062h
count_B      EQU 063h
prev_val     EQU 064h


     GOTO    init
     ORG     0x4              ; Choose starting adress

; Interrupt routine. This is called every 256 instructions.
isr
     MOVWF   temp_w           ; save w register
     MOVF    STATUS,W         ; W = STATUS
     MOVWF   temp_s           ; save STATUS register

     ; First see if we need to call the debouncing routine.
     ; We don't call the debouncing routine every interrupt, but every 10
     ; interrupts. That's once every 2560 instructions. Because an input
     ; needs to be the same for 4 consecutive calls to the debouncing
     ; routine, it needs to be the same for roughly 0.01 seconds.
     DECFSZ  bounce_timer,f   ; Decrement bounce_timer, see if it's 0
     GOTO    bookkeeping      ; Not 0, skip the debouncing routine

     MOVLW   009h             ; bounce_timer == 0
     MOVWF   bounce_timer     ; Reset bounce_timer to 9

     CALL    read_inputs
     CALL    debounce
     CALL    process_inputs

     ; Here comes the bookkeeping for the timer routine
bookkeeping
     TSTF    bres_mid         ; first test for mid==0
     SKPNZ                    ; nz = no underflow needed
     DECF    bres_hi,f        ; z, so is underflow, so dec the msb

     DECFSZ  bres_mid,f       ; dec the mid byte (subtract 256)
     GOTO    end_isr          ; nz, so definitely not one second yet.

     TSTF    bres_hi          ; test hi for zero too
     SKPZ                     ; z = both hi and mid are zero,
                              ; is one second!
     GOTO    end_isr          ; nz, so not one second yet.

     MOVLW   0x0F             ; get msb value
     MOVWF   bres_hi          ; load in msb
     MOVLW   0x42             ; get mid value
     MOVWF   bres_mid         ; load in mid
     MOVLW   0x40             ; lsb value to add
     ADDWF   bres_lo,f        ; add it to the remainder already in lsb
     SKPNC                    ; nc = no overflow, so mid is still ok

     INCF    bres_mid,f       ; c, so lsb overflowed, so inc mid

     CALL    continue         ; A second has passed, increase
                              ; the second count     
     
     ; End of the interrupt routine
end_isr
     BCF     INTCON,T0IF      ; clear the TMR0 flag bit
     MOVF    temp_s,W         ; Put STATUS and W back where they belong
     MOVWF   STATUS
     SWAPF   temp_w,f
     SWAPF   temp_w,W
     RETFIE                   ; Return from interrupt


; Initialisation routine
init
     BSF     STATUS,5         ; Choose bank 1
     CLRF    TRISA            ; All pins of port A are outputs
     BSF     TRISA,4          ; ...except RA4
     CLRF    TRISB            ; All pins of port B are outputs...
     BSF     TRISB,7          ; ...except RB7
     MOVLW   b'10001000'      ; Set the options
                              ; (prescaler for watchdog timer)
     MOVWF   OPTION_REG
     BCF     STATUS,5         ; Choose bank 0

     CALL    initial_vals     ; Set initial values

     MOVLW   0x0F             ; get msb value 
     MOVWF   bres_hi          ; put in hi
     MOVLW   0x42 +1          ; get mid value (note we added 1 to it)
     MOVWF   bres_mid         ; put in mid
     MOVLW   0x40             ; get lsb value
     MOVWF   bres_lo          ; put in mid

     CLRF    INTCON
     BSF     INTCON,T0IE
     BSF     INTCON,GIE       ; Set Timer interrupt

     ; Routine to show the numbers on the display
shownumbers
     MOVLW   0FFh
     MOVWF   PORTA            ; All LEDs off
     MOVF    sece,W           ; Load sece into W
     CALL    HEX2SEGMENTS
     MOVWF   PORTB            ; Set the LEDs
     BCF     PORTA,0          ; Show the first digit.
     CALL    delay            ; Call our delay routine

     MOVLW   0FFh
     MOVWF   PORTA
     MOVF    sect,W           ; Load sect in W
     CALL    HEX2SEGMENTS
     MOVWF   PORTB
     BCF     PORTA,1          ; Show the second digit.
     CALL    delay

     MOVLW   0FFh
     MOVWF   PORTA
     MOVF    mine,W           ; Load mine into W
     CALL    HEX2SEGMENTS
     MOVWF   PORTB
     BCF     PORTA,2          ; Show the third digit.
     CALL    delay

     MOVLW   0FFh
     MOVWF   PORTA
     MOVF    mint,W           ; Load mint into W
     CALL    HEX2SEGMENTS
     MOVWF   PORTB
     BCF     PORTA,3          ; Show the fourth digit.
     CALL    delay

;     MOVLW   0FFh
;     MOVWF   PORTA
;     MOVF    hre,W            ; Load hre into W
;     CALL    HEX2SEGMENTS
;     MOVWF   PORTB
;     BCF     PORTA,4          ; Show the fifth digit.
;     CALL    delay

;     MOVLW   0FFh
;     MOVWF   PORTA
;     MOVF    hrt,W            ; Load hrt into W
;     CALL    HEX2SEGMENTS
;     MOVWF   PORTB
;     BCF     PORTA,5          ; Show the sixth digit.
;     CALL    delay

     GOTO    shownumbers      ; Just keep on showing those numbers --
                              ; the rest of the functionality is called
                              ; from the interrupt routine.

     ; Convert digit to segment form
HEX2SEGMENTS
     ANDLW   b'00001111'      ; Use only lower nibble
     ADDWF   PCL,F            ; Add offset to current PC

SEGMENTS_CA
     RETLW   b'01000000'      ; 0
     RETLW   b'01111001'      ; 1
     RETLW   b'00100100'      ; 2
     RETLW   b'00110000'      ; 3
     RETLW   b'00011001'      ; 4
     RETLW   b'00010010'      ; 5
     RETLW   b'00000010'      ; 6
     RETLW   b'01111000'      ; 7
     RETLW   b'00000000'      ; 8
     RETLW   b'00010000'      ; 9

;SEGMENTS_CC
;     RETLW   b'00111111'      ; 0
;     RETLW   b'00000110'      ; 1
;     RETLW   b'01011011'      ; 2
;     RETLW   b'01001111'      ; 3
;     RETLW   b'01100110'      ; 4
;     RETLW   b'01101101'      ; 5
;     RETLW   b'01111101'      ; 6
;     RETLW   b'00000111'      ; 7
;     RETLW   b'01111111'      ; 8
;     RETLW   b'01101111'      ; 9


     ; This routine is called whenever a second has passed.
continue
     INCF    sece,1           ; Increase sece
     MOVF    sece,W
     ADDLW   0F6h             ; sece > 9?
     SKPNC
     GOTO    overflow_sece    ; sece == 10, so increment sect
     RETURN

overflow_sece
     CLRF    sece             ; Set sece back to 0
     INCF    sect,1           ; Increase sect with 1
     MOVF    sect,W
     ADDLW   0FAh             ; sect > 5?
     SKPNC
     GOTO    overflow_sect    ; sect == 5, so increment mine
     RETURN

overflow_sect
     CLRF    sect             ; Set sect back to 0
     CLRF    sece
     INCF    mine,1           ; Increase mine
     MOVF    mine,W
     ADDLW   0F6h             ; mine > 9?
     SKPNC
     GOTO    overflow_mine    ; mine == 10, so increment sect
     RETURN

overflow_mine
     CLRF    mine             ; Set mine back to 0
     INCF    mint,1           ; Increase mint
     MOVF    mint,W
     ADDLW   0FAh             ; mint > 5?
     SKPNC
     GOTO    overflow_mint    ; mint == 5, so increment hre
     RETURN

overflow_mint
     CLRF    mint             ; Reset mint to 0
incr_hre
     INCF    hre,1            ; Increase hre
     MOVF    hre,W
     ADDLW   0FCh             ; hre > 4?
     SKPNC
     GOTO    check_hre        ; hre > 4, so we need to check whether
                              ; we need to overflow hrt
     RETURN

check_hre
     MOVF    hrt,W
     ADDLW   0FEh             ; hrt > 1?
     SKPNC
     GOTO    overflow_hre     ; hrt == 2, hre == 5 -> overflow!
     MOVF    hre,W            ; hrt == 1
     ADDLW   0F6h             ; hre > 9?
     SKPNC
     GOTO    overflow_hre     ; hrt == 1, hre == 10 -> overflow!
     RETURN

overflow_hre
     CLRF    hre              ; Reset hre to 0
     INCF    hrt,1            ; Increase hrt
     MOVF    hrt,W
     ADDLW   0FDh             ; hrt > 2?
     SKPNC
     GOTO    overflow_hrt     ; hrt == 2; overflow hrt (it's midnight!)
     RETURN

overflow_hrt
     CLRF    sece             ; Midnight, reset everything to 0!
     CLRF    sect
     CLRF    mine
     CLRF    mint
     CLRF    hre
     CLRF    hrt
     RETURN

     ; Sets the initial values. We start 9 seconds before midnight!
initial_vals
     MOVLW   001h
     MOVWF   sece
     CLRF    sect
     CLRF    mine
     CLRF    mint
     CLRF    hre
     CLRF    hrt
     MOVLW   005h
     MOVWF   sect
     MOVLW   009h
     MOVWF   mine
     MOVLW   005h
     MOVWF   mint
     MOVLW   003h
     MOVWF   hre
     MOVLW   002h
     MOVWF   hrt
     ; Also, set the variables for the counters etc.
     MOVLW   009h
     MOVWF   bounce_timer
     CLRF    csa
     CLRF    cva
     CLRF    count_A
     CLRF    count_B
     CLRF    prev_val
     RETURN

     ; Our delay loop. Waits roughly 1000 instructions, that's 1 msec
delay
     MOVLW   00Ah             ; 10 times...
     MOVWF   teller
outerloop
     MOVLW   064h             ; 100 times...
     MOVWF   teller2
innerloop
     DECFSZ  teller2,1
     GOTO    innerloop
     DECFSZ  teller,1
     GOTO    outerloop
     RETURN

     ; Read the inputs
read_inputs
     ; Check minute pin, sets the appropriate bit in the csa variable
     BTFSC   PORTA,4
     GOTO    minute_een
minute_nul
     BCF     csa,0
     GOTO    einde_minute
minute_een
     BSF     csa,0
einde_minute

     ; Check hour pin, sets the appropriate bit in the csa variable
     BTFSC   PORTB,7
     GOTO    hour_een
hour_nul
     BCF     csa,1
     GOTO    einde_hour
hour_een
     BSF     csa,1
einde_hour
     RETURN

     ; Debounce routine
debounce
     ; Increment the vertical counter
     MOVF    count_B,W
     XORWF   count_A,F
     COMF    count_B,F

     ; See if any changes occurred
     MOVF    csa,W
     XORWF   cva,W

     ; Reset the counter if no change has occurred
     ANDWF   count_B,F
     ANDWF   count_A,F

     ; Determine the counter's state
     MOVF    count_B,W
     IORWF   count_A,W

     ; Clear all bits that are filtered-or more accurately, save
     ; the state of those that are being filtered
     ANDWF   cva,F
     XORLW   0xff

     ; Re-write the bits that haven't changed.
     ANDWF   csa,W
     IORWF   cva,F

     RETURN

     ; Process the debounced inputs
     ; We have two variables to work with: prev_val, which contains
     ; the previous (debounced) states of the inputs;
     ; and cva, the debounced current state of the inputs.
     ; Each input is set as a bit in both the variables.
process_inputs
     ; Check the previous value of the minute-input
     BTFSS   prev_val,0
     GOTO    minute_was_zero
minute_was_one
     BTFSS   cva,0
     BCF     prev_val,0       ; from 1 to 0 -> reset prev_val to 0
     GOTO    check_hour
minute_was_zero
     BTFSC   cva,0
     GOTO    minute_press
     GOTO    check_hour
minute_press
     BSF     prev_val,0
     CALL    overflow_sect    ; from 0 to 1 -> tick!
check_hour
     ; Check the previous value of the hour input
     BTFSS   prev_val,1
     GOTO    hour_was_zero
hour_was_one
     BTFSS   cva,1
     BCF     prev_val,1      ; from 1 to 0 -> reset prev_val to 0
     RETURN
hour_was_zero
     BTFSC   cva,1
     GOTO    hour_press
     RETURN
hour_press
     BSF     prev_val,1
     CALL    incr_hre         ; from 0 to 1 -> tick!
     RETURN


     END
