;*********************************************************************************
;                             W A T T M E T E R
;*********************************************************************************
; Bruce Abbott                                            bhabbott@paradise.net.nz
;
; - Measures up to 25 Volts and 50 Amps
; - Displayed on 16 character x 2 line LCD
; - Calculates Watts and Ampere Hours
; - Measures propeller rpm, 1 to 7 blades
; - Hold function.
;
;
;---------------------------------------------------------------------------------
; Changes:-
; 2008-12-01 V0.095 BLDBTN = PORTA,7 to match PCB layout (16F88 only)  
; 2008-12-21 V0.096 enabled weak pullups (required for Page and Hold buttons)
; 2008-12-22 V0.097 enabled digital I/O on RB6,7 (16F88)
; 2009-01-05 V0.098 increased blades from 2~6 to 1~7 (for brushless rpm sensor)
; 2009-02-04        support 16F876  
; 2009-02-06 V0.099 fixed bug:- ADCON1 not set properly on 16f88 
; 2010-02-07 V0.100 fixed bug:- was checking for max Volts with MAX_AMPS flag!
; 2010-10-17 V0.101 increased delay after switching A/D channel  
;---------------------------------------------------------------------------------

        radix dec	; default number format is DECIMAL!!!

;#DEFINE SIMULATE	; for simulator debugging only. Must be disabled for release!!!

;#DEFINE LCD8x2 1	; using 8x2 or 16x1 LCD display

#DEFINE	REFRESH_DELAY1 140 ; Timer0 initial load value, to slow down display refresh
#DEFINE	REFRESH_DELAY2 39  ; Timer0 reload value (to complete display refresh delay) 

;	processor  PIC16F88
;	processor  PIC16f870
;	processor  PIC16F876

	ifdef	__16F88
	include	P16F88.inc
	else
#DEFINE TMR0IF	T0IF
	endif

	ifdef	__16F870
	include	P16f870.inc
	endif

	ifdef	__16F876
	include	P16f876.inc
	endif

	errorlevel -302,-305	; not reporting bank<>0, dest=file

#DEFINE FALSE 0
#DEFINE movfw ERROR movwf?

	ifdef	__16F88
 __config _CONFIG1,_BODEN_OFF&_WDT_OFF&_LVP_OFF&_MCLR_OFF&_PWRTE_ON&_INTRC_IO
	else   ; 16F870
 __config _BODEN_OFF&_WDT_OFF&_LVP_OFF&_PWRTE_ON&_HS_OSC
	endif

; I/O pin assignments

	ifdef	__16F88

#DEFINE	PGBTN	PORTB,6	; page button   (S3)
#DEFINE	HDBTN	PORTB,7	; hold button   (S2)
#DEFINE	BLDBTN	PORTA,7	; blades button (S1)

#DEFINE	RPM_IN	PORTB,0	; rpm input

#DEFINE LCD_D7  PORTA,4	; \
#DEFINE	LCD_D6  PORTB,1	; |
#DEFINE LCD_D5  PORTB,2	; | LCD panel
#DEFINE LCD_D4  PORTB,3	; |
#DEFINE LCD_E   PORTB,4	; |
#DEFINE LCD_RS  PORTB,5	; /

	else    ; 16f870 etc.

#DEFINE	PGBTN	PORTC,0	; page button
#DEFINE	HDBTN	PORTC,1	; hold button
#DEFINE	BLDBTN	PORTC,3	; blades button

#DEFINE	RPMIN	PORTC,2	; rpm input

#DEFINE LCD_D7  PORTB,0	; \
#DEFINE	LCD_D6  PORTB,1	; |
#DEFINE LCD_D5  PORTB,2	; | LCD panel
#DEFINE LCD_D4  PORTB,3	; |
#DEFINE LCD_E   PORTB,4	; |
#DEFINE LCD_RS  PORTB,5	; /

	endif

;====================================================================
;             Macros to create variables in RAM
;

ByteAddr	SET 32     ; user RAM starts here

BYTE		MACRO	ByteName
ByteName	EQU	ByteAddr
ByteAddr	SET	ByteAddr+1
		ENDM

WORD		MACRO	WordName
WordName	EQU	ByteAddr
ByteAddr	SET	ByteAddr+2
		ENDM

LONG		MACRO	LongName
LongName	EQU	ByteAddr
ByteAddr	SET	ByteAddr+4
		ENDM


;====================================================================
;               16 bit macros (Big-Endian)
;
mov16	MACRO	src,dst
	movf	src,w
	movwf	dst
	movf	src+1,w
	movwf	dst+1
	ENDM

movi16	MACRO	lit,dst
	movlw	HIGH(lit)
	movwf	dst
	movlw	LOW(lit)
	movwf	dst+1
	ENDM

tst16	MACRO	src
	tstf	src+1
	skpz
	goto	$+1
	tstf	src
	ENDM

cmpi16	MACRO	src,lit
	movlw	(lit>>8)&0xff
	subwf	src,w
	skpz
	goto	$+3
	movlw	lit&0xff
	subwf	src+1,w
	ENDM

clr16	MACRO	dst
	clrf	dst
	clrf	dst+1
	ENDM

inc16	MACRO	dst
	incf	dst+1
	skpnz
	incf	dst
	ENDM

dec16	MACRO	dst
	tstf	dst+1
	skpnz
	decf	dst
	decf	dst+1
	ENDM

add16	MACRO	src,dst
	movf	src+1,w
	addwf	dst+1
	skpnc
	incf	dst
	movf	src,w
	addwf	dst
	ENDM

addi16	MACRO	lit,dst
	movf	LOW(lit),w
	addwf	dst+1
	skpnc
	incf	dst
	movf	HIGH(lit),w
	addwf	dst
	ENDM

sub16	MACRO   src,dst
	movf    src+1,w
	subwf   dst+1
	movf    src,w
	skpc
	incf    src,w
	subwf   dst
	ENDM

subi16	MACRO   lit,dst
	movlw   LOW(lit)
	subwf   dst+1
	movlw   HIGH(lit)
	skpc
	movlw   (HIGH(lit))+1
	subwf   dst
	ENDM

rr16	MACRO	dst
	rrf	dst
	rrf	dst+1
	ENDM

rl16	MACRO	dst
	rlf	dst+1
	rlf	dst
	ENDM

com16	MACRO	dst
	comf	dst+1
	comf	dst
	ENDM


;---------------------------------------------
;    16->32 bit macros	(Big-Endian)
;

; move 16 bit to 32 bit

mov1632	MACRO	src,dst
	clrf	dst
	clrf	dst+1
	movf	src,w
	movwf	dst+2
	movf	src+1,w
	movwf	dst+3
	ENDM

; add 16 bit to 32 bit

add1632	MACRO	src,dst ; src(15:0), dst(31:0)
	movf	src+1,w
	addwf	dst+3	; dst = dst + src(7:0)
	skpc
	goto	$+6
	incf	dst+2
	skpnz
	incf	dst+1
	skpnz
	incf	dst
	movf	src,w
	addwf	dst+2	; dst = dst + src (15:8)
	skpc
	goto	$+4
	incf	dst+1
	skpnz
	incf	dst
	ENDM

;---------------------------------------------------
;      32 bit macros (Big-Endian)

clr32	MACRO	dst
	clrf	dst
	clrf	dst+1
	clrf	dst+2
	clrf	dst+3
	ENDM

mov32	MACRO	src,dst
	movf	src,w
	movwf	dst
	movf	src+1,w
	movwf	dst+1
	movf	src+2,w
	movwf	dst+2
	movf	src+3,w
	movwf	dst+3
	ENDM


movi32	MACRO	lit,dst
	movlw	lit>>24
	movwf	dst
	movlw	(lit>>16)&0xff
	movwf	dst+1
	movlw	(lit>>8)&0xff
	movwf	dst+2
	movlw	lit&0xff
	movwf	dst+3
	ENDM

; 32 bit left shift

rr32	MACRO	dst
	rrf	dst
	rrf	dst+1
	rrf	dst+2
	rrf	dst+3
	ENDM

; 32 bit right shift

rl32	MACRO	dst
	rlf	dst+3
	rlf	dst+2
	rlf	dst+1
	rlf	dst
	ENDM

; 32 bit 1's complememt

com32	MACRO	dst
	comf	dst+3
	comf	dst+2
	comf	dst+1
	comf	dst
	ENDM

; test 32 bit for zero

tst32	MACRO	src
	tstf	src+3
	skpz
	goto	$+8
	tstf	src+2
	skpz
	goto	$+5
	tstf	src+1
	skpz
	goto	$+1
	tstf	src
	ENDM

; add 32 bit to 32 bit

add32	MACRO	src,dst
	movf	src+3,w
	addwf	dst+3
	skpc
	goto	$+6
	incf	dst+2
	skpnz
	incf	dst+1
	skpnz
	incf	dst
	movf	src+2,w
	addwf	dst+2
	skpc
	goto	$+4
	incf	dst+1
	skpnz
	incf	dst
	movf	src+1,w
	addwf	dst+1
	skpnc
	incf	dst
	movf	src,w
	addwf	dst
	ENDM

; compare 32 bit to literal

cmpi32	MACRO	src,lit
	movlw	(lit>>24)&0xff
	subwf	src,w
	skpz
	goto	$+11
	movlw	(lit>>16)&0xff
	subwf	src+1,w
	skpz
	goto	$+7
	movlw	(lit>>8)&0xff
	subwf	src+2,w
	skpz
	goto	$+3
	movlw	lit&0xff
	subwf	src+3,w
	ENDM

;====================================================================

; Five digit decimal number

	BYTE	TenT
	BYTE	Thou
	BYTE	Hund
	BYTE	Tens
	BYTE	Ones
	BYTE	dp	 ; position of decimal point

; 16 bit binary number

	WORD	Number

; 16 bit register 'a'

	WORD	aa

; 16 bit register 'b'

	WORD    bb

; 16 bit register 'c'

	WORD	cc

; 32 bit register 'dd'

	LONG	dd

; Volts
	WORD    Volts	 ; Volts*100 (0.01V resolution)

; Amps
	WORD    Amps	 ; Amps*100 (0.01A resolution)

; Watts
	WORD    Watts	 ; Watts*10 (0.1W resolution)

; Amps Accumulated
	LONG	AmpSum   ; addition of all Amp readings

; AmpereHours
	WORD	AmpHours ; Amps * Hours

; copy of CCPR1 register

	WORD	CCPR

; recorded period = 1/rpm

	WORD	Period

; previous period

	WORD	OldPeriod

; rpm
	LONG	rpm	 ; revolutions per minute
	LONG	oldrpm	 ; previous rpm reading

; number of prop blades

	BYTE	NumBlades

; Blade button debounce timer

	BYTE	Blade_Timer

; page button debounce timer

	BYTE	Page_Timer

; Zero Amps

	WORD    ZeroAmps

; Number of Readings to Average

	BYTE	Readings

; pointer to character in message string

	BYTE	msg_pointer

; display paging

	BYTE	pagenum

; display refresh timer

	BYTE	ref_timer

; misc variables

	BYTE	Temp
	BYTE	Temp1
	BYTE	Temp2

; boolean flags

	BYTE	Flags

#DEFINE GOT_ZERO	0	; Amps zero'd
#DEFINE	NEG_AMPS	1	; Amps < zero
#DEFINE	MAX_VOLTS	2	; Volts >= max
#DEFINE MAX_AMPS	3	; Amps >= max
#DEFINE PAGE_DWN	4	; Page button was pressed
#DEFINE	LEAD_ZERO	5	; leading zeros blanked
#DEFINE	LOCK_P		6	; CCPR variable in use, don't touch!
#DEFINE	CCP_RUN		7	; detected 1st blade, now waiting for 2nd

	BYTE	Flags2
#DEFINE	BLD_DWN		0	; blade button was pressed


; reset vector
;////////////////////////////////////////////////////////////////////
	ORG	0
	goto 	Start

; interrupt vector
;////////////////////////////////////////////////////////////////////
	ORG	4
	goto	Interrupt
;////////////////////////////////////////////////////////////////////


	ORG	8

	dt	"WOTMTR"
	ifdef	__16F88
	dt	"88"
	endif
	ifdef	__16F870
	dt	"70"
	endif
	ifdef	__16F876
	dt	"76"
	endif
	dt	"-V0.100-"

;--------------------------------------------------------------------
;                   get message characters
;
; Input:  W = address of character in message
; Output: W = character
;
; NOTE: Table read:- Must NOT traverse a program page boundary!
;

get_message:
        movwf   PCL
signon_msg:
	RETLW   'W'
        RETLW   'O'
        RETLW   'T'
        RETLW   ' '
        RETLW   'M'
        RETLW   'e'
        RETLW   't'
	RETLW	'e'
	RETLW	'r'
	RETLW	' '
	RETLW	'V'
	RETLW	'0'
	RETLW	'.'
        RETLW   '1'
        RETLW   '0'
        RETLW   '0'
        RETLW   0

;--------------------------------------------------------------------
;                     Interrupt Handler
;
WorkSave = 0x7f
StatSave = 0x7e

Interrupt:
	movwf	WorkSave
	swapf	STATUS,w	; save working registers
	movwf	StatSave
	clrf	STATUS		; bank 0

	btfss	PIR1,CCP1IF	; CCP1 capture Int?
	goto	TMR1_Int	; No
CCP1_Int:
	bcf	T1CON,TMR1ON	; yes, stop timer1
	movlw	4
	movwf	TMR1L		; timer1 = 0 + overhead
	clrf	TMR1H
	bsf	T1CON,TMR1ON	; restart timer1
	btfsc	Flags,CCP_RUN	; waiting for 1st blade ?
	goto	Capture		; No
	bsf	Flags,CCP_RUN	; Yes, now waiting for second blade
	goto	Capture_Done
Capture:
	btfsc	Flags,LOCK_P	; CCPR variable in use ?
	goto	Capture_Done	; Yes, don't touch!
	movf	CCPR1L,w	; No, safe to update
	movwf	CCPR+1
	movf	CCPR1H,w	; update CCPR variable
	movwf	CCPR
Capture_Done:
	bcf	PIR1,CCP1IF	; reset Capture Int flag
TMR1_Int:
	btfss	PIR1,TMR1IF	; Timer1 Overflow int?
	goto	Int_done	; No
	bcf	T1CON,TMR1ON	; yes, stop timer1
	clrf	TMR1L
	clrf	TMR1H		; timer1 = 0
	bsf	T1CON,TMR1ON	; restart timer1
	btfsc	Flags,LOCK_P	; CCPR variable in use ?
	goto	TMR1_Done	; yes, don't touch!
	clrf	CCPR		; no,
	clrf	CCPR+1		; CCPR = 0
TMR1_Done:
	bcf	Flags,CCP_RUN	; now waiting for 1st blade
	bcf	PIR1,TMR1IF	; reset Timer1 Overflow Flag
Int_done:
	swapf 	StatSave,W
	movwf	STATUS		; restore working registers
	swapf	WorkSave,F
	swapf	WorkSave,w
	RETFIE

;--------------------------------------------------------------------
;                 LCD Display Driver

	include lcd.asm

;--------------------------------------------------------------------
;            Display Sign-on Message on LCD
;
show_signon:
        movlw   0
        call    Set_Cursor
	movlw   signon_msg
;
;           Display Text Message on LCD
;
show_message:
        movwf   msg_pointer
        call    get_message     ; get next char in message
        andlw   0xFF		; end of message ?
        skpnz
        return
        call    Print_Char	; show character
 	movlw	LCDLENGTH
	subwf	lcd_address,w	; if end of 1st line
	skpz			; then skip to 2nd line
	goto	show_msg
	movlw	LCDLINE2
	call	Set_Cursor
show_msg:
        movf    msg_pointer,W
        addlw   1               ; next character
        goto    show_message




;--------------------------------------------------------------------
;                      Wait W * 100uS
;
waitx100:
        movwf   Temp		; Temp = delay
_w100:	movlw	(100-8)/4
_w101:  addlw	-1
	skpnc			; wait 100uS
	goto	_w101		 
	clrwdt			; avoid watchdog timeout	
	decfsz	Temp		
	goto	_w100		; next 100uS
	return


;--------------------------------------------------------------------
;                      Wait W * 20mS
;
waitx20k:
        movwf   Temp		; Temp = w
_w200:	movlw	80
        movwf   Temp1		 
_w201:	movlw	61
	movwf	Temp2		
_w202:	clrwdt			; avoid watchdog timeout	
	decfsz	Temp2		; wait (4*61)+2 = 246uS 
        goto    _w202
	nop
	nop
	decfsz  Temp1		; wait 250uS*80 = 20mS 
        goto    _w201
	decfsz	Temp		; wait w*20mS
	goto	_w200
	RETURN

;--------------------------------------------------------------------
;     Big-Endian 16x16 bit Multiply (32 bit result)
;
; dd = aa * bb
;
; Input:  multiplier = aa(15:0), multiplicand = bb(15:0)
;
; Output: result = dd(31:0)
;
; Uses: aa, w
;
Mult16:	clrf    dd
	clrf	dd+1
	clrf    dd+2
	movlw	0x80
	movwf	dd+3

m16_next:
	rrf	aa,f
	rrf	aa+1,f

	skpc
	goto	m16_nobit_l
	movf	bb+1,w
	addwf	dd+2,f

	movf	bb, w
	skpnc
	incfsz	bb, w
	addwf	dd+1, f
	skpnc
	incf	dd, f
	clrc

m16_nobit_l:
	btfss	aa+1, 7
	goto	m16_nobit_h
	movf	bb+1,w
	addwf	dd+1,f
	movf	bb, w
	skpnc
	incfsz	bb, w
	addwf	dd, f

m16_nobit_h:
	rrf	dd,f
	rrf	dd+1,f
	rrf	dd+2,f
	rrf	dd+3,f

	skpc
	goto	m16_next
	return


;--------------------------------------------------------------------
;     Big-Endian 32 x 16 bit integer divide
;
; dd = dd / aa
;
; Input:  dividend = dd(31:0), divisor = aa(15:0)
;
; Output: quotient = dd(31:0), remainder = bb(15:0)
;
; Uses: bb, Temp, w
;
Div32:	movlw	32		; 32 bits to divide
	movwf	Temp
	clrf	bb		; Clear remainder
	clrf	bb+1
div32_loop:
	clrc			; Set quotient bit to 0
	rlf	dd+3
	rlf	dd+2
	rlf	dd+1		; Shift dividend left into quotient
	rlf	dd
	rlf	bb+1
	rlf	bb
	skpnc			; Check for overflow
	goto	div32_sd
	movf	aa,w		; Compare partial remainder and divisor
	subwf	bb,w
	skpz 			; High Bytes equal?
	goto	div32_gt
	movf	aa+1,w 		; Yes, compare Low Bytes
	subwf	bb+1,w
div32_gt:
	skpc			; Carry set if remainder >= divisor
	goto	dvi32_lt
div32_sd:
	movf	aa+1,w		; Subtract divisor from partial remainder
	subwf	bb+1
	skpc			; Test for borrow

	decf	bb		; Subtract borrow
	movf	aa,w
	subwf	bb
	bsf	dd+3,0		; Set quotient bit (quotient replaces dividend)
dvi32_lt:
	decfsz	Temp		; next bit
	goto	div32_loop
	return

;--------------------------------------------------------------------
;     Big-Endian 16 bit Integer Divide by 12.8
;
; Input: W->dst
;
; output: dst(15:0) = dst / 12.8
;
; uses: FSR, Temp, w
;
;
; ALGORITHM:
; Add dst / 4 to dst
; Add dst / 16 to dst
; Shift dst right (Bit0 to carry)
; If carry set then increment dst, ie. round up

Divx12:	movwf	FSR
	movf	INDF, w
	movwf	Temp
	incf	FSR
	movf	INDF, w
	decf	FSR

;shift dst right twice
	clrc
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f
	decf	FSR
	clrc
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f

;add dst to dst / 4
	addwf	INDF, f
	decf	FSR
	movf	Temp, w
	skpnc
	incfsz	Temp, w
	addwf	INDF, f

;shift dst right 3 times
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f
	decf	FSR
	clrc
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f
	decf	FSR
	clrc
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f
	decf	FSR

;shift dst right once and increment if carry set
	clrc
	rrf	INDF, f
	incf	FSR
	rrf	INDF, f
	skpc
	goto	divx12_done
	incf	INDF, f
	skpz
	goto	divx12_done
	decf	FSR
	incf	INDF, f
divx12_done:
	return

;--------------------------------------------------------------------
;     Big-Endian 16 bit Binary to Decimal
;
;  Input: Number(15:0)
;
; Output: TenT, Thou, Hund, Tens, Ones
;
; Uses: Number, w
;
;
bin2dec:
	clrf	TenT
        goto	$+2
sub10k: incf	TenT
        movlw	10000&255
        subwf	Number+1
        movlw	10000>>8
        skpc
        movlw	(10000>>8)+1
        subwf	Number
        skpnc
        goto	sub10k

        movlw	10
        movwf	Thou
add1k:  decf	Thou
        movlw	1000&255
        addwf	Number+1
        movlw	1000>>8
        skpnc
        movlw	(1000>>8)+1
        addwf	Number
        skpc
        goto	add1k

	clrf	Hund
	movlw	100
	goto	$+2
sub100:	incf	Hund
	subwf	Number+1
	skpnc
	goto	sub100
	decf	Number
	btfss	Number,7
	goto	sub100

        movlw	10
        movwf	Tens
add10:  decf	Tens
        addwf	Number+1
        skpc
        goto	add10
        movf	Number+1,w
        movwf	Ones
        return

;--------------------------------------------------------------------
;            Display 5 digit Fixed-point Decimal on LCD
;
; - leading zero blanked
; - 1 digit after decimal point
;
;       "9999.9"
;	" 999.9"
;       "  99.9"
;	"   0.0"
;
ShowDec1:
	bsf	Flags,LEAD_ZERO
        movf    TenT,w		; leading 0 ?
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '		; convert to ASCII '0'~'9'
	addlw	' '
        call    Print_Char	; show Ten Thousands or ' '
	movf	Thou,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show Thousands or ' '
	movf	Hund,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show Hundreds or ' '
        movf    Tens,w
	addlw	'0'
        call    Print_Char	; show Tens
	movlw	'.'
	call	Print_Char	; show '.'
        movf    Ones,w
	addlw	'0'
        call    Print_Char	; show Ones
        RETURN


;--------------------------------------------------------------------
;            Display 4 digit Fixed-point Decimal on LCD
;
; - leading zero blanked
; - 2 digits after decimal point
;
;       "99.99"
;	" 9.99"
;	" 0.00"
;
ShowDec2:
	movf	Thou,w
	skpz			; leading zero ?
	addlw	'0'-' '		; convert to ASCII '0'~'9'
	addlw	' '
        call    Print_Char	; show thousands or ' '
	movf	Hund,w
	addlw	'0'
        call    Print_Char	; show hundreds
	movlw	'.'
	call	Print_Char	; show '.'
        movf    Tens,w
	addlw	'0'
        call    Print_Char	; show tens
        movf    Ones,w
	addlw	'0'
        call    Print_Char	; show ones
        RETURN


;--------------------------------------------------------------------
;                Display 5 digit Decimal on LCD
;
; - leading zero blanked
;
;       "99999"
;       " 9999"
;	"   99"
;	"    0"
;
ShowDec:
	bsf	Flags,LEAD_ZERO
        movf    TenT,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show ten thousands or ' '
        movf    Thou,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show thousands or ' '
        movf    Hund,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show hundreds or ' '
        movf    Tens,w
	skpz
	bcf	Flags,LEAD_ZERO
	btfss	Flags,LEAD_ZERO
	addlw	'0'-' '
	addlw	' '
        call    Print_Char	; show tens or ' '
        movf    Ones,w
	addlw	'0'
        call    Print_Char	; show ones
        RETURN


;-------------------------------------------------------------------------------
;                           Convert Period to rpm
;
; Period time = time for 16 blades to pass sensor (250kHz CCPR1 clock)
;
; rpm = 1/(Period*4uS/16/numblades)*60
;     = (240,000,000/numblades)/Period
;
; numblades     dividend    min rpm  max rpm (for < 0.1% error) 
;    1        240,000,000     3662   240,000 ) 
;    2        120,000,000     1831   120,000 ) limited to 65,535 in Show_rpm 
;    3         80,000,000     1220    80,000 )
;    4         60,000,000      916    60,000
;    5         48,000,000      732    48,000
;    6         40,000,000      610    40,000
;    7         34,285,714      523    34,286
;
Calc_rpm:
	movf	NumBlades,w
	addlw	-1
	skpnz
	goto	cr_1
	addlw	-1
	skpnz
	goto	cr_2
	addlw	-1
	skpnz
	goto	cr_3
	addlw	-1
	skpnz
	goto	cr_4
	addlw	-1
	skpnz
	goto	cr_5
	addlw	-1
	skpnz
	goto	cr_6
	addlw	-1
	skpnz
	goto	cr_7
	goto	cr_norpm
cr_1:	movi32	240000000,dd
	goto	cr_calc
cr_2:	movi32	120000000,dd
	goto	cr_calc
cr_3:	movi32	80000000,dd
	goto	cr_calc
cr_4:	movi32	60000000,dd
	goto	cr_calc
cr_5:	movi32	48000000,dd
	goto	cr_calc
cr_6:	movi32	40000000,dd
	goto	cr_calc
cr_7:	movi32	34285714,dd

cr_calc:
	mov16	Period,aa	; get period
	movf	aa,w
	iorwf	aa+1,w
	skpnz			; period = 0 ?
	goto	cr_norpm	; yes
	call	Div32
	mov32	dd,rpm		; no, rpm = 1/period
	goto	cr_done
cr_norpm:
	clr32	rpm		; rpm = 0
cr_done:
	return



;---------------------------------------------------------------------
;                  Display rpm on LCD
;
Show_rpm:
	movlw	' '
	call	Print_Char	; show ' '
	call	Calc_rpm
	mov16	rpm+2,Number
	movf	rpm,w
	iorwf	rpm+1,w		; rpm > 65535 ? 
	skpnz
	goto	sr_good		; no
sr_overload:
	movlw	'-'		; yes, show '-----'
	call	Print_Char
	movlw	'-'
	call	Print_Char
	movlw	'-'
	call	Print_Char
	movlw	'-'
	call	Print_Char
	movlw	'-'
	call	Print_Char
	goto	sr_rpm
sr_good:
	call	bin2dec
	call	ShowDec		; show "xxxxx"
sr_rpm:
	movlw	'r'
	call	Print_Char	; show 'rp'
	movlw	'p'
	call	Print_Char
	return

;---------------------------------------------------------------------
;          Display Number of Propellor Blades on LCD
;
Show_Blades:
	movlw	'm'		; show 'm' (end of 'rpm')
	call	Print_Char
	movlw	' '
	call	Print_Char	
	movlw	' '
	call	Print_Char
	movlw	' '		; show '    '
	call	Print_Char
	movlw	' '
	call	Print_Char	
	movlw	'x'		; show 'x'
	call	Print_Char
	movf	NumBlades,w
	addlw	'0'		; show numblades 2~6
	call	Print_Char
	movlw	' '
	call	Print_Char
	return


;--------------------------------------------------------------------
;                    Display Watts on LCD
;
Show_Watts:
	mov16	Watts,Number
	call	bin2dec
	call	ShowDec1	; display "xxxx.x"
	movlw	'W'
	call	Print_Char	; display 'W'
	movlw	' '
	call	Print_Char
	return

;--------------------------------------------------------------------
;                     Display Volts on LCD
;
Show_Volts:
	mov16	Volts,Number
	call	bin2dec
	movlw	' '
	call	Print_Char
	call	ShowDec2	; display "xx.xx"
	movlw	'V'
	call	Print_Char
	movlw	' '
	btfsc	Flags,MAX_VOLTS
	movlw	'+'		; display '+' if overflow
	call	Print_Char
	return

;--------------------------------------------------------------------
;                    Display Amps on LCD
;
Show_Amps:
	mov16	Amps,Number
	movlw	' '
	btfsc	Flags,NEG_AMPS
	movlw	'-'		; display '-' if negative
	call	Print_Char
	call	bin2dec
	call	ShowDec2	; display "xx.xx"
	movlw	'A'
	call	Print_Char
	movlw	' '
	btfsc	Flags,MAX_AMPS
	movlw	'+'		; display '+' if overflow
	call	Print_Char
	return

;--------------------------------------------------------------------
;                  Display Amperes * Hours on LCD
;
Show_AmpHours:
	mov16	AmpHours,Number
	call	bin2dec
	movlw	' '
	call	Print_Char
	call	ShowDec2	; display "xx.xx"
	movlw	'A'
	call	Print_Char
	movlw	'h'
	call	Print_Char
	return

;---------------------------------------------------------------------
;                     Page Button
;
page_button:
	btfsc	PGBTN		; is page button pressed ?
	goto	page_up		; no
	btfsc	Flags,PAGE_DWN	; yes, was page button pressed ?
	goto	page_down	; yes
	incf	pagenum		; no, select next page
	ifdef	LCD8x2
	movlw	3
	else			; past last page ?
	movlw	2
	endif
	subwf	pagenum,w
	skpnc			; back to page 1
	clrf	pagenum
page_down:
	bsf	Flags,PAGE_DWN	; page button is pressed
	movlw	4
	movwf	Page_Timer	; start 222mS debounce timer
	goto	page_done
page_up:
	btfss	Flags,PAGE_DWN	; was page button pressed ?
	goto	page_done	; no
	decfsz	Page_Timer	; debounce timer timed out ?
	goto	page_done	; no
	bcf	Flags,PAGE_DWN	; yes, button debounced
page_done:
	return

;---------------------------------------------------------------------
;                      Blades Button
;
blade_button:
	movlw	1
	subwf	pagenum,w	; on page 1 ?
	skpz
	goto	blade_done	; no, ignore blade button
	btfsc	BLDBTN		; yes, is blade button pressed ?
	goto	blade_up	; no
	btfsc	Flags2,BLD_DWN	; yes, was blade button pressed ?
	goto	blade_down	; yes
	incf	NumBlades	; no, select next blades number
	movlw	7+1
	subwf	NumBlades,w
	skpc			; blades > 7 ?
	goto	blade_down	; no
	movlw	1		; yes, blades = 1
	movwf	NumBlades
blade_down:
	bsf	Flags2,BLD_DWN	; blade button is pressed
	movlw	4
	movwf	Blade_Timer	; start 222mS debounce timer
	goto	blade_done
blade_up:
	btfss	Flags2,BLD_DWN	; was blade button pressed ?
	goto	blade_done	; no
	decfsz	Blade_Timer	; debounce timer timed out ?
	goto	blade_done	; no
	bcf	Flags2,BLD_DWN	; yes, button debounced
blade_done:
	return



;--------------------------------------------------------------------
;                  Main Program Starts Here!
;
Start:
	clrf 	PORTA
	clrf	PORTB
	bsf	STATUS,RP0	; bank 1
	ifdef	__16F88
	movlw	B'11101111'	; PortA directions (16F88)
	movwf	TRISA
	movlw	B'11000001'	; PortB directions (16F88)
	movwf	TRISB
	movlw	B'01100000'	; 4MHz internal osc (16F88)
	movwf	OSCCON
	else
	movlw	B'11111111'	; PortA directions (16F870)
	movwf	TRISA
	movlw	B'00000000'	; PortB directions (16F870)
	movwf	TRISB
	movlw	B'11111111'	; PortC directions (16F870)
	movwf	TRISC
	endif
	movlw   B'01010111'	; Weak pullups enabled, Timer0 src = clkout/256
	movwf	OPTION_REG
	bcf	STATUS,RP0	; bank 0

; Set up Interrupts

	clrf	INTCON		; clear any pending ints
	clrf	PIR1
	bsf	INTCON,PEIE	; enable peripheral ints
	bsf	INTCON,GIE	; enable interrupts

; set up LCD Display

	movlw	25		; wait 500mS
	call	waitx20k

	call	LCDinit		; Set up the LCD display
	call	show_signon	; show signon message

	movlw	50		; wait 1 second
	call	waitx20k

; set up A/D convertor

	bsf	STATUS,RP0	; bank 1
	ifdef	__16F88
	movlw	b'10110000'	; right-justify, ext vref (16F88)
	movwf	ADCON1
	movlw	b'00001111'
	movwf	ANSEL		; Analog inputs on RA0~RA3 (16F88)
	else
	movlw	b'10001101'	; right-justify, ext vref, RA1/RA0 (16F870)
	movwf	ADCON1
	endif
	bcf	STATUS,RP0	; bank 0
	movlw	b'01000000'	; CLK/8
	movwf	ADCON0

; initialize variables

	clrf	Flags
	clrf	Flags2
	movlw	0
	movwf	pagenum
	clrf	Page_Timer
	clrf	Blade_Timer
	clr16	CCPR
	clr16	Period
	clr32	AmpSum
	clr32	rpm
	movlw	2
	movwf	NumBlades

; Set up CCP1 and Timer1 for rpm capture

	clrf	TMR1L
	clrf	TMR1H		; clear Timer1
	movlw	b'00100001'
	movwf	T1CON		; 4:1 prescaler, Timer1 ON
	bcf	PIR1,TMR1IF
	bcf	PIR1,CCP1IF	; clear Int flags
	banksel	PIE1
	bsf	PIE1,CCP1IE	; enable CCP1 Ints
	bsf	PIE1,TMR1IE	; enable TMR1 Ints
	banksel	0
	movlw	b'00000111'	; capture every 16th leading edge
	movwf	CCP1CON		; enable Capture

ReadInputs:
	clr16	Volts
	clr16	Amps
	clr16	Watts
	movlw	64		; 64 times oversampling
	movwf	Readings
NextRead:
	movlw	b'01000001'	; CLK/8, select RA0 (Volts), A/D on
	movwf	ADCON0
	movlw	10
	call	waitx100	; wait 1mS to stabilize analog input
	bsf	ADCON0,GO_DONE	; start A/D conversion
waitv:	btfsc	ADCON0,GO_DONE
	goto	waitv		; wait until conversion Done
	movf	ADRESH,w
	movwf	Temp1
	addwf	Volts
	bsf	STATUS,RP0
	movf	ADRESL,w	; add 10 bit A/D result to Volts
	bcf	STATUS,RP0
	movwf	Temp2
	addwf	Volts+1
	skpnc
	incf	Volts

	bcf	Flags,MAX_VOLTS
	movf	Temp1,w
	xorlw	b'00000011'
	skpnz
	bsf	Flags,MAX_VOLTS	; Volts overloaded ?
	movf	Temp2,w
	xorlw	b'11111111'
	skpz
	bcf	Flags,MAX_VOLTS

	movlw	b'01001001'	; CLK/8, select RA1 (Amps), A/D on
	movwf	ADCON0
	movlw	10
	call	waitx100	; wait 1mS to stabilize analog input
	bsf	ADCON0,GO_DONE	; start A/D conversion
waita:	btfsc	ADCON0,GO_DONE
	goto	waita		; wait until conversion Done
	movf	ADRESH,w
	movwf	Temp1
	addwf	Amps
	bsf	STATUS,RP0
	movf	ADRESL,w	; get 10 bit A/D result
	bcf	STATUS,RP0
	movwf	Temp2
	addwf	Amps+1
	skpnc
	incf	Amps

	bcf	Flags,MAX_AMPS
	movf	Temp1,w
	xorlw	b'00000011'
	skpnz
	bsf	Flags,MAX_AMPS	; Amps overloaded ?
	movf	Temp2,w
	xorlw	b'11111111'
	skpz
	bcf	Flags,MAX_AMPS

	decfsz	Readings
	goto	NextRead	; accumulate readings

	clrc
	rr16	Volts		; Volts / 2

	movlw	Volts
	call	Divx12		; Volts / 12.8

	movlw	Amps
	call	Divx12		; Amps / 12.8

	btfsc	Flags,GOT_ZERO	; have Amps been zeroed ?
	goto	sub_zero
get_zero:
	mov16	Amps,ZeroAmps	; record zero Amps value
	bsf	Flags,GOT_ZERO
sub_zero:
	bcf	Flags,NEG_AMPS
	sub16	ZeroAmps,Amps	; subtract zero value from Amps
	skpnc
	goto	got_amps
	bsf	Flags,NEG_AMPS
	com16	Amps		; Amps = -0 to -0.99
got_amps:
	mov16	Amps,aa
	mov16	Volts,bb
	call	Mult16		; Watts = Volts * Amps
	movi16	1000,aa
	call	Div32		; Watts = Watts / 1000
	subi16	500,bb
	skpc			; remainder > 0.5 ?
	goto	store_watts
	inc16	dd+2		; yes, round up
store_watts:
	mov16	dd+2,Watts
calc_Ah:
	add1632	Amps,AmpSum	; AmpSum = accumulated Amps
	mov32	AmpSum,dd
	movi16	3600*3,aa	; 3 reads per second, 3600 seconds per hour
	call	Div32		; AmpHours = AmpSum  / (reads per hour)
store_Ah:
	mov16	dd+2,AmpHours


; CCPR is a copy of the CCPR1 register. It is updated from the Interrupt
; routine. This may happen at any time, so we must prevent it from being
; updated while copying it. We cannot disable interrupts while copying
; because it could upset capture timing.
;
	bsf	Flags,LOCK_P	; lock CCPR
	mov16	CCPR,Period     ; get period (1/rpm)
	bcf	Flags,LOCK_P	; unlock CCPR

; reduce rpm jitter by averaging this and previous measurement

	tst16	Period
	skpz			; got good rpm ?
	goto	sp_good
	clr16	OldPeriod	; no, old period = 0
	goto	sp_done
sp_good:
	tst16	OldPeriod
	skpnz			; got previous good rpm ?
	goto	sp_done		; no
	mov1632	Period,dd
	add1632	OldPeriod,dd
	rr32	dd		; dd = (Period+OldPeriod)/2
	mov16	Period,OldPeriod ; old period = current period
	mov16	dd+2,Period	; Period now averaged
sp_done:

	movlw	4		; set display refresh for 3 readings per second  
	movwf	ref_timer	; (value reduced to account for measurement time)
	movlw	REFRESH_DELAY1	
	movwf	TMR0		; initialize Timer0 
	bcf	INTCON,TMR0IF	; clear timer0 interrupt
	goto	display_page	

; show results on LCD screen

do_display:
	movlw	6		; 6*55.555mS = 333mS = 3 readings per second
	movwf	ref_timer
display_page:
	movlw	0
	call	Set_Cursor	; set cursor to start of line
	movf	pagenum,w
	skpnz			; page 0 ?
	goto	page0
	addlw	-1
	skpnz			; page 1 ?
	goto	page1

	ifdef	LCD8x2

; 8x2 or 16x1 display, 3 pages

	addlw	-1
	skpnz			; page 2 ?
	goto	page2

page0:	call	Show_Volts
	movlw	LCDLINE2
	call	Set_Cursor	; "99.99V 99.99A"
	call	Show_Amps
	goto	ref_delay

page1:	call	Show_Watts	; "9999.9W 99.99Ah"
	movlw	LCDLINE2
	call	Show_AmpHours
	goto	ref_delay

page2:	call	Show_rpm
	movlw	LCDLINE2
	call	Set_Cursor	; "99999rpm   x 9"
	call	Show_Blades

	else

; 16x2 display, 2 pages

page0:	call	Show_Volts
	call	Show_Amps	; "99.99V 99.99A"
	movlw	LCDLINE2
	call	Set_Cursor
	call	Show_Watts
	call	Show_AmpHours 	; "9999.9W 99.99Ah"
	goto	ref_delay

page1:	call	Show_Volts
	call	Show_Amps	; "99.99V 99.99A"
	movlw	LCDLINE2
	call	Set_Cursor
	call	Show_rpm	; "99999rpm   x 9"
	call	Show_Blades

	endif

; Wait 1/3 second (3 readings per second)
; Check buttons while waiting

ref_delay:
	btfss	INTCON,TMR0IF
	goto	ref_delay	; wait for Timer0 overflow (55.555mS)
	movlw	REFRESH_DELAY2
	addwf	TMR0		; recharge Timer0
	bcf	INTCON,TMR0IF
	call	page_button	; handle page button
	call	blade_button	; handle blades button
	decfsz	ref_timer
	goto	display_page	; wait for 333.3mS (1/3 Second)
check_hold:
	ifndef	SIMULATE
	btfss	HDBTN		; hold switch on ?
	goto	do_display	; yes, hold last readings
	endif
	goto	ReadInputs	; no, get new readings

	END

