 The 8051

 <-Previous List of Contents Next->

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 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.

 <-Previous List of Contents Next->