 title  "Funky Digital clock (3+1/2 digits)"

;
;  Refresh a 3+1/2 digit 7 segment display using TMR0 interrupts.
;  Display the time and other strange effects every now and then.
;
;  Hardware Notes:
;   This application runs on a 16F84A executing at 4 or 20 MHz
;   _MCLR is tied through a 10K Resistor to Vcc
;   RB[6:0] connect to the active low segments g through a using 220 Ohm Resistors
;   RA[3:0] connect directly to the common active high digits 
;   RA[4] connects to up to 4 switches that are connected to RA[3:0] using diodes.
;         The RA[4] line is pulled to ground with a 4.7K resistor.
;
;  Ted Rossin 3-06-2002
;             3-28-2002
;
  LIST R=DEC
 ifdef __16F84A
  INCLUDE "p16f84A.inc"
 endif
 ifdef __16F877
  INCLUDE "p16f877.inc"
 endif

; #define CLOCK_20MHZ	1
#define CLOCK_4MHZ	1

#define PRESCALE_TIMER_MODE	0xD0

;
; Set time delays.  TMR0 must never be written once the program is running
; because a write will reset the prescaler.  It is just allowed to generate
; an interrupt whenever it rolls over.  The following values set up the
; prescaler and software counters to generate an interrupt at a rate fast
; enough to update the display and slow enough not to waste too much time
; servicing interrupts.  The goal is to keep the interrupt rate between
; 400 and 1000 Hz to refresh the display and then count down with software 
; counters to get a 1 second rate.
;
; Unfortunatly, using common crystals the interrupt rate is not an integer
; value in these ranges.  For example:
;
;    4 MHz crystal:  1 MHz instruction rate / 256 for TMR0 rollover / 8 for prescale
;                    generates an interrupt at a rate of 488.28125 Hz
;                    this is 488 and 9/32 Hz
;
;    20 MHz crystal: 5 MHz instruction rate / 256 for TMR0 rollover / 32 for prescale
;                    generates an interrupt at a rate of 610.3515625 Hz
;                    this is 610 and 45/128 Hz
;
; The fractional value is handled by adding one to the delay value every numerator
; second out of denomonator seconds.  For example, using the 4 MHz crystal, count
; 489 interrupts for a time of 1 second 9 times out of 32 and use 488 for the other
; 23 out of 32.  When using the 20 MHz crystal, this would be 45 seconds that are a
; little too long followed by 83 seconds that are a little too short.  To spread the
; errors out so that the time does not drift above and below the true time so much, 
; the long and short seconds are spread out over the entire 128 seconds using a simple 
; dither pattern.
;


#ifdef CLOCK_20MHZ
#define PRESACLE_VALUE	(PRESCALE_TIMER_MODE | 0x4)	; Set prescaler to /32 (1<<(4+1))
#define TICKS_PER_SECOND	610
#define FRACTION_DENOMINATOR	128   	; Counts down
#define DITHER_THRESH		2     	; 2/3 short 1/3 long (3:L,2:S,1:S)
#define DITHER_PERIOD		3     	; Counts down
#define DITHER_OVERRIDE_THRESH	124   	; Use long for FRACTION_DENOMINATOR counts>=124
				      	; (128,127,126,125,124)
#endif

#ifdef CLOCK_4MHZ
#define PRESACLE_VALUE	(PRESCALE_TIMER_MODE | 0x2)	; Set prescaler to /8 (1<<(2+1))
#define TICKS_PER_SECOND	488
#define FRACTION_DENOMINATOR	32
#define DITHER_THRESH		3	; 3/4 short 1/4 long (4:L,3:S,2:S,1:S)
#define DITHER_PERIOD		4
#define DITHER_OVERRIDE_THRESH	31	; Use long for FRACTION_DENOMINATOR counts>=31
				      	; (32,31)
#endif


#define TICKS_PER_SECOND_LONG	(TICKS_PER_SECOND+1)
#define MAX_DIGIT	3	
#define MAX_DIGIT_CODE	(1<<MAX_DIGIT)

#define SWITCH_SENSE	PORTA,4

	;  Registers

 ifdef __16F84A
#define REG_START	0x0c
 else
#define REG_START	0x020
 endif
	cblock	REG_START
	    _w, _status, _fsr	; Context Register Save Values
	    CurrentDigitCode	; Current digit being displayed
	    CurrentDigitIndex	; Index of digit to display
	    UpperSecondCounter	; Counter to keep track of when a second
	    LowerSecondCounter	; has elapsed
	    DitherCounter	; Counter to keep track of dithering
	    DenominatorCounter	; Counter for fraction period
	    Time:7		; BCD Time second:2,minute:2,hour:2,AM/PM
	    Switches		; 4 bit value that holds switch values
	    SwitchDelay		; Delay counter to test switches
	    DisplayCodes:4	; Raw values to display
	    SecondTimer		; Decrements once per second (clamp to zero)
	    TickTimer		; Decrements once per interrupt (clamp to zero)
	    RandTimer		; Incs once per int (cleared on button down)
	    Effect		; Current effect id
	    Tmp			; Temporary value
	    Counters:8		; 8 general purpose counters
	    Digit		; Used for digit roll effect
	    RollIndex		; Used for digit roll effect
	    RandVal		; Used by the random number generator
	    EnableColon		;
 	endc

 ifdef __16F84A
 __CONFIG _CP_OFF & _WDT_OFF & _HS_OSC
 else
 __CONFIG _CP_OFF & _DEBUG_OFF & _HS_OSC & _PWRTE_ON  & _WDT_OFF & _LVP_OFF & _WRT_ENABLE_ON & _BODEN_ON & _CPD_OFF
 endif

	org     0

Reset:
	goto	Main

;****************************************************************************
;
; 			Interrupt Handler
;
;**************************************************************************** 

 	org     4
Int:				; Interrupt Handler

	    ; Save registers

  	movwf	_w		; Save Context Registers
  	movf	STATUS, w	; - Assume TMR0 is the only enabled Interrupt
  	movwf	_status
	movf	FSR,w
	movwf	_fsr
  	bcf	INTCON, T0IF	; Reset the Timer Interrupt Flag

	incf	RandTimer,f
	movf	TickTimer,w
	btfss	STATUS,Z
	  decf	TickTimer,f

	    ; Blank display to sense switches

	movf	CurrentDigitCode,w
	movwf	PORTA		; Advance to new digit
	movlw	0xff
	movwf	PORTB		; Turn off display to sense switches
				
	btfsc	SWITCH_SENSE	; Test input switches
	  goto	SwitchClosed
	comf	CurrentDigitCode,w
	andwf	Switches,f
	goto	SwitchSkip
SwitchClosed:
	movf	CurrentDigitCode,w
	iorwf	Switches,f
SwitchSkip:
	
	    ; Refresh the display

	movlw	DisplayCodes		; PortB = DisplayCodes[CurrentDigitIndex]
	addwf	CurrentDigitIndex,w
	movwf	FSR
	movf	INDF,w
  	movwf	PORTB		; Display digit
				; Advance digit
	bsf	PORTB,7
	btfsc	EnableColon,0
	  bcf	PORTB,7		; Enable Colon

	decf	CurrentDigitIndex,f
	rrf	CurrentDigitCode,f
	btfss	STATUS,C
	  goto  ProcessSwitches
	
	movlw	MAX_DIGIT_CODE	;
  	movwf	CurrentDigitCode; set up current digit to refresh
	movlw	MAX_DIGIT
	movwf	CurrentDigitIndex

	    ; Decode switch values

ProcessSwitches:
	decfsz	SwitchDelay,f
	  goto	CheckTime
	movf	Switches,w	; Check to see if any switch down
	andlw	3
	btfsc	STATUS,Z	; Skip if adjusting time
	  goto	ProcessSwitches3
	clrf	SecondTimer
	movf	RandTimer,w	; Set new random seed
	movwf	RandVal
ProcessSwitches3:
	btfsc	Switches,0
	  call	AdvanceMinutes
	btfsc	Switches,1
	  call	AdvanceHours

	    ; Check to see if time to advance seconds

CheckTime:
	decfsz	LowerSecondCounter,f
	  goto IntEnd
	movf	UpperSecondCounter,f	; check for zero
	btfsc	STATUS,Z		; Skip next if not zero
	  goto ReloadTicks
	decf	UpperSecondCounter,f
	goto 	IntEnd 

ReloadTicks:
	movlw	TICKS_PER_SECOND>>8	; Reset Ticks counter
	movwf	UpperSecondCounter
	movlw	TICKS_PER_SECOND&0xff
	movwf	LowerSecondCounter

	    ; Adjust dither values to implement fractional delay
					
	movf	DitherCounter,w		; if(DitherCounter>DITHER_THRESH) use long
	sublw	DITHER_THRESH		; DITHER_THRESH-DitherCounter
	btfsc	STATUS,C		; Skip next if Borrow not set
	  goto DecDitherCounter
	movlw	TICKS_PER_SECOND_LONG>>8 ; Reset Ticks counter to long value
	movwf	UpperSecondCounter
	movlw	TICKS_PER_SECOND_LONG&0xff
	movwf	LowerSecondCounter

DecDitherCounter:
	decfsz	DitherCounter,f		; Dec DitherCounter and check for zero
	  goto OverrideCheck
	movlw	DITHER_PERIOD		; Reload DitherCounter
	movwf	DitherCounter
	
OverrideCheck:		
	movf	DenominatorCounter,w	; if(DenominatorCounter>DITHER_OVERRIDE_THRESH) use long
	sublw	DITHER_OVERRIDE_THRESH	; DITHER_OVERRIDE_THRESH-DenominatorCounter
	btfsc	STATUS,C		; Skip next if Borrow not set
	  goto DecDenomCounter
	movlw	TICKS_PER_SECOND_LONG>>8 ; Reset Ticks counter to long value
	movwf	UpperSecondCounter
	movlw	TICKS_PER_SECOND_LONG&0xff
	movwf	LowerSecondCounter

DecDenomCounter: 
	decfsz	DenominatorCounter,f	; Dec DenomCounter and check for zero
	  goto AdvanceSecond
	movlw	FRACTION_DENOMINATOR
	movwf	DenominatorCounter
	movlw	DITHER_PERIOD		; Reload DitherCounter
	movwf	DitherCounter

	    ; Advance the seconds

AdvanceSecond:
	movf	SecondTimer,w
	btfss	STATUS,Z
	  decf	SecondTimer,f

	incf	Time+0,f	; Advance Seconds
	movf	Time+0,w	; Fetch seconds
	sublw	10		; 10 - seconds
	btfss	STATUS,Z	; Check to see if overflow 				
	  goto	IntEnd
	clrf	Time+0

	incf	Time+1,f	; Advance TensSeconds
	movf	Time+1,w	; Fetch Tensseconds
	sublw	6		; 6 - Tensseconds
	btfss	STATUS,Z	; Check to see if overflow 				
	  goto	IntEnd
	clrf	Time+1

	call	AdvanceMinutes

	    ; Clean up and return from interrupt

IntEnd:
	movf	_fsr,w		; Restore registers
	movwf	FSR
  	movf   _status, w	
  	movwf  STATUS
  	swapf  _w, f
  	swapf  _w, w
  	retfie

;*******************************************************************************
;
; AdvanceMinutes
;*******************************************************************************

AdvanceMinutes:
	incf	Time+2,f	; Advance Minutes
	movf	Time+2,w	; Fetch Minutes
	sublw	10		; 10 - Minutes
	btfss	STATUS,Z	; Check to see if overflow 				
	  return
	clrf	Time+2

	incf	Time+3,f	; Advance TensMinutes
	movf	Time+3,w	; Fetch TensMinutes
	sublw	6		; 6 - Tensseconds
	btfss	STATUS,Z	; Check to see if overflow 				
	  return
	clrf	Time+3

AdvanceHours:
	incf	Time+4,f	; Advance Hours
	movf	Time+4,w	; Fetch Hours
	btfsc	Time+5,0	; Skip if not previously 10, 11 or 12
	  goto	Roll2 
	sublw	10		; 10 - Hours
	btfss	STATUS,Z	; Check to see if overflow 				
	  return
	clrf	Time+4		; Set hours to 10
	movlw	0x01		; Set hours tens digit
	movwf	Time+5
	return

Roll2:				; Hours is 10,11 or 12
	sublw	3		; 3 - Hours
	btfss	STATUS,Z	; Check to see if advanced to 13 				
	  goto	Roll3
	movlw	1		; Reset hours to 1
	movwf	Time+4		; Set hours to 1
	clrf	Time+5		; clear hours tens digit
	return

Roll3:				; Hours is now 11 or 12
	movf	Time+4,w	; Fetch Hours
	sublw	2		; 2 - Hours
	btfss	STATUS,Z	; Check to see if overflow
	  return
	movf	Time+6,w	; Hours is now 12
	xorlw	1		; Toggle AM/PM	
	movwf	Time+6		; Save Value
	return
	
;****************************************************************************
;
; 			Main Program Setup
;
;****************************************************************************

Main:
	call	SysInit

		; Note: If time is adjusted SecondsCounter is cleared which
		;       causes return cleared which causes return to MainLoop
		;       where the switches are tested and if the time is 
		;	being set the Time will be displayed.

MainLoop:
	call	FetchRand	; Grab a random number into w
	andlw	7
	addlw	3						
	movwf	SecondTimer	; Display the time for 3 to 10 seconds
	call	DisplayTime

		; Check to see if time is being changed

	movf	Switches,w
	andlw	3
	btfsc	STATUS,Z	; Skip if adjusting time
	  goto	PerformEffect
	call	DisplayTime
	goto	MainLoop

PerformEffect:
	call	FetchRand	; Grab a random number into w
	addwf	Time+0,w	; Add current time in seconds and minutes
	addwf	Time+2,w
	andlw	0x0f		; do effect 0x0 to 0x0f
	movwf	Effect
	call	FetchRand	; Grab a random number into w
	andlw	7
	addlw	3
	movwf	SecondTimer	; Perform effect for 3 to 10 seconds
	call	DecodeEffect
	goto	MainLoop

;*****************************************************************************
;
; DisplayTime:  This routine displays the time for the number of seconds
;		in SecondTimer
;
;*****************************************************************************

DisplayTime:
	movlw	1
	movwf	EnableColon
		
	movf	Time+2,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+0

	movf	Time+3,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+1

	movf	Time+4,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+2

	movf	Time+5,w	; Fetch 10s of hours
	iorlw	0x0c		; If 10s of hours set display 1 otherwise blank
	movwf	Digit
	call	FetchSegments	; Convert to 7 segment display
	iorlw	0xf9		; Only enable segments b and c
	movwf	Tmp		; Save
	movf	Time+6,w	; Fetch PM flag
	xorwf	Tmp,w	
	movwf	DisplayCodes+3
		
	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  DisplayTime
	return;

;*****************************************************************************
;
; EffectDisplaySeconds:  This routine displays seconds for the number of 
;		         seconds in SecondTimer
;
;*****************************************************************************

EffectDisplaySeconds:
	movlw	1
	movwf	EnableColon

	movf	Time+0,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+0

	movf	Time+1,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+1

	movf	Time+2,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+2

	movlw	0xff	
	movwf	DisplayCodes+3

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectDisplaySeconds
	return;

;*****************************************************************************
;
; EffectSpin1:  This routine spins the segments for the number of seconds in 
;         SecondTimer
;
;*****************************************************************************

EffectSpin1:
	movlw	0xdf		; Light up segment f
	movwf	DisplayCodes+0
	movlw	1
	movwf	EnableColon
EffectSpin1_0:
	movlw	TICKS_PER_SECOND/10	; 
	call	TickWait		; Wait 1/10 of a second
	bsf	STATUS,C
	rrf	DisplayCodes+0,f
	btfsc	STATUS,C
	  goto  EffectSpin1_1
	movlw	0xdf
	movwf	DisplayCodes+0
EffectSpin1_1:	
	comf	DisplayCodes+1,f
	movf	DisplayCodes+0,w
	movwf	DisplayCodes+2
	comf	DisplayCodes+3,f

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectSpin1_0
	return

;*****************************************************************************
;
; EffectRollTimeUp:  This routine displays the time for the number of seconds
;		     in SecondTimer while rolling the digits
;
;*****************************************************************************

EffectRollTimeUp:
	movlw	2
	movwf	Counters+0		; Init general purpose counter
	movlw	1
	movwf	EnableColon
EffectRollTimeUpLoop:
	call	RollTime
	incf	Counters+0,f
	movf	Counters+0,w
	sublw	6
	btfsc	STATUS,Z
	  clrf	Counters+0
	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectRollTimeUpLoop
	return;
	
;*****************************************************************************
;
; EffectRollTimeDown:  This routine displays the time for the number of seconds
;                      in SecondTimer while rolling the digits
;
;*****************************************************************************

EffectRollTimeDown:
	movlw	3
	movwf	Counters+0		; Init general purpose counter
	movlw	1
	movwf	EnableColon
EffectRollTimeDownLoop:
	decf	Counters+0,f
	call	RollTime
	movf	Counters+0,w
	btfss	STATUS,Z
	  goto	EffectRollTimeDown_1
	movlw	6
	movwf	Counters+0
EffectRollTimeDown_1:
	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectRollTimeDownLoop
	return;

;*****************************************************************************
;
; RollTime:  This routine displays the time for the number of seconds in
;            SecondTimer while rolling the digits
;
;*****************************************************************************

RollTime:
	movlw	TICKS_PER_SECOND/8	; 
	call	TickWait		; Wait 1/8 of a second
	
	movf	Time+2,w
	movwf	Digit
	movf	Counters+0,w
	movwf	RollIndex
	call	FetchRollSegments	; Convert to 7 segment display
	movwf	DisplayCodes+0

	movf	Time+3,w
	movwf	Digit
	call	FetchRollSegments	; Convert to 7 segment display
	movwf	DisplayCodes+1

	movf	Time+4,w
	movwf	Digit
	call	FetchRollSegments	; Convert to 7 segment display
	movwf	DisplayCodes+2

	movf	Time+5,w	; Fetch 10s of hours
	btfsc	STATUS,Z
	  goto	RollTime_1
	movlw	1
	movwf	Digit
	call	FetchRollSegments	; Convert to 7 segment display
	movwf	DisplayCodes+3
	goto	RollTime_2
RollTime_1:				; Blank 10s of hours display
	movlw	0xff
	movwf	DisplayCodes+3
RollTime_2:
	return;

;*****************************************************************************
;
; EffectFlyBy:  This routine displays a fly by effect for the number of 
;               seconds in SecondTimer
;
;*****************************************************************************

EffectFlyBy:
	movlw	0
	movwf	Counters+0	; Init general purpose counter	
	movlw	0xff	
	movwf	DisplayCodes+3
	movlw	0
	movwf	EnableColon
EffectFlyBy_0:
	movlw	TICKS_PER_SECOND/16 
	call	TickWait	; Wait 1/16 of a second

	movlw	0xff		; Default to all segments off
	movwf	DisplayCodes+0
	movwf	DisplayCodes+1
	movwf	DisplayCodes+2
	movwf	DisplayCodes+3

	movf	Counters+0,w
	sublw	2
	btfss	STATUS,C	; Skip if Counters <= 2	
	  goto	EffectFlyBy_1	
	movlw	DisplayCodes
	movwf	FSR
	movf	Counters+0,w
	sublw	2
	addwf	FSR,f
	movlw	0xfe		; Set segment a 
	movwf	INDF		; Write Buffer
	goto	EffectFlyBy_3

EffectFlyBy_1:
	movf	Counters+0,w	; Check fo Counters 8,9,a or 18,19,1a
	andlw	0xf		
	sublw	0xa
	btfss	STATUS,C	; Skip if Counters <= 0xa	
	  goto	EffectFlyBy_2
	movf	Counters+0,w
	andlw	0xf		
	sublw	0x7
	btfsc	STATUS,C	; Skip if Counters > 7	
	  goto	EffectFlyBy_2 
	movlw	DisplayCodes
	movwf	FSR
	movf	Counters+0,w
	andlw	3
	addwf	FSR,f
	movlw	0xbf		; Set segment g 
	movwf	INDF		; Write Buffer
	goto	EffectFlyBy_3	

EffectFlyBy_2:
	movf	Counters+0,w	; Check fo Counters 0x10,0x11,0x12		
	sublw	0x12
	btfss	STATUS,C	; Skip if Counters <= 0x12	
	  goto	EffectFlyBy_3
	movf	Counters+0,w		
	sublw	0x0f
	btfsc	STATUS,C	; Skip if Counters > 0x0f	
	  goto	EffectFlyBy_3
	movlw	DisplayCodes
	movwf	FSR
	movf	Counters+0,w
	andlw	3
	sublw	2
	addwf	FSR,f
	movlw	0xf7		; Set segment d
	movwf	INDF		; Write Buffer

EffectFlyBy_3:
	incf	Counters+0,f
	movf	Counters+0,w
	sublw	0x20
	btfsc	STATUS,Z
	  clrf	Counters+0

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectFlyBy_0
	return;

;*****************************************************************************
;
; EffectRandom:  This routine set the segments to random values for the number
;                of seconds in SecondTimer
;
;*****************************************************************************

EffectRandom:
	movlw	1
	movwf	EnableColon

	movlw	TICKS_PER_SECOND/10	; 
	call	TickWait		; Wait 1/10 of a second

	call	FetchRand	; Grab a random number into w
	movwf	DisplayCodes+0
	call	FetchRand	; Grab a random number into w
	movwf	DisplayCodes+1
	call	FetchRand	; Grab a random number into w
	movwf	DisplayCodes+2
	call	FetchRand	; Grab a random number into w
	movwf	DisplayCodes+3

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectRandom
	return


;*****************************************************************************
;
; EffectFastTime:  This routine makes time fly for the number of seconds in 
;                  SecondTimer
;
;*****************************************************************************

EffectFastTime:
	movlw	1
	movwf	EnableColon

	movf	Time+2,w	; Copy hours and minutes to Counters
	movwf	Counters+0
	movf	Time+3,w
	movwf	Counters+1
	movf	Time+4,w
	movwf	Counters+2
	movf	Time+5,w
	movwf	Counters+3
	movf	Time+6,w
	movwf	Counters+4

	movlw	TICKS_PER_SECOND/3; Initial delay rate
	movwf	Counters+5
	
EffectFastTime_0:
	movf	Counters+5,w	; Fetch wait time	
	call	TickWait	; Wait for the # of ticks in Counters+5

	movf	Counters+5,w
	sublw	TICKS_PER_SECOND/100	; 10ms - Counters[5]
	btfsc	STATUS,C		; Skip if delay > 10ms	
	  goto	EffectFastTime_Advance	

	movlw	TICKS_PER_SECOND/100	; Amount to speed up delay by
	subwf	Counters+5,f	; Counters[5] -= TICKS_PER_SECOND/100
	

	    ; Advance the counters like a clock

EffectFastTime_Advance:
	incf	Counters+0,f	; Advance Minutes
	movf	Counters+0,w	; Fetch Minutes
	sublw	10		; 10 - Minutes
	btfss	STATUS,Z	; Check to see if overflow 				
	  goto 	EffectFastTime_Display
	clrf	Counters+0

	incf	Counters+1,f	; Advance TensMinutes
	movf	Counters+1,w	; Fetch TensMinutes
	sublw	6		; 6 - Tensseconds
	btfss	STATUS,Z	; Check to see if overflow 				
	  goto 	EffectFastTime_Display
	clrf	Counters+1

	incf	Counters+2,f	; Advance Hours
	movf	Counters+2,w	; Fetch Hours
	btfsc	Counters+3,0	; Skip if not previously 10, 11 or 12
	  goto	EffectFastTime_Roll2 
	sublw	10		; 10 - Hours
	btfss	STATUS,Z	; Check to see if overflow 				
	  goto 	EffectFastTime_Display
	clrf	Counters+2	; Set hours to 10
	movlw	0x01		; Set hours tens digit
	movwf	Counters+3
	goto 	EffectFastTime_Display

EffectFastTime_Roll2:		; Hours is 10,11 or 12
	sublw	3		; 3 - Hours
	btfss	STATUS,Z	; Check to see if advanced to 13 				
	  goto	EffectFastTime_Roll3
	movlw	1		; Reset hours to 1
	movwf	Counters+2	; Set hours to 1
	clrf	Counters+3	; clear hours tens digit
	goto 	EffectFastTime_Display

EffectFastTime_Roll3:		; Hours is now 11 or 12
	movf	Counters+2,w	; Fetch Hours
	sublw	2		; 2 - Hours
	btfss	STATUS,Z	; Check to see if overflow
	  goto 	EffectFastTime_Display
	movf	Counters+4,w	; Hours is now 12
	xorlw	1		; Toggle AM/PM	
	movwf	Counters+2	; Save Value		


	    ; Display values in Counters

EffectFastTime_Display:
	movf	Counters+0,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+0

	movf	Counters+1,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+1

	movf	Counters+2,w
	movwf	Digit		
	call	FetchSegments	; Convert to 7 segment display
	movwf	DisplayCodes+2

	movf	Counters+3,w	; Fetch 10s of hours
	iorlw	0x0c		; If 10s of hours set display 1 otherwise blank
	movwf	Digit
	call	FetchSegments	; Convert to 7 segment display
	iorlw	0xf9		; Only enable segments b and c
	movwf	Tmp		; Save
	movf	Counters+4,w	; Fetch PM flag
	xorwf	Tmp,w	
	movwf	DisplayCodes+3

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectFastTime_0
	return


;*****************************************************************************
;
; EffectFleas:  This routine makes fleas for the number of seconds in 
;         SecondTimer
;
;*****************************************************************************

EffectFleas:
	movlw	0
	movwf	EnableColon
	call	FetchRand	; Grab a random number into w
	andlw	0xf
	movwf	Digit
EffectFleas_0:
	movlw	TICKS_PER_SECOND/15	; 
	call	TickWait		; Wait 1/15 of a second

	movlw	0xff		; Default to all segments off
	movwf	DisplayCodes+0
	movwf	DisplayCodes+1
	movwf	DisplayCodes+2
	movwf	DisplayCodes+3

	movlw	DisplayCodes
	movwf	FSR		; Point FSR to Display Codes
	movf	Digit,w
	movwf	Tmp
	rrf	Tmp,f
	rrf	Tmp,f
	rrf	Tmp,w		
	andlw	3		; Fetch Digit[5:3] into w
	addwf	FSR,f		; Add Digit offset to FSR
	movf	Digit,w
	andlw	0x7

	movwf	Counters+0	; Tmp = 1<<Digit[2:0]
	movlw	1
	movwf	Tmp
	bcf	STATUS,C
EffectFleas_1:
	movf	Counters+0,f
	btfsc	STATUS,Z
	  goto	EffectFleas_2
	rlf	Tmp,f
	decf	Counters+0,f
	goto	EffectFleas_1

EffectFleas_2:
	comf	Tmp,w
	movwf	INDF		; Write Buffer

	call	FetchRand
	movwf	Tmp
	rrf	Tmp,f		; Get random value into C
	movf	Digit,w
	movwf	Tmp
	rlf	Tmp,w		
	movwf	Digit		; Digit = Digit*2+C
	call	FetchFleaSegments
	movwf	Digit

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectFleas_0
	return 


;*****************************************************************************
;
; EffectBinary:  This routine makes a binary counter for the number of 
;		 seconds in SecondTimer
;
;*****************************************************************************

EffectBinary:
	clrf	Counters+0
	movlw	0
	movwf	EnableColon
EffectBinary_0:
	movlw	TICKS_PER_SECOND/15	; 
	call	TickWait		; Wait 1/15 of a second

	movlw	0xff
	btfsc	Counters+0,0
	  andlw	0xf9		; Enable segments b,c
	btfsc	Counters+0,1
	  andlw	0xcf		; Enable segments e,f
	movwf	DisplayCodes+0

	movlw	0xff
	btfsc	Counters+0,2
	  andlw	0xf9		; Enable segments b,c
	btfsc	Counters+0,3
	  andlw	0xcf		; Enable segments e,f
	movwf	DisplayCodes+1

	movlw	0xff
	btfsc	Counters+0,4
	  andlw	0xf9		; Enable segments b,c
	btfsc	Counters+0,5
	  andlw	0xcf		; Enable segments e,f
	movwf	DisplayCodes+2

	movlw	0xff
	btfsc	Counters+0,6
	  andlw	0xf9		; Enable segments b,c
	btfsc	Counters+0,7
	  andlw	0xcf		; Enable segments e,f
	movwf	DisplayCodes+3

	incf	Counters+0,f

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectBinary_0
	return 

;*****************************************************************************
;
; EffectShift1:  This routine shifts the digits left for the number of 
;		 seconds in SecondTimer
;
;*****************************************************************************

EffectShift1:
	movlw	0
	movwf	EnableColon
	movlw	0xff		; Default to trail off
	movwf	DisplayCodes+1
	movwf	DisplayCodes+2
	movwf	DisplayCodes+3
	movlw	0xbf		; Default to segment g
	movwf	DisplayCodes+0

EffectShift1_0:
	movlw	TICKS_PER_SECOND/10	; 
	call	TickWait
	
	movf	DisplayCodes+2,w	; Shift left
	movwf	DisplayCodes+3
	movf	DisplayCodes+1,w
	movwf	DisplayCodes+2
	movf	DisplayCodes+0,w
	movwf	DisplayCodes+1

	call	FetchRand	; Grab a random number into w
	movwf	Tmp
	btfss	DisplayCodes+0,0 ; Clear Tmp[5] if segment a
	  bcf	Tmp,5

	movlw	0xbf		; Default to segment g
	btfsc	DisplayCodes+0,3 ; Goto EffectShift1_1 if currently not seg d
	  goto	EffectShift1_1
	btfsc	Tmp,0		; Currently segment d
	  movlw	0xf7		; Enable segment d
	goto	EffectShift1_2

EffectShift1_1:			; Currently segment a or g
	btfsc	Tmp,0
	  movlw	0xfe		; Enable segment a
	btfsc	Tmp,5
	  movlw	0xf7		; Enable segment d (only if was g)

EffectShift1_2:
	movwf	DisplayCodes+0

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectShift1_0
	return 


;*****************************************************************************
;
; EffectSpin2:  This routine spins the segments for the number of seconds in 
;	        SecondTimer
;
;*****************************************************************************

EffectSpin2:
	movlw	0
	movwf	EnableColon
	movlw	0xff
	movwf	DisplayCodes+3

	movlw	TICKS_PER_SECOND/5
	call	TickWait
	movlw	0xf6
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movwf	DisplayCodes+1

	movlw	TICKS_PER_SECOND/5
	call	TickWait
	movlw	0xf9
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xcf
	movwf	DisplayCodes+1

	movlw	TICKS_PER_SECOND/5 
	call	TickWait
	movlw	0xbf
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movwf	DisplayCodes+1

	movlw	TICKS_PER_SECOND/5
	call	TickWait
	movlw	0xcf
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xf9
	movwf	DisplayCodes+1

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectSpin2
	return 


;*****************************************************************************
;
; EffectSpin3:  This routine spins the segments for the number of seconds in 
;	        SecondTimer
;
;*****************************************************************************

EffectSpin3:
	movlw	0
	movwf	EnableColon

	movlw	0xff
	movwf	DisplayCodes+3

	movlw	TICKS_PER_SECOND/8
	call	TickWait
	movlw	0xfe
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xbf
	movwf	DisplayCodes+1


	movlw	TICKS_PER_SECOND/8
	call	TickWait
	movlw	0xfd
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xfb
	movwf	DisplayCodes+1

	movlw	TICKS_PER_SECOND/8 
	call	TickWait
	movlw	0xbf
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xf7
	movwf	DisplayCodes+1

	movlw	TICKS_PER_SECOND/8
	call	TickWait
	movlw	0xdf
	movwf	DisplayCodes+0
	movwf	DisplayCodes+2
	movlw	0xef
	movwf	DisplayCodes+1

	movf	SecondTimer,w
	btfss	STATUS,Z
	  goto  EffectSpin3
	return 

;*****************************************************************************
;
; EffectClear:  This routine clears the time one segment at a time
;
;*****************************************************************************

EffectClear:
	movlw	1
	movwf	EnableColon
	movlw	4
	movwf	Counters+0
	movlw	DisplayCodes
	movwf	FSR
EffectClear_0:
	movlw	1
	movwf	Counters+1
EffectClear_1:
	movlw	TICKS_PER_SECOND/10
	call	TickWait
	movf	Counters+1,w
	iorwf	INDF,f
	bsf	STATUS,C
	rlf	Counters+1,f
	btfss	STATUS,C
	  goto	EffectClear_1

	btfss	Counters+0,2
	  clrf	EnableColon	; Clear the colon after the 2nd digit is gone
	incf	FSR,f
	decfsz	Counters+0,f
	  goto	EffectClear_0

	return 

;*****************************************************************************
;
; TickWait(w): wait the number of timer ticks in the w register
;
;*****************************************************************************

TickWait:	
	movwf	TickTimer
Wait1:	movf	TickTimer,w
	btfss	STATUS,Z
	  goto  Wait1
	return

;*****************************************************************************
;
; SysInit: Initialize system
;
;*****************************************************************************

SysInit:
	movlw	MAX_DIGIT_CODE	;
  	movwf	CurrentDigitCode; set up current digit to refresh
	movlw	MAX_DIGIT
	movwf	CurrentDigitIndex

	movlw  	0x3f		; Start with '-'
	movwf	PORTB

	bsf    	STATUS,RP0	; Goto Bank 1 to set Port Direction
	clrf   	TRISB^0x80   	; PORTB is Output
	movlw	0x10		; All bits of port A are output except RA[4] 
	movwf	TRISA^0x80	; PortA is Output

	movlw  	PRESACLE_VALUE	; 
	movwf	OPTION_REG^0x080
	bcf    	STATUS,RP0	; Go back to Bank 0

	clrf	Switches	; Clear switch input storage
	clrf	SwitchDelay	; 
	movf	TMR0,w
	movwf	RandVal		; Set random seed (gets changed when buttons are pressed)
	clrf	TMR0		; Reset TMR0 value
				; Initialize time to 1:00:00 AM
	clrf	Time+0	
	clrf	Time+1		; Seconds	
	clrf	Time+2
	clrf	Time+3		; Minutes
	clrf	Time+4		; Hours
	incf	Time+4,f
	clrf	Time+5		; TensHours
	clrf	Time+6		; PM

	clrf	EnableColon
	movlw	TICKS_PER_SECOND>>8
	movwf	UpperSecondCounter
	movlw	TICKS_PER_SECOND&0xff
	movwf	LowerSecondCounter

	movlw	FRACTION_DENOMINATOR
	movwf	DenominatorCounter
	movlw	DITHER_PERIOD
	movwf	DitherCounter

	movlw  	(1<<GIE) | (1<<T0IE)
	movwf  	INTCON		; Enable Interrupts
	
	return


;***************************************************************************
;
; FetchRand: This routine will return a random 8 bit value in w
;
;***************************************************************************

FetchRand:   
	rlf     RandVal,w
        rlf     RandVal,w
        btfsc   RandVal,4
          xorlw   1
	btfsc   RandVal,5
          xorlw   1
	btfsc   RandVal,3
          xorlw   1
	movwf	RandVal
	return

;----------------------------------------------------------------------------
;
; Table space
;
;----------------------------------------------------------------------------

	org     0x354

;***************************************************************************
;
; DecodeEffect(Effect): This routine will jump to the effect based on the
;                       jump table index Effect.
;
;***************************************************************************

DecodeEffect:
	movlw	HIGH $			; Required for jump tables
  	movwf	PCLATH
	movf	Effect,w
	addwf 	PCL,f			; Add command to PC to implement jump table

  	goto  	EffectSpin1		; 0x00
  	goto  	EffectBinary		; 0x01
  	goto  	EffectRollTimeUp	; 0x02
	goto  	EffectRollTimeDown	; 0x03
 	goto  	EffectFlyBy		; 0x04
  	goto  	EffectRandom		; 0x05
  	goto  	EffectFastTime		; 0x06
	goto  	EffectFleas		; 0x07
	goto	EffectShift1		; 0x08
  	goto  	EffectSpin2		; 0x09
  	goto  	EffectClear		; 0x0a
	goto  	EffectSpin3		; 0x0b

 	goto  	EffectFleas		; 0x0c
  	goto  	EffectSpin3		; 0x0d
  	goto  	EffectClear		; 0x0e
	goto  	EffectBinary		; 0x0f

;****************************************************************************
;
; FetchFleaSegments returns the value to write to LED port to display digit in w
;
;**************************************************************************** 

FetchFleaSegments:
	movlw	HIGH $		; Required for jump tables
  	movwf	PCLATH
	movf	Digit,w
	addwf 	PCL,f		; Add w to PC to implement jump table

	retlw	1		; 0
	retlw	1
	retlw	6		; 1
	retlw	2
	retlw	3		; 2
	retlw	3
	retlw	10		; 3
	retlw	11
	retlw	11		; 4
	retlw	11
	retlw	0		; 5
	retlw	0
	retlw	4		; 6
	retlw	14

	retlw	22		; 7
	retlw	25

	retlw	9		; 8
	retlw	0
	retlw	14		; 9
	retlw	14
	retlw	14		; 10
	retlw	14
	retlw	19		; 11
	retlw	12
	retlw	13		; 12
	retlw	22
	retlw	8		; 13
	retlw	8
	retlw	13		; 14
	retlw	22

	retlw	18		; 15
	retlw	10

	retlw	8		; 16
	retlw	8
	retlw	8		; 17
	retlw	8
	retlw	19		; 18
	retlw	19
	retlw	20		; 19
	retlw	26
	retlw	21		; 20
	retlw	22
	retlw	16		; 21
	retlw	16
	retlw	17		; 22
	retlw	18

	retlw	0		; 23
	retlw	0
	retlw	0		; 24
	retlw	0

	retlw	16		; 25
	retlw	16
	retlw	25		; 26
	retlw	25

;****************************************************************************
;
; FetchSegments returns the value to write to LED port to display digit in w
;
;**************************************************************************** 

FetchSegments:
	movlw	HIGH $		; Required for jump tables
  	movwf	PCLATH
	movf	Digit,w
	andlw	0x0f
	addwf 	PCL,f		; Add w to PC to implement jump table
	retlw	0x40
	retlw	0x79
	retlw	0x24
	retlw	0x30
	retlw	0x19
	retlw	0x12
	retlw	0x02
	retlw 	0x78
	retlw	0x00
	retlw	0x10
	retlw	0x08
	retlw	0x03
	retlw	0x46
	retlw	0x21
	retlw	0x06
	retlw	0x0e

;****************************************************************************
;
; FetchRollSegments returns the value to write to LED port to display digit 
; in Digit with rolled by RollIndex
;
;**************************************************************************** 

FetchRollSegments:
	movlw	HIGH $		; Required for jump tables
  	movwf	PCLATH
	movf	RollIndex,w
	addwf	PCL,f
	goto	FetchD2Segments	; 0
	goto	FetchD1Segments	; 1
	goto	FetchU0Segments	; 2
	goto	FetchU1Segments	; 3 
	goto	FetchU2Segments	; 4
	retlw	0xff		; 5


FetchD2Segments:	; Display rotated down 2
	movf	Digit,w
	addwf	PCL,f
	retlw	0xf7	; 0
	retlw	0xff	; 1
	retlw	0xf7	; 2
	retlw	0xf7	; 3
	retlw	0xff	; 4
	retlw	0xf7	; 5
	retlw	0xf7	; 6
	retlw	0xf7	; 7
	retlw	0xf7	; 8
	retlw	0xf7	; 9

FetchD1Segments:	; Display rotated down 1
	movf	Digit,w
	addwf	PCL,f
	retlw	0xab	; 0
	retlw	0xfb	; 1
	retlw	0xb3	; 2
	retlw	0xb3	; 3
	retlw	0xe3	; 4
	retlw	0xa7	; 5
	retlw	0xa7	; 6
	retlw	0xbb	; 7
	retlw	0xa3	; 8
	retlw	0xa3	; 9

FetchU0Segments:
	movf	Digit,w
	addwf 	PCL,f		; Add w to PC to implement jump table
	retlw	0xc0	; 0
	retlw	0xf9	; 1
	retlw	0xa4	; 2
	retlw	0xb0	; 3
	retlw	0x99	; 4
	retlw	0x92	; 5
	retlw	0x82	; 6
	retlw 	0xf8	; 7
	retlw	0x80	; 8
	retlw	0x90	; 9

FetchU1Segments:	; Display rotated up 1
	movf	Digit,w
	addwf	PCL,f
	retlw	0x9d	; 0
	retlw	0xfd	; 1
	retlw	0x9e	; 2
	retlw	0xbc	; 3
	retlw	0xfc	; 4
	retlw	0xbc	; 5
	retlw	0x9c	; 6
	retlw	0xfd	; 7
	retlw	0x9c	; 8
	retlw	0x9c	; 9

FetchU2Segments:	; Display rotated up 2
	movf	Digit,w
	addwf	PCL,f
	retlw	0xfe	; 0
	retlw	0xff	; 1
	retlw	0xfe	; 2
	retlw	0xfe	; 3
	retlw	0xff	; 4
	retlw	0xfe	; 5
	retlw	0xfe	; 6
	retlw	0xff	; 7
	retlw	0xfe	; 8
	retlw	0xfe	; 9

 end
