; VERSION 1.1, 2009/09/24
; http://avtanski.net/projects/timer

#include <p16F690.inc>
     __config (_XT_OSC & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)

; Uncomment the line below if you want to use RB7 to control the LEDs
;#define RB7_LED_CONTROL

; Uncomment the line below if you want the timer to remember its initial value
;#define REMEMBER_INITIAL_VALUE

; Rate Adjustments
#ifdef RB7_LED_CONTROL
#define TMR_LO                  0xBF
#define TMR_HI                  0xEE
#else
#define TMR_LO                  0xBE
#define TMR_HI                  0xEE
#endif

#define INTERRUPTS_PER_SECOND   .200
#define TIMER_DEFAULT_VALUE     0     ; use packed decimal notation here - i.e. a hex where
                                      ; the two nibbles correspond to the two decimal digits
                                      ; (for example, use the hex "15" for 15 units of time)

#define TIMER_DIVIDER           .01   ; seconds per tick; set to:
                                      ;   .1 to count seconds
                                      ;   .60 to count minutes
                                      ;   .3600 to count hours

#define ROUGH_ADJUSTMENT        .10   ; how much to increase/decrease timer when the rough
                                      ; adjustments buttons are used

#define BTN_REPEAT_START        0x0200
#define BTN_REPEAT_REINIT       0x01C0

#define F_RUNNING               0    ; timer is running
#define F_TICK                  1    ; timer tick (after division)
#define F_DIGIT                 2    ; which digit LED display is ON
#define F_BLINKING              3    ; display blinking
#define F_BTN_READY             4    ; no button pressed on last check, ready for new button
#define F_BTN_REPEAT            5    ; set if latest button is set through a repeat action
#define F_RESET_ON_START        6    ; everything should be reset on start
#define F_FAN_RUNNING           7    ; current status of the fan

#define B_START                 0    ; Start button
#define B_RST                   1    ; Reset button
#define B_INC                   2    ; Increase button
#define B_DEC                   3    ; Decrease button
#define B_INC10                 4    ; Increase by 10 button
#define B_DEC10                 5    ; Decrease by 10 button
#define B_INVALID               7    ; Invalid - more than one buttons pressed

    cblock 0x20
    FLAGS
    OLD_COUNTER
    TIMER
    TIMER_INITIAL
    DISPLAY
    DIVIDER_L
    DIVIDER_H
    LOOKUP
    TMP
    BTN
    BTN_DOWN
    BTN_DOWN_COUNTER_L
    BTN_DOWN_COUNTER_H
    BLINK_COUNTER
    endc

    ; interrupt registers in shared space
    cblock 0x78
    INT_W
    INT_STATUS
    INT_PCLATH
    INT_FSR
    INT_PULSECTR
    INT_COUNTER
    endc

    org 0x00
    goto    Start
    org 0x04
    nop        ; interrupt code follows - avoid potential paging issues

Interrupt
    movwf   INT_W
    swapf   STATUS,w
    clrf    STATUS
    movwf   INT_STATUS
    movf    PCLATH,w
    movwf   INT_PCLATH
    clrf    PCLATH
    movf    FSR,w
    movwf   INT_FSR
    banksel T1CON    ; stop timer
    bcf     T1CON,TMR1ON
    banksel TMR1H    ; set TMR1 counter
    movlw   TMR_LO
    movwf   TMR1L
    movlw   TMR_HI
    movwf   TMR1H
    banksel INT_PULSECTR
    decf    INT_PULSECTR,f
    btfss   STATUS,Z
    goto    Interrupt_display
    incf    INT_COUNTER,f
    movlw   INTERRUPTS_PER_SECOND
    movwf   INT_PULSECTR
Interrupt_display
    movlw   0x01<<F_DIGIT
    xorwf   FLAGS,f
    btfss   FLAGS,F_DIGIT
    goto    Interrupt_displayHigh
    movf    DISPLAY,w
    andlw   0x0F
    lcall   Lookup_LEDs
    iorlw   0x80
    movwf   PORTC
#ifdef RB7_LED_CONTROL
    bcf     PORTB,7
#endif
    goto    Interrupt_continue
Interrupt_displayHigh
    swapf   DISPLAY,w
    andlw   0x0F
    lcall   Lookup_LEDs
    movwf   PORTC
#ifdef RB7_LED_CONTROL
    bsf     PORTB,7
#endif
Interrupt_continue
    banksel PIR1    ; clear TMR1 overflow bit
    bcf     PIR1,TMR1IF
    banksel PIE1    ; enable TMR1 interrupt
    bsf     PIE1,TMR1IE
    banksel T1CON    ; start timer
    bsf     T1CON,TMR1ON
    banksel PORTC
    movf    INT_FSR,w
    movwf   FSR
    movf    INT_PCLATH,w
    movwf   PCLATH
    swapf   INT_STATUS,w
    movwf   STATUS
    swapf   INT_W,f
    swapf   INT_W,w
    retfie

InitTimer
    banksel INTCON
    bsf     INTCON,PEIE
    bsf     INTCON,GIE
    banksel T1CON
    movlw   b'00000000'
    banksel PORTC
    movlw   INTERRUPTS_PER_SECOND
    movwf   INT_PULSECTR
    banksel PIR1        ; clear TMR1 overflow bit
    bcf     PIR1,TMR1IF
    banksel PIE1        ; enable TMR1 interrupt
    bsf     PIE1,TMR1IE
    banksel T1CON       ; start timer
    bsf     T1CON,TMR1ON
    banksel PORTC
    clrf    INT_COUNTER
    clrf    OLD_COUNTER
    clrf    TIMER
    clrf    TIMER_INITIAL
    movlw   high TIMER_DIVIDER
    movwf   DIVIDER_H
    movlw   low TIMER_DIVIDER
    movwf   DIVIDER_L
    return

InitPorts
    banksel PORTA
    clrf    PORTA
    clrf    PORTB
    movlw   0xFF
    movwf   PORTC
    banksel ANSEL
    clrf    ANSEL
    banksel ANSELH
    clrf    ANSELH
    banksel TRISA
    movlw   b'00001011'  ; 1=input, 0=output
    movwf   TRISA
#ifdef RB7_LED_CONTROL
    movlw   b'01111111'  ; all inputs
#else
    movlw   b'11111111'  ; all inputs
#endif
    movwf   TRISB
    clrf    TRISC        ; all outputs
    movlw   b'11000000'  ; enable interrupts
    movwf   INTCON
    banksel PORTA
    return

Start
    clrf    FLAGS
    call    InitPorts
    call    InitTimer
    movlw   TIMER_DEFAULT_VALUE
    movwf   TIMER
    movwf   TIMER_INITIAL
    clrf    DISPLAY
    clrf    BTN
    bsf     FLAGS,F_RESET_ON_START

MainLoop
    call    HandleDisplay
    call    Delay
    call    Divider
    btfsc   FLAGS,F_TICK
    call    HandleTick
    call    GetBtnDown
    call    CheckButtons
    call    HandleButtonEvents
    call    CheckTimerExpired
    call    HandleFan
    goto    MainLoop


HandleDisplay
    btfss   FLAGS,F_BLINKING
    goto    HandleDisplay_show
    incf    BLINK_COUNTER,f
    movf    TIMER,w
    btfss   BLINK_COUNTER,7
    movlw   0xFF
    movwf   DISPLAY
    return
HandleDisplay_show
    movf    TIMER,w
    movwf   DISPLAY
    return


; Sets the F_TICK flag once every TIMER_DIVIDER seconds, if timer is running
Divider
    btfss   FLAGS,F_RUNNING
    return
    movf    INT_COUNTER,w
    subwf   OLD_COUNTER,w
    btfsc   STATUS,Z
    return
    incf    OLD_COUNTER,f
    decf    DIVIDER_L,f
    btfss   STATUS,Z
    return
    movf    DIVIDER_H,f
    btfsc   STATUS,Z
    goto    Divider_continue
    decf    DIVIDER_H,f
    return
Divider_continue
    bsf     FLAGS,F_TICK
Divider_reset
    movlw   high TIMER_DIVIDER
    movwf   DIVIDER_H
    movlw   low TIMER_DIVIDER
    movwf   DIVIDER_L
    return


ResetCounterAndDivider
    bcf     FLAGS,F_TICK    ; shouldn't happen; just in case
    clrf    INT_PULSECTR
    movf    INT_COUNTER,w
    movwf   OLD_COUNTER
    goto    Divider_reset


; Handles a tick - decreases timer by one
; (Note that if timer is not running, there are no ticks coming, so no
; need to check for timer status here.)
HandleTick
    bcf     FLAGS,F_TICK
    call    DecreaseTimer
    return


HandleFan
    btfss   FLAGS,F_RUNNING
    goto    HandleFan_Stop
    ; Start fan, if needed
    btfsc   FLAGS,F_FAN_RUNNING
    return
    bsf     PORTA,2
    bsf     FLAGS,F_FAN_RUNNING
    return
HandleFan_Stop
    ; Stop fan, if needed
    btfss   FLAGS,F_FAN_RUNNING
    return
    bcf     PORTA,2
    bcf     FLAGS,F_FAN_RUNNING
    return


; Returns the code of the button that is down
GetBtnDown
    clrf    BTN_DOWN
    clrf    TMP
    btfsc   PORTB,6
    goto    $+3
    bsf     BTN_DOWN,B_START
    incf    TMP,f
    btfsc   PORTA,3
    goto    $+3
    bsf     BTN_DOWN,B_RST
    incf    TMP,f
    btfsc   PORTB,5
    goto    $+3
    bsf     BTN_DOWN,B_INC
    incf    TMP,f
    btfsc   PORTB,4
    goto    $+3
    bsf     BTN_DOWN,B_DEC
    incf    TMP,f
    btfsc   PORTA,1
    goto    $+3
    bsf     BTN_DOWN,B_INC10
    incf    TMP,f
    btfsc   PORTA,0
    goto    $+3
    bsf     BTN_DOWN,B_DEC10
    incf    TMP,f
    movf    BTN_DOWN,f
    btfsc   STATUS,Z
    return
    movf    TMP,w
    sublw   0x01
    btfss   STATUS,Z
    bsf     BTN_DOWN,B_INVALID
    return


; Check the buttons - sets BTN on button press and button auto-repeat
CheckButtons
    movf    BTN_DOWN,f
    btfss   STATUS,Z
    goto    CheckButtons_1
    ; No button pressed, ready for next button
    bsf     FLAGS,F_BTN_READY
    return
CheckButtons_1
    btfss   FLAGS,F_BTN_READY
    goto    Button_Hold
    ; New button pressed
    clrf    BTN_DOWN_COUNTER_L
    clrf    BTN_DOWN_COUNTER_H
    bcf     FLAGS,F_BTN_READY
    btfsc   BTN_DOWN,B_INVALID
    return
    movf    BTN_DOWN,w
    movwf   BTN
    bcf     FLAGS,F_BTN_REPEAT
    return
Button_Hold
    incf    BTN_DOWN_COUNTER_L,f
    btfsc   STATUS,Z
    incf    BTN_DOWN_COUNTER_H,f
    movf    BTN_DOWN_COUNTER_L,w
    sublw   low BTN_REPEAT_START
    btfss   STATUS,Z
    return
    movf    BTN_DOWN_COUNTER_H,w
    sublw   high BTN_REPEAT_START
    btfss   STATUS,Z
    return
    movf    BTN_DOWN,w
    movwf   BTN
    bsf     FLAGS,F_BTN_REPEAT
    movlw   low BTN_REPEAT_REINIT
    movwf   BTN_DOWN_COUNTER_L
    movlw   high BTN_REPEAT_REINIT
    movwf   BTN_DOWN_COUNTER_H
    return


; Handles button events
HandleButtonEvents
    movf    BTN,w
    btfsc   STATUS,Z
    return
    bcf     FLAGS,F_BLINKING
    btfsc   BTN,B_INC
    call    IncreaseTimer_X
    btfsc   BTN,B_DEC
    call    DecreaseTimer_X
    btfsc   BTN,B_INC10
    call    IncreaseTimer_Rough_X
    btfsc   BTN,B_DEC10
    call    DecreaseTimer_Rough_X
    btfsc   BTN,B_START
    call    StartTimer
    btfsc   BTN,B_RST
    call    StopTimer
    clrf    BTN
    return


IncreaseTimer_X
    btfss   FLAGS,F_RUNNING
    bsf     FLAGS,F_RESET_ON_START
    call    IncreaseTimer
    goto    StoreInitialValue


DecreaseTimer_X
    btfss   FLAGS,F_RUNNING
    bsf     FLAGS,F_RESET_ON_START
    call    DecreaseTimer
    goto    StoreInitialValue


IncreaseTimer_Rough_X
    btfss   FLAGS,F_RUNNING
    bsf     FLAGS,F_RESET_ON_START
    call    IncreaseTimer_Rough
    goto    StoreInitialValue


DecreaseTimer_Rough_X
    btfss   FLAGS,F_RUNNING
    bsf     FLAGS,F_RESET_ON_START
    call    DecreaseTimer_Rough
    goto    StoreInitialValue


StoreInitialValue
    movf    TIMER,w
    movwf   TIMER_INITIAL
    return

StartTimer
    btfsc   FLAGS,F_RUNNING
    return
    movf    TIMER,f
    btfsc   STATUS,Z
    return
    bsf     FLAGS,F_RUNNING
    btfsc   FLAGS,F_RESET_ON_START
    call    ResetCounterAndDivider
    movf    INT_COUNTER,w
    movwf   OLD_COUNTER
    bcf     FLAGS,F_RESET_ON_START
    return


StopAndResetTimer
#ifdef REMEMBER_INITIAL_VALUE
    movf    TIMER_INITIAL,w
    movwf   TIMER
#else
    clrf    TIMER
#endif
StopTimer
    bcf     FLAGS,F_RUNNING
    btfss   FLAGS,F_BTN_REPEAT
    return
    clrf    TIMER
    bsf     FLAGS,F_RESET_ON_START
    return


CheckTimerExpired
    btfss   FLAGS,F_RUNNING
    return
    movf    TIMER,f
    btfss   STATUS,Z
    return
    bsf     FLAGS,F_RESET_ON_START
    bsf     FLAGS,F_BLINKING
    goto    StopAndResetTimer


DecreaseTimer
    movf    TIMER,w
    btfsc   STATUS,Z
    return
    andlw   0x0F
    btfsc   STATUS,Z
    goto    DecreaseTimer_decTens
    decf    TIMER,f
    return
DecreaseTimer_decTens
    swapf   TIMER,f
    decf    TIMER,w
    andlw   0x0F
    iorlw   0x90
    movwf   TIMER
    swapf   TIMER,f
    return


IncreaseTimer
    movf    TIMER,w
    sublw   0x99
    btfsc   STATUS,Z
    return
    movf    TIMER,w
    andlw   0x0F
    sublw   0x09
    btfsc   STATUS,Z
    goto    IncreaseTimer_incTens
    incf    TIMER,f
    return
IncreaseTimer_incTens
    swapf   TIMER,f
    incf    TIMER,w
    andlw   0x0F
    movwf   TIMER
    swapf   TIMER,f
    return


DecreaseTimer_Rough
    movlw   ROUGH_ADJUSTMENT
    movwf   TMP
    call    DecreaseTimer
    decf    TMP,f
    btfss   STATUS,Z
    goto    $-3
    return


IncreaseTimer_Rough
    movlw   ROUGH_ADJUSTMENT
    movwf   TMP
    call    IncreaseTimer
    decf    TMP,f
    btfss   STATUS,Z
    goto    $-3
    return


Delay
    clrf    TMP
Delay_1
    decf    TMP,f
    btfss   STATUS,Z
    goto    Delay_1
    return


    org 0x0700
Lookup_LEDs
    movwf   LOOKUP
    movlw   high $
    movwf   PCLATH
    movf    LOOKUP,w
    addwf   PCL,f
    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
    retlw   b'00001000'     ; A
    retlw   b'00000011'     ; b
    retlw   b'01000110'     ; C
    retlw   b'00100001'     ; d
    retlw   b'00000110'     ; E
    retlw   b'01111111'     ; (empty)
    END

