;	************************************************************
;	AudiomVmeter.asm -firmware for Digital Audio Level Meter
;
;	Written by Jim Rowe for Silicon Chip, using a PIC16F88
;	processor running from its internal clock oscillator
;	at 8MHz, so one machine cycle (mc) = 0.5us.

;	Program last revised 28/01/2009.
;
;	Note: Program makes use of 24-bit and 32-bit floating point &
;	fixed point maths routines for Microchip Technology Inc's
; 	8-bit PIC processors, written by Frank J. Testa and
;	described in MTI's App Notes AN575, AN617, AN660 and AN670,
; 	downloadable from the MTI website at www.microchip.com
;	(Routines used here are all in FPRF24.TXT)
;
;	**********************************************************
; 
;	CPU configuration
;
	list p=PIC16f88
	#include		"p16f88.inc"
	__CONFIG        _CONFIG1, h'3F39'
	__CONFIG		_CONFIG2, _IESO_OFF & _FCMEN_OFF
;
;**********************************************************
;
;	First define some handy macros
;
;	Select Register Bank 0
bank0	macro
	errorlevel	+302		; Re-enable bank warning
	BCF		STATUS,RP0		; Select Bank 0
	BCF STATUS, RP1
	BCF STATUS, IRP			; and also indir addr banks 0,1
	endm

;	Select Register Bank 1
bank1	macro
	BSF		STATUS,RP0		; Select Bank 1
	errorlevel	-302		; disable warning
	endm

;	Swap bytes in register file via W
swap	macro	this,that
	MOVF	this,w		; get this
	XORWF	that,f		; Swap using Microchip
	XORWF	that,w		; Tips'n Tricks
	XORWF	that,f		; #18
	MOVWF	this
	endm

;	Copy bytes in register file via W
copy	macro	from,to
	MOVF	from,W
	MOVWF	to
	endm

;	Prepare to call or goto page1 (800-FFFh)
Ppage1	macro
	BCF PCLATH,4	; clear bit4 of PCLATH
	BSF PCLATH,3	; but set bit3
	endm

;	Prepare to call or goto page0 (000-7FFh)
Ppage0	macro
	BCF PCLATH,4	; clear bit4 of PCLATH
	BCF PCLATH,3	; and also clear bit3
	endm
;
;	**********************************************************
;     STATUS bit definitions (used in FPRF24.TXT)

#define	_C	STATUS,0
#define	_Z	STATUS,2
;
;**************************************************************
;
;	storage register declarations:

Counter1	equ	h'20'	; gp counter variable 1
Counter2	equ h'21'	; gp counter variable 2
Counter3	equ h'22'	; gp counter variable 3
Counter4	equ h'23'	; gp counter variable 4
Temp1		equ	h'24'	; working storage location 1
Temp2		equ h'25'	; working storage location 2
RangeCtr	equ h'26'	; counter for measurement range (3>2>1)

VDig1		equ h'27'	; first digit of volts reading
VDig2		equ h'28'	; second digit of volts reading
VDig3		equ h'29'	; third digit of volts reading
VDig4		equ h'2A'	; fourth digit of volts reading
VDig5		equ h'2B'	; fifth digit of volts reading
Vmult		equ h'2C'	; multiplier for volts (u,m or blank)

dBVSign		equ h'2D'	; sign for dBV reading (+ or -)
dBVDig1		equ h'2E'	; first digit of dBV reading
dBVDig2		equ h'2F'	; second digit of dBV reading
dBVDecimal	equ h'30'	; decimal digit of dBV reading

dBmSign		equ h'31'	; sign for dBm reading (+ or -)
dBmDig1		equ h'32'	; first digit of dBm reading
dBmDig2		equ h'33'	; second digit of dBm reading
dBmDecimal	equ h'34'	; decimal digit of dBm reading

;	storage for dBV value in 24-bit form, after calculation
dBVEXP		equ h'35'	; exponent
dBVB0		equ h'36'	; MSB & sign bit
dBVB1		equ h'37'	; LSB

;	storage of the 24-bit FP range factor (pre calculated)
RFactEXP	equ h'38'	; exponent
RFactB0		equ h'39'	; MSB
RFactB1		equ h'3A'	; LSB

;	storage for dBm value in 24-bit form, after calculation
dBmEXP		equ h'3B'	; exponent
dBmB0		equ h'3C'	; MSB & sign bit
dBmB1		equ h'3D'	; and LSB

;	storage for the ADC result, after conversion to 24-bit FP
RAWEXP		equ h'3E'	; exponent
RAWB0		equ h'3F'	; most significant byte with sign
RAWB1		equ h'40'	; least significant byte

;	storage for the voltage in 24-bit form, after calculation
VoltEXP		equ h'41'	; exponent
VoltB0		equ h'42'	; MSB & sign bit
VoltB1		equ h'43'	; LSB

;	***********************************************************
;   Floating Point Stack & other locations used by FPRF24.TXT
;	routines
;
AARGB7	equ h'50'	; AARGB7 byte for FP argument A
AARGB6	equ h'51'	; AARGB6 byte
AARGB5	equ h'52'	; AARGB5 byte
AARGB4	equ	h'53'	; AARGB4 byte
AARGB3	equ h'54'	; AARGB3
AARGB2	equ h'55'	; AARGB2
AARGB1	equ h'56'	; AARGB1
AARGB0	equ h'57'	; AARGB0
AEXP	equ h'58'	; 8 bit biased exponent for argument A

BARGB3	equ h'59'	; BARGB3 byte for argument B
BARGB2	equ h'5A'	; BARGB2
BARGB1	equ h'5B'	; BARGB1
BARGB0	equ h'5C'	; BARGB0
BEXP	equ h'5D'	; 8 bit biased exponent for argument B

TEMPB3	equ h'5E'	; TEMPB3 byte
TEMPB2	equ h'5F'	; TEMPB2 byte
TEMPB1	equ h'60'	; TEMPB1 byte
TEMPB0	equ h'61'	; TEMPB0 byte

CARGB1	equ h'62'	; CARGB1 byte for argument C
CARGB0	equ h'63'	; CARGB0 byte
CEXP	equ h'64'	; 8 bit biased exponent for argument C

DARGB3	equ h'65'	; DARGB3 byte for argument D
DARGB2	equ h'66'	; DARGB2 byte
DARGB1	equ h'67'	; DARGB1 byte
DARGB0	equ	h'68'	; DARGB0 byte
DEXP	equ	h'69'	; 8-bit biased exponent for argument D

EARGB3	equ h'6A'	; needed by EXP1024, it seems

SIGN	equ	h'6B'	; location for saving sign in MSB
FPFLAGS	equ h'6C'	; floating point library exception flags
FPError	equ h'6D'	; floating point routine error code (FFh = error)

;	Fixed point storage locations

REMB3	equ h'70'	; remainder LSB
REMB2	equ h'71'	; remainder middle byte
REMB1	equ h'72'	; remainder upper middle byte
REMB0	equ h'73'	; remainder MSB
LOOPCOUNT	equ h'74'	; loop counter

;	Locations used by float_ascii to store result digits

	cblock h'75'
	ones			; where units digit is stored (75h)
	tenths			; where tenths digit is stored (76h)
	hundredths		; where hundredths digit is stored (77h)
	thousandths		; where thousandths digit is stored (78h)
	tenthous		; where ten thousandths digit is stored (79h)
	endc

digit_count	equ	h'7A'	; digit counter used by float_ascii 

;       floating point library exception flag bits
;
IOV     equ     0	; bit0 = integer overflow flag
FOV     equ     1   ; bit1 = FP overflow flag
FUN     equ     2   ; bit2 = FP underflow flag
FDZ     equ     3   ; bit3 = FP divide by zero flag
NAN		equ		4	; bit4 = not-a-number exception flag
DOM		equ		5	; bit5 = domain error exception flag
RND     equ     6   ; bit6 = FP rounding flag
					; 0 = truncation
                    ; 1 = unbiased rounding to nearest LSB
SAT     equ     7   ; bit7 = FP saturate flag
					; 0 = term on exception w/out saturation
					; 1 = term on exception with saturation
					; to appropriate value

EXP		equ	AEXP 
TEMP	equ	TEMPB0

;	define assembler constants
B0		equ	0
B1		equ	1
B2		equ	2
B3		equ	3
B4		equ	4
B5		equ	5
B6		equ	6
B7		equ	7

MSB		equ	7
LSB		equ	0

;       Floating point literal constants
;
EXPBIAS   equ h'7F'		; = 127d, bias for exponents

;*************************************************************

;	Program now begins
;
	org	h'00'		; normal start vector
	GOTO Initialise
	org h'04'		; interrupt service vector
	GOTO Initialise	; (we are not using interrupts)

Initialise:
	; first we set up CPU and INTOSC, I/O ports and ADC modules
	bank0			; make sure we're set for bank 0
	CLRF INTCON		; disable interrupts
	Ppage0			; make sure PCLATH is set for page0
	CLRF CCP1CON	; disable the CCP module
	CLRF RCSTA		; and also the serial port
	CLRF PORTA		; clear PORTA (turns on LED1)
	CLRF PORTB		; also PORTB (turns on LEDs 2 and 3 as well)
	bank1			; then switch to bank1
	MOVLW h'70'		; set INTOSC for 8MHz
	MOVWF OSCCON
	CLRF OSCTUNE	; and also set to centre frequency
	CLRF PIE1		; turn off peripheral interrupts
	CLRF CVRCON		; now disable comparator Vref module
	MOVLW  h'07'	; and disable the comparators
	MOVWF CMCON		; (by setting them for mode 111)
	CLRF TRISB		; then set RB0-RB7 as outputs
	MOVLW h'3F'		; also set RA0-RA5 as inputs, RA6-7 as outputs
	MOVWF TRISA
	MOVLW h'0F'		; then set RA0-RA3 to be AN0-AN3
	MOVWF ANSEL
	MOVLW h'E0'		; now set ADC for right justify, range 0V to Vref+
	MOVWF ADCON1	; and divide system clock by 2
	bank0			; then down to bank 0 again, so we can
	BSF PORTB,7		; turn off LED2 and LED3
	BSF PORTB,6
	MOVLW h'11'		; now turn on ADC, make Tad = Toscx4, reset ADC
	MOVWF ADCON0	; and set AN2 as initial input channel
	MOVLW h'03'		; now initialise range counter to 3
	MOVWF RangeCtr
	MOVLW h'85'		; and set range factor to default value
	MOVWF RFactEXP	; of 100d in 24-bit FP form (pre calc)
	MOVLW h'48'		; (which is 85 48 00 -- from Fprep)
	MOVWF RFactB0
	CLRF RFactB1

;	next section is to initialise (blank) display digits
	MOVLW h'27'		; now load indir addr register
	MOVWF FSR
	MOVLW h'0E'		; also set loop counter for
	MOVWF Counter1	; 14 loops
	MOVLW h'20'		; finally load w with space code
	MOVWF INDF		; then copy into next location
	INCF FSR,1		; and increment indir addr pointer
	DECFSZ Counter1,1	; decr counter, skip if zero
	GOTO $-3		; otherwise keep looping 

	CALL DispInit	; now initialise the LCD module
	CALL Display1	; and show greeting display
	CALL Wait4sec	; then pause for 4 seconds
	CALL Display2	; before showing normal display

Mainloop
;	Main operating loop begins here, to process and display
;	readings via the ADC and also change ranges when pushbutton
;	S1 is pressed. Current range is also shown via LEDs1-3
;	We start by taking a reading via the ADC module. Note that
;	result appears in ADRESH (1Eh) and ADRESL (9Eh) registers

	BSF ADCON0,2		; start ADC conversion by setting GO bit
	BTFSC ADCON0,2		; then wait until conversion complete by
	GOTO $-1			; looping back until GO bit cleared again

	CALL ProcessIt		; when done, go work out dBV, V and dBm
	CALL Display2		; then update LCD display

	BTFSS PORTA,4		; check RA4, skip if set (S1 not pressed)
	CALL RangeChange	; RA4=0 so S1 pressed. Go change range
	GOTO Mainloop		; otherwise just keep looping
 
;	main program ends -- subroutines follow
;**************************************************************
;
Check4FPE:
	; routine to check if floating point functions had errors
	IORLW h'00'			; check if w came back with 00h or FFh
	BTFSC STATUS,Z		; if Z=0, must have been FFh (= an error)
	RETURN				; if Z=1, must have been 00h (= no error)
	CALL ErrorDisp		; was an error, so display message
	RETURN				; and then return to resume anyway

ClearLCD:
	;routine to clear LCD and reset address counter
	MOVLW h'01'			; clear display & reset addr ptr
	CALL DispAddress
	CALL Delay160ms		; pause 160ms to give it time to clear
	CALL Delay160ms		; and again, just for sloooow LCDs
	RETURN				; then return	
	
Delay1ms:
	;routine to delay approx 1ms (2058 x 0.5us = 1029us) before return
	MOVLW h'0A'			; set Counter1 for 10 outer loops
	MOVWF Counter1
OuterLoop:
	MOVLW h'42'			; and Counter2 for 66 inner loops
	MOVWF Counter2		; (66 x 3mc = 198mc, + 7mc)
	DECFSZ Counter2, 1	; decrement Counter2 & skip when zero
	GOTO $-1			; not zero yet, so loop back
	DECFSZ Counter1, 1	; did reach zero, so decrement Counter1
	GOTO OuterLoop		; didn't hit zero, so loop back
	RETURN				; reached zero (10 x 66 loops) so return

Delay10ms:
	;routine to delay approx 10ms before returning
	MOVLW h'0A'			; set Counter3 for 10 outer loops
	MOVWF Counter3
	CALL Delay1ms		; then wait 1ms
	DECFSZ Counter3, 1	; then decrement Counter3 & skip when zero
	GOTO $-2			; not zero yet, so keep going
	RETURN				; reached zero, so return	
	
Delay160ms:
	;routine to delay approx 160ms before returning
	MOVLW h'A0'			; set Counter3 for 160 outer loops
	MOVWF Counter3
	CALL Delay1ms		; then wait 1ms
	DECFSZ Counter3, 1	; then decrement Counter3 & skip when zero
	GOTO $-2			; not zero yet, so keep going
	RETURN				; reached zero, so return	
	
DispAddress:
	;routine to translate & load display address (in w reg) into LCD
	BCF PORTB,5			; first set RS pin of LCD low, for instr/addr
	CALL Nibbles2LCD	; then send addr/cmd nibbles to LCD
	BCF PORTB,5			; make sure RS is is left low
	GOTO BusyWait		; then jump to delay 160us before return
	
DisplayData:
	;routine to display a data byte in w reg at the current LCD address
	BSF PORTB,5			; RS pin of LCD high, for sending data
	CALL Nibbles2LCD	; then send data nibbles to LCD

BusyWait:
	; routine to wait until LCD module is not busy, after writing
	MOVLW h'64'			; set delay counter for 100 loops
	MOVWF Counter1		; (should give about 100 x 4 x 0.5 = 200us)
	NOP
	DECFSZ Counter1,1	; decrement counter & skip when zero
	GOTO $-2			; loop back until we reach zero
	RETURN				; then return

DispInit:
	; routine to initialise LCD display module
	BCF PORTB,4			; first make sure EN and RS lines are low
	BCF PORTB,5
	CALL Delay160ms		; then wait about 160ms before proceeding
	BSF PORTB,0			; now load init code 03h into RB0-3 
	BSF PORTB,1			; (= DB4 to DB7, so 03 -> 30h)
	CALL ToggleEN		; then toggle EN to write to LCD
	CALL Delay10ms		; then wait about 10ms
	CALL ToggleEN		; then toggle EN to write to LCD again
	CALL Delay10ms		; then wait about 10ms again
	CALL ToggleEN		; then toggle EN to write to LCD again
	CALL Delay10ms		; then wait about 10ms a third time
	BCF PORTB,5			; make sure RS is still low
	BCF PORTB,0			; now change code in RB to 02 (-> 20h)
	CALL ToggleEN		; then toggle EN to write to LCD
	CALL Delay10ms		; then wait about 10ms one last time

	MOVLW h'28'			; now set LCD functions (4 bit i/f, 2 lines, 5x10 chars)
	CALL DispAddress	; (also delays for 200us)
	MOVLW h'0C'			; also set display mode (disp on, no blink or cursor)
	CALL DispAddress	; (also delays for 200us)
	CALL ClearLCD		; then clear LCD screen (& delay 320ms)
	MOVLW h'06'			; set entry mode (increm addr, no shift)
	CALL DispAddress	; (also delays for 200us)
	RETURN				; should now be set up & ready to go, so leave

Display1:
	; routine to display initial greeting info on LCD
	MOVLW h'80'			; first set address to line 1, char 0
	CALL DispAddress	; (also delays for 160us)
	MOVLW "S"			; then send "Silicon Chip AF "
	CALL DisplayData
	MOVLW "i"
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "i"
	CALL DisplayData
	MOVLW "c"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW "n"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "C" 
	CALL DisplayData
	MOVLW "h"
	CALL DisplayData
	MOVLW "i"
	CALL DisplayData
	MOVLW "p"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "A"
	CALL DisplayData
	MOVLW "F"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW h'C0'			; now move down to line 2
	CALL DispAddress
	MOVLW " "			; and display " Millivoltmeter "
	CALL DisplayData
	MOVLW "M"
	CALL DisplayData
	MOVLW "i"
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "i"
	CALL DisplayData
	MOVLW "v"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "m"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	RETURN				; before leaving

Display2:
	; routine to display measurement info on LCD
	MOVLW h'80'			; first set address to line 1, char 0
	CALL DispAddress	; (also delays for 160us)
	MOVF VDig1,0		; then send voltage reading
	CALL DisplayData
	MOVF VDig2,0
	CALL DisplayData
	MOVF VDig3,0
	CALL DisplayData
	MOVF VDig4,0
	CALL DisplayData
	MOVF VDig5,0
	CALL DisplayData
	MOVF Vmult,0
	CALL DisplayData
	MOVLW "V" 
	CALL DisplayData
	MOVLW "="
	CALL DisplayData
	MOVF dBVSign,0
	CALL DisplayData
	MOVF dBVDig1,0
	CALL DisplayData
	MOVF dBVDig2,0
	CALL DisplayData
	MOVLW "."
	CALL DisplayData
	MOVF dBVDecimal,0
	CALL DisplayData
	MOVLW "d"
	CALL DisplayData
	MOVLW "B"
	CALL DisplayData
	MOVLW "V"
	CALL DisplayData
	MOVLW h'C0'			; now move down to line 2
	CALL DispAddress
	MOVLW "="			; then show equiv dBm value
	CALL DisplayData	; (for 600 ohm impedance)
	MOVF dBmSign, 0
	CALL DisplayData
	MOVF dBmDig1,0
	CALL DisplayData
	MOVF dBmDig2,0
	CALL DisplayData
	MOVLW "."
	CALL DisplayData
	MOVF dBmDecimal,0
	CALL DisplayData
	MOVLW "d"
	CALL DisplayData
	MOVLW "B"
	CALL DisplayData
	MOVLW "m"
	CALL DisplayData
	MOVLW " "			; followed by reminder
	CALL DisplayData
	MOVLW "("
	CALL DisplayData
	MOVLW "6"
	CALL DisplayData
	MOVLW "0"
	CALL DisplayData
	MOVLW "0"
	CALL DisplayData
	MOVLW h'F4'
	CALL DisplayData
	MOVLW ")" 
	CALL DisplayData
	RETURN				; before leaving

ErrorDisp:
	; routine to display "Error" on LCD, then pause & return
	; (called only if FP functions flag an error)
	CALL ClearLCD
	MOVLW h'80'			; next set address to line 1, char 0
	CALL DispAddress	; (also delays for 160us)
	MOVLW "F"			; then send "FP Error!"
	CALL DisplayData
	MOVLW "P"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "E"
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	MOVLW "o" 
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	MOVLW "!"
	CALL DisplayData
	CALL Wait4sec		; now pause for 4 seconds for user to see
	CALL ClearLCD		; then wipe away again
	CALL Display2		; restore normal display
	RETURN				; and return

Nibbles2LCD:
	; routine to test bits of data byte (passed in w) then send
	; to LCD module as two nibbles (high nibble first)
	MOVWF Temp2			; first save byte to be sent in Temp2
	BTFSC Temp2,7		; now test bit 7 (upper nibble)
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,3			; if it's a 0, clear RB3
	GOTO $+2			; and proceed to next bit
	BSF PORTB,3			; was a 1, so set RB3 instead
	BTFSC Temp2,6		; now test bit 6
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,2			; if it's a 0, clear RB2
	GOTO $+2			; and proceed to next bit
	BSF PORTB,2			; was a 1, so set RB2 instead
	BTFSC Temp2,5		; now test bit 5
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,1			; if it's a 0, clear RB1
	GOTO $+2			; and proceed to next bit
	BSF PORTB,1			; was a 1, so set RB1 instead
	BTFSC Temp2,4		; now test bit 4
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,0			; if it's a 0, clear RB0
	GOTO $+2			; and proceed to next bit
	BSF PORTB,0			; was a 1, so set RB0 instead
	CALL ToggleEN		; now toggle EN to write hi nibble to LCD

	BTFSC Temp2,3		; next test bit 3 (lower nibble)
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,3			; if it's a 0, clear RB3
	GOTO $+2			; and proceed to next bit
	BSF PORTB,3			; was a 1, so set RB3 instead
	BTFSC Temp2,2		; now test bit 2
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,2			; if it's a 0, clear RB2
	GOTO $+2			; and proceed to next bit
	BSF PORTB,2			; was a 1, so set RB2 instead
	BTFSC Temp2,1		; now test bit 1
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,1			; if it's a 0, clear RB1
	GOTO $+2			; and proceed to next bit
	BSF PORTB,1			; was a 1, so set RB1 instead
	BTFSC Temp2,0		; now test bit 0
	GOTO $+3			; if it's a 1, skip down
	BCF PORTB,0			; if it's a 0, clear RB0
	GOTO $+2			; and proceed to next bit
	BSF PORTB,0			; was a 1, so set RB0 instead
	CALL ToggleEN		; toggle EN again to write low nibble to LCD
	RETURN				; and return, since both nibbles sent

;	---------------------------------------------------------------
ProcessIt:
;	routine to process the ADC reading and calculate dBV, V and dBm
;	values from it. Here's where we do the number crunching...

	copy RFactEXP,BEXP	; first copy range factor into BARG regs so 
	copy RFactB0,BARGB0	; it can be used as the multiplier, shortly
	copy RFactB1,BARGB1	; (BARG regs now have 24-bit range factor)

	MOVF ADRESH,0		; now fetch high byte of ADC result into w
	MOVWF AARGB0		; and place into AARGB0
	bank1
	MOVF ADRESL,0		; then fetch low byte of result as well
	bank0
	MOVWF AARGB1		; and place it into AARGB1
	CALL FLO24			; then go convert it into 24-bit FP form
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  so 24-bit ADC result now in AARG regs...
	CALL FPM24			; now we can do multiplication (AARG*BARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  (AARG*BARG product now in AARG)
	MOVLW h'88'			; now load 24b FP version of 3FFh/1023d
	MOVWF BEXP			; (pre calc using Fprep) into BARG regs
	MOVLW h'7F'			; so it can be used as divisor
	MOVWF BARGB0
	MOVLW h'C0'
	MOVWF BARGB1
	CALL FPD24			; now do the division AARG/BARG 
	CALL Check4FPE		; (duck away to check for any FP errors)
	copy AEXP,RAWEXP	; now transfer quotient into rawdB regs
	copy AARGB0,RAWB0
	copy AARGB1,RAWB1	; (RAWdB regs now have quotient, = dBraw)
						; (a 24-bit number, value 000 - 100)
	MOVLW h'85'			; next prepare 24-bit FP version of 96.4782d
	MOVWF BEXP			; (found using Fprep.exe program, which
	MOVLW h'40'			; gave 96.4782d = 85 40 F5)
	MOVWF BARGB0
	MOVLW h'F5'
	MOVWF BARGB1		; and store it in BEXP, BARGB0, BARGB1

	copy RAWEXP,AEXP	; now copy rawdB back into AARG
	copy RAWB0, AARGB0
	copy RAWB1, AARGB1
	CALL FPS24			; and subtract BARG from AARG
	CALL Check4FPE		; (duck away to check for any FP errors)
SavedBV:
	copy AEXP,dBVEXP	; dBV now in AARG, so save it in 24-bit form
	copy AARGB0,dBVB0
	copy AARGB1,dBVB1	; ready for use and display soon...

	MOVLW h'85'			; next prepare 24-bit FP version of 94.2602d
	MOVWF BEXP			; (found using Fprep.exe program, which
	MOVLW h'3C'			; gave 94.2602d = 85 3C 85)
	MOVWF BARGB0
	MOVLW h'85'
	MOVWF BARGB1		; and store it in BEXP, BARGB0, BARGB1
	copy RAWEXP,AEXP	; then copy rawdB back into AARG
	copy RAWB0, AARGB0
	copy RAWB1, AARGB1
	CALL FPS24			; now go subtract BARG from AARG
	CALL Check4FPE		; (duck away to check for any FP errors)
SavedBm:
	copy AEXP,dBmEXP	; dBm value now in AARG, so save it
	copy AARGB0,dBmB0	; in 24-bit form,
	copy AARGB1,dBmB1	; ready for use and display soon...

;	next we calculate voltage value from dBV value, first by dividing
; 	dBV by 20 and then raising 10 to that power...
	MOVLW h'83'			; next prepare 24-bit FP version of 20d
	MOVWF BEXP			; (found using Fprep.exe program)
	MOVLW h'20'
	MOVWF BARGB0
	CLRF BARGB1			; and store it in BEXP, BARGB0, BARGB1

	copy dBVEXP,AEXP	; now copy dBV values back into AARG
	copy dBVB0,AARGB0
	copy dBVB1,AARGB1
	CALL FPD24			; then go do the division (AARG/BARG)
	CALL Check4FPE		; (duck away to check for any FP errors)

	Ppage1
	CALL EXP1024		; result in AARG, so go raise 10 to that power
	Ppage0
	CALL Check4FPE		; (duck away to check for any FP errors)
	copy AEXP,VoltEXP	; AARG now has voltage value, so save it
	copy AARGB0,VoltB0	; in the Volt arguments
	copy AARGB1,VoltB1	; ready for display soon

;	--------------------------------------------------------------------
;	Last section - using the calculated 24-bit FP values to produce
;	the display chars for each value: Volts, dBV and dBm

;	First we work out the multiplier char for Volts display,
;	by testing value of Volts exponent...
	BTFSC VoltEXP,7		; first check if b7 of VoltEXP is clear
	GOTO DefVolts		; if not, must be 80 or more, so def volts
	MOVLW h'7F'			; but if it was, see if we have 7F instead
	XORWF VoltEXP,0		; 
	BTFSC STATUS,Z		; did we get a match? (Z=1)
	GOTO DefVolts		; if Z=1, EXP=7F so go to volts processing
	BTFSS VoltEXP,4		; but if not 7F, try b4 (=0 for 6X or less)
	GOTO MustbeuV		; b4=0 (ie EXP=6X or less), so go uV proc
	BTFSC VoltEXP,3		; 7X, so try b3 of VoltEXP - clear?
	GOTO DefmV			; b3=1, go to mV processing (EXP=78-7E)
;	(otherwise drop through to uV processing)
MustbeuV:
;	VoltEXP <78h, so process for display in uV
	MOVLW "m"			; set multiplier to 'm'
	MOVWF Vmult			; (still applies)
	MOVLW "."			; setting VDig2 as DP
	MOVWF VDig2			; for correct display this option
	copy VoltEXP, AEXP	; now copy Volts value into AARG
	copy VoltB0, AARGB0
	copy VoltB1, AARGB1
	CALL SetBARG1k		; also set BARG regs for 1000d scaling
	CALL FPM24			; then multiply them to upscale volts
	CALL Check4FPE		; (duck away to check for any FP errors)
;	returns with 1000*Volt args in AARG regs, so now proceed
	CLRF AARGB2			; make sure AARGB2 is cleared
	CALL SetBARG10k		; set BARG for initial multiply by 10k
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy ones, VDig1	; and place result in VDig positions
	copy tenths, VDig3
	copy hundredths, VDig4
	copy thousandths, VDig5	; (we don't use 5th digit this time)
	GOTO dBVpart		; done, so skip to dBV display section

DefVolts:
;	Display processing for volts (when VoltEXP >= 7Fh)
	MOVLW " "			; but if Z=1, set Vmult for blank
	MOVWF Vmult
	MOVLW "."			; and also set VDig2 as Volts DP
	MOVWF VDig2 
	copy VoltEXP, AEXP	; now copy Volts value into AARG
	copy VoltB0, AARGB0
	copy VoltB1, AARGB1
	CLRF AARGB2			; make sure AARGB2 is cleared
	CALL SetBARG10k		; set BARG for initial multiply by 10k
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy ones, VDig1	; and place result in VDig positions
	copy tenths, VDig3
	copy hundredths, VDig4
	copy thousandths, VDig5	; (we ignore 5th digit here)
	GOTO dBVpart		; done, so skip to dBV display section

DefmV:
	MOVLW "m"			; VoltEXP not >=7Fh, nor <78h
	MOVWF Vmult			; so process for display in mV
	MOVLW "."			; setting VDig4 as DP
	MOVWF VDig4			; for correct mV display
	copy VoltEXP, AEXP	; now copy Volts value into AARG
	copy VoltB0, AARGB0
	copy VoltB1, AARGB1
	CLRF AARGB2			; make sure AARGB2 is cleared
	CALL SetBARG10k		; set BARG for initial multiply by 10k
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy tenths, VDig1	; and place result in VDig positions
	copy hundredths, VDig2
	copy thousandths, VDig3
	copy tenthous, VDig5	; (we ignore ones digit here)

;	Next we work out the sign for dBV display
dBVpart:
	BTFSS dBVB0,7		; test sign bit 7 of dBVB0 byte
	GOTO $+4			; if positive (b7=0), go make "+"
	BCF dBVB0,7			; but if negative (b7=1), clear it
	MOVLW "-"			; before making a "-" for display
	GOTO $+2
	MOVLW "+"
	MOVWF dBVSign		; now place into dBVSign

;	Next we work out the digits for dBV display
	copy dBVEXP, AEXP	; now copy dBV value into AARG
	copy dBVB0, AARGB0
	copy dBVB1, AARGB1
	CLRF AARGB2			; make sure AARGB2 is cleared
	CALL SetBARG1k		; set BARG for initial multiply by 1000
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy ones, dBVDig1	; and place result in dBVDig positions
	copy tenths, dBVDig2
	copy hundredths, dBVDecimal	; (we ignore 4th & 5th digits here)

;	Next we work out the sign for dBm display
	BTFSS dBmB0,7		; test sign bit 7 of dBmB0 byte
	GOTO $+4			; if positive (b7=0), go make "+"
	BCF dBmB0,7			; but if negative (b7=1), clear it
	MOVLW "-"			; before making a "-" for display
	GOTO $+2
	MOVLW "+"
	MOVWF dBmSign		; now place into dBmSign

;	Finally we work out the digits for dBm display
	copy dBmEXP, AEXP	; now copy dBm value into AARG
	copy dBmB0, AARGB0
	copy dBmB1, AARGB1
	CLRF AARGB2			; make sure AARGB2 is cleared
	CALL SetBARG1k		; set BARG for initial multiply by 1000
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	copy ones, dBmDig1	; and place result in dBmDig positions
	copy tenths, dBmDig2
	copy hundredths, dBmDecimal ; (we ignore 4th & 5th digits here)
	Ppage0
	RETURN				; all done now (whew!), so return

;	------------------------------------------------------------
RangeChange:
;	routine to change measurement range (ADC input) and LED
;	indication when button S1 is pressed for more than 160ms
;	also changes 24-bit range factor to suit new range
	CALL Delay160ms		; first wait 160ms to ensure valid keypress
	BTFSC PORTA,4		; skip if RA4 is still low
	RETURN				; otherwise leave without changing
	DECFSZ RangeCtr,1	; still low, so decrement RangeCtr, skip if 0
	GOTO $+3			; otherwise go change range
	MOVLW h'03'			; did go to zero, so reset to 3
	MOVWF RangeCtr		; then change range
	MOVLW h'03'			; now check if RangeCtr = 3
	XORWF RangeCtr,0
	BTFSS STATUS,Z		; skip if result was zero (i.e., match)
	GOTO TryTwo			; otherwise keep looking
	BSF PORTB,7			; it is range 3 (+0dBV), so turn off LED2
	BSF PORTB,6			; and LED3, then
	BCF PORTA,7			; turn on LED1
	BCF ADCON0,3		; also set ADC input to AN2
	BSF ADCON0,4
	MOVLW h'85'			; and reset range factor to value
	MOVWF RFactEXP		; of 100d in 24-bit FP form (pre calc)
	MOVLW h'48'			; (which is 85 48 00 -- from Fprep)
	MOVWF RFactB0
	CLRF RFactB1
	RETURN				; before leaving
TryTwo:
	MOVLW h'02'			; next check if RangeCtr = 2
	XORWF RangeCtr,0
	BTFSS STATUS,Z		; skip if result was zero (i.e., match)
	GOTO MustBOne		; otherwise move on
	BSF PORTA,7			; it is range 2 (-20dBV), so turn off
	BSF PORTB,6			; LED1 and LED3, then
	BCF PORTB,7			; turn on LED2
	BCF ADCON0,4		; also set ADC input to AN1
	BSF ADCON0,3
	MOVLW h'85'			; and reset range factor to value
	MOVWF RFactEXP		; of 80d in 24-bit FP form (pre calc)
	MOVLW h'20'			; (which is 85 20 00 -- from Fprep)
	MOVWF RFactB0
	CLRF RFactB1
	RETURN				; then leave
MustBOne:
	BSF PORTB,7			; must be range 1 (-40dBV), so turn off LED2
	BSF PORTA,7			; and LED1, then
	BCF PORTB,6			; turn on LED3
	BCF ADCON0,3		; also set ADC input to AN0
	BCF ADCON0,4
	MOVLW h'84'			; and reset range factor to value
	MOVWF RFactEXP		; of 60d in 24-bit FP form (pre calc)
	MOVLW h'70'			; (which is 84 70 00 -- from Fprep)
	MOVWF RFactB0
	CLRF RFactB1
	RETURN				; then leave

SetBARG10k:
	; routine to set BARG to 10000d, before calling float_ascii
	; for mV and uW conversions (NNN.N display format)
	MOVLW h'8C'			; exponent first
	MOVWF BEXP
	MOVLW h'1C'			; then MSB/sign byte
	MOVWF BARGB0
	MOVLW h'40'
	MOVWF BARGB1		; then remaining two bytes
	CLRF BARGB2
	RETURN				; and return

SetBARG1k:
	; routine to set BARG to 1000d, before calling float_ascii
	; for Volts and power conversions (N.NNN display format)
	MOVLW h'88'			; exponent first
	MOVWF BEXP
	MOVLW h'7A'			; then MSB/sign byte
	MOVWF BARGB0
	CLRF BARGB1			; then remaining two bytes
	CLRF BARGB2
	RETURN				; and return

SetBARG100:
	; routine to set BARG to 100d, before calling float_ascii
	; for dBV and dBm conversions (NN.N display format)
	MOVLW h'85'			; exponent first
	MOVWF BEXP
	MOVLW h'48'			; then MSB/sign byte
	MOVWF BARGB0
	CLRF BARGB1			; then remaining two bytes
	CLRF BARGB2
	RETURN				; and return

ToggleEN:
	;routine to toggle EN line of LCD, to write an instr or data nibble
	BSF PORTB,4			; take LCD's EN line high (RB4)
	NOP					; pause 1.5us (3mc) to let it stabilise
	NOP
	NOP
	BCF PORTB,4			; then low again, to write into LCD controller
	RETURN				; then return
		
Wait4sec:
;	routine to pause for about 4 seconds, to allow display read
	MOVLW h'19'			; first set Counter4 for 25d loops
	MOVWF Counter4		; (because 25 x 160ms = 4sec)
	CALL Delay160ms		; then wait for 160ms
	DECFSZ Counter4,1	; now decrement Counter4, skip when zero
	GOTO $-2			; otherwise keep looping
	RETURN				; return when done

;	**********************************************************
;	include floating point routines
;	(in FPRF24.TXT)
;
	#include 	<FPRF24.TXT>


 	END


