| |
 |
The 8051
|
|
|
|
| |
|
| |
|
| |
16-bit Addition |
| |
|
| |
In the previous section, a subroutine was written for getting the average
of a set of 8-bit numbers (detailed again below). However, there is a
major limitation with this piece of code; it can only get the average
of a set of numbers if the sum of the set is less than or equal to 255
(FFH).
|
| |
|
| |
;the main program
using 0 ; assembler directive that indicates to the assembler which
register bank is being used (in this case bank 0) |
MOV R0, #30H; initialise
the subroutine by putting the start address into R0
MOV R1, #05H; and by putting the size of the set into R1
CALL average
|
| |
| ;the subroutine |
| average: |
PUSH PSW
PUSH AR0
PUSH AR1
MOV B, R1; copy the size of the set into the B register
CLR A ; clear the ACC
|
| loop: ADD A, @R0; add
to the ACC the data in the location pointed to by R0 |
INC R0; increment
R0 so that it points to the next memory location
DJNZ R1, loop; decrement R1 and if it is still not zero jump back
to loop
DIV AB; once all the numbers have been added together, divide them
by the size of the set, which is stored in B
POP AR1
POP AR0
POP PSW
RET; return from subroutine
|
|
| |
|
| |
Because the sum of the set of numbers is stored in the accumulator
alone, this sum cannot be greater than FFH. We need to alter the code to
deal with this problem. |
| |
If we use another register so save the high byte of the sum,
then our subroutine will be capable of calculating the average of a set
of 8-bit numbers as long as the sum of the set is less than or equal to
65535 (FFFFH). |
| |
|
| |
The example below shows how the 16-bit sum of the following
set of 8-bit numbers can be achieved. |
| |
{23, 4C, D8, 45, B9} |
| |
|
| |
The high byte is shown in italics.
|
| |
| 00 |
23 |
+
|
4C |
|
| 00 |
6F |
| + |
D8 |
|
| 01 |
47 |
| + |
45 |
|
| 01 |
8C |
| + |
B9 |
|
| 02 |
45 |
|
| |
The high byte starts at zero. Lets say the high byte is stored
in register 3, while the low byte is stored in the accumulator. When the
first two numbers are added together (23 plus 4C) the result does not result
in a carry (because the result is less than 100H the carry flag is not set).
The high byte remains at 00H. |
| |
D8 is then added to the sum in the accumulator (6F). The
8-bit sum (47) is stored in the accumulator but the carry flag is set because
the overall result of adding D8 to 6F is greater than FFH (147H). The high
byte is incremented. |
| |
When 45 is added to the sum, the carry is not set because
47 plus 45 = 8C, ie; less than 100H. The overall sum (147 + 45) is 18C,
therefore the high byte remains unchanged. |
| |
Finally, B9 is added to the sum in the accumulator (8C) resulting
in 45 in the accumulator. But the carry is set because the overall sum of
adding 8C to B9 is 145. Since we are actually adding the 16-bit sum of 018C
to B9, the overall 16-bit result is in fact 0245, therefore the high byte
is incremented from 1 to 2. |
| |
|
| |
In other words, everytime the carry is set after an addition
the high byte must be incremented. If the carry is not set after an addition,
the result of adding the two 8-bit numbers must have been less than 100H,
therefore the high byte must not be incremented. |
| |
|
| |
A flowchart for adding a set of 8-bit numbers and storing
the 16-bit result is shown below. |
| |
|
| |
 |
| |
|
| |
16-bit Division |
| |
Now that we have the 16-bit sum (the hi-byte in R3 and the
lo-byte in ACC) we need to divide it by the sum to get the average
of the data set. |
| |
We cannot use DIV AB because this instruction divides an
8-bit number by an 8-bit number whereas we need to divide a 16-bit number
by an 8-bit number. |
| |
To achieve this we will repeatedly subtract the denominator
(the set size, ie; the number we are dividing by) from the 16-bit sum until
the overall result is negative. At the same time, we keep a count of the
number of times we subtracted the denominator, as detailed in the flowchart
below. |
| |
|
| |
 |
| |
|
| |
The integer result of the division will be stored in R2.
Since we will be incrementing it as we subtract the denominator, we need
to first make sure its contents are zero. |
| |
We then clear the carry. The subtract instruction in the
8051 (SUBB A, src) is subtract with borrow. In other words, it subtracts
the src and the carry from A. To ensure we only subtract the
denominator (R1) from A we need to first make sure the carry
is zero. |
| |
We then subtract R1 from the accumulator and increment
R2. |
| |
Next we test the carry. If it is zero, the result of the
subtraction is not negative, so we simply jump back to subtracting the denominator
again. |
| |
If the carry is 1, the result in A is negative, therefore
we must decrement the high byte (R3). |
| |
If the high-byte is still positive we jump back to clearing
the carry and subtracting the denominator again. It is important to jump
back to clearing the carry because the instruction used to check and see
if the high byte is equal to FFH (CJNE R3, FFH, label) effects the
carry. In other words, when we jump back the carry may be set. Therefore
we must again clear it before proceeding to the SUBB instruction. |
| |
If the high byte is now negative (ie; it was decremented
from 0 to -1, which is FFH) the task is complete - the overall 16-bit sum
has been reduced to a negative value. |
| |
Because the overall 16-bit sum has been subtracted by the
denominator until it goes negative, our answer in R2 has been incremented
once too often. Therefore we complete the division by decrementing R2. |
| |
The subroutine is being designed to put the integer of the
result in the accumulator. Therefore, the last line of code puts the result
(which is in R2) into ACC. |
| |
|
| |
Getting the Remainder |
| |
A small change to the above flowchart is needed if we want
to put the remainder of the division into B. |
| |
|
| |
 |
| |
|
| |
The flowchart is the same as above up to decrement R2.
At this point the division is complete. We have continually subtracted the
denominator (R1) from ACC until the 16-bit number (lo-byte
in ACC and hi-byte in R3) has gone negative. As stated above,
this means we have subtracted the denominator once too often. To correct
this in the integer result (in R2) we decrement R2. |
| |
The remainder was the value in ACC before we subtracted
R1 once too often. Therefore, to get the remainder, we simply add
R1 back onto ACC. The remainder is now in ACC, so we
move it to B. Then we move the integer result to ACC. |
| |
The entire code is shown below. |
| |
|
| |
;the main program
using 0 ; assembler directive that indicates to the assembler which
register bank is being used (in this case bank 0) |
MOV R0, #40H; initialise
the subroutine by putting the start address into R0
MOV R1, #08H; and by putting the size of the set into R1
CALL getSum
CALL sixteenBitDivision
|
| |
| getSum: |
PUSH PSW
PUSH AR0
PUSH AR1
MOV B, R1
CLR A
|
| loop: ADD A, @R0 |
JNC skipHiByteInc
INC R3
|
| skipHiByteInc: |
INC R0
DJNZ R1, loop
POP AR1
POP AR0
POP PSW
RET; return from subroutine
|
| |
| sixteenBitDivision: |
PUSH PSW
PUSH AR2
PUSH AR3
MOV R2, #0
|
| clearC: |
CLR C
|
| subA: |
SUBB A, R1
INC R2
JNC subA
DEC R3
CJNE R3, #0FFH, clearC
DEC R2
ADD A, R1
MOV B, A
MOV A, R2
POP AR3
POP AR2
POP PSW
RET
|
|
| |
|
| |
The above program gets the average of a set of eight numbers
stored in RAM starting at address 40H. |
| |
|
| |
|
| |
|
| |
|
| |
|
| |
Copyright
(c) 2005-2006 NyCelt LLC
|