I find that in most of my applications, I have to use 16 bit numbers. I use the code snippets below for the various required functions.
These pieces of code can be "cut and pasted" into your applications.
1. Defining 16 Bit Numbers | 5. Comparisons with 16 Bit Variables |
2. Increments and Decrements | 6. Multiplication |
3. Addition/Subtraction | 7. Division |
4. Other Operations on Constants and Variables |
I define 16 bit numbers in a manner similar to that of an 8-bit number and just give them to bytes in the RAM Register Space. The example below shows how to define an eight bit Variable, followed by two 16 bit Variables:
CBLOCK 0x020 ; Start of RAM for the PIC16F877 Reg_8 ; Define the 8 Bit Register Reg_16:2 ; Define the first 16 Bit Register Reg2_16:2 ; Define the 2nd 16 Bit Register ENDC
Incrementing a 16 Bit value is very simple:
incf Reg, f ; Increment the Low byte btfsc STATUS, Z ; Do we have Zero (Multiple of 256)? incf Reg + 1, f ; Increment High byte (if necessary)
If a PIC17Cxx or a PIC18Cxx is used, the "infsnz" instruction to simplify the sixteen bit increment by one instruction:
infsnz Reg, f ; Increment "Reg's" Low Byte and Skip incf Reg + 1, f ; High Byte Increment if Result is Not ; Equal to Zero
The Decrement of a 16 Bit value isn't quite so simple:
movf Reg, f ; Set "Z" if LOW "Reg" == 0 btfsc STATUS, Z decf Reg + 1, f ; If Low byte is Zero, Decrement High decf Reg, f
Addition and Subtraction of 16 Bit Variables to Constants follows the same format with the most significant byte processed first. Addition follows the format:
movlw HIGH 0x01234 ; Add the high byte first addwf Reg + 1, f movlw LOW 0x01234 ; Add the Low Byte Next addwf Reg, f btfsc STATUS, C ; Don't Inc high byte if carry Reset incf Reg + 1, f
For the PIC17Cxx and PIC18Cxx, the "addwfc" instructions can be used to simplify the operations and eliminate the need for checking the status of the carry flag. For the addition above, the PIC17Cxx or PIC18Cxx code would be:
movlw LOW 0x01234 ; Add Low Byte First addwf Reg, f movlw HIGH 0x01234 ; Add High Byte Next addwfc Reg + 1, f
The corresponding subtraction, ie:
Reg = Reg - 0x01234
Looks like:
movlw HIGH 0x01234 ; Subtract the High Byte First subwf Reg + 1, f movlw LOW 0x01234 ; Subtract the Low Byte Next subwf Reg, f btfss STATUS, C ; Don't Dec high byte if carry Set decf Reg + 1, f
Again, the enhanced instructions in the PIC17Cxx and PIC18Cxx can used for the sixteen bit subtraction operation. Using the "subwfb" instruction, the sixteen bit subtraction can be simplified to:
movlw LOW 0x01234 ; Subtract the Low Byte First bsf STATUS, C ; Don't pass any "Borrow" subwfb Reg, f ; Reg = Reg – w - !C movlw HIGH 0x01234 subwfb Reg + 1, f ; Reg + 1 = Reg + 1 – w - !C
The general case 16 Bit addition,
Destination = Source + 0x05678
Will look like:
movlw HIGH 0x05678 ; Add High Byte First addwf Source + 1, w movwf Destination + 1, f ; Store Result in Destination movlw LOW 0x05678 ; Add Low Byte Next addwf Source, w movwf Destination, f ; Store Result btfsc STATUS, C ; Is the Carry Flag Set? incf Destination + 1, f ; Yes, Increment High Byte
If the Destination is different from both values to be added, ie:
c = a + b
the code is changed to save the sums in "w" and then store it in "c" like:
movf a + 1, w ; Add the High Bytes addwf b + 1, w movwf c + 1 movf a, w ; Add the Low Bytes addwf b, w movwf c btfsc STATUS, C ; Increment due to Carry incf c + 1
Subtraction is carried out in the same way, but care must be taken to ensure that the subtracting Register is kept straight (something that is less of an issue with addition). If you want to do the following statement:
c = a - b
You would use the code:
movf b + 1, w ; Get Value to be subtracted subwf a + 1, w ; Do the High Byte movwf c + 1 movf b, w ; Get the Value to be Subbed subwf a, w movwf c btfss STATUS, C ; Look for the Carry decf c + 1
Doing other operations (bitwise or whatever) on 16 bit values can use the code shown above as a base.
For example, ANDing a 16 Bit Variable with 0x0A5A5 would be done like:
movlw 0x0A5 ; Get Value for ANDING andwf Reg + 1, f ; Do the High Byte andwf Reg, f ; Do the Low Byte
There is one difference, however and that is to do with rotating 16 Bit values. Rotating must be carried out in such a way that the carry flag is always correct for the shift.
To shift left:
bcf STATUS, C ; Clear the Carry Flag for new bit rlf Reg, f ; Shift the Low Byte rlf Reg + 1, f ; Shift High Byte with Low Carry
and to shift right:
bcf STATUS, C ; Clear Carry Flag for the New bit rrf Reg + 1, f ; Shift down the High Byte rrf Reg, f ; Shift Low Byte with Valid Carry
Comparisons involving 16 Bit Variables require that the comparison value (or Register) is subtracted from the Register to be checked. The results of this will then tell you what is going on with the condition. I use the same code as is shown above and save the result in temporary values and then look at the result. The subtraction code used for comparing a 16 Bit Variable to another 16 Bit variable is:
movf Reg2 + 1, w ; Get the High Byte of the Result subwf Reg1 + 1, w movwf _2 ; Store in a Temporary Register movf Reg2, w ; Get the Low Byte subwf Reg1, w btfss STATUS, C ; Decrement High if Necessary decf _2
At the end of this series of instructions, "w" contains Reg2 - Reg1 and "_2" contains Reg2HI - Reg1HI with the borrow result of Reg2 - Reg1.
If the Variable is to be compared against an immediate value, then the "movf" Instructions would be replaced with "movlw" and the two bytes of the immediate value.
There are six basic conditions that you can look for: Equals, Not Equals, Greater Than, Greater Than or Equal, Less Than, Less Than or Equals. So, to discover whether or not I have any of these conditions, I add the following code:
For Equals and not equals, the value in "w" is ORed with "_2" to see if the Result is equal to zero.
iorwf _2, w ; Is the Result == 0?
for Equals add the lines:
btfss STATUS, Z ; Execute following Code if == 0 goto Zero_Skip ; Else, Code != 0, Skip Over
for Not Equals, append:
btfsc STATUS, Z ; Execute following if != 0 goto NotZero_Skip ; Else, Code == 0, Skip Over
If a Greater than (The 16 Bit Variable is Greater than the Comparison Value), then the result will not be less than Zero. Actually, the same code (just with a different Bit Skip) can be used to test.
For Greater Than:
btfsc _2, 7 ; Not Negative, 16 Bit is Greater goto NotGreater_Skip ; Else, Skip if Not Greater than iorwf _2, w ; Is it Equal to Zero? btfsc STATUS, z ; No, It is Greater than Goto NotGreater_Skip ; Else, if Zero, Not Greater than
Note that just the most significant bit of the 16 but difference is checked. If this bit is set (= 1), then the 16 Bit Variable is less than the Comparison. If it is reset (= 0), then it is greater than and you should check to see if the result is not equal to zero (or else it is equal).
For Less Than:
btfss _2, 7 ; Negative, 16 Bit is Less Than goto NotLess_Skip ; Else, Skip because Not Less Than
To check for Greater or Equal To, the last three lines of the code checking for Greater Than are simply erased. To check for Less or Equal To, the three lines from Not Equal to are added before the check for less than.
Here is the complete code for compare and skip on Reg1 less than or equal to Reg2:
movf Reg2 + 1, w ; Get the High Byte of the Result subwf Reg1 + 1, w movwf _2 ; Store in a Temporary Register movf Reg2, w ; Get the Low Byte subwf Reg1, w btfss STATUS, C ; Decrement High if Necessary decf _2 iorwf _2, w ; Check for Equal to Zero btfsc STATUS, Z ; If Not Zero, Jump Over goto EqualLess_Skip ; Equals, Jump to the Code btfsc _2, 7 ; If Number is Negative, execute goto EqualLess_Skip ; Else, Jump Over
For both multiplication and division, repeated addition could be used, but I find that using a scaling routine works much better and faster. These algorithms test bits and only operate if it is appropriate.
Here is a Sixteen Bit Multiplication with a Sixteen Bit Result:
clrf Product clrf Product + 1 movlw 16 ; Operating on 16 Bits movwf BitCount Loop ; Loop Here for Each Bit rrf Multiplier + 1, f ; Shift the Multiplier down rrf Multiplier, f ; by one btfss STATUS, C ; If the bit is set, add goto Skip ; the Multiplicand to the ; "Product" movf Multiplicand + 1, w addwf Product + 1, f movf Multiplicand, w addwf Product, f btfsc STATUS, C incf Product + 1, f Skip ; Shift up Multiplicand and bcf STATUS, C ; Loop Around rlf Multiplicand, f rlf Multiplicand + 1, f decfsz BitCount goto Loop
The code given below is the most efficient way of doing a sixteen bit multiply with a 32 bit result. It is not immediately obvious, but it's very clever. Rather than use a 32 bit add each time the shifted data is detected, it provides a sixteen bit (with valid carry) add and then shifts the data down. This Code does not change "Multiplicand", but does change "Multiplier". Note that in the code, I use a thirty two bit value for "Product" (using a "Product:4" line in the "CBLOCK" variable declare statement).
clrf Product + 2 ; "Product" will be the clrf Product + 3 ; Result of the Operation movlw 16 ; Operating on 16 Bits movwf BitCount Loop ; Loop Here for Each Bit rrf Multiplier + 1, f ; Shift the Multiplier down rrf Multiplier, f ; by one btfss STATUS, C ; If the bit is set, add goto Skip ; the Multiplicand to the ; "Product" clrf Product + 4 movf Multiplicand + 1, w addwf Product + 3, f btfsc STATUS, C ; Make Sure the Carry is Passed incf Product + 4, f ; to the Next Byte movf Multiplicand, w addwf Product + 2, f btfsc STATUS, C incfsz Product + 3, f ; Make Sure Carry is Passed with goto $ + 2 ; the Shift incf Product + 4, f Skip ; Shift "Product" Down with bcf STATUS, C rrf Product + 4, f rrf Product + 3, f ; the Reset Carry from the rrf Product + 2, f ; Multiplier shift down or rrf Product + 1, f ; the result of the sixteen rrf Product, f ; bit addition. decfsz BitCount goto Loop
For the PICmicros that have built in eight by eight multipliers, the code for sixteen bit multiplication uses the techniques taught in high school mathematics for multiplying together to variable factors. Instead of thinking of a sixteen bit number as just a contiguous set of sixteen bits, the number is broken up as two sets of numbers that are eight bits in size. The PIC18CXXX sixteen bit multiplication with a thirty two bit result is:
clrf Product + 2 ; Clear the High-Order Bits clrf Product + 3 movf Al, w ; Do the "L" Multiplication first mulwf Bl movf PRODL, w ; Save result movwf Product movf PRODH, w movwf Product + 1 movf Al, w ; Do the "I" Multiplication mulwf Bh movf PRODL, w ; Save the Most Significant Byte First addwf Product + 1, f movf PRODH, w addwfc Product + 2, f ; Add to the Last Result movf Ah, w ; Do the "O" Multiplication mulwf Bl movf PRODL, w ; Add the Lower Byte Next addwf Product + 1, f movf PRODH, w ; Add the High Byte First addwfc Product + 2, f btfsc STATUS, C ; Add the Carry incf Product + 3, f movf Ah, w ; Do the "F" Multiplication mulwf Bh movf PORDL, w addwf Product + 2, f movf PRODH, w addwfc Product + 3, f
The division routine provided here first finds how far the divisor can be shifted up before comparing to the quotient. The "Count" variable in this routine is a 16 Bit variable that is used to both count the bits as well as add to the quotient. "Temp" is an 8 Bit temporary Storage Variable. At the end of the division routine, "Dividend" will contain the remainder of the operation.
clrf Quotient clrf Quotient + 1 movlw 1 ; Initialize Count movwf Count clrf Count + 1 StartLoop ; Find How Large "Divisor" can ; be btfsc Divisor + 1, 7 ; If at the "top", then do goto Loop ; the Division bcf STATUS, C ; Shift Count and Divisor Up rlf Count, f rlf Count + 1, f rlf Divisor, f rlf Divisor + 1, f goto StartLoop Loop ; Now, Take Away "Divisor" ; from "Dividend" movf Divisor + 1, w ; If Divisor < Dividend then subwf Dividend + 1, w ; Don't Take Away movwf Temp movf Divisor, w subwf Dividend, w btfss STATUS, C decf Temp, f btfsc Temp, 7 ; If "Temp" Negative then goto Skip ; Divisor < Dividend movwf Dividend ; Save the New Dividend movf Temp, w movwf Dividend + 1 movf Count, w ; Add Count to the Quotient addwf Quotient + 1, f movf Count, w addwf Quotient + 1, f ; No Opportunity for Carry Skip ; Shift Divisor/Count Down bcf STATUS, C rrf Divisor + 1, f rrf Divisor, f rrf Count + 1, f ; If Carry Set after Count rrf Count, f ; Shift, Finished btfss STATUS, C ; If Carry NOT Set, then goto Loop ; Process next Bit
This division routine is designed to only handle positive numbers - there is no general algorithm that handles both positive and negative numbers and passes back both the quotient and remainder with the correct polarity efficiently.
A general form for a division routine (using the algorithm shown above) could be the division of the core of the pseudo-code is a bit-shift analogous algorithm to multiplication that can handle positive and negative numbers.
if (Dividend < 0) { // Change dividend to positive number Dividend = 0 – Dividend; dividendneg = 1; // Mark we have to change it back } else dividendneg = 0; if (Divisor < 0) { // Repeat with the Divisor Divisor = 0 – Divisor; divisorneg = 1; } else divisorneg = 0; Count = 0; // Going to Count where division starts Quotient = 0; // Store the Quotient while (( Divisor & 0x0400 ) != 0) { // Find the Start of the Division Count = Count + 1; // Increment the Number of Bits Shifted Divisor = Divisor << 1; } while (Count != 0) { // Now, do the Division if (Dividend >= Divisor) {// A subtract can take place Quotient = Quotient + 2 ^ Count; Dividend = Dividend – Divisor; } Count = Count – 1; Divisor = Divisor >> 1; } if (Dividendneg == 1) // Now, change the values if (Divisorneg == 1) { Quotient = Quotient; Remainder = 0 – Dividend; } else { Quotient = 0 – Quotient; Remainder = 0 – Dividend; else // The Dividend was Positive if (Divisorneg == 1) { Quotient = 0 – Quotient; Remainder = Dividend; } else { Quotient = Quotient; Remainder = Dividend; }