;	************************************************************
;	MegMeterMk2.asm - firmware for Mk2 version of
;	Digital Megohm and Insulation Leakage 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 30/04/2010.
;	(added 10-reading averaging, leading zero blanking)
;
;	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
MLoopCtr	equ h'24'	; loop counter for measurement averaging
Temp1		equ	h'25'	; working storage location 1
Temp2		equ h'26'	; working storage location 2
VoltFlag	equ h'27'	; test voltage flag
 						; (02h = 1000V, 01h = 500V, 00h = 250V)
MRFlag		equ h'28'	; current range flag (bit 0 = 1 for 10mA)

VDig1		equ h'29'	; first digit of test volts indication (1/ / )
VDig2		equ h'2A'	; second digit of test volts indication (0/5/2)
VDig3		equ h'2B'	; third digit of test volts indication (0/0/5)

IDig1		equ h'2C'	; first digit of leakage current reading
IDig2		equ h'2D'	; second digit of leakage current reading
IDig3		equ h'2E'	; third digit of leakage current reading
IDig4		equ h'2F'	; fourth digit of leakage current reading (m/mu)

RDig1		equ h'30'	; first digit of resistance reading
RDig2		equ h'31'   ; second digit of resistance reading (DP on Hi range)
RDig3		equ h'32'	; third digit of resistance reading

;	storage of the 24-bit FP test voltage (pre calculated)
TVoltEXP	equ h'33'	; exponent
TVoltB0		equ h'34'	; MSB
TVoltB1		equ h'35'	; LSB

;	storage for Ix value in 24-bit form, after calculation
IxEXP		equ h'36'	; exponent
IxB0		equ h'37'	; MSB & sign bit
IxB1		equ h'38'	; LSB

;	storage for Rx value in 24-bit form, after calculation
RxEXP		equ h'39'	; exponent
RxB0		equ h'3A'	; MSB & sign bit
RxB1		equ h'3B'	; and LSB

;	storage for ADC reading in 24-bit form, after calculation
ADCEXP		equ h'3C'	; exponent
ADCB0		equ h'3D'	; MSB & sign bit
ADCB1		equ h'3E'	; 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 itself now begins
;
	org	h'00'		; normal start vector
	GOTO Initialise
	org h'04'		; interrupt service vector
	GOTO Initialise	; (we are not using interrupts here)

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
	CLRF PORTB		; also PORTB
	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)
	MOVLW h'40'		; then set RB0-5, RB7 as outputs, RB6 as an input
	MOVWF TRISB
	MOVLW h'9D'		; also set RA0, RA2-4, RA7 as inputs, RA1,RA5-6 as outputs
	MOVWF TRISA
	MOVLW h'0C'		; then set RA2 to be AN2, RA3 to be Vref+ input 
	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
	MOVLW h'11'		; now turn on ADC, make Tad = Toscx4, reset ADC
	MOVWF ADCON0	; and set AN2 as only ADC input channel
	CALL SetVolts	; now check S1, set TestVolt flag and constant values 
	CALL DispInit	; now initialise the LCD module
	CALL Display1	; and show greeting display
	CALL Wait4sec	; then pause for 4 seconds
	CALL Display2	; now display 'begin test' information
	CALL SetHiRange	; then ensure we're initially set for 10mA range

Mainloop
;	Main operating loop begins here, to process and display
;	readings via the ADC and also sense S1 (test voltage range)
;	We take a reading via the ADC module. Note that
;	result appears in ADRESH (1Eh) and ADRESL (9Eh) registers

	CALL InitDig		; first initialise VDig, IDig, RDig display digits
	CALL SetVolts		; then check S1, set TestVolt flag & constant

	BTFSS PORTA,4		; now test RA4 to see if S2 is pressed
	GOTO $-1			; else keep looping until it is pressed
	CALL TakeaReading	; once S2 pressed, go take a set of 10 readings
	CALL Display3		; then update LCD display
						; (a delay here would slow down sampling rate)
	GOTO Mainloop		; and keep looping
 
;	main program loop 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)
	BTFSS FPFLAGS,FDZ	; check if divide-by-zero flag is set
	GOTO $+8			; if not, must be some other error
	MOVLW h'88'			; but if it is, just set Rtotal to 1010M
	MOVWF AEXP			; (88 7C 80 in Microchip 24b FP format)
	MOVLW h'7C'			; which we place in AARG regs so it will
	MOVWF AARGB0		; be used in calculating Rx
	MOVLW h'80'			; (so O/C input should display as Ix = 0,
	MOVWF AARGB1		;  and Rx = 999)
	RETURN				; then resume
	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 250us 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'7D'			; set delay counter for 125 loops
	MOVWF Counter1		; (should give about 125 x 4 x 0.5 = 250us)
	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 "SC Megohm Meter"
	CALL DisplayData
	MOVLW "C"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "M"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "g"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW "h"
	CALL DisplayData
	MOVLW "m" 
	CALL DisplayData
	MOVLW " "
	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
	MOVLW h'C0'			; now move down to line 2
	CALL DispAddress
	MOVLW " "			; and display " & Leakage Meter"
	CALL DisplayData
	MOVLW "&"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "L"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "a"
	CALL DisplayData
	MOVLW "k"
	CALL DisplayData
	MOVLW "a"
	CALL DisplayData
	MOVLW "g"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "M"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	RETURN				; before leaving

Display2:
	; routine to display Test begin info on LCD
	MOVLW h'80'			; first set address to line 1, char 0
	CALL DispAddress	; (also delays for 160us)
	MOVLW "S"			; then send "Set Volts, Press"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "V"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "s" 
	CALL DisplayData
	MOVLW ","
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "P"
	CALL DisplayData
	MOVLW "r"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "s"
	CALL DisplayData
	MOVLW "s"
	CALL DisplayData
	MOVLW h'C0'			; now move down to line 2
	CALL DispAddress
	MOVLW "b"			; and display "button to Test: "
	CALL DisplayData
	MOVLW "u"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW "n"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "o"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "T"
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "s"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW ":"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	RETURN				; before leaving

Display3:
	; routine to display measurement info on LCD
	MOVLW h'80'			; set address to line 1, char 0
	CALL DispAddress	; (also delays for 160us)
	MOVLW "T"			; then send "Test Volts="
	CALL DisplayData
	MOVLW "e"
	CALL DisplayData
	MOVLW "s"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "V"
	CALL DisplayData
	MOVLW "o" 
	CALL DisplayData
	MOVLW "l"
	CALL DisplayData
	MOVLW "t"
	CALL DisplayData
	MOVLW "s"
	CALL DisplayData
	MOVLW "="
	CALL DisplayData
	MOVF VDig1,0		; followed by VDig1, VDig2 & VDig3
	CALL DisplayData
	MOVF VDig2,0
	CALL DisplayData
	MOVF VDig3,0
	CALL DisplayData
	MOVLW "0"			; and then "0V"
	CALL DisplayData
	MOVLW "V"
	CALL DisplayData
	MOVLW h'C0'			; now move down to line 2
	CALL DispAddress
	MOVLW "I"			; then show "Ix="
	CALL DisplayData
	MOVLW "x"
	CALL DisplayData
	MOVLW "="
	CALL DisplayData
	MOVF IDig1,0		; followed by IDig1-3
	CALL DisplayData
	MOVF IDig2,0		; (IDig2 = decimal point on hi range)
	CALL DisplayData
	MOVF IDig3,0
	CALL DisplayData
	MOVF IDig4,0		; and IDig4 (= m on hi range, mu on low range)
	CALL DisplayData
	MOVLW "A"
	CALL DisplayData
	MOVLW " "
	CALL DisplayData
	MOVLW "R"
	CALL DisplayData
	MOVLW "="
	CALL DisplayData
	MOVF RDig1,0		; followed by RDig1-3
	CALL DisplayData
	MOVF RDig2,0		; (RDig2 = "." on hi range)
	CALL DisplayData
	MOVF RDig3,0
	CALL DisplayData
	MOVLW "M"			; and ending with "Mohm"
	CALL DisplayData
	MOVLW h'F4'
	CALL DisplayData
	RETURN				; before leaving

ErrorDisp:
	; routine to display "Error" on LCD, then pause & return
	; (called only if FP functions flag an error other than
	;  divide-by-zero error which does occur with O/C input)
	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

InitDig:
;	routine to initialise display digits for VDig, IDig, RDig
;	(setting multiplier IDig4 according to the range)
	CALL SetVolts		; first go check S1 & set volts digits etc
	MOVLW h'20'			; now load IDig1-3 with blank code
	MOVWF IDig1
	MOVWF IDig2
	MOVWF IDig3
	MOVWF RDig1			; also RDig1-3
	MOVWF RDig2
	MOVWF RDig3
	BTFSS MRFlag,0		; then test range flag (bit 0), skip if 1
	GOTO LowRange		; if not set, go to low range setup
	MOVLW "m"			; on high range, so make IDig4 = "m"
	MOVWF IDig4
	MOVLW "."			; and IDig2 = "." (as decimal point)
	MOVWF IDig2
	RETURN				; then return
LowRange:
	MOVLW h'E4'			; on low range, so make IDig4 = micro symbol
	MOVWF IDig4
	RETURN				; before returning

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

;	---------------------------------------------------------------

SetBARG10k:
	; routine to set BARG to 10000d, before calling float_ascii
	; for Rx conversions (N.NN 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 Rx conversions (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 Ix conversions (NNN 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

SetBARG10d:
	; routine to set BARG to 10d, for use in TakeaReading
	; routines (10d = 82 20 00 00, precalc using Fprep)
	MOVLW h'82'			; exponent first
	MOVWF BEXP
	MOVLW h'20'			; then MSB/sign byte
	MOVWF BARGB0
	CLRF BARGB1			; then remaining two bytes
	CLRF BARGB2
	RETURN				; and return

;	---------------------------------------------------------------
SetHiRange:
;	routine to set the PIC for measurement on the 0-10mA range

	BSF MRFlag,0	; set range flag for 10mA (9.9mA) range
	BCF PORTA,1		; also drop RA1 to turn on relay
	CALL InitDig	; then re-initialise digits & multiplier
	RETURN			; then return

SetLoRange:
;	routine to set the PIC for measurement on the 0-100uA range

	BCF MRFlag,0	; set range flag for 100uA range
	BSF PORTA,1		; also raise RA1 to turn off relay
	CALL InitDig	; then re-initialise digits & multiplier
	RETURN			; then return

;	---------------------------------------------------------------
SetVolts:
	; routine to check position of S1, set TestVolt flag for setting
	; and also set VDig1-3 and Test Voltage Constant value
	; for calculation of Rx
	BTFSC PORTA,7		; begin by testing RA7, skipping if clear
	GOTO Try500V		; but if set, go look for 500V setting
	CLRF VoltFlag		; must be set for 1000V, so set flag bits
	BSF VoltFlag,1		; (02h = 1000V)
	MOVLW h'31'			; then set VDig1-3 to "100"
	MOVWF VDig1
	MOVLW h'30'
	MOVWF VDig2
	MOVLW h'30'
	MOVWF VDig3
	MOVLW h'88'			; also set 24-bit TVolt values for 1000V
	MOVWF TVoltEXP		; first exponent
	MOVLW h'7A'
	MOVWF TVoltB0		; then MSB/sign byte
	MOVLW h'00'
	MOVWF TVoltB1		; and finally LSbyte
	RETURN				; then return
Try500V:
	BTFSC PORTA,0 		; now skip if RA0 is clear
	GOTO Mustbe250V		; but if set, go set up for 250V
	CLRF VoltFlag		; must be set for 500V, so set flag bits
	BSF VoltFlag,0		; (01h = 500V)
	MOVLW h'20'			; then set VDig1-3 to " 50"
	MOVWF VDig1
	MOVLW h'35'
	MOVWF VDig2
	MOVLW h'30'
	MOVWF VDig3
	MOVLW h'87'			; also set 24-bit TVolt values for 500V
	MOVWF TVoltEXP		; first exponent
	MOVLW h'7A'
	MOVWF TVoltB0		; then MSB/sign byte
	MOVLW h'00'
	MOVWF TVoltB1		; and finally LSbyte
	RETURN				; then return
Mustbe250V:
	CLRF VoltFlag		; since S1 is set for 250V, clear flag bits
	MOVLW h'20'			; then set VDig1-3 to " 25"
	MOVWF VDig1
	MOVLW h'32'
	MOVWF VDig2
	MOVLW h'35'
	MOVWF VDig3
	MOVLW h'86'			; also set 24-bit TVolt values for 250V
	MOVWF TVoltEXP		; first exponent
	MOVLW h'7A'
	MOVWF TVoltB0		; then MSB/sign byte
	MOVLW h'00'
	MOVWF TVoltB1		; and finally LSbyte
	RETURN				; then return

;	---------------------------------------------------------------

TakeaReading:
;	routine to use the PIC's ADC module to take 10 readings, which
;	are added together and then divided by 10 to get an average. Then 
;	we check to see if an uprange is needed (ADC average = 1023) or
;	a downrange is needed (ADC average <10). Then it upranges or
;	downranges if necessary, before taking another set of readings and
;	then (or otherwise) calculating the corresponding leakage current
;	(Ix) and leakage resistance (Rx) values ready for display.

	MOVLW h'0A'			; first set MLoopCtr for a loop/sequence
	MOVWF MLoopCtr 		; of 10 readings
	MOVLW h'76'			; then set ADCEXP, ADCB0 and ADCB1 for a near
	MOVWF ADCEXP		; zero initial number (76 03 13 = 0.0020000338,
	MOVLW h'03'			; which is close enough to zero for our purposes)
	MOVWF ADCB0
	MOVLW h'13'
	MOVWF ADCB1

MeasLoop:
	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
	MOVF ADRESH,0		; next 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)
	copy ADCEXP,BEXP	; now AARG reg have ADC reading, so bring
	copy ADCB0,BARGB0	; accumulated readings into BARG regs
	copy ADCB1,BARGB1
	CALL FPA24			; now add the two together using FPA24
	CALL Check4FPE		; (duck away to check for any FP errors)
	copy AEXP,ADCEXP	; then save sum back into ADC reading regs
	copy AARGB0,ADCB0	; (so sum of readings will be accumulating
	copy AARGB1,ADCB1	;  in these regs)
	DECFSZ MLoopCtr,1	; decrement loop counter, skip if it -> 0
	GOTO MeasLoop		; otherwise loop back for another reading
	CALL SetBARG10d		; done 10, so now load dec10 into BARG regs
	copy ADCEXP, AEXP	; and bring sum of the 10 readings into AARG regs
	copy ADCB0,AARGB0
	copy ADCB1,AARGB1
	CALL FPD24			; then call FPD24 to divide the sum by 10
	CALL Check4FPE		; (duck away to check for any FP errors)
	copy AEXP,ADCEXP	; then save average back into ADC reading regs
	copy AARGB0,ADCB0	; (so these regs will now have the average
	copy AARGB1,ADCB1	; of the 10 readings, in 24b FP form)

;	AARG regs still have 24b version of average reading, so now check 
;	for full scale reading, in case upranging may be needed 
	MOVLW h'88'			; now load 24b FP version of 1023
	MOVWF BEXP			; (pre calc using Fprep) into BARG regs
	MOVLW h'7F'			; so it can be used as subtractant
	MOVWF BARGB0		; (to see if we should uprange)
	MOVLW h'C0'
	MOVWF BARGB1
	CALL FPS24			; now subtract BARG from AARG (-> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  AARG regs now have (average ADC result - 1023)
	BTFSS AARGB0,7		; so check sign bit of AARGB0, skip if set (neg result)
	GOTO ChekHirange	; positive result, so we do have a FS reading
	copy ADCEXP,AEXP	; neg result, so bring back average ADC reading
	copy ADCB0,AARGB0	; into AARG regs
	copy ADCB1,AARGB1	
	CALL SetBARG10d		; and load BARG regs with 10d in 24b FP form
	CALL FPS24			; now subtract BARG from AARG (-> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  AARG regs now have (ADC result - 10)
	BTFSS AARGB0,7		; so check sign bit of AARGB0, skip if set (neg result)
	GOTO KeepGoing		; otherwise continue to process (reading > 10)

	BTFSS MRFlag,0		; reading is <10, so check if we are in low range
	GOTO KeepGoing		; flag clear, so OK to continue (in low range already)
	CALL SetLoRange		; range flag was set, so swing down to lo range
	GOTO TakeaReading	; and go back for another set of readings

ChekHirange:
	BTFSC MRFlag,0		; FS reading, but check if we are already in hi range
	GOTO KeepGoing		; flag is set, so OK to continue (in hi range already)
	CALL SetHiRange		; flag is clear, so swing up to hi range
	GOTO TakeaReading	; and go back for another set of readings

KeepGoing:
;	reading average in normal value range or at least we're on the right
;	range, so proceed to process reading and calculate leakage current
	copy ADCEXP,AEXP	; first bring back ADC reading
	copy ADCB0,AARGB0	; into AARG regs
	copy ADCB1,AARGB1
	BTFSS MRFlag,0		; are we on high range or low range?
	GOTO Lower			; flag clear, so we must be on low range
	MOVLW h'85'			; flag set, so set FP divisor for
	MOVWF BEXP			; 0-10mA conversion (854635 = 99.103515)
	MOVLW h'46'			; (cf theoretical divisor = 99.103125)
	MOVWF BARGB0
	MOVLW h'35'
	MOVWF BARGB1
	CALL FPD24			; now do the division AARG/BARG (-> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  AARG regs have Ix reading in mA (0.0 - 9.9mA)
	copy AEXP, IxEXP 	; so save them in Ix regs
	copy AARGB0, IxB0
	copy AARGB1, IxB1	; (but also leaves them in AARG)

	MOVLW h'82'			; now we do need to check if Ix >9.9mA
	MOVWF BEXP			; so load BARG with 24b version of decimal 9.9
	MOVLW h'1E'			; (9.9021d = 821E6F)
	MOVWF BARGB0
	MOVLW h'6F'
	MOVWF BARGB1
	CALL FPS24			; then subtract 9.9 from Ix (AARG - BARG -> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						now AARG regs now have Ix - 9.9, in 24b form
	BTFSC AARGB0,7		; so check if sign bit in AARGB0 is 1 or not
	GOTO NotOverFSD		; if it is, neg result so proceed (Ix < 9.9mA) 
	MOVLW "9"			; but if not, pos result so Ix > 9.9; fix it
	MOVWF IDig1			; by changing digits to "9.9"
	MOVLW "."
	MOVWF IDig2
	MOVLW "9"
	MOVWF IDig3
	GOTO ScaleupmA		; then skip over Ix calculation

NotOverFSD:
	copy IxEXP, AEXP	; restore Ix to AARG regs
	copy IxB0, AARGB0
	copy IxB1, AARGB1
	CLRF AARGB2			; next make sure AARGB2 is cleared, just in case
	CALL SetBARG10k		; set BARG for initial multiply by 10000
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy ones, IDig1	; and place result in IDig positions
	copy tenths, IDig3
						; (we don't use third/fourth/fifth digits)
ScaleupmA:
	copy IxEXP, AEXP	; now bring back Ix values (in mA) into AARG regs
	copy IxB0, AARGB0
	copy IxB1, AARGB1
	CALL SetBARG1k		; set BARG for multiply by 1000d
	CALL FPM24			; then multiply them (so AARG will have Ix in uA)
	CALL Check4FPE		; (duck away to check for any FP errors)
	copy AEXP, IxEXP 	; then re-save the revised Ix values (now in uA)
	copy AARGB0, IxB0
	copy AARGB1, IxB1
	GOTO CalculateRx	; now skip to Rx calculation section

Lower:
 	MOVLW h'82'			; on lower range, so set FP divisor for	
	MOVWF BEXP			; 0-100uA conversion (821E91 = 9.9104)
	MOVLW h'1E'			; (cf theoretical divisor = 9.9103125)
	MOVWF BARGB0
	MOVLW h'91'
	MOVWF BARGB1
	CALL FPD24			; now do the division AARG/BARG (-> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						  AARG regs have Ix reading in uA (00.00 - 99.99uA)
	copy AEXP, IxEXP 	; so save them in Ix regs
	copy AARGB0, IxB0
	copy AARGB1, IxB1

	CLRF AARGB2			; make sure AARGB2 is cleared, just in case
	CALL SetBARG1k		; set BARG for initial multiply by 1000
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	Ppage0
	copy ones, IDig2	; and place result in IDig positions
	copy tenths, IDig3
						; (we don't use third/fourth/fifth digits)
	MOVLW h'20'			; then make IDig1 a space
	MOVWF IDig1
	MOVLW h'30'			; now we look in case IDig2 & IDig3 = "0"
	XORWF IDig2,0		; by checking them -- IDig2 first
	BTFSS STATUS,Z		; if Z=1, we had a match (IDig2 = "0")
	GOTO CalculateRx	; but if Z=0, IDig2 wasn't "0" - just move on
	MOVLW h'20'			; IDig2 was "0", so blank it
	MOVWF IDig2			; (i.e., blank leading zero)
	MOVLW h'30'			; and now check IDig3 as well
	XORWF IDig3,0
	BTFSS STATUS,Z		; if Z=1, we had a match (IDig3 = "0" also)
	GOTO CalculateRx	; but if Z=0, IDig3 wasn't "0" - so move on
	MOVLW h'88'			; both are "0", so set Rx regs for 999M#
	MOVWF RxEXP			; (because we can't divide by zero)
	MOVLW h'79'			; (999d = 8879C0 in MTI 24-bit FP format)
	MOVWF RxB0			; (pre calc using Fprep)
	MOVLW h'C0'
	MOVWF RxB1
	GOTO MakeRxDigs		; and just skip to display them

CalculateRx:
	copy IxEXP,BEXP		; next copy Ix values (in uA) into BARG regs
	copy IxB0,BARGB0	; so we can use it as divisor for next step
	copy IxB1,BARGB1

	copy TVoltEXP,AEXP	; then copy TVolt reg values into AARG regs
	copy TVoltB0,AARGB0
	copy TVoltB1,AARGB1
	CALL FPD24			; now do the division AARG/BARG (TVolt/Ix) 
	CALL Check4FPE		; (duck away to check for any FP errors)
;						 so AARG regs now have Rtotal in Megohms

	MOVLW h'78'			; now load 24b FP version of 0.01010108
	MOVWF BEXP			; (pre calc using Fprep) into BARG regs
	MOVLW h'25'			; so it can be used as subtractant
	MOVWF BARGB0		; (to allow for internal 0.0101M)
	MOVLW h'7F'
	MOVWF BARGB1
	CALL FPS24			; and subtract BARG from AARG (-> AARG)
	CALL Check4FPE		; (duck away to check for any FP errors)
;						AARG regs now have Rtotal - 0.0101M, = Rx (Megohms)
	BTFSS AARGB0,7		; now check sign bit of AARGB0
	GOTO $+8			; if bit not set, just continue (pos result)
	MOVLW h'72'			; but if bit is set (neg result), make Rx zero
	MOVWF RxEXP			; (or effectively zero: +0.00016)
	MOVLW h'27'
	MOVWF RxB0
	MOVLW h'C6'
	MOVWF RxB1
	GOTO MakeRxDigs		; and skip final transfer	
	copy AEXP,RxEXP		; + result, so transfer result from AARG regs
	copy AARGB0,RxB0	; into Rx regs (24-bit number, value 000-999)
	copy AARGB1,RxB1	; this is value of Rx, in megohms

MakeRxDigs:
;	Last section - using the calculated 24-bit FP values to produce
;	the display chars for Rx (000-999M# or 0.0 - 9.9M# on hi range)
	copy RxEXP, AEXP	; first copy 24-bit Rx values into AARG
	copy RxB0, AARGB0
	copy RxB1, AARGB1
	CLRF AARGB2			; make sure AARGB2 is cleared
	BTFSC MRFlag,0		; now check whether hi or lo current range
	GOTO HiRangeFmt		; if hi range, go to format as "N.N M#"
	CALL SetBARG100		; lo range, so set BARG for initial multiply by 100
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	copy ones, RDig1	; and place result in RxDig positions
	copy tenths, RDig2
	copy hundredths, RDig3 ; (we ignore 4th & 5th digits here)
	Ppage0
	MOVLW h'30'			; before we leave, check if we have leading zeroes
	XORWF RDig1,0		; in the first two positions, & blank them if so
	BTFSS STATUS,Z		; (will have Z=1 if RDig1 = "0")
	RETURN				; if not, just return
	MOVLW h'20'			; since RDig1 was a zero, make it a blank
	MOVWF RDig1
	MOVLW h'30'			; now try RDig2
	XORWF RDig2,0
	BTFSS STATUS,Z		; (will have Z=1 if RDig2 = "0")
	RETURN				; if not, just return
	MOVLW h'20'			; but if RDig2 was zero too, blank it as well
	MOVWF RDig2
	RETURN				; before returning
HiRangeFmt:
	CALL SetBARG10k		; hi range, so set BARG for initial multiply by 10k
	Ppage1
	CALL float_ascii	; then call FP-ASCII conversion routine
	copy ones, RDig1	; and place result in RxDig positions
	copy tenths, RDig3
	Ppage0
	MOVLW "."			; also make RDig2 a decimal point
	MOVWF RDig2
	RETURN				; all done now, so 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 2us (4mc) to let it stabilise
	NOP
	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 reading display
	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

