; speedtrap.asm for the Microchip PIC 16C84/16F84
; oscillator = XT 4MHz
; www.sciencezero.com - Bjørn Bæverfjord 2004-2006

	ERRORLEVEL -305,-302,-224		;Remove less than helpful warnings

	list    p=16F84
	include <p16F84.inc>
	__config _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

version equ	.100

	CBLOCK	.12		;Start of RAM in 16C84/16F84
	cont1, cont2, cont3
	disp1, disp2, disp3
	temp1, temp2, temp3
	shft1, shft2, shft3
	key
	keymask
	cpcn			;Common Positive/Common Negative display
	unit
	trig
	temp
	dist
	teller
	frekv
	offset7seg
	dat
	ENDC

	org     0x00		;Reset Vector
	call	init
	goto 	main

	org	0x04		;Interrupt Vector
	incf	cont2		;Only source of interrupts is TMR0 overflow so no need to check what caused the interrupt
	skpnz
	incf	cont3
	bcf	INTCON,T0IF	;Clear overflow interrupt flag or we will be caught in an endless loop
	retfie
	


;****  Main program  ****
main	movlw	version		;Display program version
	movwf	cont1
	clrf	cont2
	clrf	cont3
	movlw	.150
	movwf	temp
	call	bin2dec
wv	call 	dispAll
	call	pause2
	decfsz	temp
	goto	wv

mainL	movlw	.18	;r
	movwf	disp3
	movlw	.13	;d
	movwf	disp2
	movlw	.29	;y
	movwf	disp1

	call	readkey

	btfsc	key,0
	call	armed		;Arm timer

	btfsc	key,1
	call	prev		;Display previous measurements from EEPROM

	btfsc	key,2
	call	cngUnit		;Change display unit

	btfsc	key,3
	call	cngDist		;Change light gate spacing

	btfsc	key,4
	call	cngTrig		;Trigger on 1 or 0

	btfsc	key,5
	call	debug		;Display state of RA3 and RA4 inputs
	
	call	dispAll
	call 	pause2
	goto	mainL


;****  Wait for an event and time it  ****
armed	call	timer
	call	calc
	movlw	.32
	call	readEE
	addlw	.2		;Get next slot in EEPROM
	andlw	.31		;Wrap around
	movwf	temp

	clrf	cont3

	movfw	disp2		;Store MSB
	movwf	cont2
	movwf	EEDATA
	movfw	temp
	movwf	EEADR
	call	writeEE

	movfw	disp1		;Store LSB
	movwf	cont1
	movwf	EEDATA
	incf	temp,W
	movwf	EEADR
	call	writeEE

	movfw	temp		;Store Pointer
	movwf	EEDATA
	movlw	.32
	movwf	EEADR
	call	writeEE

	call	bin2dec
ml2	call	dispAll
	call 	pause2
	call	readkey
	movf	key
	skpnz
	goto	ml2
	clrf	key
	bsf	keymask,0	;Jump straight to Timer mode if Button 0 is pressed
	return


;****  Calculate  ****
calc	movf	unit
	skpz			;unit = uS, do nothing because our base unit is uS
	goto	calc4
	movfw	cont1
	movwf	disp1
	movfw	cont2
	movwf	disp2
	movfw	cont3
	movwf	disp3
	return

calc4	movlw	.1	
	xorwf	unit,W
	skpz
	goto	calc1		;unit = mS so divide by 1000
	movfw	cont1
	movwf	disp1
	movfw	cont2
	movwf	disp2
	movfw	cont3
	movwf	disp3
	movlw	0xE8		;1000 = 03E8h
	movwf	cont1
	movlw	0x03
	movwf	cont2
	clrf	cont3
	call	div
	return

calc1	movlw	.2		
	xorwf	unit,W
	skpz
	goto	calc2		;unit = U/S - s = ((100 000 * u ) / t)

	movlw	0xA0		;A0h + (86h * 256) + (01h * 65536) = 100 000
	movwf	temp1
	movlw	0x86
	movwf	temp2
	movlw	0x01
	movwf	temp3

	incf	dist,W
	movwf	temp
	call	mul
	call	div
	return

calc2	movlw	.3
	xorwf	unit,W
	skpz
	goto	calc3		;unit = U/H - s = ((100 000 * u ) / t) * (3600/1000)

	movlw	0x40		;40h + (7Eh * 256) + (05h * 65536) = 360 000
	movwf	temp1
	movlw	0x7E
	movwf	temp2
	movlw	0x05
	movwf	temp3

	incf	dist,W
	movwf	temp
	call	mul
	call	div

calc3	return


;**** 	24x8 multiplication - disp3:disp2:disp1 = temp3:temp2:temp1 * temp
mul	clrf	disp1
	clrf	disp2
	clrf	disp3

mulLoop	movfw	temp1
	addwf	disp1

	movfw	temp2
	skpnc
	incfsz	temp2,W
	addwf	disp2

	movfw	temp3
	skpnc
	incfsz	temp3,W
	addwf	disp3
	
	decfsz	temp
	goto	mulLoop
	return


;****  24/24 bit division - disp3:disp2:disp1 = disp3:disp2:disp1 / cont3:cont2:cont1  ****
;****  Remainder in temp3:temp2:temp1 - Original code by Tony Nixon, remainder probably buggy
div	movlw	.24
	movwf	temp		;Bit count
	movfw	disp3
	movwf	shft3
	movfw	disp2
	movwf	shft2
	movfw	disp1
	movwf	shft1
	clrf	disp3
	clrf	disp2
	clrf	disp1
	clrf	temp3
	clrf	temp2
	clrf	temp1

dloop	clrc
	rlf	shft1
	rlf	shft2
	rlf	shft3
	rlf	temp1
	rlf	temp2
	rlf	temp3
	movfw	cont3
	subwf	temp3,W
	skpz
	goto	noCheck
        
	movfw	cont2
	subwf	temp2,W
	skpz
	goto	noCheck
        	
	movfw	cont1
	subwf	temp1,W
noCheck	skpc
	goto	nogo
        
	movfw	cont1
	subwf	temp1
	skpc
	decf	temp2
	movfw	temp2
	xorlw	.255
	skpnz
	decf	temp3
	movfw	cont2
	subwf	temp2
	skpc
	decf	temp3
	movfw	cont3
	subwf	temp3
	setc
nogo	rlf	disp1
	rlf	disp2
	rlf	disp3
	decfsz	temp
	goto	dloop  
	return


;****  Display previous 16 measurements  ****
prev	movlw	.32
	call	readEE
	movwf	temp2
prev1	movlw	.100
	movwf	temp	
	call	blank
pw	call	pause2
	call	readkey
	movf	key
	skpz
	goto	prev3
	decfsz	temp
	goto	pw

prev3	movfw	temp2
	call	readEE
	movwf	cont2
	incf	temp2,W
	call	readEE
	movwf	cont1
	clrf	cont3
	call	bin2dec
prev4	call	dispAll
	call	pause2
	call	readkey
	movf	key
	skpnz
	goto	prev4
	btfss	key,1
	goto	prev2
	movfw	temp2
	addlw	.254
	andlw	.31
	movwf	temp2
	goto	prev1
prev2	clrf	key
	return


;****  Select trigger on 0 or 1
cngTrig	movf	trig
	skpz
	goto	trig1
	movlw   .26		;-
	movwf	disp3
	movwf	disp2
	movlw   .27		;L
	movwf	disp1
	goto	trig2

trig1	movlw   .25		;_
	movwf	disp3
	movwf	disp2
	movlw	.28		;T
	movwf	disp1

trig2	call	dispAll
	call	pause2
	call	readkey
	movf	key
	skpnz
	goto	cngTrig

	btfss	key,4
	goto	trig3
	comf	trig
	goto	cngTrig

trig3	movfw	trig
	movwf	EEDATA
	movlw	.34
	movwf	EEADR
	call	writeEE	
	clrf	key
	return


;****  Select display unit uS, mS, U/S, U/H
cngUnit	movf	unit		;Microsecond uS
	skpz
	goto	unit1
	movlw	.22	;Blank
	movwf	disp3	
	movlw	.21	;u
	movwf	disp2
	movlw	.5	;S
	movwf	disp1
	goto	gotunit

unit1	movlw	.1		;Milisecond mS (nnS)
	xorwf	unit,W
	skpz
	goto	unit2
	movlw	.20	;n
	movwf	disp3
	movlw	.20	;n
	movwf	disp2
	movlw	.5	;S
	movwf	disp1
	goto	gotunit

unit2	movlw	.2		;Unit/Second
	xorwf	unit,W
	skpz
	goto	unit3
	movlw	.23	;U
	movwf	disp3
	movlw	.17	;/
	movwf	disp2
	movlw	.5	;S
	movwf	disp1
	goto	gotunit

unit3	movlw	.23	;U	;Unit/Hour
	movwf	disp3
	movlw	.17	;/
	movwf	disp2
	movlw	.24	;H
	movwf	disp1

gotunit	call	dispAll
	call	pause2
	call	readkey
	movf	key
	skpnz
	goto	cngUnit
	btfss	key,2
	goto	enduni
	incf	unit,W
	andlw	.3
	movwf	unit
	goto	cngUnit
enduni	movlw	.35		;Write selected unit to EEPROM
	movwf	EEADR
	movfw	unit
	movwf	EEDATA
	call	writeEE
	clrf	key
	return


;****  Select light gate spacing L01-L10 as a fraction of unit
cngDist	movlw	.10	;01 - 10
	subwf	dist,W
	skpc
	goto	dist3
	clrf	dist
	goto	cngDist

dist3	incf	dist,W
	movwf	cont1
	clrf	cont2
	call	bin2dec
	movlw	.27		;L
	movwf	disp3

gotdist	call	dispAll
	call	pause2
	call	readkey
	movf	key
	skpnz
	goto	cngDist
	btfss	key,3
	goto	enddis
	incf	dist
	goto	cngDist

enddis	movlw	.36		;Write selected spacing to EEPROM
	movwf	EEADR
	movfw	dist
	movwf	EEDATA
	call	writeEE
	clrf	key
	return


;****  Read EEPROM - Address in W, data returned in W
readEE
	banksel EEADR
	movwf	EEADR		;Address to read
	banksel	EECON1
	bsf	EECON1,RD
	banksel 0
	movf	EEDATA,W
	return


	;****  Write EEPROM - Address inn EEADR, data in EEDATA
writeEE
	banksel	EECON1
	clrf	INTCON		;Disable all interrupts
	bsf	EECON1,WREN
	movlw	0x55
	movwf	EECON2
	movlw	0xAA
	movwf	EECON2
	bsf	EECON1,WR
waitEE	btfsc	EECON1,WR	;Wait for EEPROM write to complete (10ms typical)
	goto	waitEE
	banksel	0
	return


;****  Erase the first W bytes of EEPROM  ****
clearEE	movwf	temp			
clrL	clrf	EEDATA
	decf	temp,W
	movwf	EEADR
	call	writeEE
	decfsz	temp
	goto	clrL
	return


;****  Display "Err" message and wait for keypress
errMsg	movlw	.18
	movwf	disp1
	movlw	.18
	movwf	disp2
	movlw	.14
	movwf	disp3
	call	dispAll
	call	pause2
	call	readkey
	movf	key
	skpnz
	goto	errMsg
	return


;****  Wait for RA3 trigger then count until RA4 triggers  ****
timer	movf	trig		;Check if trigger is to be done on 1 or 0
	skpnz
	goto	t1
	btfsc	PORTA,3		;Display "Err" if not both gate inputs are low
	goto	timeErr
	btfsc	PORTA,4
	goto	timeErr
	goto	t2

t1	btfss	PORTA,3		;Display "Err" if not both gate inputs are high
	goto	timeErr
	btfss	PORTA,4
	goto	timeErr

t2	clrf 	TMR0 		;Clear Timer0 register
	clrf 	INTCON 		;Disable interrupts and clear T0IF
	banksel	OPTION_REG
	bcf 	OPTION_REG,T0CS	;TMR0 clock source select = Internal instruction cycle count
	bsf 	OPTION_REG,PSA	;Prescaler assignment = WDT = 1:1
	banksel	0
	
	call	enDisp
	movlw	.19		;Display lightgate symbol [= =]
	call	disp
	movlw	.5
	xorwf	cpcn,W
	movwf	PORTA

	bsf 	INTCON, T0IE 	; Enable TMR0 interrupt
	bsf 	INTCON, GIE 	; Enable all interrupts

	movf	trig		;Check if trigger on 0 or 1
	skpnz
	goto	trgon0

	movlw	.2		;Trigger on 1
tim1	btfss	PORTA,3		;Wait for gate 1 to trigger
	goto	tim1
	movwf	TMR0		;Timer0 is is disabled for 2 cycles after it is modified, so we start it at 2 (only valid for prescaler 1:1)
	clrf	cont2
	clrf	cont3
tim2	btfss	PORTA,4		;Wait for gate 2 to trigger
	goto	tim2
	movfw	TMR0
	clrf 	INTCON 		;Disable interrupts and clear T0IF
	movwf	cont1
	return

trgon0	movlw	.2		;Trigger on 0
tim10	btfsc	PORTA,3		;Wait for gate 1 to trigger
	goto	tim10
	movwf	TMR0		;Timer0 is is disabled for 2 cycles aftere it is modified, so we start it at 2 (only valid for prescaler 1:1)
	clrf	cont2
	clrf	cont3
tim20	btfsc	PORTA,4		;Wait for gate 2 to trigger
	goto	tim20
	movfw	TMR0
	clrf 	INTCON 		;Disable interrupts and clear T0IF
	movwf	cont1
	call	disDisp
	return

timeErr	call	disDisp
	call	errMsg
	return


;****  Read keypad and ignore any keys that are being held down  ****
readkey	movlw	.1
	movwf	temp1
keyloop	xorlw	.255
	movwf	PORTB
	xorlw	.255
	call 	pause8
	btfss	PORTB,7
	goto	gotkey
	addwf	temp1,W
	movwf	temp1
	andlw	.127
	skpz
	goto	keyloop
	clrf	keymask
	comf	keymask
	clrf	key
	call	blank
	return
gotkey	movwf	key
	movwf	temp1
	comf	temp1
	movfw	keymask
	andwf	key
	movfw	temp1
	movwf	keymask
	call	blank
	return


;****  Wait for all keys to be released and update the display
key0	call	dispAll
	call	pause2
	clrf	keymask
	comf	keymask
	call	readkey
	movf	key
	skpz
	goto	key0
	return


;****  Debug - Displays the state of RA03 and RA04  ****
debug	clrf	disp1
	clrf	disp3
	movlw	.16
	movwf	disp2
	movlw	.1
	btfsc	PORTA,3
	movwf	disp1
	btfsc	PORTA,4
	movwf	disp3

debugl	call	dispAll
	call 	pause2
	call	readkey
	movf	key
	skpz
	return

	movlw	.1	;Only read input lines every 16th time so 1 and 0 does not visually merge and look like 0 on rapid changes
	addwf	dat	;Make sure dat is not changed a in any of the functions
	skpdc		;Digit Carry happens on overflow of bit 3 (2^(3+1) = 16)
	goto	debugl
	goto	debug


;****  Convert cont2:cont1 binary to disp3:disp2:disp1 decimal  ****
bin2dec	clrf	disp1
	clrf	disp2
	clrf	disp3
	movf	cont1
	skpnz
	goto	doneC1
next1	incf	disp1
	movfw	disp1
	sublw	.10
	skpz
	goto	doneDig
	
	clrf	disp1
	incf	disp2
	movfw	disp2
	sublw	.10
	skpz
	goto	doneDig

	clrf	disp2
	incf	disp3
	movfw	disp3
	sublw	.10
	skpnz
	goto	errCon

doneDig	decfsz	cont1
	goto	next1

doneC1	movf	cont2
	skpnz
	goto	doneC2
	decf	cont2
	clrf	cont1
	goto 	next1
doneC2	return

errCon	movlw	.16	;Display -E- on numbers larger than 999
	movwf	disp1
	movwf	disp3
	movlw	0x0E
	movwf	disp2
	return


;**** Display disp1:disp2:disp3 in turn on the 7 segment displays  ****
dispAll	clrf	PORTA
	movfw	disp1
	call 	disp
	movlw	.1
	xorwf	cpcn,W
	movwf	PORTA
	call	enDisp
	call 	pause2
	call	disDisp

	clrf	PORTA
	movfw	disp2
	call 	disp
	movlw	.2
	xorwf	cpcn,W
	movwf	PORTA
	call	enDisp
	call 	pause2
	call	disDisp

	clrf	PORTA
	movfw	disp3
	call 	disp
	movlw	.4
	xorwf	cpcn,W
	movwf	PORTA
	call	enDisp
	call 	pause2
	call	disDisp

	call	blank
	return


;**** Look up 7 segment data in the table and output it to the display
disp	movwf	offset7seg
	movlw 	LOW ledLUT 	;Get low 8 bits of address
	addwf 	offset7seg	;Do an 8-bit add operation
	movlw 	HIGH ledLUT 	;Get high 5 bits of address
	btfsc 	STATUS,C 	;Page crossed?
	addlw 	.1 		;Yes then increment high address
	movwf 	PCLATH 		;Load high address in latch
	movf 	offset7seg,W 	;Load computed offset in w reg
	call 	ledLUT
	xorlw	.127
	xorwf	cpcn,W
	movwf	PORTB
	return		

enDisp	movlw	B'11111000'		;Set display select lines to output to disable all display activity
	tris	PORTA
	return

disDisp	movlw	B'11111111'		;Set display select lines to input so no current can flow
	tris	PORTA
	return

;**** Clear the display ****
blank	movfw	cpcn
	clrf	PORTA
	clrf	PORTB
	return


;**** Pause for 256*3 uS ****
pause2	clrf	frekv
wl12	nop
	decfsz	frekv
	goto	wl12
	return

pause8	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	return


;**** Initialise all ports and memory locations
init	clrf	INTCON
	call	disDisp
	movlw	B'10000000'
	tris	PORTB
	banksel OPTION_REG
	bcf	OPTION_REG,NOT_RBPU	;Weak pullup on PORTB 	
	banksel PORTA
	call 	blank

	movlw	.63
	call	readEE
	xorlw	.197
	skpz
	call	clrAll		;Content of EE63 was not 197 so erase EEPROM and set default values

	movlw	.33		;Read config Common Cathode/Anode
	call	readEE
	movwf	cpcn
	movlw	.34		;Read config Trigger
	call	readEE
	movwf	trig
	movlw	.35		;Read config Display
	call	readEE
	movwf	unit
	movlw	.36		;Read config Distance
	call	readEE
	movwf	dist

	clrf	keymask
	comf	keymask
	call	readkey

	btfsc	key,0		
	call	clrAll		;On startup - Button 0 - Clear everything in EEPROM

	btfsc	key,1
	call	clr16		;On startup - Button 1 - Clear previously recorded values and position byte

	btfsc	key,2
	call	selDisp		;On startup - Button 2 - Select display type Common cathode or Common anode
	return


;****  Clear all 64 bytes of EEPROM and enter default values
clrAll	movlw	.64
	call	clearEE

	movlw	.0		;Default value cpcn = 0
	movwf	EEDATA	
	movlw	.33
	movwf	EEADR
	call	writeEE

	movlw	.255		;Default value Trig on 1
	movwf	EEDATA	
	movlw	.34
	movwf	EEADR
	call	writeEE

	movlw	.2		;Default value Unit = U/S
	movwf	EEDATA	
	movlw	.35
	movwf	EEADR
	call	writeEE

	movlw	.2		;Default value Distance = 0.3
	movwf	EEDATA	
	movlw	.36
	movwf	EEADR
	call	writeEE

	movlw	.197		;Mark EEPROM as cleared. 197 is a magic number thought not rarely arise in EEPROM by accident
	movwf	EEDATA	
	movlw	.63
	movwf	EEADR
	call	writeEE

	movlw	0x0C		;C
	movwf	disp3
	movlw	.27		;L
	movwf	disp2
	movlw	.18		;r
	movwf	disp1
	call	key0
	return
			

;****  Select Common Anode or Common Cathode displays
selDisp	clrf	keymask
	comf	keymask
	call	readkey
	btfss	key,2		;Invert Common Anoded/Cathode displays
	goto	doneSD
	movlw	.123
	movwf	cont1
	clrf	cont2
	call	bin2dec
	call	dispAll
	call	pause2

	incf	dat
	skpnz
	comf	cpcn
	goto	selDisp

doneSD	movlw	.33
	movwf	EEADR
	movfw	cpcn
	movwf	EEDATA
	call	writeEE
	return


;****  Clear the 16 recorded values stored in EEPROM
clr16	movlw	.33		;32 data bytes + position byte
	call	clearEE
	movlw	0x0C		;C
	movwf	disp3
	movlw	.1		;1
	movwf	disp2
	movlw	.6		;6
	movwf	disp1
	call	key0
	return




;****  Table for converting a character to 7 segment display data  ****
	org     0x300		;The last 256 bytes of program memory belongs to the look-up table
ledLUT
	addwf	PCL
		; gfedcba
	retlw	B'0111111'	;0
	retlw	B'0000110'	;1
	retlw	B'1011011'	;2
	retlw	B'1001111'	;3
	retlw	B'1100110'	;4
	retlw	B'1101101'	;5
	retlw	B'1111101'	;6
	retlw	B'0000111'	;7
	retlw	B'1111111'	;8
	retlw	B'1101111'	;9
	retlw	B'1110111'	;A 10
	retlw	B'1111100'	;B 11
	retlw	B'0111001'	;C 12
	retlw	B'1011110'	;D 13
	retlw	B'1111001'	;E 14
	retlw	B'1110001'	;F 15
	retlw	B'1000000'	;- 16
	retlw	B'1010010'	;/ 17
	retlw	B'1010000'	;r 18
	retlw   B'0001001'	;= 19 light gate symbol
	retlw   B'1010100'	;n 20
	retlw   B'0011100'	;u 21
	retlw   B'0000000'	;  22 blank 
	retlw   B'0111110'	;U 23
	retlw   B'1110110'	;H 24
	retlw   B'0001000'	;_ 25
	retlw   B'0000001'	;- 26
	retlw   B'0111000'	;L 27
	retlw   B'0110001'	;T 28
	retlw   B'1101110'	;y 29
		; gfedcba
	fill	(retlw B'10101010'),(0x3ff-$)+1	;Fill the rest of memory with an invalid display code
	end


; Segment Identification
;
;	 aaaaa
;	f     b		
;	f     b
;	 ggggg	
;	e     c
;	e     c	
;	 ddddd .

; RA0(17) -> Select display 1
; RA1(18) -> Select display 2
; RA2(1)  -> Select display 3
; RA3(2)  <- Light gate 1
; RA4(3)  <- Light gate 2

; RB0(6)  -> Display segment a
; RB1(7)  -> Display segment b
; RB2(8)  -> Display segment c
; RB3(9)  -> Display segment d
; RB4(10) -> Display segment e
; RB5(11) -> Display segment f
; RB6(12) -> Display segment g
; RB7(13) <- Button multiplexer (RB0-RB6)


;Pin-out of a noname 7 segment display
;1 2 3 4 5 
;g f _ a b
;e d + c .


; Buttons (R0-R6 -> R7)
; 0 Arm timer
; 1 Previous reading (from eeprom)
; 2 Display unit uS, nnS, U/S, U/H
; 3 Light gate spacing d01 - d10
; 4 Trigger on 0 or 1
; 5 Debug (show state of gate inputs)


;EEPROM content
;0-31 - The 16 last timings (cont2:cont1)
;32 - Position of last timing value stored
;33 - cpcn
;34 - Trigger low->high or high->low
;35 - Display Unit uS, nnS, U/S, U/H
;36 - Distance d1-d10
;63 - Run before = 197
