
;****************************************************************************
;
;                           LCD Tachometer 
;
;****************************************************************************
; Bruce Abbott                                       bhabbott@paradise.net.nz
;
; Hold button on RA0 (pin 17)
; Blade Select button on RA1 (pin 18)
; Opto sensor input on RB0 (pin 7)
;

	processor  PIC16f628
	include    P16f628.inc

        radix dec

	errorlevel -302,-305

#DEFINE FALSE           0
#DEFINE	TRUE		1

#DEFINE	CLKFRQ		4000	; CPU clock frequency in KHz

; Port A bit assignments

#DEFINE	BLADE_BTN	1	; pin 18, blade select button	
#DEFINE	HOLD_BTN	0	; pin 17, hold button

; Port B bit assignments

; RB7 = pin 13, LCD D7 
;   6 = pin 12, LCD D6
;   5 = pin 11, LCD D5
;   4 = pin 10, LCD D4
;   3 = pin 9,  LCD E
;   2 = pin 8,  LCD RS 
;   1 = pin 7,  n/c

#DEFINE	PULSE_IN	0	; RB0 (pin 6) = opto sensor input

#DEFINE	LCD2LINE	FALSE	; true if LCD display has two logical lines 

 __config	_BODEN_ON&_WDT_OFF&_LVP_OFF&_PWRTE_ON&_HS_OSC

;============================================================================
; Macro to create offsets for file registers in RAM
;

ByteAddr        SET 	32     ; user file registers start here

BYTE            MACRO	ByteName
ByteName        EQU	ByteAddr
ByteAddr        SET	ByteAddr+1
                ENDM
;============================================================================
   
; number of propellor blades

	BYTE Choice	      


; decimal digits

	BYTE Digit_0		; units            
	BYTE Digit_1		; tens 
	BYTE Digit_2		; hundreds 
	BYTE Digit_3		; thousands
	BYTE Digit_4		; ten thousands

; LCD variables

	BYTE ADRESS	
	BYTE ADRESS2	
	BYTE CHAR		
	BYTE CHAR2	
	BYTE dx_temp1	
	BYTE dx_temp2
	BYTE dx_temp3
	BYTE LCDtemp	

; message pointer
 
	BYTE msg_ptr	

; period to rpm converison constant  (rpm = constant/period)

	BYTE const_0  
	BYTE const_1
	BYTE const_2 

; rpm

	BYTE rpm      	; 1 digit of decimal rpm

; remainder 

 	BYTE remain_0  
	BYTE remain_1  
	BYTE remain_2 
 
; period (measured using timer0)

	BYTE period_0	
	BYTE period_1	
	BYTE period_2	

; misc variables

	BYTE temp
	BYTE dflags  			
	BYTE pulse_count 


; reset vector
;//////////////////////////////////////////////////////////////////////
	ORG	0
	goto 	MAIN


; interrupt vector
;//////////////////////////////////////////////////////////////////////
	ORG	4

	cblock	0x70		; allocate variables in RAM 
	WorkSave		; that is common to all banks!
	StatSave
	endc

interrupt:
	movwf	WorkSave
	movf	STATUS,w		; save working registers
	movwf	StatSave
	bcf	STATUS,RP0		; register bank 0			
check_int:	
	btfss	INTCON,T0IF		; timer0 overflowed ?	
	goto	RB0_int			; if not, must be RB0 change int
	
TMR0_int:				; timer0 overflow
	incfsz	period_1		; count number of overflows	
	goto	tmr0_done
	incf	period_2
	movf	period_2,W
	sublw	4			; total > 196607 ? 
	skpc
	goto	low_rpm		
tmr0_done:
	bcf     INTCON,T0IF		; clear timer int		
	btfss	INTCON,INTF		; input pulse detected?
	goto	Int_Done
	nop	
	nop
	nop				 
	nop
	nop
	nop
RB0_int:				
	incfsz  pulse_count,F		; first pulse ?
	goto	next_pulse		
first_pulse:
	clrf	TMR0		        ; reset timer
	clrf	period_1
	clrf	period_2
	bcf     INTCON,T0IF		; clear timer0 int
RB0_done:
	bcf	INTCON,INTF		; clear RB0 int flag
Int_Done:
	movf	StatSave,w
	movwf	STATUS
	swapf	WorkSave		; restore working registers
	swapf	WorkSave,w	
	RETFIE				

next_pulse:
	movlw	10			; integrate pulses
	subwf	pulse_count,W		
	skpz				; all pulses received ?
	goto	RB0_done		

got_all_pulses:
	bsf 	STATUS,RP0		; bank 1		
	bsf	OPTION_REG,T0CS		; stop timer0	
	bcf 	STATUS,RP0		; bank 0
	btfss	INTCON,T0IF		; timer0 overflowed ?		
	goto	get_time		
	incfsz	period_1,F		; count number of overflows	
	goto	get_time
	incf	period_2,F
	movf	period_2,W
	sublw	4			; total > 196607 ? 
	skpc
	goto	low_rpm		
get_time:
	movf	TMR0,W			; get timer0			
	movwf	period_0		
	goto	Calc_RPM		; calculate and display RPM

low_rpm:				; timed out
	goto	start			
	
;//////////////////////////////////////////////////////////////////////
;*---------------------------- user code -----------------------------*

message:                                    
        addwf   PCL,F
msg:
        RETLW   'L'
        RETLW   'C'
        RETLW   'D'
        RETLW   ' '
        RETLW   'T'
        RETLW   'A'
        RETLW   'C'
        RETLW   'H'
        RETLW   0
ver_:
        RETLW   'V'		
        RETLW   'E'
        RETLW   'R'
        RETLW   ' '
        RETLW   '0'
        RETLW   '.'
        RETLW   '1'
	RETLW	'2'
        RETLW   0

; blades messages

two_:
        RETLW   '2'			
        RETLW   0
three_:
        RETLW   '3'			
        RETLW   0
four_:
        RETLW   '4'			
        RETLW   0
five_:
        RETLW   '5'			
        RETLW   0

six_:   RETLW   '6'			
        RETLW   0

;-----------------------------------------------
;     Set constant (k) for prop blades
;
;             rpm = k/period
;
set_prop:
	clrf	PCLATH
        movf    Choice,W       
	addwf	PCL		
	goto	seq_TWO     ; 2 blades
	goto	seq_THREE   ; 3 blades
	goto    seq_FOUR    ; 4 blades
	goto	seq_FIVE    ; 5 blades
	goto	seq_SIX     ; 6 blades
        return

seq_TWO
	movlw	0		
	movwf	const_2
	movlw	high 15000
	movwf	const_1
	movlw	low 15000
	movwf	const_0		
        return

seq_THREE
	movlw	0		
	movwf	const_2
	movlw	high 10000
	movwf	const_1
	movlw	low 10000
	movwf	const_0			
        return

seq_FOUR
	movlw	0			
	movwf	const_2
	movlw	high 7500
	movwf	const_1
	movlw	low 7500
	movwf	const_0			
        return

seq_FIVE
	movlw	0			
	movwf	const_2
	movlw	high 6000
	movwf	const_1
	movlw	low 6000
	movwf	const_0			
        return

seq_SIX
	movlw	0			
	movwf	const_2
	movlw	high 5000
	movwf	const_1
	movlw	low 5000
	movwf	const_0			
        return



#include lcd_v_a1.asm

;-------------------------------------------
;   Display Sign-on Message on LCD
;
display_signon
        movlw   0
        call    ddram_adress            
	movlw   msg-msg
	goto	display_message
	

;-------------------------------------------
;   Display Software Version on LCD
;
display_sw_version
        movlw   0
        call    ddram_adress            
	movlw   ver_-msg
	goto	display_message
	

;-------------------------------------------
;   Display Number of Blades on LCD
;	
display_prop:
        movlw   0
        call    ddram_adress    
        movf    Choice,W        
	ADDWF	PCL,F	           ; 2-6 blades	
	goto	display_TWO
	goto	display_THREE
	goto    display_FOUR
	goto	display_FIVE
	goto	display_SIX
	return
display_TWO
	movlw	two_-msg
	goto	display_message
display_THREE
	movlw	three_-msg
	goto	display_message
display_FOUR
	movlw	four_-msg
	goto	display_message
display_FIVE
	movlw	five_-msg
	goto	display_message
display_SIX
	movlw	six_-msg
	goto	display_message

;---------------------------------------------------------
;            Display 5 digits of RPM on LCD
;
; - leading zeros blanked
; - ',' separator between thousands and hundreds
; - last digit forced to '0' if > 9,999 rpm
;
;	" 99,990"
;	"  9,999"
;	"    999"	
;	"     99"
;	"      9"
;
display_rpm
	clrf	dflags		; assume all digits zero
	movlw	B'00001111'
	andwf	Digit_4
	skpz			; ten thousands > zero ?
	bsf	dflags,4
	andwf	Digit_3
	skpz			; thousands > zero ?
	bsf	dflags,3
	andwf	Digit_2
	skpz			; hundreds > zero ?
	bsf	dflags,2
	andwf	Digit_1
	skpz			; tens > zero ?
	bsf	dflags,1
        movlw   1		; set cursor position
        call    ddram_adress            
        movlw   B'00110000'	; ASCII mask
        iorwf   Digit_0               
        iorwf   Digit_1
        iorwf   Digit_2		; 0 to 9 -> "0" to "9"
        iorwf   Digit_3
        iorwf   Digit_4
        movlw   ' '
        call    prt_char	; show ' '
	movf    Digit_4,w
	btfsc	dflags,4	; ten thou = 0 ?
	bsf	dflags,0	; leading zeros off
	btfss	dflags,0	; ' ' if leading zero
	movlw	' '
        call    prt_char	; show ten thousands or ' '
        movf    Digit_3,w
	btfsc	dflags,3	
	bsf	dflags,0
	btfss	dflags,0
	movlw	' '
        call    prt_char	; show thousands or ' '
	movlw	','
	btfss	dflags,0	
	movlw	' '
	call	prt_char	; show ',' or ' '
        movf    Digit_2,w
	btfsc	dflags,2
	bsf	dflags,0
	btfss	dflags,0
	movlw	' '
        call    prt_char	; show hundreds or ' '
        movf    Digit_1,w
	btfsc	dflags,1
	bsf	dflags,0
	btfss	dflags,0
	movlw	' '
        call    prt_char	; show tens or ' '
        movf    Digit_0,w
	btfsc	dflags,4	; units = '0' if rpm > 9,999	
	movlw	'0'		
        call    prt_char	; show units or '0'
        RETURN

;---------------------------------------------------------
;           Display Text Message on LCD
;
display_message
        movwf   msg_ptr
        call    message         ; get next char in message        
        andlw   0xFF		; end of message ?
        skpnz                  
        RETURN                         
        call    prt_char	; show character
        movf    msg_ptr,W
        addlw   1               ; next character        
        goto    display_message

;--------------------------------------------------------------
;       Convert period to rpm (1 decimal digit) 
;
;           W = const / period 
;
;  IN: const  = blades constant eg. 15000 (1st digit), or 
;               previous remainder * 10 (subsequent digits)
;
;      period = measured time between pulses
;
; OUT:      W = decimal digit 0~9
;      remain = remaining part of rpm 
;
divide:
	clrf	temp		; digit = 0
div_1:	movf	const_0,W
	movwf	remain_0
	movf	const_1,W
	movwf	remain_1	; remain = const
	movf	const_2,W
	movwf	remain_2
	movf	period_0,W
	subwf	const_0
	skpnc
	goto	div_2
	movlw	1
	subwf	const_1		; const = const - period	
	skpc
	subwf	const_2
	skpc
	goto	div_3		; done if negative
div_2:	movf	period_1,W		
	subwf	const_1		
	movlw	1
	skpc   		
	subwf	const_2		
	skpc			; done if negative
	goto	div_3							
	movf	period_2,w
	subwf	const_2
	skpc			; done if negative
	goto	div_3
	incf	temp		; increment rpm
	goto	div_1		; next subtract
div_3:	movf	temp,W
	return


;---------------------------------------------------
;            Multiply times 10
;
;       constant = remainder * 10
;
mult_10:				
	bcf	STATUS,C		
	rlf	remain_0	; remainder * 2
	rlf	remain_1		
	rlf	remain_2		

	movf	remain_0,W
	movwf	const_0
	movf	remain_1,W	; (remainder * 2) -> constant
	movwf	const_1			
	movf	remain_2,W
	movwf	const_2			

	bcf	STATUS,C		
	rlf	remain_0
	rlf	remain_1	; remainder * 4	
	rlf	remain_2		

	bcf	STATUS,C		
	rlf	remain_0
	rlf	remain_1	; remainder * 8	
	rlf	remain_2		

	movf	remain_0,W
	addwf	const_0		
	skpc 
	goto	mul_101
	incfsz	const_1		; constant=(remainder*8)+(remainder*2)	
	goto	mul_101
	incf	const_2
mul_101	movf	remain_1,W
	addwf	const_1		
	skpnc
	incf	const_2					
	movf	remain_2,W
	addwf	const_2		
	return


;---------------------------------------------------------------------
;   Calculate amount of time to wait before refreshing display.
;   Ensures constant display update rate, even though measurement
;   period varies with rpm.
;
refresh_delay:
	movlw	6	
refr_1	bcf	STATUS,C
	rlf	period_1
	rlf	period_2	; period * 64
	addlw	-1
	skpz
	goto	refr_1
	movf	period_2,w	; 0~255 = 0~512mS measurement time
	sublw	0
	goto	dx2k		; delay for ( 512 - period ) * 2mS 


;-----------------------------------------------------------------
;                         Main Program 
;
MAIN        
	IFDEF	__16F628
	movlw	B'00000111'
	movwf	CMCON			; disable comparator 
	ENDIF

	clrf 	PORTA		        
	clrf	PORTB
	movlw	B'00001111'		; set PortA directions
	tris	PORTA		        
	movlw	B'00000001'		; set PortB directions
	tris	PORTB	

; no weak pullups, RB0 int rising edge, TMR0 on RA4, prescale 1:2 

        movlw   B'11110000'		 
	option  			

	movlw	120
	call	dx2k			; wait 0.25 seconds
        call    set_4bit_mode          
 	call 	init_display		; init LCD display		
	call	clear_display		; clear LCD display
	movlw	0			; choose 2 blades			
	movwf	Choice			
	call	display_signon		; show signon message
	movlw	250			; wait 0.5 second
	call	dx2k
	call	display_sw_version	; show software version
	movlw	250
	call	dx2k			; wait 0.5 second
start:
        movlw   0
        call    ddram_adress		; set cursor    
	call	display_prop		; show number of blades

	clrf	Digit_0
	clrf	Digit_1
	clrf	Digit_2			; rpm = 0
	clrf	Digit_3
	clrf	Digit_4
	call	display_rpm		; show rpm

mainloop:
	bsf 	STATUS,RP0			
	bsf	OPTION_REG,T0CS		; stop timer0	
	bcf 	STATUS,RP0		
	bcf     INTCON,GIE		; global interrupt disable
	bcf     INTCON,T0IE             ; disable timer int
	bcf	INTCON,INTE		; disable RB0 int	
	movlw	-1
	movwf	pulse_count		; pulse_count = -1
	clrf	period_0
	clrf	period_1		; total time = 0
	clrf	period_2		
	clrf	TMR0			; reset timer0
	bcf	INTCON,T0IF		; clear timer0 int flag
	bsf	INTCON,T0IE		; enable timer0 int
	bcf	INTCON,INTF		; clear RB0 int flag      
	bsf	INTCON,GIE		; global interrupt enable
	bsf 	STATUS,RP0			
	bcf	OPTION_REG,T0CS		; start timer0	
	bcf 	STATUS,RP0		

; Timer is running. If too much time passes without any signal, 
; jump directly from Interrupt back to Start.

wait_no_pulse:
	btfsc	PORTB,PULSE_IN		; wait until blade passes
	goto	measure
	btfsc	PORTA,BLADE_BTN		; button pressed ?
	goto	wait_no_pulse
	goto 	key_press
measure:
	bcf	INTCON,INTF		; clear RB0 int flag      
	bsf	INTCON,INTE		; enable RB0 int
;
; RB0 Interrupt occurs when next blade passes the sensor. After  
; 10 blade passes, jump directly from Interrupt to calc_rpm.   
;
wait_measure:				
	nop						
	btfsc	PORTA,BLADE_BTN		; button pressed ?
	goto 	wait_measure	     
key_press:
	bcf	INTCON,GIE		
	bcf	INTCON,T0IF		; disable ints
	bcf	INTCON,INTF
	call	clear_display		; clear LCD display
	incf	Choice,F		; next blade choice	
	movlw	5
	subwf	Choice,W		; 6 blades maximum 
	skpnz 		
	clrf	Choice			
	call	set_prop		; set number of prop blades		
	call	clear_display		
	call	display_prop		; show number of prop blades
key_hold:
	movlw	50
	call	dx2k			; debounce 100mS
	btfss	PORTA,BLADE_BTN		; button still pressed ?
	goto	key_hold						
	movlw	50
	call	dx2k			; debounce 100mS
	goto	start


;-------------------------------------------------------------------
;  Calculate decimal RPM digits from period, and Display it on LCD
;
;  rpm  = constant / period
;
Calc_RPM:
	call	set_prop		; get constant
	call	divide			; rpm digit = constant/period		
	movwf	Digit_4			; put Ten Thousands digit			
	call	mult_10			; remainder * 10
	call	divide			; rpm digit = remainder/period
	movwf	Digit_3			; store Thousands digit
	call	mult_10
	call	divide
	movwf	Digit_2			; put Hundreds digit
	call	mult_10
	call	divide
	movwf	Digit_1			; put Tens digit
	call	mult_10
	call	divide
	movwf	Digit_0			; put Units digit
        movlw   0			
        call    ddram_adress            ; set cursor position
        movlw   ' '			; blank out # of blades 
        call    prt_char				
	call	display_rpm		; show the rpm digits

	call	refresh_delay		; reduce refresh rate

hold:	btfss	PORTA,HOLD_BTN		; hold button pressed ?
	goto	hold			; halt until released!

	goto	mainloop			 

	END

