

The 8051









16bit Addition 



In the previous section, a subroutine was written for getting
the average of a set of 8bit 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 8bit numbers as long as the sum of the set is less
than or equal to 65535 (FFFFH). 



The example below shows how the 16bit sum of
the following set of 8bit 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 8bit 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 16bit sum of 018C to B9, the overall 16bit 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 8bit numbers must have
been less than 100H, therefore the high byte must not be incremented. 



A flowchart for adding a set of 8bit numbers
and storing the 16bit result is shown below. 







16bit Division 

Now that we have the 16bit sum (the hibyte in
R3 and the lobyte 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 8bit number by an 8bit number whereas we need to divide a
16bit number by an 8bit number. 

To achieve this we will repeatedly subtract the
denominator (the set size, ie; the number we are dividing by) from the
16bit 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 highbyte 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 16bit sum has been reduced to a negative value. 

Because the overall 16bit 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
16bit number (lobyte in ACC and hibyte 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 R3, #0
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) 20052006 James Rogers
