;--------
; Noblink.asm
;"The Propeller" mechanically scanned LED clock
;
; Original file mclock.asm
; copyright Bob Blick, all rights reserved February 12, 1997
;
; modified by F.M. Bemelman / fbemel@euronet.nl, display of seconds added, 
; colon between digits will blink every half a second
; 
; modified by Victor Tihonov / jvt@iname.com 15-10-1997 
; turned off the blinking double dots
;
; ############### This file is "noblink.asm" downloaded from the web 
; modified 31-01-2006 by neelandan
; pull down applied to inputs instead of pullup,
; comparisons inverted - clear changed to set & versa.
; changes marked by three stars ***
;
;--------
                errorlevel -224         ; *** added to stop complaints about the option instr
                list    p=16F84A        ; *** changed processor type   
                radix   hex             ; all numbers in hexadecimal
                include "p16f84a.inc"   ; *** changed to reflect proc type
;--------
; remember to set blast-time options: OSC=regular xtal, WDT=ON
; timings all based on 4 MHz crystal
               __config 0x3ff9          ; *** fuse settings added
;--------
w               equ     0
f               equ     1
;--------
; Start of available RAM.
;--------
        cblock  0x0C
                safe_w          ;not really temp, used by interrupt svc
                safe_s          ;not really temp, used by interrupt svc
                period_count    ;incremented each interrupt
                period_dup      ;copy of period_count safe from interrupt
                period_calc     ;stable period after hysteresis calc.
                period_use      ;stable period after hysteresis calc.
                flags           ;b2=int b1=minute b4=edge b5=seconds
                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
                seconds ;00 to 59
                bigtick_dbl     ;incremented each interrupt
                bigtick_hi
                bigtick_lo
                sectick_hi
                sectick_lo
                keys            ;key value
                scratch         ;scratch value
                tick            ;used by delay
                skip_cnt        ; *** counter added to increase display width
        endc
;--------
; Start of ROM
;--------
                org     0x00            ;Start of code space
                goto    Start
;--------
; INTERRUPT SERVICE ROUTINE
;--------
                org     0x04            ;interrupt vector
Intsvc          movwf   safe_w          ;save w
                swapf   STATUS,w        ;swap status, w
                movwf   safe_s          ;save status(nibble swap, remember)
;--------
; done saving, now start working
;--------
; clear watchdog timer to ensure startup
                clrwdt
;
; increment period count
        incf    skip_cnt,f              ; *** uncommented block to
        btfss   STATUS,Z                ; *** increase displayed width 
        goto    Bigticker
        movlw   0xfc                    ;play with this value (0xfc) for
                                        ;changing the width of displayed characters
        movwf   skip_cnt
                incf    period_count,f
                btfsc   STATUS,Z        ;zero set means overflow
                decf    period_count,f
; 234375 interrupts every minute. Increment the bigtick each time.
Bigticker
                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   0xFC            ;234375 = 0x039387
                movwf   bigtick_dbl     ;0 - 0x039387 = 0xFC6C79
                movlw   0x6C
                movwf   bigtick_hi
                movlw   0x79
                movwf   bigtick_lo
                bsf     flags,1         ;notify Keep_time
Bigtick_out
;-------- now start with the seconds
                incf    sectick_lo,f
                btfsc   STATUS,Z
                incfsz  sectick_hi,f
                goto    Sectick_out
;--------

; here? sigtick has rolled over to zero and one second has passed.
; reload sectick and set a flag for the main counter
;--------
                movlw   0xF0            ;3907 = 0x0F43
                movwf   sectick_hi      ;0 - 0x0F43 = 0xF0BD
                movlw   0xBD
                movwf   sectick_lo
                bsf     flags,5         ;notify Keep_time
Sectick_out
;--------
; done working, start restoring
;--------
                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
;--------
; CHARACTER LOOKUP TABLE
; ignore high bit. set=LED off, clear=LED on, bit0=bottom LED, bit6=top LED
;--------
;Char_tbl
;                addwf   PCL,f
;                dt      0xC1,0xBE,0xBE,0xBE,0xC1        ;"O"
;                dt      0xFF,0xDE,0x80,0xFE,0xFF        ;"1"
;                dt      0xDE,0xBC,0xBA,0xB6,0xCE        ;"2"
;                dt      0xBD,0xBE,0xAE,0x96,0xB9        ;"3"
;                dt      0xF3,0xEB,0xDB,0x80,0xFB        ;"4"
;                dt      0x8D,0xAE,0xAE,0xAE,0xB1        ;"5"
;                dt      0xE1,0xD6,0xB6,0xB6,0xF9        ;"6"
;                dt      0xBF,0xB8,0xB7,0xAF,0x9F        ;"7"
;                dt      0xC9,0xB6,0xB6,0xB6,0xC9        ;"8"
;                dt      0xCF,0xB6,0xB6,0xB5,0xC3        ;"9"
;               dt      0xE0,0xDB,0xBB,0xDB,0xE0        ;"A"
;               dt      0x80,0xB6,0xB6,0xB6,0xC9        ;"B" 
;               dt      0xC1,0xBE,0xBE,0xBE,0xDD        ;"C" 
;               dt      0x80,0xBE,0xBE,0xBE,0xC1        ;"D" 
;               dt      0x80,0xB6,0xB6,0xBE,0xBE        ;"E" 
;               dt      0x80,0xB7,0xB7,0xBF,0xBF        ;"F"
;                dt      0xFF,0xFF,0xC9,0xFF,0xFF        ;":"

;Char_tbl_end

;--------
; CHARACTER LOOKUP TABLE
; ignore high bit. set=LED off, clear=LED on, bit0=top LED, bit6=bottom LED
;--------
Char_tbl
                addwf   PCL,f
                dt      0xC1,0xBE,0xBE,0xBE,0xC1        ;"O"
                dt      0xFF,0xbd,0x80,0xbf,0xFF        ;"1"
                dt      0xbd,0x9e,0xae,0xB6,0xb9        ;"2"
                dt      0xde,0xBE,0xba,0xb4,0xce        ;"3"
                dt      0xe7,0xEB,0xed,0x80,0xef        ;"4"
                dt      0xd8,0xba,0xba,0xba,0xc6        ;"5"
                dt      0xc3,0xb5,0xB6,0xB6,0xcf        ;"6"
                dt      0xfe,0x8e,0xf6,0xfa,0xfc        ;"7"
                dt      0xC9,0xB6,0xB6,0xB6,0xC9        ;"8"
                dt      0xf9,0xB6,0xB6,0xd6,0xe1        ;"9"
                dt      0xFF,0xFF,0xC9,0xFF,0xFF        ;":"

Char_tbl_end
;--------
; SUBROUTINES STARTING HERE
;--------
; clear important bits of ram
;--------
Ram_init        movlw   0x00
                movwf   keys
                movlw   0x12            ;why do clocks always start
                movwf   hours           ;at 12:00 ?
                clrf    minutes
                clrf    seconds
                clrf    dot_index
                clrf    digit_index
                movlw   0xFC            ;234375 = 0x039387
                movwf   bigtick_dbl     ;0 - 0x039387 = 0xFC6C79
                movlw   0x6C
                movwf   bigtick_hi
                movlw   0x79
                movwf   bigtick_lo
                movlw   0xF0            ;3907 = 0x0F43
                movwf   sectick_hi      ;0 - 0x0F43 = 0xF0BD
                movlw   0xBD
                movwf   sectick_lo
                movlw   0x40
                movwf   period_calc
                retlw   0
;--------
; unused pins I am setting to be outputs
;--------
Port_init       movlw   0x00            ;all output, b7=unused
                tris    PORTB           ;on port b attached to LEDs
                movlw   b'00010111'     ;port a has 5 pins. I need 4 inputs
                                        ;b0=minutes, b1=10mins, b2=hours
                                        ;b3=unused, b4=rotation index
                tris    PORTA           ;on port a
                retlw   0
;--------
; get timer-based interrupts going
;--------
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'11011000'     ;set up timer. prescaler(bit3)bypassed 
                option                  ;send w to option. generate warning.
                clrf    TMR0            ;start timer
                retlw   0
;--------
; test for index in rotation and store period in period_dup
;--------
Check_index     movf    PORTA,w         ;get the state of port a
                xorwf   flags,w         ;compare with saved state
                andlw   b'00010000'     ;only interested in bit 4
                btfsc   STATUS,Z        ;test for edge
                retlw   0               ;not an edge, same as last
                xorwf   flags,f         ;save for next time
                btfsc   flags,4         ;test for falling edge        ***
                retlw   0               ;must have been a rising edge ***
                movf    period_count,w  ;make a working copy
                movwf   period_dup      ;called period dup
                clrf    period_count    ;a fresh start for next rotation
                clrf    digit_index     ;set to first digit
                clrf    dot_index       ;first column
; calculate a period that does not dither or jitter
; period will not be changed unless new period is really different
                movf    period_calc,w
                subwf   period_dup,w    ;find difference
                btfss   STATUS,C        ;carry flag set means no borrow
                goto    Calc_period_neg ;must be other way
                sublw   3               ;allowable deviation = 4
                btfss   STATUS,C        ;borrow won't skip
                incf    period_calc,f   ;new value much larger than calc
                movf    period_calc,w
                movwf   period_use
                bcf     STATUS,C        ;clear carry before a rotate
                rrf     period_use,f    
                bcf     STATUS,C        ;clear carry before a rotate
                rrf     period_use,f    
                retlw   0
Calc_period_neg addlw   3               ;allowable deviation = 4
                btfss   STATUS,C        ;carry will skip
                decf    period_calc,f   ;no carry means it must be changed
                retlw   0
;--------
; 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_10hr		swapf	hours,w	;D_10hr		
;swapf		period_calc,w
		goto	D_lookup	;what a great rush of power
D_1hr		movf	hours,w		;I feel when modifying
;D_1hr		movf	period_calc,w		;I feel when modifying
		goto	D_lookup	;the program counter
D_colon	movlw	0x0A   ;the semicolon
		goto	D_lookup
D_10min	swapf	minutes,w
		goto	D_lookup
D_1min		movf	minutes,w
		goto	D_lookup
D_colon2	movlw	0x0A   ;the semicolon
		goto	D_lookup
D_10sec	swapf	seconds,w
		goto	D_lookup
D_1sec		movf	seconds,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
		movf	digit_index,f	;this is just to test/set flag
		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 leading 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
 		movfw	period_use
		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
                rlf     tick,f
                rlf     tick,f
Delay_loop      decfsz  tick,f
                goto    Delay_loop      ;w is not damaged, so Delay can
                return                  ;be recalled without reloading
;--------
; test for keypress and call time adjust if needed
;--------
Check_keys      movf    PORTA,w         ;get port "a"
                xorwf   keys,w          ;compare with previous
                andlw   b'00000111'     ;only care about button pins
                btfsc   STATUS,Z        ;zero set=no buttons
                retlw   0               ;return
                xorwf   keys,f          ;store key value
                movlw   0x64            ;a fairly long delay will
                movwf   scratch         ;prevent key bounces
Key_delay       movlw   0xFF
                call    Delay
                decfsz  scratch,f
                goto    Key_delay
                btfsc   keys,2          ;test "minutes" button ***
                goto    Inc_mins
                btfsc   keys,1          ;test "tens" button    ***
                goto    Inc_tens
                btfsc   keys,0          ;test "hours" button   ***
                goto    Inc_hours
                retlw   0               ;must be a glitch. yeah, right!
;--------
; increment ten minutes
;--------
Inc_tens        movlw   0x0A
                movwf   scratch         ;scratch has ten
Inc_tens_loop   call    Inc_mins
                decfsz  scratch,f
                goto    Inc_tens_loop   ;another minute added
                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 loop also used by time-setting routine
;--------
Keep_time       btfss   flags,5         ;the minutes flag
                goto    Keep_time2              ;no change in seconds
                bcf     flags,5         ;clear the minutes flag
                movlw   0x07            ;start incrementing time
                addwf   seconds,f       ;add 7 seconds into f
                movlw   0x06            ;'oops' decrementing time
                btfsc   STATUS,DC       ;did adding 7 cause digit carry?
                goto    Keep_time2      ;then test for an hour change
                subwf   seconds,f       ;dec 6 seconds into f
Keep_time2      btfss   flags,1         ;the minutes flag
                retlw   0               ;not this time
                bcf     flags,1         ;clear the minutes flag
                clrf    seconds ; clear the seconds & seconds timervalues
                movlw   0xF0            ;3907 = 0x0F43
                movwf   sectick_hi      ;0 - 0x0F43 = 0xF0BD
                movlw   0xBD
                movwf   sectick_lo
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,f       ;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
;--------
; End of subroutines
; 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
;--------
; Done initializing, start the endless loop.
;--------
;
Circle                                  ;begin the big loop
; detect falling edge on PORTA,4 to determine rotary index
; calculate rotation period and store in period_dup
; compare with working period(period_calc) and adjust if way different
                call    Check_index
; check display state and change if needed
                call    Display_now
; check keyboard and adjust time
                call    Check_keys
; check minute flag and increment time if a minute has passed
                call    Keep_time
                goto    Circle          ;you heard the man, get going!
                end
;--------
; end of file
;--------


