The 8051


 
<-Previous
   
  Conditional Jumps and Time Delays
   
  We have already looked at the conditional jump JC rel, the operation for which is detailed below:
 

JC rel
jump if carry set
(PC) <- (PC) + 2
IF (C) = 1
THEN (PC) <- (PC) + rel

   
  If the carry is equal to 1, branch to the indicated address.
   
   
  The JNC rel instruction is the opposite to this:
 
JNC rel
jump if carry not set
(PC) <- (PC) + 2
IF (C) = 0 THEN (PC) <- (PC) + rel
   
  If the carry is equal to 0, branch to the indicated address.
   
   
  There are two conditional jump instructions that are used for testing the contents of the accumulator.
 
JZ rel
JNZ rel
   
  JZ rel
  Jump if the accumulator is equal to 0
 
JZ rel
jump if accumulator zero
(PC) <- (PC) + 2
IF (A) = 0 THEN (PC) <- (PC) + rel
   
  This instruction tests the contents of the accumulator. If the contents are equal to 0 it causes a jump to the indicated address, otherwise execution continues with the next instruction.
   
   
  JNZ rel
  Jump if the accumulator is not equal to 0
 
JNZ rel
jump if accumulator not zero
(PC) <- (PC) + 2
IF (A) <> 0 THEN (PC) <- (PC) + rel
   
  This instruction also tests the contents of the accumulator. However, if the contents are NOT equal to 0 it causes a jump to the indicated address, otherwise execution continues with the next instruction.
   
   
  Time Delays
  There are times when it is necessary to wait before executing the next part of a program. For this discussion we will take the example of flashing an LED.
   
 
   
  The above flowchart details program for flashing an LED. It works by turning on the LED, turning it off and then looping back to the start again. This program is in a never-ending loop.
  However, while the above program may flash an LED, because of the speed at which microcontrollers operate, the frequency would be too fast for the human eye to see.
   
 
   
  Above is a modification of the LED flashing program. It turns on the LED, waits half a second, turns it off, waits another half second and then loops back to the start again.
   
  One method of forcing the microcontroller to wait is the time delay. The flow chart for a single-loop time delay is shown below.
   
 
   
  A number is loaded into the accumulator. It is then decremented. Following this, a check on the contents of the accumulator is made. If the contents of the accumulator is not equal to zero a jump back to the decrement instruction is made.
   
  If 0AH is loaded into the accumulator, then the loop is executed ten times. To increase the length of the time delay we load a larger number into the accumulator. Therefore, the longest time delay (using this single loop time delay) is achieved by loading the accumulator with FFH - the loop would be executed 255 times.
   
   
  The DJNZ Instruction
  The DJNZ instruction is specifically designed for this kind of program. It stands for decrement jump not zero and the operation is detailed below.
 

DJNZ byte, rel
decrement and jump if not zero
(PC) <- (PC) + instructionSize
(byte) <- (byte) - 1
IF (byte) <> 0 THEN (PC) <- (PC) + rel

   
  There are two versions - a 2-byte version and a 3-byte version.
   
  The 2-byte version works on the register set R0 to R7.
 
DJNZ Rn, rel
decrement and jump if not zero
(PC) <- (PC) + 2
(Rn) <- (Rn) - 1
IF (Rn) <> 0 THEN (PC) <- (PC) + rel
   
  Rn can be any of the eight registers R0 to R7. The encoding for the instruction is
 
11011rrr rel
  where rrr is replaced by the register number.
  For example, if the instruction DJNZ R5, rel was used, the opcode would be 11011101.
  The second byte of the instruction, the operand, is the relative offset. Like all jumps, you the programmer don't need to work out the offset; you use a label and the assembler works out the offset for you.
   
  The 3-byte version allows you to work with any RAM location.
 

DJNZ direct, rel
decrement and jump if not zero
(PC) <- (PC) + 3
(direct) <- (direct) - 1
IF (direct) <> 0 THEN (PC) <- (PC) + rel

   
  direct can be any 8-bit address. The encoding for the instruction is
 

11010101 add rel

  This code states - decrement a location specified by the second byte (add), test to see if the result is non-zero, if so jump to location specified by the third byte (rel).
  An example of this instruction: DJNZ 30H, rel - decrement the contents of RAM location 30H and, if the result is non-zero, jump to location specified by rel.
   
   
  This instruction is prefect for the time delay program, as detailed below.
   
 

MOV R0, 0FFH
DJNZ R0, $

   
  The $ Operator
  Most assemblers use the $ character as a pointer to the current program location. Therefore, the instruction DJNZ R0, $ means - decrement the contents of R0 and if the result is non-zero jump back to this line. In this way, the contents of R0 are continuously decremented until they reach zero.
   
   
  Longer Delays
  As stated above, the longest single-loop delay is achieved by initialising the decrement register (ie; the register that will be decremented) to FFH. To achieve longer delays we can use multiple-loop time delays, as detailed in the flow chart below.
   
 
   
  The inner loop (nested loop) takes 255 iterations before R1 reaches 0. When this happens, the loop is exited and R0 is decremented. If R0 is not equal to zero the program jumps back to the line that loads R1 with FFH again. Another 255 iterations of the nested loop must occur before R0 is decremented again. Therefore, for every one iteration of the outer loop there are 255 iterations of the inner loop.
  We control the length of the time delay by changing the value loaded into R0. If R0 is initialised to 03H, then the overall number of iterations is 3 * 255 = 765. The maximum number of iteration for a two-loop time delay is 255 * 255 = 65025.
   
   
  Calling a Subroutine from within a Subroutine
  To write a three-loop delay we could create two nested loops inside a third outer loop as detailed in the flowchart below.
   
 
   
  Take note that the inner two loops are identical to the maximum two-loop delay shown above. Therefore, if we write the three-loop delay in this manner we must rewrite the two-loop delay and test it. This is not an efficient programming practice. It would be better if we reused our two-loop delay as detailed in the flowchart below.
   
 
   
  The inner two-loop delay is replaced by a call to our original maximum two-loop delay. In this manner we are reusing the code that was written and tested earlier.
   
  The code for the above three-loop delay is:
   
 
threeLoopDelay:

MOV R2, #0AH

loop: CALL twoLoopDelay

DJNZ R2, loop
RET

   
  and the code for the two-loop delay is:
   
 
twoLoopDelay:

MOV R0, #0FFH

loadR1:

MOV R1, #0FFH
DJNZ R1, $
DJNZ R0, loadR1
RET

   
  Calling the three-loop delay will therefore result in 10 * 255 * 255 iterations.
   
  We can now use our two delays for flashing the LED at something other than a 50% duty cycle.
   
 
start:

SETB P1.0
CALL twoLoopDelay
CLR P1.0
CALL threeLoopDelay
JMP start

   
  The above program sets the least significant bit of port 1 and then calls the two-loop delay, after which it clears the least significant bit of port 1 and calls the three-loop delay. The three-loop delay itself calls the two-loop delay ten times. Therefore, the LED (on P1.0) will be off for approximately ten times longer than it is on.
   
   
 
<-Previous
 
   
 
Copyright (c) 2005-2006 James Rogers