;****************************************************************************************************
;                              		    William Lazure									*
;								   W2EB - 15 Mar 13									*
;****************************************************************************************************
;****************************************************************************************************		
; * 26 Nov 14 - This program read a forward ADC and a reverse ADC.  It then adds the two readings,	*
; *             subtracts the two readings, and divides these two results.  It then multiplies 	*
; *             that number by ten to allow the display to show tenths.  It formats this number 	*
; *             into three AASCI digits suitable for display on the LCD.  The three digits 		*											
; *             are used to fill a bar graph on the bottom of the LCD that roughly corresponds		*
; *             to the SWR.  Finally, the battery voltage is monitored through a voltage divider    *
; *             and sent to another ADC for battery monitoring.								*
; *			***Verified Functional													*
;****************************************************************************************************
;							 		 |-----____-----|								*
;			     MCLR		       	     -|			 |-	LCD DB7						*
;				SWR(+) ADC			-|			 |-	LCD DB6						*
;				SWR(-) ADC			-|			 |-	LCD DB5						*
;				SPARE				-|			 |-	LCD DB4						*
;				SPARE				-|			 |-	LCD DB3						*
;				N/C					-|			 |-	LCD DB2						*
;				Battery				-|			 |-  LCD DB1	      				*
;				GND					-|			 |-	LCD DB0						*
;				SPARE				-|			 |-  V+							*
;				SPARE				-|			 |-  GND							*
;				SPARE				-|			 |-	LCD "ENABLE"					*
;				SPARE				-|			 |-	LCD READ/WRITE SELECT			*
;				SPARE				-|			 |-	LCD DATA/INSTRUCTION SELECT		*
;				SPARE				-|			 |-	SPARE						*
;									 |--------------|								*					
;****************************************************************************************************

        	processor       PIC18F2520
			#include		<P18F2520.inc>

;****************************************************************************************************
;******************************		DECLARE CONSTANTS      **************************************
;****************************************************************************************************
; The "EQU" statement is used to define constants that are substituted in the program for the value	*
; they represent.  For example, LCDENBL equals 7.  In the program, whenever LCDENBL appears, a "7"  *
; is actually called there.	 For example, BUSY EQU 7 is the pin number of the Busy signal on PORTB*
; ***************************************************************************************************

;Port B pin Representations
BUSY 		equ    	07             ;LCD busy bit 

;Port C pin Representations
LCDENBL   	equ     07             ;LCD Enable Pin
LCDRW  		equ     06             ;LCD Read/Write Pin
LCDRS  		equ     05             ;LCD Register Status Pin
z			equ		02
c			equ		00

;****************************************************************************************************
;************************************  DECLARE VARIABLES   ******************************************
;****************************************************************************************************
; The "CBLOCK" directive tells MPASM to automatically assign addresses to all entries that follow	*	
; it starting with the address next to the directive and continuing sequentially.	               *
; ***************************************************************************************************

        CBLOCK  0x000
  			counter				;Used to control Binary to ASCII conversion
			huns					;ASCII Hundreds digit for the LCD
			tens					;ASCII Tens digit for the LCD
			ones   	 	        	;ASCII Ones digit for the LCD  
            RESULT 	   				;Result of the division, Lower-Byte
			RESULT1				;Result of the division, Upper byte
            DIVISOR 	  			; Divisor (bottom part) Lower-Byte
			DIVISOR1				; Divisor Upper-Byte
			MULRES				;Storage for multiplier value, Lower-Byte
			MULRES1				;Storage for multiplier value, Upper-Byte
            DIVIDEND 	 			; Dividend (top part) Lower-Byte
			DIVIDEND1				; Dividend Upper-Byte
			TEMP1				; Counter for Multiplier routine
			HBYTE				;Data storage for all ADC reads
			LBYTE				; "		"		"
        	HBYTEFWD 					;Holds raw Upper-Byte information from the FWD ADC read		                   
        	LBYTEFWD 		       			;Holds raw Lower-Byte information from the FWD ADC read
			HBYTEREV					;Holds raw Upper Byte information from the Rev ADC Read
			LBYTEREV					;Holds raw Lower-Byte information from the Rev ADC Read
			HBYTEBAT					;Holds raw "Range" information from the Power ADC read
			LBYTEBAT					;Holds value information from the Power ADC read
			Batt1					;Battery Condition Display Digits
			Batt2					;Battery Condition Display Digits
			Batt3					;Battery Condition Display Digits
        	TEST      		   			;Holds Range information from the HIGHTBL table
        	DIGIT1   						;The LSD sent to the LCD. Called from DIGTBL1              
        	DIGIT2    		      		;The second digit sent to the LCD. Called from DIGTBL2
        	DIGIT3						;The MSD of the LCD.  Called from DIGTBL3
        	CHAR     		       			;Used to transfer a character to the write subroutines     
        	TEMP 						;Temporarily holds the LCD word for "Busy Status" testing                 
       		COUNT1  					;These two variables Hold the values that are counted                  
        	COUNT2    					;  down for the Delay routines.                 
        	RSFLAG						;Used to tell whether we're writing Data or Instructions.                  
			tbloffst					;Used to compute the Table-Jump address
			grphhuns
			grphtens					;Holder for the Solid blocks used in the Bar Graph
			grphones					;Holder for the custom character used in the Bar Graph
			SPCCNTR					;Counter that ensures each digit of the Bar Graph is written
			Graph
	      ENDC                      

; *******************************************************************************
;    The ORG 0x00 command tells the assembler the official "power-Up" start of	* 
;	the program in case of internal fault.						    		*
; *******************************************************************************            

        ORG     0x0000                

;********************************************************************************
;***************     DEVICE "OVERHEAD" (Configuration and Setup)        *********
;********************************************************************************

	  	clrf    INTCON      		;Clear the interrupt controller
      	clrf	BSR      				;Work from registry bank 1

		movlw	0x5E				;Set up for a 2 MHz internal Clock
		movwf	OSCCON

		movlw	0x08		
		movwf	_CONFIG1H			;set for internal oscillator, A6 & A7 are I/O pins

		movlw	0x01
		movwf	_CONFIG2L			;Brown-Out Reset off, Power-Up Timer Disabled

		clrf	_CONFIG2H				;Watchdog Timer Disabled

		movlw	0x81	
		movwf	_CONFIG3H			;Pin 1 is MCLR, CCP2 input/output is multiplexed with RC1 

		Movlw	0x84
		movwf	_CONFIG4L			;Turn off various accessories

		movlw	0x07
		movwf	CMCON			;Turn off the comparators

		movlw	0x80				;Turn on internal pullup resistors on PORTB
		movwf	INTCON2

		clrf	CCP2CON				;Turn CCP functions off

		clrf	PORTA				;Clear all data from Port A pins	

		movlw	0xFF
		movwf	TRISA			;Set Port A up for the ADC, All pins are inputs

        clrf    TRISB       			;Set Port B up for the LCD data, all pins are outputs

		clrf	TRISC				;Set Port C up for the LCD control, all pins are outputs

;****************************************************************************************************
;Following are steps of the program that need only be called once at the beginning of the program.  *	  *																				  *												
;****************************************************************************************************
     	
   	Call    LCDINIT         			;Initialize the LCD


;****************************************************************************************************
;*******************************	CREATE CUSTOM LCD CHARACTERS	************************************
; This routine creates 5 custom LCD characters.  These characters are actually	just vertical lines.*
; The first character is blank.  The second has a single line at the left of the position, the 	*
; third has two lines starting from the	left side, the fourth has three lines, and so on.  These    *
; characters  correspond to LCD memory addresses 0 through 5. They are used in the Bargraph.		* 
;****************************************************************************************************

		bcf		PORTC,LCDRS			;clear R/S line to send an instruction
		movlw	0x40
		movwf	PORTB				;Place CGRAM starting address command on Port B
		bsf		PORTC,LCDENBL			;Set the LCD Enable Line High
	Call 	SMLDELAY
		bcf		PORTC,LCDENBL			;Lower the LCD Enable line
	Call 	SMLDELAY
		bsf		PORTC,LCDRS			;Set R/S line to send Data
	Call 	SMLDELAY
		movlw	0x00					;Define my first custom Character (a blank)
	Call	Write_Custom
		nop
		nop
		movlw	0x10					;Define my second custom Character
	Call	Write_Custom	
		nop
		nop
		movlw	0x10					;Define my third custom Character
	Call	Write_Custom					; (The same as the second character)
		nop
		nop
		movlw	0x18					;Define my fourth Custom Character		
	Call	Write_Custom
		nop
		nop
		movlw	0x1C					;Define my fifth custom character		
	Call	Write_Custom	
		nop
		nop
		movlw	0x1E					;Define my sixth custom character	
	Call	Write_Custom
		nop
		nop
		movlw	0x1E					;Define my seventh custom character	
	Call	Write_Custom					;  (The same as the sixth)
		nop
		nop
		movlw	0x1F					;Define my eighth custom character		
	Call	Write_Custom
		nop
		nop
		bcf	PORTC,LCDRS				;Return the R/S line to instruction mode
		movlw	0x01					;Reset the cursor to position 1, and reset the display
		movwf	PORTB				;  -(which essentially ends the custom writing)
		bsf	PORTC,LCDENBL				;Set the LCD enable line high
		Call SMLDELAY
		bcf	PORTC,LCDENBL				;Clear the LCD Enable line 

	Call	HEADER1						;Display the Device name "Bill Lazure"
	Call	MIDDELAY						;Hold the Device name for a while
	Call	MIDDELAY
	Call	MIDDELAY
	Call	HEADER2						;Display File Name and Version Number
	Call	MIDDELAY						;Hold this display for a while
	Call 	MIDDELAY
	Call 	MIDDELAY
		movlw	0x01
	Call	SENDINSTR						;Clear the display
	
;****************************************************************************************************
;								The Main Program Loop								*
;																				*
;****************************************************************************************************
;****************************************************************************************************

Main									;Return point for the Main Program Loop
		CLRF	DIVIDEND					;Clear variables
		CLRF	DIVIDEND1					;		"
		CLRF	DIVISOR					;		"
		CLRF	DIVISOR1					;		"
		clrf	TEST						;**Added in V30 as a safety measure
		bcf		STATUS,C				;Clear registers
		bcf		STATUS,N				;	"
	Call	ADCREADBAT					;Read the Battery Voltage
	Call 	ADCREADREV				;Read Reverse Voltage
	Call 	ADCREADFWD				;Read Forward Voltage
		CLRF	STATUS,C

;****************************************************************************************************
;***********************************		16-bit Add	*****************************************
;	This routine takes the data from the Forward and Reverse ADC Reads, and add is all together  	*
;   into a 16-bit word in Dividend and Dividend1											*
;****************************************************************************************************

		movff	LBYTEREV, DIVIDEND		;Move LBYTEREV into Dividend
		movff	LBYTEFWD,WREG
		addwf	DIVIDEND				;Add LBYTEFWD and Dividend
		movff	HBYTEREV,DIVIDEND1		;Move HBYTEREV into DIVIDEND+1
		movff	HBYTEFWD,WREG
		addwfc	DIVIDEND1				;Add Dividend+1, Hbytefwd, and any carry together
		
;****************************************************************************************************

	Call	Multiply						;Multiply Dividend by 10 to enable a "xx.x" 
									; display format for SWR

;****************************************************************************************************
;**********************		16-bit Subtract		**********************************************
;  This routine subtracts the data from the Forward and Reverse (FWD - REV) ADC					*
;  Reads and puts it into a 16-bit word, Divisor & Divisor1									*
;****************************************************************************************************
		bcf		STATUS,N
		movff	LBYTEFWD, DIVISOR
		MOVFF	HBYTEFWD, DIVISOR1

		movff	LBYTEREV, WREG			;Move LBYTEFWD into DIVISOR 
    	SUBWF  	DIVISOR,F        			;Subtract LBYTEFWD(low) - LBYTEREV(low)
   		MOVFF   HBYTEREV,WREG   			; Now get high byte of subtrahend
    	BTFSS   STATUS,C        				; If there was a borrow, rather than
    	INCF    WREG	       				; decrement high byte of dst we inc src
    	SUBWF   DIVISOR1,F      				; Subtract the high byte and we're done
;***************************************************************************************************
	
	Call	Divide						;Divide Dividend by Divisor and place in Result			

		MOVLW	0X00					;Set up to test Result & Result 1 to see if
		CPFSEQ	RESULT1				;result has rolled over 0xFF.  If so, rather
	GOTO	HISWR						;   than display digits, display "SWR HIGH"
	Call	BIN2BCD						;OK to display numbers, so convert binary to
									;   ASCII

	Call	DISPLAYX						;Display ASCII Digits on SWR area

	Call	Write_Graph					;Write the Bargraph
	goto	Main							;The main body of the program is complete.  The remainder 
									;of the program is the subroutines and data tables. This
									;command simply return us to the beginning of the Program 
									;loop again.

;***************************************************************************************************
;*******************		Subroutines, Displays, and Tables				********************
;***************************************************************************************************

; **************************************************************************************************
; ************************** 			LCD INITIALIZATION         *********************************
; -Initialization of the display involves sending several specific commands in a very specific	    *
;  order:															    *
;		-Send 0x30 thrice to tell the LCD we're going to program it.					    *
;		-Tell it whether we'll be writing in 4- or 8-bit mode							    *
;		-Tell it how many lines we wish to use and what type of characters				    * 
;		-Tell it whether we want a visible cursor, and if so, what type					    *
;		-Tell it whether each character emanates from the center, right, or left		         *
;		-Finally, clear the display for use										    *
;  -This subroutine accomplishes initialization.  It need only be called once.  			    *  
;  -Unfortunately, when sending the initial setup codes, the LCD can not be tested to see if it    *
;   has finished.  Delays must be used to allow the LCD time to complete its instructions. That    *
;   makes this routine relatively slow.			    									    *
; **************************************************************************************************

LCDINIT
 	Call    SMLDELAY      				;Wait for LCD to fully power up

        movlw   0x30              			;The first 0x03              
        movwf   PORTB             			;Place the 0x03 on PORTB
        bsf     PORTC,LCDENBL       		;Set the LCD-Enable line high
  	Call    SMLDELAY         			; hold it high for a time
        bcf     PORTC,LCDENBL       		;Clear the LCD-enable line
        movlw   0x30              			;This group of instructions is
        movwf   PORTB             			; exactly the same as the previous.
        bsf     PORTC,LCDENBL       		; It is used to send the second
 	Call    SMLDELAY         			; 0x03.
        bcf     PORTC,LCDENBL      
        movlw   0x30              			;Finally, the third 0x03
        movwf   PORTB             
        bsf     PORTC,LCDENBL       
 	Call    SMLDELAY         
        bcf     PORTC,LCDENBL       
		bcf		PORTC,LCDRS			;Clear the Register-Select Line
		bcf		PORTC,LCDRW			;Clear the Read/Write Line
        movlw   0x38              			;Place it in 8-bit mode, single line, 5X7 character.
        movwf   PORTB             			; and Send the instruction
        bsf     PORTC,LCDENBL       		;Set the LCD-Enable line high
   	Call    SMLDELAY         			; wait a minimum time
        bcf     PORTC,LCDENBL       		;Clear the LCD-Enable line

;***************************************************************************************************
;    At this point, the display is initialized enough that we can use the "write" subroutines.     *
;***************************************************************************************************

        movlw   0x08              			;Turn the Display and cursor off
	Call    SENDINSTR          			;Send the instruction
        movlw   0x01              			;Clear the display
	Call    SENDINSTR          			;Send the instruction
        movlw   0x06              			;Move cursor after each byte is written
  	Call    SENDINSTR          			;Send the instruction
        movlw   0x0C              			;Turn Display on with no visible cursor
  	Call    SENDINSTR          			;Send the instruction
        return                    

;***************************************************************************************************
;************** This routine writes the Custom Characters to the LCD********************************
;**************                                                     ********************************
;***************************************************************************************************
;  Each custom character consists of a matrix of dots: 8 dots vertically by 5 dots horizontally.   *
;  To define each character, we send a series of 8 "words" to the LCD.  Each word represents one   *
;  of the 8 horizontal lines.  Each word is 5 bits wide (right justified), representing the 5      *
;  pixels in each row. In this fashion, my first character, a single vertical line at the left of  *
;  the digit, would be represented by repeating the number b'10000' 8 times.  The next character   *
;  is the two left-most lines, which is simply b'11000' 8 times leading up to a full digit which   *
;  would be b'11111' repeated 8 times. This routine accomplishes the task of sending these to the  *
;  LCD.  The value to be written is sent in the working register.  That value is put on Port B	and *
;  the enable line is raised.  This state is held for a brief period, and the enable line is       *
;  lowered.  Ultimately, the enable line is toggled 8 times, once for each line to be written.	    *
;***************************************************************************************************

Write_Custom
		movwf	PORTB				;Place the information on Port B
		bsf	PORTC,LCDENBL				;Set the LCD Enable line
		movlw	0x01					;Preset the delay routine,
		movwf	COUNT1				;		then
	Call VARLOOP						; run the delay routine
		bcf	PORTC,LCDENBL				;Clear the LCD Enable line
		movlw	0x01	
		movwf	COUNT1				;Repeat the preceding procedure 7 more times
	Call VARLOOP						; (because we're writing a specific "column"
		bsf	PORTC,LCDENBL				; on the character into all 8 rows of that
		movlw	0x01					;  character)
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL	
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bsf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Call VARLOOP
		bcf	PORTC,LCDENBL
		movlw	0x01
		movwf	COUNT1
	Return


;***************************************************************************************************
;*******************  	          Multiply by 10		            *********************************
;*******************		                                     *********************************
;***************************************************************************************************
;-  After an extensive search of the internet, I gave up and made my own 16-bit x 8-bit multiplier *
;   It simply loads a temp register with the 8-bit value and keeps adding the 16-bit to itself that*
;   number of times. This particular sub is set up to multiply by 10, but any 8-bit value COULD be *
;   loaded into it (with the addition of a third "result" byte).				                   *
;	-FULLY TESTED & WORKING OK!!											              *
;***************************************************************************************************
Multiply
		movlw	0x0A					;Place '10' in the loop counter
		movwf	TEMP1

		MOVFF	DIVIDEND,MULRES		;place the dividend into the placeholder
		MOVFF	DIVIDEND1,MULRES1 		;		"
	
Mul		decfsz	TEMP1,f				;decrement the loop counter, skip the next instruction
		goto	ADD						;   if the result is 0
		Return						;Loop counter is zero, we added 10 times, return.

;***************************************************************************************************		
;*  DIVISOR = DIVISOR + MULRES                                                                     *                      
;*  DIVIDEND is replaced, MULRES is preserved, Carry is set correctly                              *
;***************************************************************************************************

ADD									;Add mulres to dividend, leave in dividend
        MOVF    MULRES,W        			;Get low byte
        ADDWF   DIVIDEND,F      			;Add to destination
        MOVF    MULRES1,W       			;Get high byte
        BTFSC   STATUS,C        			;Check for carry
        INCF    MULRES1,W       			;Add one for carry
        ADDWF   DIVIDEND1,F     			;Add high byte into DST
	goto	Mul

; **************************************************************************************************
; **********************           LCD WRITE OPERATIONS     		******************************
; This section performs the actual writing to the LCD.  It begins one of two ways: Send an         *
;  Instruction, or Send Data.  If it is sending an instruction it clears the RS bit;  if data, it  *
;  sets the RS bit.  Once that operation is complete, it tests the LCD to assure that it's ready to*
;  be written. If not, it waits until the LCD says it is ready.  Once ready, the information is    *
;  written.                                                                                        *
; **************************************************************************************************

SENDINSTR   
        movwf   CHAR          			;Save byte to write to LCD
        clrf    RSFLAG          			;Remember to clear RS (clear RSFLAG)   
        bcf     PORTC,LCDRS      			;Set RS for Command to LCD
        goto    WRITELCD         			;Go straight to the write routine, bypassing
									; the following section
SENDDAT  						
        movwf   CHAR          			;Save byte to write to LCD
        bsf     RSFLAG,0        			;Remember to set RS (set bit 0 of RSFLAG)
        bsf     PORTC,LCDRS      			;Set RS for Data to LCD


;***************************************************************************************************
; The next section tests the LCD to see if the previous operation has been	completed.  Rather than *
;  simply use delays which waste time, we ask the LCD if it is done, and wait for it to respond    *
;  affirmatively.							                                                 *
;***************************************************************************************************

WRITELCD 
        clrf    PORTB             			;Clear all old data on PORTB
        movlw   0x80       				;Make pin 3 an input (to read the "busy" pin)             
        movwf   TRISB             	
        bcf     PORTC,LCDRS      			;Set RS pin to the instruction mode
        bsf     PORTC,LCDRW      			;Set the R/W pin to "read"

LCDBUSY
        bsf     PORTC,LCDENBL       		;Set LCD-Enable high
        movff   PORTB,TEMP          		;Move this reading into a temporary register for testing
        bcf     PORTC,LCDENBL       		;Clear the LCD-Enable line
        btfsc   TEMP,BUSY 				;If the "busy" bit in temp is clear, skip the next line
        goto    LCDBUSY       			; If not, keep trying until it is.
        clrf    PORTB             			;The "Busy signal is clear now, so clear Port B
        movlw   0x00              			;Make all Port B pins outputs again
        movwf   TRISB             	
        bcf     PORTC,LCDRW      			;Set LCD R/W pin back to Write mode
        btfsc   RSFLAG,0        			;If RSFlag is clear, skip the next instruction
        bsf     PORTC,LCDRS      			;Set the RS pin on Port C                          
   	   movff	 CHAR,PORTB				;Place the character to be written on Port B
	   bsf     PORTC,LCDENBL       		;Set the LCD Enable pin high
	   nop
	   nop							;Take a very brief pause
        nop
	   nop
	   nop                       	
        bcf     PORTC,LCDENBL       		;Clear the LCD Enable pin
	   nop
	   nop
	   nop
	Return							;Return to the calling point





;***************************************************************************************************
;***************		         BINARY To 3-Digit ASCII	     	******************************
;***************									          ******************************
;  I obtained this from an anonymous source on the internet.  It inputs an 8-bit word into         *
;   "RESULT" and outputs into huns, tens, ones in ASCII suitable for display on an LCD.	         *
;  - Verified correct operation...This subroutine works well!	  	                             *
;***************************************************************************************************

BIN2BCD
	movlw 8
	movwf counter
		clrf huns
		clrf tens
		clrf ones

BCDADD3
		movlw 5
		subwf huns,0
		btfsc STATUS,C
	CALL ADD3HUNS

		movlw 5
		subwf tens,0
		btfsc STATUS,C
	CALL ADD3TENS

		movlw 5
		subwf ones,0
		btfsc STATUS,C
	CALL ADD3ONES

		decf counter,1
		bcf STATUS,C
		rlcf RESULT,1
		rlcf ones,1
		btfsc ones,4  
	CALL CARRYONES
		rlcf tens,1

		btfsc tens,4 
	CALL CARRYTENS
		rlcf huns,1
		bcf STATUS,C

		movf counter,0
		btfss STATUS,Z
	GOTO BCDADD3


		movlw	0x00					;Here, I'm setting up for leading zero	
		cpfsgt	huns					; suppression.  If the value in "hundreds"
		goto	null						; is zero, I skip writing and write a blank instead.
		movf huns,0 					; add ASCII Offset
		addlw h'30'
		movwf huns
back
		movf tens,0 					; add ASCII Offset
		addlw h'30'
		movwf tens

		movf ones,0 					; add ASCII Offset
		addlw h'30'
		movwf ones

	RETURN

null
		movlw	' '
		movwf	huns
		goto 	back

ADD3HUNS
		movlw 3
		addwf huns,1
	RETURN

ADD3TENS
		movlw 3
		addwf tens,1
	RETURN

ADD3ONES
		movlw 3
		addwf ones,1
	RETURN

CARRYONES
		bcf ones, 4
		bsf STATUS,C
	RETURN

CARRYTENS
		bcf tens, 4
		bsf STATUS,C
	RETURN


;***************************************************************************************************
;*****************************		DIVIDE			****************************************
;***************************************************************************************************
;18 Apr 11 - Digital dividing can be accomplished by "successive Subtraction", wherein the divisor *
;  (bottom number) is subtracted from the dividend repeatedly until the operation produces a       *
;  negative number.  The number of subtractions is the result.	   						    *
; **This routine has been tested and is functional.								         *
;***************************************************************************************************
 
Divide
	Clrf	RESULT
	CLRF	RESULT1
	BCF		STATUS,C
	BCF		STATUS,N
	TSTFSZ	DIVISOR					;If the divisor is zero, return undone
	Goto	test							; Otherwise, start the division
   Return

test
    MOVF   DIVISOR,WREG        			; Get low byte of subtrahend
    SUBWF  	DIVIDEND,F         			; Subtract DST(low) - SRC(low)
    MOVF   DIVISOR1,WREG       			; Now get high byte of subtrahend
    BTFSS   STATUS,C        				; If there was a borrow, rather than
    INCFSZ  DIVISOR1,WREG	     		; decrement high byte of dst we inc src
	BCF		STATUS,N
    SUBWF  	DIVIDEND1,F       			; Subtract the high byte and we're done

	btfss	STATUS,N					;Is the result negative?
	Call 	Increment					;Result is NOT negative, Increment result and repeat

	return							;Result is negative, end here		

Increment
	Incf	RESULT						;result is not negative, Increment Result
	BTFSC	STATUS,C					;If Carry bit is set, we "rolled" Result
	INCF	RESULT1						; over, so increment the higher byte
	goto	test							; we counted up, return for another test
	Return
	
;***************************************************************************************************
;**********************           WRITE THE BAR GRAPH           ************************************	
;  This subroutine writes the bar graph on the lower line of the LCD.  It uses the variables HIgrph*
;  (the High nibble) to tell how many full blocks to write,and LOgrph (the low nibble) to tell     *
;  which custom character to write.  It finally fills in the rest of the line with spaces.  It does*
;  all this by setting a "Space Holder" to 16, and checking HIgrph's number.  If Higrph is zero, it*
;  checks LOgrph's value.  If HIgrph isn't 0, it writes a Full block and decrements HIgrph and     *
;  SPACHLDR.  It checks for 0 again, and if still not 0, writes another full block and decrements  *
;  HIgrph and SPACHLDR again.  It repeats this process until HIgrph reaches zero at which point it * 
;  checks	LOgrph. Whatever LOgrph contains is written and SPACHLDR is decremented. Finally,        *
;  SPACHOLDER itself is checked for 0.  If it is, we return to the point where this subroutine was *
;  called.  If not, we write a space, decrement SPACHLDR, and test again. When SPACHLDR reaches    *
;  zero, we simply return to the program where this routine was called.  In this fashion, all 16   *
;  digits of the line are accounted for and filled in.									    *
;***************************************************************************************************

Write_Graph							;The actual subroutine name


			movff	huns,grphhuns
			movlw	0x0F
			andlw	grphhuns

			movff	tens,grphtens		;Move the data into a local register
			movlw	0x0F				;Setup mask to remove
			andwf	grphtens			; the "3" that precedes all ASCII Characters
		
			movff	ones,grphones		;Move the data into a local register
			movlw	0x0F				;set up a data mask to remove
			andwf	grphones			; the 3 that precedes all ASCII characters
		
			movlw	0x07				;Setup comparator so we won't exceed
									;  the LCD character size after it's multiplied
			cpfslt	grphtens			;Is grphtens greater than 8?
	Call 	loadhuns					;Yes, preload 8 into grphtens
									;No, use the actual number
			movlw	' '
			cpfseq	grphhuns
	Call		loadhuns

			rlncf	grphtens			;Multiply grphten by 2 to make the bar look better

														
			movlw	0xC0
	Call		SENDINSTR					;Start writing on the second line of the LCD.

			movlw	0x0F				;Set the Space Counter to 16
			movwf	SPCCNTR
			movff	grphtens,WREG		;Place the value of "tens" into the working register
			subwf	SPCCNTR,F			;Subtract the value of "tens" from the Space Counter

			tstfsz	grphtens			;If tens is zero, go straight to LOWRITE, otherwise,
	goto		HIWRITE					; go to HIWRITE
	goto		LOWRITE

HIWRITE
			clrf		WREG				;HIWRITE first tests "tens" to see if it is 0, this allows
			cpfsgt	grphtens			;  it to write the same number of blocks as the value of "tens"
	goto 	LOWRITE					;If HIgrph is zero, go to LOWRITE
			setf		WREG				;If HIgrph isn't 0, load 0xFF into W, and
	Call 	SENDDAT					;  write that value (a solid block) to the LCD
			decf		grphtens			;Decrement "tens" by one
	goto 	HIWRITE					;Return to HIWRITE for another test/write

LOWRITE
			movff	grphones,WREG		;Move the value of "ones" into the working register
	Call		SENDDAT					;Write that custom value to the LCD

SPCWRITE
			clrf		WREG				;Clear the working register
			cpfsgt	SPCCNTR			;If space counter is zero, 
	goto 	Retn						;  return to where this subroutine was called
			movlw	' '				;Otherwise, place a space into the working register
	Call 	SENDDAT					;   and write that space to the LCD
			decf	   	SPCCNTR			;decrement the Space Counter by one
	goto 	SPCWRITE					;Return to SPCWRITE for another test/write

			clrf	   	grphtens			;Safety measure, clear both holders
			clrf	   	grphones

Retn	Return

loadhuns
			movlw   	0x07
			movwf   	grphtens
	Return



;***************************************************************************************************
;********************************      DELAY SUBROUTINES       *************************************
;These subroutines work by "wasting" operations.  Specifically, they place the program into a loop *
; to count down from preset values.  Since we can know the exact amount of time it takes for each  *
; instruction cycle (1/Fosc/4- about 2 microseconds), we can simply set each preset to multiply    *
; that time up to the desired delay length.											    *
; **************************************************************************************************

LNGDELAY 								;Delay of about 1S					
 	Call 	MIDDELAY
	Call		MIDDELAY
	Call 	MIDDELAY
 	Call 	MIDDELAY
	Call		MIDDELAY
	Call 	MIDDELAY
	Call		MIDDELAY
MIDDELAY								;MIDDELAY provides about 130mS delay 
			movlw   	0xFF           	;Preset COUNT1 with 255
			movwf   	COUNT1          
     goto    VARLOOP         				;Go to the loops to begin decrementing
SMLDELAY   							;SMLDELAY provides about 32mS of delay    
			movlw   	0x40            	;Preset COUNT1 with 0x40
			movwf   	COUNT1          
     goto    VARLOOP        				;Go to the loops to begin decrementing

VARLOOP                        
			movlw   	0xFF   			;Preset Fixed loop counter to FF which
			movwf   	COUNT2          	; corresponds to about a 500 microS delay
FIXDLOOP
			decfsz  	COUNT2,f        	;Decrement fixed loop count, skip next if COUNT2 = 0
	goto    FIXDLOOP        				;COUNT2 isn't 0 yet, so decrement again
			decfsz  	COUNT1,f        	;Decrement the variable count loop, skip next when COUNT1 reaches 0
	goto    VARLOOP        				;COUNT1 isn't 0 yet, so load COUNT2 again and restart the loops
	Return                  				;Both counters are zero, return to caller
	
       
;***************************************************************************************************
;**********************************  ADC READ SUBROUTINES   ****************************************
; ADCREAD is used to read the Analog to Digital Converter. On a 18F2520, this is simply a matter of*
; setting a "start" bit high in the ADCON0 register.  This bit, in conjunction with some presets   *
; at the beginning of the program, starts the ADC.  When it has completed its reading, it returns  *
; a bit in the same register that we can use to tell us the conversion is complete.                *
;	Once the "ADC is Complete" bit is set, we then proceed to format the data for our purposes.   *
; This involves simply transferring the readings from the ADC-result registers "ADRESH" and        *
; "ADRESL" to our test registers "HBYTE" and	"LBYTE, and moving the MSB of LBYTE to the LSB of     *
; HBYTE.																		    *
;***************************************************************************************************

ADCREADFWD

			movlw	0x0B				;Set ADC references Internal, Set AN0 thru AN3 as Analog
			movwf	ADCON1
			movlw	0x01				;Enable ADC
			movwf	ADCON0			;		"
			movlw	0xBA				;Set up for Right justified result, Long Tad, Fosc/8
			movwf	ADCON2
			bsf		ADCON0,1			;Begin the actual reading		
Statloop		btfsc	ADCON0,1			;Read the "ADC Complete" flag, keep reading
			goto 	Statloop			;  until the ADC is finished and sets the flag.
			movff	ADRESH,HBYTEFWD	;Move the ADC High byte to HBYTE
			movff	ADRESL,LBYTEFWD	;Move the ADC Low byte to LBYTE
			clrf		ADRESH			;Clear these registers prior to the next read
			clrf		ADRESL
		RETURN

ADCREADREV
			movlw	0x08				;Set ADC references Internal, Set AN0 thru AN4 as Analog
			movwf	ADCON1
			movlw	0x05				;Enable ADC
			movwf	ADCON0			;		"
			movlw	0xBA				;Set up for Right justified result, Long Tad, Fosc/8
			movwf	ADCON2
			bsf		ADCON0,1			;Start the reading
Statloop2		btfsc	ADCON0,1				;Read the "ADC Complete" flag, keep reading
			goto 	Statloop2			;  until the ADC is finished and sets the flag.
			movff	ADRESH,HBYTEREV	;Move the ADC High byte to HBYTE
			movff	ADRESL,LBYTEREV	;Move the ADC Low byte to LBYTE
			clrf		ADRESH				;Clear these registers prior to the next read
			clrf		ADRESL
		RETURN

ADCREADBAT
			movlw	0x08				;Set Internal ADC references, Set AN0 thru AN4 as Analog
			movwf	ADCON1
			movlw	0x11				;Enable ADC
			movwf	ADCON0			;		"
			movlw	0xBA				;Set up for Right justified result, Long Tad, Fosc/8
			movwf	ADCON2
			BSF		ADCON0,1			;Start the actual reading
Statloop3		btfsc	ADCON0,1			;Read the "ADC Complete" flag, keep reading
			goto 	Statloop3			;  until the ADC is finished and sets the flag.
			movff	ADRESH,HBYTEBAT	;Move the ADC High byte to HBYTE
			movff	ADRESL,LBYTEBAT	;Move the ADC Low byte to LBYTE
			rrncf	LBYTEBAT			;Rotate the ADC Low byte one digit to the Right
			btfsc	HBYTEBAT,0		;Is the ADC High byte's LSD High?
			bsf		LBYTEBAT,7		;If so, set the Low-byte's MSD High too.
			rrncf	HBYTEBAT			;Rotate the ADC High Byte one digit to the right
			rrncf	LBYTEBAT			;Rotate the ADC Low byte one digit to the Right
			btfsc	HBYTEBAT,0		;Is the ADC High byte's LSD High?
			bsf		LBYTEBAT,7		;If so, set the Low-byte's MSD High too.

			movlw	0x89
			cpfsgt	LBYTEBAT			;Is LBYTEBAT Greater than 8 volts?
		goto BattHi					; Yes, make the battery warning
		goto	BatBlank					; No, so no Battery warning
	

Endbatt
			clrf		ADRESH				;Clear these registers prior to the next read
			clrf		ADRESL
			
		RETURN
		
BatBlank
			movlw 	' '
			movwf	Batt1
			movlw	' '
			movwf	Batt2
			movlw	' '
			movwf	Batt3
		goto Endbatt	
		
BattHi		movlw	'B'
			movwf	Batt1
			movlw	'a'
			movwf	Batt2
			movlw	't'
			movwf	Batt3
		goto Endbatt	



;***************************************************************************************************
;*********************************       DISPLAY HEADERS    ****************************************
;  This sections displays 2 brief messages at the device turn-on.  It serves no operational purpose*
;  to the program.																    *
;***************************************************************************************************	

HEADER1
        movlw   0x80              			;Start writing at the first digit
	Call    SENDINSTR          
        movlw   'B'      				;Send each of these characters to the LCD         	    
    Call    SENDDAT          				; to be written as character strings
        movlw  	'i'                   
    Call    SENDDAT         
        movlw   'l'              
    Call    SENDDAT          
        movlw  	'l'              
    Call    SENDDAT          
        movlw	' '             
    Call    SENDDAT         
        movlw   'L'          
    Call    SENDDAT         
        movlw   'a'    
    Call    SENDDAT 
        movlw   'z'   
    Call    SENDDAT
        movlw   'u'    
    Call    SENDDAT 
        movlw   'r'      
    Call    SENDDAT 
        movlw   'e'      
    Call    SENDDAT  
        movlw   ' '     
    Call    SENDDAT 
        movlw   'W'      
    Call    SENDDAT 
        movlw   '2'      
    Call    SENDDAT   
        movlw   'E'        
    Call    SENDDAT  
        movlw   'B'       
    Call    SENDDAT  
  	Return

HEADER2
        movlw   0x80              			;Start writing at the first digit
    Call    SENDINSTR          
        movlw   ' '      				;Send each of these characters to the LCD         	    
    Call    SENDDAT          				; to be written as character strings
        movlw  	' '                   
    Call    SENDDAT         
        movlw   ' '             
    Call    SENDDAT          
        movlw  	' '              
    Call    SENDDAT          
        movlw	'S'             
    Call    SENDDAT         
        movlw   'W'          
    Call    SENDDAT         
        movlw   'R'    
    Call    SENDDAT 
        movlw   ' '   
    Call    SENDDAT
        movlw   'M'    
    Call    SENDDAT 
        movlw   'T'      
    Call    SENDDAT 
        movlw   'R'      
    Call    SENDDAT  
        movlw   ' '     
    Call    SENDDAT 
        movlw   'v'      
    Call    SENDDAT 
        movlw   '1'      
    Call    SENDDAT   
        movlw   '0'        
    Call    SENDDAT  
        movlw   ' '       
    Call    SENDDAT  
 	Return


;***************************************************************************************************
;********************************    DISPLAY TEMPLATES       ***************************************
; These are the templates called by the divide subroutine.  HISWR is invoked if the divide rolls   *
;  over (meaning the divisor is equal to or larger than the dividend.  Otherwise, the normal       *
;  display is used.																    *
;***************************************************************************************************

DISPLAYX
									;SWR Display Template
		movlw   0x82              		;Start writing at the first digit
    Call    SENDINSTR          			;2	;Send the instruction
        movlw   'S'               
    Call    SENDDAT          				;3
		movlw	'W'
	Call	SENDDAT						;4	
		movlw	'R'
	Call	SENDDAT						;5
		movlw	' '
	Call	SENDDAT						;6
		movff huns,WREG
	Call	SENDDAT						;7
 		movff tens,WREG
	Call	SENDDAT						;8
       movlw   '.'               
    Call    SENDDAT						;9
		movff	ones,WREG
	Call	SENDDAT          				;10			
		movlw	':'
	Call	SENDDAT						;11
		movlw	'1'
	Call	SENDDAT						;12
		movlw	' '			
	Call SENDDAT						;13
		movf	Batt1,W
	Call SENDDAT						;14 If BAT voltage is too low,
		movf	Batt2,W
	Call SENDDAT						;15  these digits will show
		movf	Batt3,W
	Call SENDDAT						;16  "BAT" as a warning.
	Return 

HISWR								;Over-Range condition for SWR readings
		movlw   0x82              		;Start writing at the first digit
    Call    SENDINSTR          			;Send the instruction
        movlw   'S'   					;3            
    Call    SENDDAT          
		movlw	'W'					;4
	Call	SENDDAT					
		movlw	'R'					;5
	Call	SENDDAT
		movlw	' '					;6
	Call	SENDDAT	
 		movlw	'H'					;7
	Call	SENDDAT	
		movlw  	'I'         			;8
	Call    SENDDAT
		movlw	'G'					;9
	Call	SENDDAT          					
		movlw	'H'					;10
	Call	SENDDAT
		movlw	' '					;11
	Call	SENDDAT
		movlw	' '					;12
	Call SENDDAT
		movlw	' '					;13
	Call SENDDAT				
		movff	Batt1,WREG			;14  Like above, if the battery
	Call SENDDAT			
		movff	Batt2,WREG			;15  voltage is too low, these
	Call SENDDAT				
		movff	Batt3,WREG			;16  digits display "BAT"
	Call SENDDAT				

	goto Main            




        END
