; PROGRAM: SPEEDO.SRC	Version 2.3

;	************************************
;	* LED matrix km counter for speedo *
;	************************************

;	Copyright by WolfRam Technologies Inc. All rights reserved.
;	July 17th 1997

; *********************** Identify used controller ***************************

		device	pic16c74a,xt_osc,wdt_off,pwrt_on,protect_off

; ************************** Port connections ********************************

ACC		=	rb.0		; Ignition: 1 if on
PULSE		=	rb.1		; WATCH! 1 if no pulse (default)
INKEY		=	rd.0		; Control key: 1 if pushed
TACHO		=	rd.1		; Clock for speedo: WATCH! 1 if pulse

D		=	ra.0		; Pins DI and DO of the EEPROM
CLK		=	ra.1		; Clock pin: data valid on rising edge
CS		=	ra.2		; Chip select: high = active

; ***************************** Constants ************************************

TML		=	71		; Restart for 1465 counts: at 12 MHz
TMH		=	250		; this results in 3M/1465 = 2048 Hz

ROP		=	192		; Opcode for read
WROP		=	160		; Opcode for write
EWEN		=	152		; Opcode to enable erase and write
EWDS		=	128		; Opcode to disable erase and write
ERAS		=	224		; Opcode to erase a byte

; ******** Set RAM origin above special registers, declare variables *********

		org	32

first2		ds	1		; Register data to display
second2		ds	1
third2		ds	1
line		ds	1		; Actual line displayed
key		ds	1		; [interrupt] Pushed key
pushed		ds	1		; [interrupt] Duration of key push
pushl		ds	1		; [interrupt] Low byte of key push
tick		ds	1		; [interrupt] IT counter
startdelay	ds	1		; 1 second delay at startup
phase		ds	1		; Phase of the intro
acc_off		ds	1		; Counts how long ACC is off

countl		ds	1		; Distance meter for total
counth		ds	1
countl1		ds	1		; Distance meter for daily 1
counth1		ds	1
countl2		ds	1		; Distance meter for daily 2
counth2		ds	1

day1s		ds	4		; Counters for daily digits
day2s		ds	4
indexes		ds	6		; Counters for total digits
temp_inds	ds	6		; Temporary counters for display
mode		ds	1		; 0 : total, 1 : daily 1, 2 : daily 2
skipper		ds	1		; Stores pulse index

bit_temp	ds	1		; Flags
NOW		=	bit_temp.0	; Handshake between prg and interrupt
RP0_temp	=	bit_temp.1	; Value of RP0 at the IT call
CHANGE		=	bit_temp.2	; Counter still changing

temp		ds	1		; Temporary variable for EE routines
eedata		ds	1		; Pass data to eewrite, from eeread
eeaddr		ds	1		; Pass address to eewrite or eeread
clocks		ds	1		; Number of clock cycles for shout

all_tmp		ds	1		; Buffer for any LOWEST level routine
all_tmp2	ds	1
int_tmp		ds	1		; Buffer for the interrupt routine
int_tmp2	ds	1
count1		ds	1		; Counter

w_copy		ds	1		; [interrupt] Store for W
s_copy		ds	1		; [interrupt] Store for status reg

; ****************************** Reset vector ********************************

		org	0

		mov	PCLATH,#6	; Sets the jmp pc+w address high byte
					; to the end of the first 2K page.
					; Tables start at 500H, 600H, 700H.
		jmp	initialize

; *************************** Interrupt routine ******************************

		org	4

interrupt	mov	w_copy,w	; Make a copy of W
		mov	s_copy,status	; Make a copy of status
		jnb	RP0,inter2	; We are on page 0
		clrb	RP0
		setb	RP0_temp	; Remember page
inter2		jnb	TMR1IF,inter1	; Not timer1 overflow
		clrb	TMR1IF		; Clear the timer interrupt flag
		mov	TMR1L,#TML	; Restart timer
		mov	TMR1H,#TMH
		inc	tick		; Increment counter

		jb	ACC,power_on	; ACC is still on
		inc	acc_off		; Count power off period
		jmp	int_main

power_on	clr	acc_off		; Clear counter if power present again

int_main	cjb	phase,#11,inter1 ; No not check key during intro
		jnb	INKEY,inter4	; Key not pushed
		inc	pushl		; Increment low byte
		jnz	inter1		; No overrun yet
		cja	pushed,#8,inter4 ; No need for further indication
		inc	pushed		; Indicate "tick"
inter4		cje	pushed,#0,inter1 ; Nothing happened
		jb	INKEY,inter5	; Key still pushed
		cja	pushed,#8,inter6
		mov	key,#1		; Short push
inter7		clr	pushed		; Reset duration
		jmp	inter1
inter6		clr	key		; Long push already indicated
		jmp	inter7
inter5		cjne	pushed,#8,inter1 ; No need to indicate
		mov	key,#2		; Indicate long push only once

inter1		cje	phase,#11,inter9 ; Skip in normal mode
		cjne	tick,#0,intro1	; 1/8 second steps
		cje	startdelay,#0,intro2 ; 1 second delay at start
		dec	startdelay
		jmp	intro1		; Skip phase increase
intro2		inc	phase
intro1		mov	int_tmp2,phase
		cjb	int_tmp2,#8,intro3 ; 3/8 second delay at the end
		mov	int_tmp2,#7	; Restore phase 8
intro3		clrb	c
		rl	int_tmp2	; Multiply by 2
		mov	int_tmp,int_tmp2 ; Store
		rl	int_tmp2	; By 4
		add	int_tmp2,int_tmp ; By 6
		mov	int_tmp,int_tmp2 ; Store
		rl	int_tmp2
		rl	int_tmp2	; By 24
		add	int_tmp2,int_tmp ; By 30
		add	int_tmp2,line	; Add line offset
		mov	w,int_tmp2	; Get offset
		call	fading		; Get 1st digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		add	int_tmp2,#5
		mov	w,int_tmp2	; 2nd digit
		call	fading		; Get offset
		add	int_tmp,w	; Add it
		rl	int_tmp
		rl	int_tmp
		mov	first2,int_tmp	; Store
		add	int_tmp2,#5
		mov	w,int_tmp2	; 3rd digit
		call	fading		; Get 3rd digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		add	int_tmp2,#5
		mov	w,int_tmp2	; 4th digit
		call	fading
		add	int_tmp,w	; Add it
		mov	second2,int_tmp	; Store
		add	int_tmp2,#5
		mov	w,int_tmp2	; 5th digit
		call	fading		; Get 5th digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		add	int_tmp2,#5
		mov	w,int_tmp2	; 6th digit
		call	fading
		add	int_tmp,w	; Add it
		rl	int_tmp
		rl	int_tmp
		mov	third2,int_tmp	; Store
		cjne	phase,#11,inter8 ; Write ports & end interrupt
		mov	PCLATH,#5	; Switch to digits page	
		jmp	inter8		; Write ports & end interrupt

inter9		clrb	c
		mov	w,temp_inds 	; Get first character
		add	w,line		; Add line offset
		call	fading		; Get 1st digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		mov	w,temp_inds+1
		add	w,line		; 2nd digit
		call	fading
		add	int_tmp,w	; Add it
		rl	int_tmp
		rl	int_tmp
		mov	first2,int_tmp	; Store
		mov	w,temp_inds+2
		add	w,line		; 3rd digit
		call	fading		; Get 3rd digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		mov	w,temp_inds+3
		add	w,line		; 4th digit
		call	fading
		add	int_tmp,w	; Add it
		mov	second2,int_tmp	; Store
		mov	w,temp_inds+4
		add	w,line		; 5th digit
		call	fading		; Get 5th digit
		mov	int_tmp,w	; Store
		rl	int_tmp
		rl	int_tmp
		rl	int_tmp		; Shift by 3
		mov	w,temp_inds+5
		add	w,line		; 6th digit
		call	fading
		add	int_tmp,w	; Add it
		rl	int_tmp
		rl	int_tmp
		mov	third2,int_tmp	; Store
		mov	w,rd		; Get port value
		and	w,#3		; Leave tacho pulse
		or	third2,w	; Add port value

inter8		mov	re,line		; Switch to next line
		mov	rb,first2
		mov	rc,second2
		mov	rd,third2
		inc	line		; Next line of display
		cjb	line,#5,inter10
		clr	line		; Restart at first line
inter10		setb	NOW		; Do it!
		jnb	RP0_temp,inter3 ; Stay on this page
		clrb	RP0_temp	; Forget it
		setb	RP0		; Restore page
inter3		mov	status,s_copy	; Restore status register
		swap	w_copy		; Prepare for swapped move
		mov	w,<>w_copy	; Swap/move to w, status unaffected
		reti			; Return to main program

; ***************************** Start program ********************************

initialize	clr	ra
		clr	rb
		clr	rc
		clr	rd
		clr	re		; Clear all ports
		clrb	RP1		; RP1 not used, set to 0
		setb	RP0		; Set page for bank1 registers
		clr	trisa		; Initially output for EEPROM
		mov	trisb,#03H	; Pins 0 and 1 are input
		mov	trisc,#0C0H	; Pins 6 and 7 are input
		mov	trisd,#01H	; Pin 0 is input
		clr	trise		; Port E is output
		mov	ADCON1,#7	; Everybody go digital IO
		setb	INTEDG		; Interrupt on rb.0 rising edge
		clrb	INTE		; Disable interrupt
		clrb	TMR1IE		; No interrupts on timer overflow
		clrb	RP0		; Back to page 0
		setb	GIE		; Enable interrupts
		clrb	TMR1IF		; Clear interrupt flag
		clr	T1CON		; Stop timer, clear prescaler
		setb	PEIE		; Enable peripheral interrupts

startup		mov	PCLATH,#6	; WolfRam logo page
		clr	mode		; Start in total mode
		clr	key		; No push
		clr	pushed		; Clear duration
		clr	bit_temp	; Clear any info [interrupt]
		mov	startdelay,#8	; Set 1 second initial delay
		clr	line		; Display starts at top line
		clr	phase		; Start at phase 0
		clr	skipper		; Start at no skip
		setb	RP0
		setb	TMR1IE		; Enable timer interrupts
		clrb	RP0
		mov	fsr,#countl	; Read data from EEPROM
		clr	eeaddr
start7		call    eeread
		mov     indf,eedata
		inc	fsr
		inc     eeaddr
		cjne	eeaddr,#20,start7

		mov	TMR1L,#TML	; Restart timer
		mov	TMR1H,#TMH
		setb	TMR1ON		; Start timer

		mov	fsr,#temp_inds	; Clear temporary counters
start1		mov	indf,#1
		inc	fsr
		cjb	fsr,#temp_inds+6,start1
start3		cjne	phase,#11,start3 ; Wait for intro to happen
		clrb	NOW		; Not now

start2		jnb	NOW,start2	; Wait for the IT
		mov	w,tick
		and	w,#7FH
		jz	start6		; 1/16 seconds
		clrb	NOW
		jmp	start2
start6		clrb	CHANGE		; Forget changes
		mov	fsr,#indexes
start4		mov	all_tmp,indf
		add	fsr,#6
		cje	indf,all_tmp,start5 ; Displays already actual value
		setb	CHANGE		; Remember the change
		inc	indf
start5		sub	fsr,#5
		cjb	fsr,#temp_inds,start4
		jnb	CHANGE,speedo	; Start normal operation
		clrb	NOW		; Wait for next IT
		jmp	start2

; ********************** Normal operation of the speedo **********************

speedo		mov	w,acc_off	; Check if ACC is off for at least
		and	w,#0C8H		; 1/10 seconds. This way we can avoid
		mov	w,#0C8H-w	; unwanted resets on temporary contact
					; failures when the car bumps.
		jnz	speed1		; ACC still on

		clrb	TMR1ON		; Stop timer
		call	eenable		; Turn off write/erase protection
		setb	RP0
		clrb	TMR1IE		; Disable timer interrupts
		clrb	RP0
		clr	rb		; Stop any display
		clr	rc
		clr	rd
		clr	re
		mov	fsr,#countl
		clr	eeaddr

speed22		mov	eedata,indf
		call	eerase		; Clear byte
		call	busy
		call	eewrite		; Write data into EEPROM
		call	busy
		inc	fsr
		inc	eeaddr
		cjne	eeaddr,#20,speed22
		call	eedisbl		; Turn write/erase protection on

		clrb	INTF		; Clear IT flag
		setb	INTE		; Allow wake-up
		clrb	GIE		; Avoid IT at wake-up
		sleep			; Go to sleep
		nop
		clrb	INTF		; Clear interrupt flag
		clrb	INTE		; Disable interrupt
		setb	GIE		; Re-enable interrupts
		jmp	startup		; Startup without initialization

speed1		cjne	key,#0,speed2	; Button pressed, check it
		jb	PULSE,speedo	; No pulse, no key, no nothing

		inc	skipper
		cjne	skipper,#4,speed20 ; Do not throw away
		clr	skipper		; Restart
		jmp	speed21
speed20		setb	TACHO		; Output pulse

speed21		inc	countl		; Increment counters
		jnz	speed3
		inc	counth
speed3		inc	countl1		; Increment day 1
		jnz	speed4
		inc	counth1
speed4		inc	countl2		; Increment day 2
		jnz	speed5
		inc	counth2

speed5		cjne	counth,#17,daily1 ; Last 50 meters not reached yet
		cje	countl,#28H,speed6
		cje	countl,#50H,speed6
		cje	countl,#78H,speed6
		cje	countl,#0A0H,speed6
		cje	countl,#0C8H,speed6
		cje	countl,#0F0H,speed6
		cjne	countl,#0FEH,daily1 ; No overrun yet
		mov	countl,#83	; Reset
		clr	counth
		jmp	daily1

speed6		mov	fsr,#indexes+5	; Address of last digit
		inc	indf		; Increment last digit
speed8		cjbe	indf,#55,speed7 ; Only the last digit changes
		dec	fsr		; Check next digit
		inc	indf		; Increment it
		cje	fsr,#indexes,speed7 ; First digit also changed
		cjae	indf,#56,speed8	; Change next digit as well

speed7		cjne	mode,#0,daily1	; Indicate only in mode 0
		mov	fsr,#indexes	; Copy indexes into temp_inds
speed9		mov	all_tmp,indf
		add	fsr,#6
		mov	w,all_tmp
		mov	indf,w
		sub	fsr,#5
		cjb	fsr,#temp_inds,speed9

daily1		cjne	counth1,#17,daily2 ; Last 50 meters not reached yet
		cje	countl1,#28H,daily11
		cje	countl1,#50H,daily11
		cje	countl1,#78H,daily11
		cje	countl1,#0A0H,daily11
		cje	countl1,#0C8H,daily11
		cje	countl1,#0F0H,daily11
		cjne	countl1,#0FEH,daily2 ; No overrun yet
		mov	countl1,#83	; Reset
		clr	counth1
		jmp	daily2

daily11		mov	fsr,#day1s+3	; Address of last digit
		inc	indf
daily12		cjbe	indf,#55,daily13 ; Only the last digit changes
		dec	fsr		; Check next digit
		inc	indf
		cje	fsr,#day1s,daily13 ; First digit also changed
		cjae	indf,#56,daily12 ; Change the digit

daily13		cjne	mode,#1,daily2	; Display only in mode 1
		mov	fsr,#day1s	; Copy indexes into temp_inds
daily14		mov	all_tmp,indf
		add	fsr,#16
		mov	w,all_tmp
		mov	indf,w
		sub	fsr,#15
		cjb	fsr,#day2s,daily14
		mov	temp_inds,#67	; Letter A
		mov	temp_inds+1,#77	; ':'

daily2		cjne	counth2,#17,speed10 ; Last 50 meters not reached yet
		cje	countl2,#28H,daily21
		cje	countl2,#50H,daily21
		cje	countl2,#78H,daily21
		cje	countl2,#0A0H,daily21
		cje	countl2,#0C8H,daily21
		cje	countl2,#0F0H,daily21
		cjne	countl2,#0FEH,speed10 ; No overrun yet
		mov	countl2,#83	; Reset
		clr	counth2
		jmp	speed10

daily21		mov	fsr,#day2s+3	; Address of last digit
		inc	indf
daily22		cjbe	indf,#55,daily23 ; Only the last digit changes
		dec	fsr		; Check next digit
		inc	indf
		cje	fsr,#day2s,daily23 ; First digit also changed
		cjae	indf,#56,daily22 ; Change the digit

daily23		cjne	mode,#2,speed10	; Display only in mode 2
		mov	fsr,#day2s	; Copy indexes into temp_inds
daily24		mov	all_tmp,indf
		add	fsr,#12
		mov	w,all_tmp
		mov	indf,w
		sub	fsr,#11
		cjb	fsr,#indexes,daily24
		mov	temp_inds,#72	; Letter B
		mov	temp_inds+1,#77	; ':'

speed10		mov	fsr,#day1s	; Check all counters
speed11		cjne	indf,#61,speed12 ; Not overturned yet
		mov	indf,#1		; Reset if overturned
speed12		inc	fsr
		cjb	fsr,#temp_inds,speed11 ; Next

		cje	key,#0,speed13	; Key not pushed
speed2		cje	key,#2,speed14	; Reset on long push
		clr	key		; Key checked
		inc	mode		; Enter new mode
		cjne	mode,#3,speed19
		clr	mode		; Back to total
speed19		cje	mode,#0,speed7	; Redraw
		cje	mode,#1,daily13
		jmp	daily23

speed13		jnb	ACC,speedo	; ACC shut off
		cjne	key,#0,speed2	; Check key press
		jnb	PULSE,speed13
		clrb	TACHO		; End of pulse
		jmp	speedo		; Continue

speed14		clr	key		; Key checked
		cje	mode,#0,speed13	; Don't reset total (later: adjust km)
		cje	mode,#2,speed15	; Counter #2
		mov	countl1,#83	; Reset counter #1
		clr	counth1
		mov	fsr,#day1s	; Counter #1
speed16		mov	count1,#4	; 4 byte counters
speed17		mov	indf,#1
		inc	fsr
		djnz	count1,speed17
		jmp	speed19		; Redraw

speed15		mov	countl2,#83	; Reset counter #2
		clr	counth2
		mov	fsr,#day2s
		jmp	speed16

; ************************ EEPROM read/write routines ************************

; This routine, SHiftOUT, serves all of the other subroutines by shifting
; opcodes, addresses and data out to pin D of the EEPROM. Before using it, put
; the data to be sent into the variable temp (if required), put the number of
; shifts into clocks, and set CS. After the transaction is done, clear CS.

shout		rl	temp		; Rotate bit7 of temp into carry
		movb	D,c		; Move carry bit to input of EEPROM
		call	pause
		setb	CLK		; Clock the bit into EEPROM
		call	pause		; Clock must be high > 500 ns
		clrb    CLK
		call	pause
		djnz	clocks,shout
		ret

; Read the byte in EEaddr into EEdata

eeread		mov	temp,#ROP	; Move the read opcode into temp  
		mov	clocks,#4	; Number of bits to shift out (op+1)
		setb	CS
		call	pause
		call	shout
		mov	clocks,#8
		mov	temp,eeaddr
		call	shout
		setb	RP0
		mov     trisa,#1
		clrb	RP0
		mov     clocks,#8
read1		setb	CLK
		call	pause
		movb	c,D
		rl	temp 
		call	pause
		clrb	CLK
		call	pause           
		djnz	clocks,read1
		mov	eedata,temp
		setb	RP0
		clr	trisa
		clrb	RP0
		clrb    CS
		jmp	pause

; Call this procedure before first write attempt after power up. The EEPROM
; wakes up write/erase protected. Also use to unprotect after EEdisbl.

eenable		setb	CS
		call	pause
		mov	clocks,#12
		mov	temp,#EWEN
		call	shout
		clrb    CS
		jmp	pause

; Call this procedure to protect against accidental write/erasure of the
; EEPROM. Power glitches can write/erase bytes of an enabled chip.

eedisbl		setb	CS
		call	pause
		mov	clocks,#12
		mov	temp,#EWDS
		call	shout
		clrb    CS
		jmp	pause

; Write the byte in EEdata to EEaddr.

eewrite		mov	temp,#WROP
		mov	clocks,#4
		setb	CS
		call	pause
		call	shout
		mov	clocks,#8
		mov	temp,eeaddr
		call	shout        
		mov	clocks,#8
		mov	temp,eedata
		call    shout
		clrb	CS
		jmp	pause
		ret

; Erase the byte in EEaddr. Erasure leaves FFh (all 1s) in the byte.

eerase		mov	temp,#ERAS
		mov	clocks,#4
		setb	CS
		call	pause
		call	shout
		mov	clocks,#8
		mov	temp,eeaddr
		call	shout
		clrb	CS
		jmp	pause

; Check flag to determine whether the EEPROM has finished its self-timed
; erase/write activity.

busy    	setb	RP0
		mov	trisa,#1
		clrb	RP0
		setb	CS
		call	pause
busy1   	jnb     D,busy1
		clrb	CS
		setb	RP0
		clr	trisa
		clrb	RP0		; "jmp pause"

pause		mov	all_tmp,#10	; Take a break for the slow EEPROM
paus1		nop
		djnz	all_tmp,paus1
		ret

; ********************************* Tables ***********************************

		org	1280		; 500H

digits		jmp	pc+w
		retw	0,7,5,5,5,7,0	; Digits 0..9, 0 : 7 words
		retw	2,2,2,2,2,0	; 1: 6 words
		retw	6,1,6,4,7,0	; 2
		retw	7,1,2,1,7,0	; 3
		retw	3,5,5,7,1,0	; 4
		retw	7,4,7,1,7,0	; 5
		retw	7,4,7,5,7,0	; 6
		retw	7,1,2,2,2,0	; 7
		retw	7,5,7,5,7,0	; 8
		retw	7,5,7,1,7,0	; 9
		retw	7,5,5,5,7,0	; 0
		retw	2,5,7,5,5	; 'A'
		retw	6,5,6,5,6	; 'B'
		retw	0,2,0,2,0	; ':'

		org	1536		; 600H

fading		jmp	pc+w

		retw	5,5,7,7,5	; Describes a WOLTEC text fading
		retw	7,5,5,5,7	; into six zeros in eight phases.
		retw	4,4,4,4,7	; Phase 1: WOLTEC
		retw	7,2,2,2,2
		retw	7,4,6,4,7
		retw	7,4,4,4,7
		retw	5,1,2,7,5	; Phase 2
		retw	7,5,5,5,5
		retw	4,4,4,4,7
		retw	7,3,2,2,2
		retw	7,4,6,4,7
		retw	7,4,4,6,7
		retw	4,0,2,7,1	; Phase 3
		retw	0,0,1,5,5
		retw	4,5,0,5,1
		retw	7,2,2,0,3
		retw	7,6,4,4,6
		retw	7,4,4,6,3
		retw	7,0,6,2,0	; Phase 4
		retw	5,4,3,7,5
		retw	5,1,4,0,3
		retw	7,2,7,4,7
		retw	3,5,4,4,7
		retw	1,5,4,5,3
		retw	7,1,1,6,5	; Phase 5
		retw	4,4,1,5,6
		retw	6,7,5,5,3
		retw	4,1,5,3,7
		retw	3,0,5,4,3
		retw	5,7,7,5,2
		retw	7,5,1,7,7	; Phase 6
		retw	3,1,5,1,3
		retw	3,7,0,1,5
		retw	5,5,5,5,6
		retw	0,5,5,4,7
		retw	6,1,7,5,7
		retw	2,5,4,5,3	; Phase 7
		retw	3,5,4,5,6
		retw	7,5,5,5,7
		retw	2,5,7,4,3
		retw	7,5,7,5,7
		retw	3,7,5,5,7
		retw	7,5,5,5,7	; Phase 8 : all zeros
		retw	7,5,5,5,7
		retw	7,5,5,5,7
		retw	7,5,5,5,7
		retw	7,5,5,5,7
		retw	7,5,5,5,7
