;**************************************************************************************************** ; 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 ;**************************************************************************************************** ;****************************** 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