repeats a set of instructions according to repeated addition or subtraction operations on a control variable.
The For/Next loop is extremely versatile. It replaces many different kinds of loop structures used in assembly language. This section will not present a For/Next instruction per se, but will show you how to build appropriate loops for your assembly-language programs.
The most basic use of a For/Next loop is to execute some instructions a fixed
number of times:
FOR I = 1 TO 3 GOSUB FLASH_LED ' Flash LED 3 times. NEXT
The instructions within the loop can also use the control variable (I in the examples here):
FOR I = 50 TO 100 pins = I ' Output 50,51,52...100 NEXT
Although the control variable is frequently incremented by 1 each time through the loop, the programmer can have the loop count backwards or by some value other than 1:
FOR I = 1000 TO 100 STEP -2 ' Output low byte of pins = I ' 1000,998...100 NEXT
PBASIC's For/Next instructions support all of these loops, using bits, bytes, or words for the control variable, start value, end value, and step. The interpreter seems to use the same mechanism, based on 16-bit comparisons, regardless of the values and variables used.
In assembly language, programmers use different loop structures depending on the number of iterations required and whether or not the value of the control variable will be used within the loop. These specialized loops take no more time (instruction cycles) or space (program memory) than is absolutely required to do the job.
Take the first example. To perform some instructions from 1 to 256 times calls for a djnz loop based on the Parallax decrement-and-jump-if-not-zero instruction. Here's an example. It assumes that the variable I has been defined, and that there's a subroutine called flash_LED somewhere in the program.
mov I,#3 ; I = 3. :loop call flash_LED ; Flash the LED. djnz I,:loop ; I=I-1: IF I=0 THEN GOTO :loop
The code inside this loop (call flash_LED) executes at least once regardless of the initial value of I. It may seem strange at first, but to get the maximum number of iterations, I must initially be assigned a value of 0. The djnz instruction decrements the variable before it makes the looping decision. In eight-bit math, 0 decrements to 255.
Let's try a 16-bit example of the same sort of loop. We'll flash the LED 500 times. We can't use djnz, because it only works with eight-bit values. In fact, for the most compact loop, we don't want to count backwards at all. When you do 16-bit operations on an 8-bit machine, it is faster to add the binary equivalent of a negative number (called the two's complement) than it is to subtract a positive one. Many of the routines in this book do just that; see Let x = x + y, or look at the code at the beginning of Pulsout. Those routines convert input values to their two's complements.
Assuming that you know the number of iterations ahead of time, you can do the two's complement conversion. The easiest way is to use a calculator capable of binary math (a must-have for assembly programming, in my opinion), set it for 16-bit operations, then subtract the number from 0. In hex, 500 is 1F4h, and 0 - 1F4h = FE0Ch. An alternative method for taking the two's complement is to take the NOT of the number and add 1.
For the loop, we will count upward from FE0Ch to 0.
mov hi,#0FEh ; hi=0FEh mov lo,#0Ch ; lo=0Ch (FE0Ch = -500 in 2's complement) :loop call flash_LED ; Flash the LED. ijnz lo, :loop ; lo=lo-1: IF lo=0 THEN GOTO :loop ijnz hi, :loop ; hi=hi-1: IF hi=0 THEN GOTO :loop
Note that this loop executes the second ijnz instruction only when lo rolls over to 0. This means that those trips through the loop take 3 instruction cycles longer than trips in which lo does not roll over. This is OK in most cases, but timing loops should always take the same number of cycles. For an example of an alternative 16-bit timing loop, see the :loop portion of the Pulsout listing.
Now look at the second type of For/Next loop--the one in which the control
variable is used by the code inside the loop. In such cases the control variable
may not start or end on easy-to-detect values like 0. Assuming that all values
fit into byte-wide variables, we end up with a loop that looks like this:
mov I,#50 ; I = 50. :loop mov rb,I ; rb = I. inc I ; I=I+1. cjbe I,#100,:loop ; IF I <= 100 THEN GOTO :loop
If you needed to increment I by some other value, say 5, you could substitute ADD I,#5 for inc I in the example. This would be equivalent to FOR I = 50 TO 100 STEP 5 in BASIC. You would have to be careful here to avoid a potentially frustrating bug: What if the STEP value is larger than the difference between the stop value and 255? In other words, you wanted to use this structure to write FOR I = 100 to 250 STEP 60. Look at the values of I through several iterations of the loop: 100, 160, 220, 24... Since I is an eight-bit variable, it rolls over when you add 220 + 60. Since 24 is less than 200, the loop keeps executing. You can prevent this runaway situation by sampling the carry bit and exiting the loop when a carry occurs:
mov I,#100 ; I = 100. :loop mov rb,I ; rb = I. ADD I,#60 ; I=I+60. jc :exit ; IF I > 255 THEN GOTO :exit cjbe I,#200,:loop ; IF I <= 100 THEN GOTO :loop :exit ... ; Continue with next instruction.
If your loop decrements the control variable, you'll have to use jnc (jump-if-not-carry) to detect an underflow and exit the loop.
The third type of For/Next loop can use 16-bit values for the start, stop and step values, and has a 16-bit control variable. It can increment or decrement from start to stop, and the value of the control variable may be used by the code inside the loop. It is a true For/Next loop.
Before we look at how to code this loop in assembly, let's consider what would happen if BASIC didn't include this instruction. Could programmers still write this kind of loop in BASIC? The answer is yes:
LET I = 1000 ' FOR I=1000... LOOP: ' Label for GOTO destination. pins = I ' Output 1000,998,996...100 I=I-2 ' Decrement I by 2. IF I >= 100 THEN LOOP ' ...TO 100
This is pretty much how you handle more-complex types of For/Next loops in assembly. If you can avoid coding this kind of structure, do so. It uses the most program space and runs the most slowly of the options we've discussed. Here's how the example could be coded using subroutines from the If/Then and Let x = x+y sections of this book:
mov n1H,#03h ; n1 = 1000 (03E8h) mov n1L,#0E8h :loop mov rb,n1L ; rb = n1L. mov rc,n1H ; rc = n1H mov n2H,#0FFh ; n2 = -2 (two's complement: FFFEh) mov n2L,#0FEh call Add16 ; LET n1=n1-2 clr n2H ; n2 = 100 (0064h) mov n2L,#64h call Comp16 ; Compare n1 to n2. jmp pc+w ; W contains result of comparison: jmp :loop ; 0 means n1 = n2, so loop. jmp :loop ; 1 means n1 > n2, so loop. :exit ... ; 2 means n1 < n2, so exit loop.
Note that this technique has the same flaw as the For/Next instructions of the PBASIC interpreter chip. What happens when the step is larger than the value (65535-end) for incrementing loops, or if the step is larger than the end value for decrementing loops? The value of n1 will roll over and the loop will execute more times than you expected. The problem is the same one we encountered previously with eight-bit variables. The cure is the same as for the eight-bit version; for incrementing loops add a jc (jump-if-carry) instruction right after the call to Add16 and set the destination to exit the loop. For decrementing loops, use a jnc (jump-if-not-carry) instead.
There is another type of loop that can eliminate the eccentricities of the
others. This one maintains two control variables, a public one for the use
of the code inside the loop, and a private one that counts iterations. Here
is an example:
mov I,#50 ; Public variable I starts at 50. mov count,#6 ; Private counter for 6 loops :loop mov rb,I ; Output I ADD I,#33 ; I=I+33 djnz count,:loop ; Repeat 6 times.
This is equivalent to FOR I = 50 to 248 STEP 33, but there's no chance of a flawed compare-and-jump allowing the loop to run away. It stops after six iterations. This approach is also handy when you want to let the value of the public control variable roll over. For example, if you wanted to code something like FOR I = 0 to 255 STEP 8, but execute the whole loop three times. In BASIC, you might nest that loop inside a FOR J = 1 to 3 loop. In assembly, you would just write a loop like the public/private one above. Assign count an initial value of 96, and keep adding 8 into I.
|file: /Techref/microchip/seepicsrc/psbpix/for.htm, 12KB, , updated: 2000/3/15 17:33, local time: 2023/3/22 15:34,
|©2023 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?|
<A HREF="http://techref.massmind.org/Techref/microchip/seepicsrc/psbpix/for.htm"> microchip seepicsrc psbpix for</A>
|Did you find what you needed?|
Welcome to massmind.org!
Welcome to techref.massmind.org!