MicroChip pic 16c84 16f84 asm code for propeller clock

; Copyright Don Zehnder all rights reserved December 1997
; Code made for "the propeller clock"
; Copyright Bob Blick all rights reserved February 1997
; This code simulates a analog or digital clock on the propeller
; i=improved, reduced analog jitter
; Assignments on portb. Say, here's a dumb way to hook things up!
; BIT    LED (from center to edge)
; 0    5
; 1    1
; 2    2
; 3    3
; 4    rotation index input
; 5    4
; 6    7
; 7    6
; hours bit 1,2,3,5
; min bit 1,2,3,0,5,7
; seconds bit 6
; time set porta,4
; ~10 MHz crystal

    list    p=16f84
    include "p16f84.inc"

    cblock    0x0c
;----unique to analog---        
      DIVISOR1    ;these are to count # of interrupts
      DIVISOR2    ;required to increment ZSEC, decrement DSEC
      DSEC        ;There are 256 DSECs in a minute (1 rev)
      ZSEC        ;When this rolls over, decrement DMIN
      DMIN        ;There are 256 DMINs in an hour (1 rev)
      ZMIN        ;When this rolls over, decrement DHOUR
      DHOUR        ;There are 256 DHOURs in 12 hours (1 rev)
      mulcnd           ; 8 bit multiplicand
      H_byte           ; High byte of the 16 bit result
      L_byte           ; Low byte of the 16 bit result - (unused result)
      TEMP        ; part of routine for setting hands when period=0
;----unique to digital---
          dot_index       ;which column is being displayed
          digit_index     ;which digit is being displayed
          hours           ;in display format, not hex(01-12)
          minutes         ;00 to 59
          bigtick_dbl     ;incremented each interrupt
          scratch         ;scratch value
          tick            ;used by delay
      COUNTER2      ;used as secondary counter to slow time set
      ADJUST      ;fine adjust on timing
      COUNTER    ;used to slow time set  
            PERIOD_CNT    ;incremented each interrupt
      PERIOD     ;stable period after hysteresis calc.
       safe_w    ;copy of W safe from interrupt
      safe_s    ;copy of STATUS safe from interrupt
      period_dup    ;copy of period_count safe from interrupt
      flags         ;b2=int, b1=minute, b0=prev_flag, b3=mode_bit, b4=toggle

#define divisor1_val      d'147'        ;roll-over at 109 (256-109)=147
#define divisor2_val      d'235'        ;roll-over at 21
#define zsec_val    d'196'        ;min hand moves 1/60 fast as sec hand
#define zmin_val    d'244'        ;hour hand moves 1/12 fast as min hand
#define offset_val    d'57'        ;offset is n/256 rev for analog.
                    ;needed for proper display position
                    ;since we want analog noon to be where
                    ;the digital colon is. Determined empirically.
#define nths_adj_div2    d'2'        ;these are adjusted to correct
#define nths_adj_zsec    d'8'        ;for observed crystal error in analog mode
#define    nths_adj_zmin    d'1'        ;time is kept similarly to AN590

#define    in_bit        PORTB,4        ;using phototransistor connect to ground
#define    time_set    PORTA,4        ;Hall-effect, externally pulled-up
#define prev_flag    flags,0
#define mode_bit    flags,3        ;1=analog, default=digital

    org    0x00         
    goto    Start

    org     0x04            ;interrupt vector
Int      movwf   safe_w          ;save w
       swapf   STATUS,w        ;swap status, w
    movwf   safe_s          ;save status(nibble swap, remember)
    btfss    mode_bit    ;test mode, 1=analog
    goto    digital        
;ANALOG---main time keeping code
    incfsz    DIVISOR1,1
    goto    exit
    movlw    divisor1_val    ;reset DIVISOR1 every time it rolls over
    movwf    DIVISOR1        
    incfsz    DIVISOR2,1
    goto     exit
    movlw    divisor2_val    ;reset DIVISOR2 each time it rolls
    movwf    DIVISOR2
    movlw    nths_adj_div2    ;adj-roll later (plus)
    subwf    DIVISOR1,1    
    decf    DSEC,1        ;decrement: reverses direction of display
    incfsz    ZSEC,1
    goto    exit
    movlw    zsec_val    ;reset ZSEC when it rolls
    movwf    ZSEC
    movlw    nths_adj_zsec    ;earlier  (minus)
    addwf    DIVISOR1,1    
    decf    DMIN,1
    incfsz    ZMIN,1
    goto    exit
    movlw    zmin_val    ;reset ZMIN when it rolls
    movwf    ZMIN
    movlw    nths_adj_zmin
    subwf    DIVISOR1,1    ;later (plus)    
    decf    DHOUR,1

;start setting hands on the clock based on time in the registers    

    movlw    b'11111111'    ;make all outputs high
    movwf    PORTB        ;to turn off all the LEDs

    movf    PERIOD,W    ;some junk is here to fix problem
    movwf    TEMP        ;where hands turn off when PERIOD_CNT is 0
    movlw    d'2'
    subwf    TEMP,1        ;TEMP=PERIOD-2

    movf    DSEC,W
    movwf    mulcnd         
    call    mpy_F        ;do the math->PERIOD x time

    movf    H_byte,W    ;if H_byte>PERIOD-2 and
    subwf    TEMP,W        
    btfsc    STATUS,C
    goto    ng
    movf    PERIOD_CNT,1    ;if PERIOD_CNT=0 turn on LED
    btfsc    STATUS,Z
    goto    sec_on
ng    movf    PERIOD_CNT,W    ;divide result by 256 then compare to PERIOD_CNT
    xorwf    H_byte,W    
    btfsc    STATUS,Z    

sec_on    bcf    PORTB,6     


    movf    DMIN,W
    movwf    mulcnd        
    call    mpy_F    

    movf    H_byte,W    ;if H_byte>PERIOD-2 and
    subwf    TEMP,W        
    btfsc    STATUS,C
    goto    ngm
    movf    PERIOD_CNT,1    ;if PERIOD_CNT=0 turn on LEDs
    btfsc    STATUS,Z
    goto    min_on

ngm    movf    PERIOD_CNT,W
    xorwf    H_byte,W    

    btfss    STATUS,Z    ;defacto divide by 256 then compare to PERIOD_CNT
    goto    skipm

min_on    bcf    PORTB,0        ;light up the minute hand
    bcf    PORTB,5
    bcf    PORTB,7
    bcf    PORTB,1
    bcf    PORTB,2
    bcf    PORTB,3
skipm                ;skipped to here means no turn on

    movf    DHOUR,W
    movwf    mulcnd        
    call    mpy_F    

    movf    H_byte,W    ;if H_byte>PERIOD-2 and
    subwf    TEMP,W        ;if PERIOD_CNT=0 turn on
    btfsc    STATUS,C
    goto    ngh
    movf    PERIOD_CNT,1
    btfsc    STATUS,Z
    goto    hour_on
ngh    movf    PERIOD_CNT,W
    xorwf    H_byte,W    

    btfss    STATUS,Z    ;defacto divide by 256 then compare to PERIOD_CNT
    goto    skiph

hour_on    bcf    PORTB,1
    bcf    PORTB,2
    bcf    PORTB,3
    bcf    PORTB,5

skiph    goto    end_int
; ?????????? interrupts every minute. Increment the bigtick each time.
                incf    bigtick_lo,f
                btfsc   STATUS,Z
                incf    bigtick_hi,f
                btfsc   STATUS,Z
                incfsz  bigtick_dbl,f
                goto    Bigtick_out
; here? bigtick has rolled over to zero and one minute has passed.
; reload bigtick and set a flag for the main counter
        movlw   0xf7            ;these timings for no prescale
                movwf   bigtick_dbl     ;and 10Mhz crystal
                movlw   0x0c        ;corrected for observed error
                movwf   bigtick_hi    
                movlw   0xf8        
                movwf   bigtick_lo
                bsf     flags,1         ;notify Keep_time

        incfsz    ADJUST        ;make correction every 256 minutes
        goto    Bigtick_out
        movlw    d'243'
        subwf    bigtick_lo,f
;restore and return--
        btfsc    flags,4        ;increment PERIOD_CNT only every other
        goto    clear_flag    ;interrupt. Since hand position can only
        incf    PERIOD_CNT,1    ;be determined to the nearest interrupt,
        bsf    flags,4        ;this produces less hand jitter than using
        goto    go_there    ;tmr0 prescale of 2x
        bcf    flags,4        ;flags,4 is toggled each interrupt
        bsf    PCLATH,0    ;point to block starting at 0x0100    
                swapf   safe_s,w        ;fetch status, reswap nibbles
                movwf   STATUS          ;restore status
                swapf   safe_w,f        ;swap nibbles in preparation
                swapf   safe_w,w        ;for the swap restoration of w
                bcf     INTCON,2        ;clear interrupt flag before return
                retfie                  ;return from interrupt

;Time, whether sec, min, or hour is kept as a value between 0 to 255.   
;To scale the time in order to compare to the current value of PERIOD_CNT,
;multiply time*PERIOD and divide by 256, which is just the high
;byte of time*PERIOD result
;          ( Fast Version : Straight Line Code )
; Before calling the subroutine " mpy_F ", the multiplier is PERIOD
; multiplicand is " mulcnd " . The 16 bit result is stored in locations
; H_byte & L_byte.      
;****   Define a macro for adding & right shifting  **

mult    MACRO   bit             ; Begin macro
    btfsc   PERIOD,bit
    addwf   H_byte,1
    rrf     H_byte,1
    rrf     L_byte,1
    ENDM                    ; End of macro

; *****************************         Begin Multiplier Routine
mpy_F   clrf    H_byte
    clrf    L_byte
    movf    mulcnd,W        ; move the multiplicand to W reg.
    bcf     STATUS,C        ; Clear the carry bit in the status Reg.
    mult    0
    mult    1
    mult    2
    mult    3
    mult    4
    mult    5
    mult    6
    mult    7
    retlw   0

;--initialize all RAM-digital and analog---------------------
Ram_init    ;***digital*****        
                movlw   0x12            ;why do clocks always start
                movwf   hours           ;at 12:00 ?
                clrf    minutes
                clrf    dot_index
                clrf    digit_index
                movlw   0xFB
                movwf   bigtick_dbl
        movlw    d'5'
        movwf    COUNTER2

        movlw    divisor1_val    ;initialize DIVISOR1
        movwf    DIVISOR1
        movlw    divisor2_val    ;initialize DIVISOR2
        movwf    DIVISOR2
        movlw    zsec_val    ;and other timekeeping
        movwf    ZSEC        ;counters
        movlw    zmin_val
        movwf    ZMIN
        movlw    offset_val    ;initialize to start all hands at 12:00
        movwf    DHOUR
        movwf    DSEC
        movwf    DMIN

                  retlw   0
Port_init       movlw   b'00010000'     ;all but bit 4 are output
                tris    PORTB           ;portb 4 is index sensor
                movlw   b'00010000'     ;porta 4 is timeset
                tris    PORTA           
                retlw   0
Timer_init      bcf     INTCON,2        ;clear TMR0 int flag
                bsf     INTCON,7        ;enable global interrupts
                bsf     INTCON,5        ;enable TMR0 int
                clrf    TMR0            ;clear timer
                clrwdt                  ;why is this needed? just do it..
                movlw   b'01011000'       ;set up timer. no prescale
                    ;portb pullups enabled
                option                  ;send w to option. generate warning.
                clrf    TMR0            ;start timer
                retlw   0

    org     100     ;all code from which interrupts occur is
            ;within the 256 word page starting here. PCLATH
            ;forced to point here after every interrupt
;Check_index-  common to both modes
;        Bob Blick's original code also works. This method
;        stabilizes period faster.

Check_index    btfss    in_bit                    
        goto    no_phase
        btfsc    prev_flag
        goto    cont
        bsf    prev_flag
        movf    PERIOD_CNT,w    ;make a working copy
        movwf    period_dup    ;called period dup
        clrf    PERIOD_CNT    ;a fresh start for next rotation
        clrf    digit_index     ;set to first digit
                clrf    dot_index       ;first column
        bcf    flags,4        ;clear period count toggle bit. PERIOD_CNT
        goto    cont        ;will be incremented on the next interrupt
no_phase    bcf    prev_flag

; calculate a period that does not dither or jitter
; period will not be changed unless new period is really different
cont        movf    PERIOD,W
        subwf    period_dup,W    ;find difference
        btfss    STATUS,C    ;carry flag set means no borrow
        goto    Calc_neg    ;must be other way
        sublw    2        ;allowable deviation = 3
        btfss    STATUS,C    ;borrow won't skip
        incf    PERIOD        ;new value much larger than calc
        retlw    0
Calc_neg    addlw    2        ;allowable deviation = 3
        btfss    STATUS,C    ;carry will skip
        decf    PERIOD        ;no carry means it must be change
        retlw    0
;set_analog  for adjusting time
    btfsc    time_set    ;input uses pull-up- normally set
    retlw    0        ;exit if no magnet near

    decfsz    COUNTER,1    ;COUNTER used to take action only every 256 loops
    retlw    0        ;this slows down time setting
    movlw    offset_val    ;reset seconds hand to 12:00 position
    movwf    DSEC        ;to make it pretty

    decf    DMIN,1        ;adjust time one minute
    incfsz    ZMIN,1            ;this one goes the other way
    retlw    0            ;if ZMIN rolls, reset it and decrement DHOUR
    movlw    zmin_val
    movwf    ZMIN
    decf    DHOUR,1        
    retlw    0

;---Digital Code--subs below here
; ignore bit 4. set=LED off, clear=LED on
;-flipped digits upside down-pattern modified for my crazy LED wiring

       addwf   PCL,f
    dt    0x42    ,0xAD    ,0xAD    ,0xAD    ,0x42
    dt    0xEF    ,0xED    ,0x0    ,0x6D    ,0xEF
    dt    0x6C    ,0x8D    ,0xA5    ,0xA9    ,0x6D
    dt    0xA3    ,0xD    ,0xAC    ,0xAD    ,0xAB
    dt    0xE7    ,0x0    ,0x67    ,0xE6    ,0xC7
    dt    0x83    ,0xAC    ,0xAC    ,0xAC    ,0x2A
    dt    0xE3    ,0x8D    ,0x8D    ,0x4D    ,0xC2
    dt    0x2F    ,0xAE    ,0x8F    ,0xA1    ,0xAF
    dt    0x62    ,0x8D    ,0x8D    ,0x8D    ,0x62
    dt    0x46    ,0x8B    ,0x8D    ,0x8D    ,0x6E
    dt    0xEF    ,0xEF    ,0x62    ,0xEF    ,0xEF

; change LED pattern based on state of digit_index and dot_index
Display_now     movlw   0x05
                xorwf   dot_index,w     ;test for end of digit
                movlw   0xFF            ;pattern for blank column
                btfsc   STATUS,Z
                goto    D_lookup_3      ;it needs a blank
                bcf     STATUS,C        ;clear carry before a rotate
                rlf     digit_index,w   ;double the index because each
                addwf   PCL,f           ;takes two instructions

D_1min          movf    minutes,w    ;DZ-reversed order of display
                goto    D_lookup    ;since my motor is counterclockwise
D_10min         swapf   minutes,w
                goto    D_lookup
D_colon         movlw   0x0A
                goto    D_lookup
D_1hr           movf    hours,w
                goto    D_lookup        
D_10hr          swapf   hours,w
                goto    D_lookup        
D_nothing       retlw   0

D_lookup        andlw   b'00001111'     ;strip off hi bits
                movwf   scratch         ;multiply this by 5 for lookup
                addwf   scratch,f       ;table base position
                addwf   scratch,f       ;is this cheating?
                addwf   scratch,f       ;I think not.
                addwf   scratch,f       ;I think it is conserving energy!
                btfss   STATUS,Z        ;test for zero
                goto    D_lookup_2      ;not a zero
        movlw    0x04           ;-DZ changes- 10 hours is now digit 5
        xorwf    digit_index,w  ;-if digit 5 is 0, display blank
        movlw   0xFF            ;this makes a blank LED pattern
                btfsc   STATUS,Z        ;test if it is 10 hrs digit
                goto    D_lookup_3      ;it's a trailing zero
D_lookup_2      movf    dot_index,w     ;get column
                addwf   scratch,w       ;add it to digit base
                call    Char_tbl        ;get the dot pattern for this column
D_lookup_3      movwf   PORTB           ;send it to the LEDs
        movf    PERIOD,w       ;made delay longer than original program
                call    Delay           ;width of digits with this delay
                incf    dot_index,f     ;increment to the next column
                movlw   0x06            ;6 columns is a digit plus space
                xorwf   dot_index,w     ;next digit test
                btfss   STATUS,Z
                retlw   0               ;not a new digit
                clrf    dot_index       ;new digit time
                incf    digit_index,f
                retlw   0               ;Display_now done.
; a short delay routine
Delay           movwf   tick
Delay_loop         nop                   ;added NOPs to increase delay
        nop            ;to make really wide display
        decfsz  tick,f
                goto    Delay_loop      ;w is not damaged, so Delay can
                return                  ;be recalled without reloading
; test for magnet and adjust time if needed
Set_digital     btfsc    time_set    ;input uses pull-up- normally set
        retlw    0        ;exit if no magnet near
        decfsz    COUNTER,1    ;COUNTER used to take action only every 256 loops
        retlw    0        ;this slows down time setting
        decfsz    COUNTER2,1    ;COUNTER2 slows things even more
        retlw    0        
        movlw    d'8'        ;adjust to suit for setting speed
        movwf    COUNTER2    ;reset COUNTER2 when it rolls
        call    Inc_mins    ;got to here, now adjust time
        retlw    0
; increment one hour
Inc_hours       movlw   0x12
                xorwf   hours,w
                btfsc   STATUS,Z
                goto    Inc_hours_12
                movlw   0x07            ;this part gets a little sloppy
                addwf   hours,w
                movlw   0x07
                btfss   STATUS,DC
                movlw   1
                addwf   hours,f
                retlw   0
Inc_hours_12    movlw   0x01
                movwf   hours
                retlw   0
; increment the time based on flags,1 as sent by interrupt routine
; Inc_mins also used by time setting routine
Keep_time       btfss   flags,1         ;the minutes flag
                retlw   0               ;not this time
                bcf     flags,1         ;clear the minutes flag
Inc_mins        movlw   0x07            ;start incrementing time
                addwf   minutes,w       ;add 7 minutes into w
                btfsc   STATUS,DC       ;did adding 7 cause digit carry?
                goto    Sixty_mins      ;then test for an hour change
                incf    minutes         ;otherwise add 1 for real
                retlw   0               ;and go back
Sixty_mins      movwf   minutes         ;save the minutes
                movlw   0x60            ;test for 60
                xorwf   minutes,w       ;are minutes at 60?
                btfss   STATUS,Z
                retlw   0               ;no? go back
                clrf    minutes         ;otherwise zero minutes
                goto    Inc_hours       ;and increment hours
; Program starts here
Start                 call    Ram_init        ;set variables to nice values
                call    Port_init       ;set port directions
                call    Timer_init      ;start timer based interrupt

        bcf    mode_bit    
        btfsc    time_set    ;do analog if magnet present on startup
        goto    Digital_circle    ;otherwise default to digital
        bsf    mode_bit    ;if mode bit is set-it's analog time

Analog_circle    call    Check_index        
        call    Set_analog
        goto    Analog_circle

Digital_circle  call    Check_index
                call    Display_now
                call    Set_digital
                call    Keep_time
                goto    Digital_circle