Software for the Floating-Point Calculator
In this section I will provide a detailed explination of how the software performs each of the math functions included in this calculator.
Much of the code used for the floating-point calculator is the same as the code used for the fixed-point version, so I will only present code examples that are uniquely used in the floating-point version.
Samples of code will be presented for each of the four basic math functions, but due to length, not all of the utility subroutines will be listed in this article. To see the full unabridged, uncensored, unedited version of the code, open the file, lcdcalc.asm (included in the Project Package using any standard text editor, or by creating and opening a project using MPLAB IDE. Also refer to last month's article, Software for the Basic Fixed-Point Calculator for examples of the basic code structures re-used by the floating-point routines.
Several macros are also used within the code to simplify basic PIC functions. These macros are:
So in the following code examples, the use of the two macros PAGE0 and PAGE1 refer to ROM page addressing and can be omitted if the routines and their calls are located in the lower page of memory.
A functionally operational mathematical calculator can be realized using several different techniques. This project incorporates the technique of performing math operations using BCD (Binary Coded Decimal) unpacked numbers. This means that for each number entered on the keyboard there is a separate memory location for that number. Typically when working with BCD numbers it is common practice to "pack" the digits, or combine two of the 4-bit BCD numbers into a single 8-bit word where the most significant nibble is the "tens" place digit and the least significant nibble is the "ones" place. This saves memory space but can complicate matters later on. Instead, I have chosen to handle the numbers individually, with ZERO as the most significant digit of the byte. This technique requires more RAM memory but has its advantages when it comes to smooth and quick math operations.
Unlike the fixed-point calculator, the floating-point calculator needs more and larger registers. Some of the same registers are used as were on the fixed-point version and some are used for the same purpose, only with more cells.
The calculator requires two main operational registers, each 8 digits long, representing the (X) register and the (Y) register. There also needs to be an "overflow" byte one step higher than the most significant byte (MSB) of the actual 8-digit number and a "sign" byte representing the numerical polarity (positive or negative). Therefore, the (X) and the (Y) register file stack is 10 bytes wide each.
There also needs to be several internal registers used by the operation routines as "scratchpad" registers. These registers vary in size. In this project, the addition and subtraction routines have been extended beyond 8 digits to accomodate the decimal alignment. Therefore, addition and subtraction use the (X) and the (Y) registers and re-use the (P) and the (T) registers. Since floating-point multiplication differs from fixed-point only in the placement of the decimal, the multiply routine uses the same registers as the fixed-point--two very lengthy registers, the (T) register, or totalizer register, and the (P) register, or the product register. During multiplication, the software will actually calculate the product to 16 digits and then reduce the product to 8 digits. This lenthy process is necessary in the event an operation such as 88888888 * 1 is done. The division routine however is a total wash from the original software in almost every way, although the original divide subroutine is actually where the division takes place. Fixed point division requires several special registers, the (B) register or the "bank" register and the (Q) register or the quotient register. There is also a (Z) register used to store the dividend data during the division process. For floating-point, a much lengthier final quotient register (V) has been added so that decimal shifting can be done. A separate modulus register has also been added to carry the modulus data from division call to division call (J).
The floating-point calculator also offers several math functions such as 1/x, x2 and MEAN. Since these operations affect ONLY the (X) register AND re-use the four basic functions to perform each task, the contents of the (Y) register must be saved in a special, isolated register (R). Memory also requires a separate and isolated register bank (M) along with sign and decimal registers.
All together, 140 RAM locations are dedicated to the register files.
(B) is 8 bytes wide plus decimal register, used by the divdr subroutine
As you can see most of the registers are used either by the divdr subroutine or the division routine. This is simply because division is the most complicated of the four basic functions.
Since each operation uses sequential file banks, each of these registers' individual files (or cells) MUST be sequential and cannot be re-arranged to compress the code.
There also need to be several "flag" bits used as steering control bits. Therefore there are three RAM locations reserved for these flag bits.
There are only two basic functions included in the calculator -- addition and subtraction. These two functions are further broken down into Fixed-point and Floating-Point routines. Multiplication and division are derivatives of both addition and subtraction. Since any operation on two variables can be quantumized into a set of "rules" that apply to the signage, all operations can be unsigned and deal only with positive integers. Once the operation is complete, the sign is then dealt with. This simplifies the entire sequence of events and makes it so the main addition and main subtraction routines can be "re-used" by the multiplication and division routines without modification.
The calculator will use the ADD16 routine to add two numbers where there is a decimal in either of the two numbers, and the SUB16 for subtraction. If there are no decimals, the older ADD8 and SUB8 routines are used to make the machine a bit faster. Multiplication is exactly the same for floating-point as it is for fixed-point except for the placement of the decimal so an additional call to a "discriminator" routine is necessary to not only position the decimal properly but to determine how many decimal places can be displayed after the integer component within the display size (8 digits). Division requires a sequencer to call division "cycles" but the actual division is the exact same thing as used in the fixed-point machine.
In order to deal with the sign, a set of mathematical rules must apply to all equations. These rules are derivatives of the Axioms of Addition, Multiplication and Division.
Unpacked BCD Addition adds the number in the (X) register to the number in the (Y) register.
This is easily done by mimicking the way you would add up two numbers on paper.
But before you can add two decimal point numbers on paper you must first line up the decimals. This is done by scrutinizing the digit data saved in (X) by shifting the data to fit within a "range" of area inside a much larger register.
Here's How:
Example: X = 12.345, Y = 5678.9
First copy the two variables into larger registers. The (T) and the (P) registers used by the 8-digit multiply routine works fine.
Remember that the contents of each register does not indicate the location of the decimal point. The decimal point location is saved in two separate registers, DPx and DPy.
Next, designate a location within each of the registers where the break point occurs between the fraction and the integer. The lower part of the (P) register would be a good place, so all of the digits from (P1) to (P8) are the fraction part of the number and all the digits from (P9) to (P16) are the integer part. These location designations will be the same for the (T) register with (T9) and up being the integer and (T8) and below the fraction. This "aligns" the decimals in both numbers to the same cell assignments in both registers.
In a calculator, all of the leading zeroes are valid numbers, so in our example, 12.345 would actually be 00012.345 and 5678.9 would be 0005678.9 .
Notice that the decimals do not line up in a single column. This has to be corrected before addition can be done. The (P) register must be "shifted" so that the "9" is in cell (P8) and the "8" is in cell (P9). The (T) register also must be shifted so that the "3" is in cell (T8) and the "2" is in cell (T9).
The register DPy tells us that the decimal point is at the 1st decimal location (the far right display location is the "0" decimal location). So when copying (Y) to (P) this can tell the software where to begin copying the integer component. So first, locate the first integer and subtract it from the decimal location, then subtract that result from 8 (8 digits on the display). This will be the first location in (P) to begin copying (Y). When done, the integer component will all be above (P9) and the fraction part will all be below (P8), inclusive.
Do the same for (X) and copy it to the (T) register. Then add as if it were a regular fixed-point add except 16-digits long (Refer to the section on Addition in last month's article Software for the Basic Fixed-Point Calculator for more information on how the actual addition routine runs).
To finalize the effort, the display must be "discriminated", or "clipped" down to 8-digits. It would be fit and proper to bias the display in favor of the integer component. So a discriminator is run which once again measures the integer part to see how long the integer is and if there is any room left over for the fraction component. Then to make the display more presentable, any trailing zeroes are removed from the fraction component.
Here is the PIC code for how this is done:
!!WARNING!! Long code example including several service subroutines!
Most of the following explination is exactly the same as that for the fixed-point subtractor described in last month's article. The only difference between fixed and floating point is the alignment of the decimal in larger registers--done exactly the same way using the same subroutines as used in the floating-point addition described earlier.
Unpacked BCD Subtraction subtracts the number in the (X) register from the number in the (Y) register.
It is done in exactly the same manner as Unpacked BCD Addition is done, except for when the difference between Y and X forms a negative number. In this case, the subtraction has to be done twice in order to reverse the negativity of the difference, or in other words, convert the 2's compliment into a positive integer.
Here's How:
Example: X = 56789, Y = 12345
    12345 (Y register value)
Notice that the difference above (-44444) is the same as if you "got rid of the signs, reversed the two terms and subtracted, then made the sign negative". This would work but ONLY if (Y) were always positive. So if we simply went ahead and subtracted the larger number from the smaller number, the software can essentially "borrow on credit" a "1" at the top of the (Y) register stack, leaving the (Y9) register in "underflow" state, and the (Y1) through (Y8) registers representing a 2's compliment result.
Therefore to solve this problem, unlike addition, the rules MUST be considered before the subtraction routine is called. If (X) is numerically greater than (Y) then the subtraction subroutine must be called TWICE -- the first time to subtract the greater number from the lesser, and the second time to subtract the 2's compliment from zero and "borrow on credit" the necessary "1" in the (Y) MSB to complete the subtraction.
Once the second subtraction is completed, then the RULES will assign the sign to the difference.
Remember, if (X) is numerically less than (Y) the subtraction subroutine only has to be run ONCE.
To finalize floating point subtraction, the final answer is discriminated and the trailing zeros are removed.
Here's the PIC code for how this is done:
Since floating-point multiplication is exactly the same as fixed-point multiplication except for the simple placement of the decimal, the actual multiply software is exactly the same as that for the fixed-point routine used in last month's article. The decimal is handled by a simple decimal handling subroutine called after the main multiplier sub call.
To place the decimal, the rule of math says to take the number of decimal places in (X) and the number of decimal places in (Y) and add them together, then shift the decimal to the LEFT this sum of places. This is done to the (P) register before the number is sent on to the (X) register and display.
First (DPx) and (DPy) are added together to find out how many shifts to do. Then a loop counter is established with this value and the shift to the left takes place until the loop is zero.
Next and finally, the (P) register is discriminated to be integer-dominant and to format the product to fit the 8-digit display.
Since the actual multiply is the same as for fixed-point, only the decimal adjust routine will be presented here.
Here's the PIC code for how this is done:
Format
Setting Up the Registers
Register Assignments
(J) is 8 bytes wide, used by the floating division routine
(M) is 10 bytes wide plus decimal register, and is the calculator's memory
(P) is 17 bytes wide, used by the multiplication, SUB16 and ADD16 routines
(Q) is 8 bytes wide, used by the divdr subroutine
(R) is 10 bytes wide plus decimal register, and is used as an isolated save for (Y)
(T) is 20 bytes wide, used by the multiplication, SUB16 and ADD16 routines
(V) is 25 bytes wide, used by the floating division routine
(X) is 10 bytes wide plus decimal register, the machine's main accumulator
(Y) is 10 bytes wide plus decimal register, the machine's secondary operand register
(Z) is 8 bytes wide plus decimal register, used by the divdr subroutine
Registers MEANCTR,MEAN1,MEAN2,MEAN3 are used for the MEAN calculation divisor
Doing the Math
The Rules
Addition:
Subtraction:
Multiplication:
Division:
Unpacked BCD Unsigned Floating-Point Addition
P8 P7 P6 P5 P4 P3 P2 P1
0 0 0 5 6 7 8 . 9
0 0 0 1 2 . 3 4 5
;****************************************************************************
;16-DIGIT FLOATING POINT ADDER
;****************************************************************************
ADD16 CLRF XSIGN ;CLEAR X AND Y SIGN REGS
CLRF YSIGN
CLRF P17 ;BE SURE CARRY/STATUS REG IS CLEAR!
MOVLW P1 ;SET UP FSRP, FSRT POINTERS
MOVWF FSRP
MOVLW T1
MOVWF FSRT
MOVLW 016
MOVWF ADDCNTR ;LOOP COUNTER
SUM16 MOVF FSRP,0
MOVWF FSR
MOVF INDR,0 ;GET T DIGIT
MOVWF TEMP2 ;SAVE
MOVF FSRT,0 ;SETUP T DIGIT
MOVWF FSR
MOVF TEMP2,0 ;RETRIEVE P DIGIT FROM TEMP
ADDWF INDR,0 ;ADD TO T
PAGE0
CALL ADDBCD ;CONVERT
PAGE1
MOVWF TEMP ;SAVE
MOVF FSRP,0
MOVWF FSR
MOVF TEMP,0 ;RETRIEVE
MOVWF INDR ;SAVE ON P
SWAPF INDR,0 ;SWAP TO FIND CARRY IF ANY
ANDLW B'00001111' ;MASK OUT HIGH NIBBLE
INCF FSR,1 ;POINT TO NEXT BYTE IN P
ADDWF INDR,1 ;ADD!
DECF FSR,1 ;RESTORE P POINTER
MOVLW B'00001111' ;CORRECT CARRY BITS IN P REG
ANDWF INDR,1 ;SAVE FINAL P VALUE
INCF FSRP,1 ;INCREMENT DATA POINTERS
INCF FSRT,1
DECFSZ ADDCNTR,1 ;LOOP 16 DIGITS
GOTO SUM16
CALL DISCRIM ;DISCRIMINATE DIGITS
CLRF XSIGN ;ALWAYS POSITIVE
MOVF P17,0 ;OVERFLOW?
BTFSC Z
RETURN ;NO OVERFLOW
PAGE0
CALL XCLEAR ;CLEAR OFF GARBAGE...
PAGE1
MOVLW 0x01
MOVWF X9 ;ERROR CODE FOR OVERFLOW
RETURN
;****************************************************************************
;8-DIGIT ADD/SUBTRACT DISCRIMINATOR SUBROUTINE, INTEGER DOMINANT
;****************************************************************************
DISCRIM PAGE0
CALL XCLEAR ;CLEAR THE (X) REGISTER
PAGE1
MOVLW P16
MOVWF FSR ;LOAD FSR WITH HIGHEST INT
MOVLW 08
MOVWF DIGCTR ;LOOP UP TO 8 TIMES
FINDINT MOVF INDR,0 ;GET (P) VALUE
BTFSS Z
GOTO INT1 ;FIRST INTEGER FOUND
DECF FSR,1 ;POINT TO NEXT (P) CELL
DECFSZ DIGCTR,1
GOTO FINDINT
INT1 MOVF DIGCTR,0
BTFSC Z ;IF ZERO INTEGERS, ALL FRACTIONS
GOTO NOADINTS ;NO INTEGERS!
MOVF DIGCTR,0
MOVWF TEMP2 ;SAVE THIS FOR FRACTION
MOVLW P8
ADDWF DIGCTR,0 ;FIGURE OUT FIRST INT LOCATION
MOVWF FSRP ;PUT IN (P) POINTER
MOVLW X8 ;LOAD UP (X)
MOVWF FSRX
MVINT MOVF FSRP,0
MOVWF FSR ;POINT TO (P)
MOVF INDR,0 ;READ IN (P)
MOVWF TEMP ;SAVE IN TEMP
MOVF FSRX,0
MOVWF FSR
MOVF TEMP,0
MOVWF INDR ;SAVE (P) TO (X)
DECF FSRX,1
DECF FSRP,1 ;NEXT BYTES
DECFSZ DIGCTR,1 ;NEXT!
GOTO MVINT
MOVF TEMP2,0 ;FIGURE OUT DECIMALS
SUBLW 08
MOVWF DPX ;FOUND DECIMAL POSITION
BTFSC Z
RETURN ;NO DECIMALS!
ZAINT MOVLW P8 ;FIRST DEC POSITION
MOVWF FSRP ;SET UP (P) POINTER
MOVLW X1
ADDWF DPX,0 ;FIGURE OUT (X) POS
MOVWF FSRX
DECF FSRX,1 ;OFFSET!
MOVF DPX,0
MOVWF DIGCTR ;LOOP COUNTER
MVFRAC MOVF FSRP,0
MOVWF FSR ;SET UP (P) POINTER
MOVF INDR,0
MOVWF TEMP ;SAVE IN TEMP
MOVF FSRX,0
MOVWF FSR ;SET UP (X) POINTER
MOVF TEMP,0
MOVWF INDR
DECF FSRX,1
DECF FSRP,1 ;NEXT BYTES
DECFSZ DIGCTR,1
GOTO MVFRAC
CALL ZEROTRAIL ;RID TRAILING ZEROS
RETURN
NOADINTS
MOVLW 08 ;ALL FRACTIONS
MOVWF DPX
GOTO ZAINT ;RE-ENTER FRACTION ROUTINE
;****************************************************************************
;16-DIGIT DECIMAL SPLITTER SUBROUTINE
;****************************************************************************
SPLIT16 PAGE0
CALL PCLEAR
CALL TCLEAR
PAGE1
MOVLW T9 ;FIRST DIGIT OF INTEGER
MOVWF TEMP
MOVF DPX,0 ;GET DEC POSITION
SUBWF TEMP,0 ;MAKE SUBTRACTION
MOVWF FSRT ;PUT IN POINTER REGISTER
MOVLW X1
MOVWF FSRX ;SET UP (X) POINTER
MOVLW 08
MOVWF DIGCTR ;LOOP COUNTER
XTLP MOVF FSRX,0 ;GET (X) POINTER DATA
MOVWF FSR ;PUT IN FSR
MOVF INDR,0 ;READ IN (X) DATA
MOVWF TEMP2 ;SAVE IN TEMP2
MOVF FSRT,0
MOVWF FSR ;SET UP (T) POINTER
MOVF TEMP2,0
MOVWF INDR ;MOVE TO (T) REGISTER
INCF FSRX,1
INCF FSRT,1 ;NEXT POINTERS
DECFSZ DIGCTR,1 ;LOOP
GOTO XTLP ;COPY LOOP
;CENTER (Y) WITHIN (P) REGISTER
MOVLW P9 ;FIRST DIGIT OF INTEGER
MOVWF TEMP
MOVF DPY,0 ;GET DEC POSITION
SUBWF TEMP,0 ;MAKE SUBTRACTION
MOVWF FSRP ;PUT IN POINTER REGISTER
MOVLW Y1
MOVWF FSRY ;SET UP (Y) POINTER
MOVLW 08
MOVWF DIGCTR ;LOOP COUNTER
YPLP MOVF FSRY,0 ;GET (Y) POINTER DATA
MOVWF FSR ;PUT IN FSR
MOVF INDR,0 ;READ IN (Y) DATA
MOVWF TEMP2 ;SAVE IN TEMP2
MOVF FSRP,0
MOVWF FSR ;SET UP (P) POINTER
MOVF TEMP2,0
MOVWF INDR ;MOVE TO (P) REGISTER
INCF FSRY,1
INCF FSRP,1 ;NEXT POINTERS
DECFSZ DIGCTR,1 ;LOOP
GOTO YPLP ;COPY LOOP
RETURN
;*************************************************************
;TRAILING ZEROS SUPRESSION SUBROUTINE
;*************************************************************
ZEROTRAIL
MOVLW X1
MOVWF FSR ;SET UP POINTER TO (X)
MOVF INDR,0 ;READ IN (X) CELL
BTFSS Z ;TEST FOR ZERO
RETURN ;DONE!
MOVF X2,0
MOVWF X1
MOVF X3,0
MOVWF X2
MOVF X4,0
MOVWF X3
MOVF X5,0
MOVWF X4
MOVF X6,0
MOVWF X5
MOVF X7,0
MOVWF X6
MOVF X8,0
MOVWF X7
MOVLW 00
MOVWF X8
DECFSZ DPX,1 ;ADJUST POINT
GOTO ZEROTRAIL ;NO MORE ZEROS!
RETURN
Unpacked BCD Unsigned Floating-Point Subtraction
  -56789 (X register value)
  ---------
  -44444 (difference)
SUB16 CLRF X9 ;CLEAR OVERFLOW REGISTERS
CLRF Y9
CLRF XSIGN
CLRF YSIGN ;CLEAR SIGN REGISTERS TOO!
CALL SUBT1D ;DO UNSIGNED SUBTRACTION
MOVF P17,0 ;IF P17 IS ZERO THEN WE'RE DONE!
BTFSC Z
GOTO DSDIM ;ANSWER POSITIVE, IN (T) REGISTER
;IF WE'RE HERE THEN NUMBER IS AUTOMATICALLY NEGATIVE!
PAGE0
CALL PCLEAR ;CLEAR (P) REGISTER
PAGE1
CALL SUBT1D ;SUBTRACT FROM ZERO
CALL PTSWAP ;SWAP (T) AND (P)
CALL DISCRIM ;DISCRIMINATE DIGITS
MOVF P17,0
BTFSC Z
GOTO SUBUNDR16 ;UNDERFLOW
CLRF XSIGN ;NO UNDERFLOW
BSF XSIGN,0 ;SET SIGN BIT NEGATIVE
CLRF X9 ;CLEAR ERROR REGISTER
RETURN
SUBUNDR16
CLRF XSIGN
MOVLW 0x01 ;SHOW UNDERFLOW ERROR
MOVWF X9
RETURN
DSDIM CALL PTSWAP ;SWAP (T) AND (P)
CALL DISCRIM ;DISCRIMINATE DIGITS
CLRF X9
CLRF XSIGN ;NO UNDERFLOW, POSITIVE
RETURN
;*************************************************************
;16-DIGIT SUBTRACTOR--SUBTRACTS T FROM P UNSIGNED
;*************************************************************
SUBT1D BANK1
CLRF OVER16 ;CLEAR OVERFLOW REG
BANK0
MOVLW T1 ;SET UP FSRP FSRT POINTERS
MOVWF FSRT
MOVLW P1
MOVWF FSRP
MOVLW 016
MOVWF ADDCNTR ;LOOP COUNTER
NXSUB16 MOVF FSRT,0
MOVWF FSR ;SET UP T POINTER
MOVF INDR,0
MOVWF TEMP2 ;SAVE T VALUE
MOVF FSRP,0
MOVWF FSR ;SET UP P POINTER
MOVF TEMP2,0
SUBWF INDR,0 ;SUBTRACT!
PAGE0
CALL SUBBCD ;CONVERT
PAGE1
MOVWF TEMP2 ;SAVE CONVERTED DATA
MOVF FSRT,0
MOVWF FSR ;SET UP T POINTER
MOVF TEMP2,0
MOVWF INDR ;SAVE ON T REGISTER
SWAPF INDR,0
ANDLW B'00001111'
MOVWF TEMP2 ;SAVE MASKED DATA
MOVF FSRP,0
MOVWF FSR ;SET UP P POINTER
INCF FSR,1
MOVF TEMP2,0
SUBWF INDR,1 ;UPDATE P REGISTER
MOVF FSRT,0
MOVWF FSR ;SET UP T POINTER
MOVLW B'00001111'
ANDWF INDR,1 ;CLEAR OFF CARRY BITS
INCF FSRT,1 ;UPDATE FSR POINTERS
INCF FSRP,1
DECFSZ ADDCNTR,1
GOTO NXSUB16 ;LOOP
RETURN
Unpacked BCD Unsigned Floating-Point Multiplication
DECADJ MOVWF DPY ;FROM EARLIER ADD, DEC POS
PAGE0
CALL XCLEAR ;CLEAR THE (X) REGISTER
PAGE1
;DETERMINE IF (P) NEEDS TO BE SHIFTED OR NOT
MOVLW 08
XORWF DPY,0
BTFSC Z
GOTO MXNOSHIFT ;NO SHIFT REQUIRED
;DETERMINE SHIFT DIRECTION
MOVF DPY,0
SUBLW 08
BTFSS CARRY
GOTO MSHR
;SHIFT LEFT OPERATION
MOVWF DIGCTR ;SAVE FROM SUBTRACTION EARLIER
MSL2 MOVLW 16
MOVWF M1CTR ;LOOP COUNTER
MOVLW P17 ;SET UP POINTERS
MOVWF FSRP1
MOVLW P16
MOVWF FSRP
LSLP MOVF FSRP,0
MOVWF FSR ;(P) POINTER
MOVF INDR,0 ;READ (P)
MOVWF TEMP ;SAVE IN TEMP
MOVF FSRP1,0
MOVWF FSR ;(P+1) POINTER
MOVF TEMP,0
MOVWF INDR ;MOVE DATA!
DECF FSRP,1
DECF FSRP1,1 ;DECREMENT POINTERS
DECFSZ M1CTR,1
GOTO LSLP
CLRF P1 ;SET P1 TO ZERO EACH TIME!
DECFSZ DIGCTR,1
GOTO MSL2
GOTO MXNOSHIFT ;DETECT FOR OVERFLOW NOW!
MSHR MOVLW 08
SUBWF DPY,0 ;DETERMINE SHIFT POSITION
MOVWF DIGCTR ;SAVE FROM SUBTRACTION EARLIER
MSR2 MOVLW 16
MOVWF M1CTR ;LOOP COUNTER
MOVLW P1 ;SET UP POINTERS
MOVWF FSRP1
MOVLW P2
MOVWF FSRP
LSRP MOVF FSRP,0
MOVWF FSR ;(P+1) POINTER
MOVF INDR,0 ;READ (P)
MOVWF TEMP ;SAVE IN TEMP
MOVF FSRP1,0
MOVWF FSR ;(P) POINTER
MOVF TEMP,0
MOVWF INDR ;MOVE DATA!
INCF FSRP,1
INCF FSRP1,1 ;INCREMENT POINTERS
DECFSZ M1CTR,1
GOTO LSRP
CLRF P17 ;SET P17 TO ZERO EACH TIME!
DECFSZ DIGCTR,1
GOTO MSR2
MXNOSHIFT
MOVF P17,0
BTFSC Z
GOTO DODISC ;DISCRIMINATE
PAGE0
CALL XCLEAR ;CLEAR OFF TRASH...
MOVLW 0x01 ;UNDERFLOW ERROR CODE
MOVWF X9
RETURN
DODISC CALL DISCRIM ;DISCRIMINATE
RETURN
Unpacked BCD Unsigned Floating-Point Division
Floating-point division is accomplished by repeatedly calling the 8-digit fixed-point division routine over and over until a certain resolution of accuracy is achieved.
The first time the divdr subroutine is called the integer component of the quotient is solved. There is no more action necessary to complete the integer divide.
However, it may take an unknown number of loops to fully divide out to any particular given accuracy the fraction component. The more calls, the more digits, thus the more accurate.
The first two divdr calls have certain requirements. Each and every call afterwards will fall into a separate category.
These differences are due to the way the modulus component (undivisable remainder) is left over after each division stage has completed. In the first call, the integer component is solved. Whatever is left over is called the modulus and is passed on to the next division stage. The next stage will be shifted to the RIGHT by 1 position. If there are no integers in the quotient, there still remains a "0" (zero) to the left of the decimal and of course, zero as a whole number is always part of a fraction. This "leading" zero must first be shifted away before the first of the fractional divisions can be associated with the integer component.
Since there will be many divisions done, there is the need for an extraordinarily long register to put all of these digits together at the end. So a new register, the (V) register is established, and is 25 cells in size.
First, (Y) is divided by (X) as usual. The result is then moved into the upper 1/3 of the (V) register (V17) through (V24). The modulus component is passed on to the next stage of division to be used as the dividend. The modulus is then shifted all the way to the LEFT of the (Y) register before the division call is made. This sets up the quotient component to be the next set of numbers in-line to the previously divided integer component. Although shifted, the quotient, which will be the first "8" digits of the fraction, is off by 1 digit to the RIGHT due to the leading zero. So to correct for this alignment and to properly place the decimal, the quotient is then shifted to the LEFT by the number of digits in the modulus. This will properly align the first digit of the quotient with the location of the decimal in the (V) register. Then the quotient is copied into the center set of cells within the (V) register (V9) through (V15).
To extend the accuracy of the division, one more additional stage of division is done. The modulus component left from the first fractional division is transferred to the (Y) register and once again, shifted to the far LEFT. This time, unlike earlier division cycles, the quotient from this stage will be shifted to the LEFT by the sum of the modulus digits from the first fractional division (Stage 2)AND this division (Stage 3). This process will continue for each additional stage of division added to the software. Each time a division is called, the number of modulus digits is added to a "running total" of modulus digits and accumulated so that further stages can be added later (The running total does not need to include any values from the Stage 1 division since there is no modulus in Stage 1, esentially it would be zero). So if you wanted to add another stage, you would once again take the modulus from the last stage, move it to the (Y) register, then shift the (Y) register to the far LEFT, call divdr, then shift the quotient to the LEFT the sum of all of the modulus digits, starting with the very first (integer) division. This number of shifts can be quite a few after several stages but it is necessary to align the digits to the position within the (V) register.
After the third division, the quotient is then moved into the lowest 8 digits of the (V) register (V1) through (V8). Since the (Q) register is only 8-digits long, the LEFT shift is actually done by copying the highest cell address in the (Q) register to the proper address location within the (V) register. So you must first determine which cell in the (V) register will accept the highest cell in the (Q) register. This is done by subtracting the number of accumulated modulus digits from the fixed decimal location (P16). Therefore, there is actually no physical "shifting"; it is all done by math in software.
Additional stages of division can be added as long as the difference [(P16) - (sum of modulus digits)] is greater than or equal to 1, and there are locations within the (V) register to copy the stage results to. Remember that each division stage results in an 8-digit quotient, then this quotient is shifted to the LEFT by the running total of modulus digits. Therefore, if the stage quotient returns only one or two useable digits, you may be able to add up to 6 more stages.
The actual "divide" is the exact same routine as used by the fixed-point division routine so only the cycle handler routine is shown here.
Here's the PIC code for how this is done:
DIVIDE MOVLW 08 MOVWF DIGCTR ;DIVIDE BY ZERO CHECK CLRF TEMP ;USE TEMP AS CHECKSUM REG MOVLW X1 MOVWF FSR ;DO X REGISTER CHECKSUM XZCHECK MOVF INDR,0 ADDWF TEMP,1 ;ADD IT UP! INCF FSR,1 ;NEXT BYTE DECFSZ DIGCTR,1 GOTO XZCHECK ;LOOP AROUND... MOVF TEMP,0 BTFSC Z ;IF ZERO, THEN X=0, NO GO! GOTO DE2 ;EXIT IN ERROR MOVF DPX,0 BANK1 MOVWF DPX1 BANK0 MOVF DPY,0 BANK1 MOVWF DPY1 ;SAVE DP REGS BANK0 PAGE0 CALL VCLEAR PAGE1 BTFSC XSIGN,0 ;CHECK FOR POSITIVE X GOTO DIVNEG ;X IS POSITIVE BTFSC YSIGN,0 ;CHECK FOR POSITIVE Y GOTO DIVNEG2 ;Y IS POSITIVE GOTO DIVPOS DIVNEG BTFSC YSIGN,0 ;IF BOTH NEG, THEN POS RESULT GOTO DIVPOS ;DO POS! DIVNEG2 BANK1 BSF DIVNEGFLAG ;DIVIDE NEGATIVELY! BANK0 ;NOW DIVIDE! ;STAGE 1 DIVIDE DIVPOS CALL XJCOPY ;SAVE (X) FOR FRACTION PAGE0 CALL DIVDR ;DO DIVIDE BOTH POSITIVE PAGE1 CALL XVHCOPY ;COPY INTO (V-HI) MOVLW 08 BANK1 MOVWF MODUL ;MODULUS COUNTER TSYSH1 BANK0 MOVF Y8,0 ;SHIFT! BTFSS Z GOTO NSH1 CALL YSHIFT ;SHIFT Y POSITION BANK1 DECFSZ MODUL,1 ;UPDATE MODULUS COUNTER GOTO TSYSH1 BANK0 ;STAGE 2 DIVIDE NSH1 BANK1 MOVF MODUL,0 MOVWF MSAV ;SAVE FIRST MODULUS BANK0 CALL JXCOPY ;LOAD (X) PAGE0 CALL DIVDR PAGE1 DVXSHL1 BANK0 CALL XSHIFT ;SHIFT (X) LEFT BANK1 DECFSZ MODUL,1 GOTO DVXSHL1 ;LOOP! BANK0 CALL XVMCOPY ;COPY TO (V-MID) MOVLW 08 BANK1 MOVWF MODUL ;MODULUS COUNTER TSYSH2 BANK0 MOVF Y8,0 ;SHIFT! BTFSS Z GOTO NSH2 CALL YSHIFT ;SHIFT Y POSITION BANK1 DECFSZ MODUL,1 ;UPDATE MODULUS COUNTER GOTO TSYSH2 BANK0 ;STAGE 3 DIVIDE NSH2 CALL JXCOPY ;LOAD (X) PAGE0 CALL DIVDR PAGE1 DVXSHL2 BANK0 CALL XSHIFT ;SHIFT (X) LEFT BANK1 DECFSZ MODUL,1 GOTO DVXSHL2 ;LOOP! BANK0 BANK1 MOVLW V8 ADDWF MSAV,0 ;CALCULATE COPY POS BANK0 MOVWF TEMP2 CALL XVLCOPY ;COPY TO (V-LO) BANK1 MOVF DPX1,0 ;GET (X) DECIMAL REG BANK0 MOVWF DIGCTR ;LOOP COUNTER BTFSC Z GOTO DSHR1 ;NO SHIFT LEFT VLDS1 CALL VLSHIFT ;LEFT SHIFT DECFSZ DIGCTR,1 ;LOOP GOTO VLDS1 DSHR1 BANK1 MOVF DPY1,0 ;GET (Y) DECIMAL REG BANK0 MOVWF DIGCTR ;LOOP COUNTER BTFSC Z GOTO NDIVSH ;NO SHIFTING VRDS1 CALL VRSHIFT ;SHIFT RIGHT DECFSZ DIGCTR,1 GOTO VRDS1 NDIVSH CALL VDISCRIM ;DISCRIMINATE! CLRF XSIGN ;SET POSITIVE BANK1 BTFSC DIVNEGFLAG GOTO NEGDIV BANK0 GOTO DIVERROR NEGDIV BCF DIVNEGFLAG BANK0 BSF XSIGN,0 ;SET NEGATIVE DIVERROR BANK1 ;TEST FOR ERROR CONDITION MOVF V25,0 BANK0 BTFSC Z RETURN ;DONE! DE2 PAGE0 CALL XCLEAR ;CLEAR X AND DISPLAY PAGE1 MOVLW 01 MOVWF X9 ;SET X ERROR REGISTER RETURN ;RETURN TO MAIN
Advanced Math Functions
Now that the calculator can do floating-point arithmetic, more advanced math functions can be added.
Some of the more obvious features are to add a reciprocal key, percent key and MEAN, or average key.
Reciprocal simply divides the number 1 by the number on the display. A simple handling routine moves the data in the (Y) register to a safekeeping register (R), loads (Y) with 1 and calls the division routine. It finalizes up by restoring the original (Y) data.
Percent is also easily implemented. Once again, the (Y) register is saved on (R). Then 2 is added to register (DPx) to shift the decimal two places to the left (percent). Then the multiply routine is called. Once again, to finalize the function, the (Y) register is restored from (R).
MEAN is done by counting the number of times the M+ key is pressed in a separate register MEANCTR. Then, once the MEAN key is pressed, the MEANCTR register is broken up from a binary number into a 3-digit unpacked decimal number and moved into the (X) register. Then the data in memory (M) is moved to the (Y) register. Finally the divide routine is called and the (Y) register is restored.
Each of these functions are implemented as semi-functions within the key strobe routine. This is most practical since the solutions to these functions only affect the (X) register and can be passed on to pending operations or become the first entry in a new operation.
A Final Note
The routines outlined in this section can and could be modified for processor effiency, however these routines were written for speed at the expense of processor resources.
Let me suggest that you play around with the code, add additional functions or port the code to a more potent PIC such as a 17C42 or an 18LF4552. With a more powerful processor you could realize one mean machine!
Which is exactly what we did! Click Here to go to the next article, "Build an Advanced Floating-Point Nixie Tube Desk Calculator".
Back to the Lupine Systems Homepage
Lupine Systems Download Section
How to Make PC Boards
How to Use EasyTrax CAD Software
© 2007 Lupine Systems. It's a WOLF Thing! ®