You are on page 1of 30

WARS PIC Assembly Programming Guide

1 CONDITIONAL EXPRESSIONS
1.1 THE BASICS
For a program to be useful, it must be able to make decisions. Is one variable greater than another is? Are two variables equal or not equal? Which part of the program should it jump to next? Is a bit set or cleared? Applying one or more of these questions to the contents of a microcontrollers registers and acting on the results make a program.

1.2 HIGHER LEVEL IMPLEMENTATIONS


In a BASIC style psudo-code comparison you could test for equality with a line of code like:

IF (expression1) = (expression2) THEN (DoSomething)


Or non-equality:

IF (expression1) <> (expression2) THEN (DoSomething)


Similarly, you could test the relative magnitude:

IF (expression1) Or IF (expression1) Or IF (expression1) Or IF (expression1)

> (expression2) THEN (DoSomething) < (expression2) THEN (DoSomething) >= (expression2) THEN (DoSomething) <= (expression2) THEN (DoSomething)

In PIC assembly language there are no relational operators (=, <>, >=, etc.) for doing comparisons you must look at the Instruction Set Summary for the microcontroller you are using and find an instruction which will cause something distinct to happen when the comparison is made.

1.3 MAKING COMPARISONS


The question now turns to: how do you make a comparison in the first place? The answer is easy; subtraction. Subtraction of two unknown values stored in two registers may seem like it wouldnt produce any useful result. Think of it this way: If you took some apples away from a bunch of apples, and ended up with zero apples, you would know you had exactly that amount of apples to begin with. In essence, you compared the amount of apples you removed to the amount of apples you originally had, and concluded that the two amounts were equal.

Winnipeg Area Robotics Society

2 1.3.a SUBTRACTION

WARS PIC Assembly Programming Guide

Subtraction with a PIC microcontroller is very simple. Load the W register (the working register) with the number you wish to subtract.

Example: movlw
Or

0x03

; Move literal value 3 to W register

movf

reg1, 0

; Move contents of reg1 to W register

Then subtract the value in the W register from another register with this instruction:

subwf
1.3.b THE CARRY BIT

reg2,0

; Subtract W from reg2

Now that we have performed our subtraction, we can test for magnitude or equality by checking the bits of the STATUS register.

If the value in W was greater than the value in reg2 then the Carry bit in the STATUS register will be clear. (i.e. C=0; the result was negative) If the value in W was less than or equal to the value in reg2 then the Carry bit in the STATUS register will be set. (i.e. C=1; the result was positive or zero)

This last statement is a bit of a disappointment. It implies that we need to rule out the possibility of the registers being equal which would require an extra step, extra code, and extra clock cycles to complete the comparison. In reality since you are writing the code, and presumably you know what you are trying to accomplish, you can change any less than comparisons into greater than comparisons. If A is less than B obviously, B is greater than A. You can do this by loading B into the W register then subtract it from A. 1.3.c THE ZERO BIT If youve been thinking ahead, you can see the other problem this creates. If you are testing for equality, you must rule out the possibility of one register being less than the other. Thankfully, the designers of microcontrollers realized that we would need an easy way to test for equality, and they provided a separate bit in the STATUS register: the Zero bit.

If the result of a subtraction equals exactly zero, the Zero bit will be set in the STATUS register (Z=0; the result is zero)

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide 1.3.d ALTERNATIVES TO SUBTRACTION The PIC microcontrollers with 14 bit cores have no alternative to subtraction when testing for greater than or less than.

1.3.d1 TESTING FOR EQUALITY


There is an alternative to subtraction when testing for equality. Any number when it is Exclusive-ORed (XOR) with itself is zero. The XORWF and XORLW commands can be used to test for equality. An equality comparison is usually made this way in a microcontroller without a compare instruction. XOR is a logic operation, which means it executes quickly, and it only affects the Z bit in the Status register, not the C bit.

Example: movf reg1,0 xorlw 0x03

; Move reg1 to W register ; Is W = 0x03 ?

If the contents of reg1 equaled 0x03 the Zero bit would now be set in the STATUS register. Similarly, to compare two registers to each other:

Example: movf reg1,0 xorwf reg2

; Move reg1 to W register ; XOR W register with reg2

If the contents of reg1 equaled the contents of reg2, the Zero bit would now be set in the STATUS register.

1.3.d2 TESTING FOR INEQUALITY


If you wish to test a statement like:

IF (expression1) <> (expression2) then (DoSomething)


You simply use the XOR operators and test if the Zero bit in the STATUS register is clear. (i.e. Z=0)

Example: movf reg1,0 xorwf reg2

; Move reg1 to W register ; XOR W register with reg2

If the contents of reg1 equaled the contents of reg2, the Zero bit would now be clear in the STATUS register.

1.3.d3 TESTING A REGISTER FOR ZERO


To quickly test if a register contains a zero value, you can move the contents of the register to itself. This eliminates an extra step involved with subtraction or XORing.

Example: movf reg1

; Move reg1 to itself

If the contents of reg1 equaled zero then the Zero bit would be set in the STATUS register.

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide The W register cannot be tested this way because it is not an addressable register. However, since the value of the W register is always known following the execution of an instruction it does not need to be tested.

1.4 TESTING BITS


The only other tests needed to complete our conditional expressions are bit tests. Previously, I mentioned that the Zero and Carry bits could be used to determine relative magnitude and/or equality but I didnt mention how to test these bits. Once again, the PICs designers have foreseen the usefulness of bit tests and made our job simple. There is a reason why we are testing the bit in the first place. We will be executing different pieces of code depending on the outcome of our conditional expression. Conveniently, each bit test instruction also contains a conditional jump instruction. There are two bit test instructions:

BTFSC Bit Test F Skip if Clear BTFSS Bit Test F Skip if Set

These two instructions allow you to test a bit in any addressable register and skip the next instruction if the bit is clear or set depending on which instruction you use.

Example:
or

btfsc status, Z ; Test the Zero bit in STATUS register goto somewhere ; Skip this line if Z=0

Example:

btfss status, Z ; Test the Zero bit in STATUS register goto somewhere ; Skip this line if Z=1

As in these examples, the line following the bit test is usually a jump instruction. Since the BTFSS and BTFSC instructions only skip one line, you need to jump over the block of code that will be executed if the bit test fails. We will see more of this in the following section.

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

2 IMPLEMENTING THE IF STATEMENT


Now that we know how to compare values, test bits, and use the results of the test to jump to different sections of code, we can implement perhaps the most important programming construct, the IF statement. The IF statement is a high-level language construct which is implemented in almost every programming language. It generally takes the form of:

If (condition is true) then (statement 1) Else (statement 2) Endif


The Else clause and Endif keyword are sometimes optional, or not implemented.

2.1 THE IF..THEN..ELSE CLAUSE


We can use what we learned in the previous section to create a simple If..Then..Else clause in assembly language.

BASIC Code If (a = b) then a=1 else a=2 endif

Assembly Code movf xorwf btfss goto movlw movwf goto movlw movwf _endif: _a,0 _b,0 status, Z $+4 0x01 _a _endif 0x02 _a

In this example, you can see that it generally takes many assembly language instructions to make up one higher-level expression. You can see which parts of the BASIC code relate to its Assembly language counterpart by looking at the color-coding.

(rest of program)

Winnipeg Area Robotics Society

6 2.1.a THE $ OPERATOR

WARS PIC Assembly Programming Guide

This code fragment also demonstrates how to jump over small sections of code using the $ operator. The $ symbol refers to the current value of the program counter. So a statement like:

goto $+4
means goto here plus four instructions. Which, in this case points to the line containing:

movlw 0x02
which is the beginning of our Else clause. This saves us from having to come up with labels for each jump we make. For instance the next time we had an IF statement we would have to come up with another _endif: label. We could use _endif2: or _endif3: but this could easily get out of hand in a large program. 2.1.b AN INEQUALITY To see how the code would change here is an example of an inequality:

BASIC Code If (a <> b) then a=1 else a=2 endif

Assembly Code movf xorwf btfsc goto movlw movwf goto movlw movwf _endif: _a,0 _b,0 status, Z $+4 0x01 _a _endif 0x02 _a

As you can see, only the bit test has changed. We are now using BTFSC instead of BTFSS to test the Zero bit of the STATUS register.

(rest of program)

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide 2.1.c MAGNITUDE COMPARISONS Examples of magnitude comparisons are as follows:

BASIC Code If (a > b) then a=1 else a=2 endif

Assembly Code movf _a,0 subwf _b,0 btfsc status, C goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of prog.) Assembly Code movf _b,0 subwf _a,0 btfss status, C goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of prog.)

BASIC Code If (a < b) then a=1 else a=2 endif

Assembly Code movf _b,0 subwf _a,0 btfsc status, C goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of prog.) Assembly Code movf _a,0 subwf _b,0 btfss status, C goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of prog.)

BASIC Code If (a >= b) then else a=2 endif

BASIC Code If (a <= b) then else a=2 endif

As you can see, the code remains almost entirely the same for each of the examples. Notice that the (a<b) example is actually implemented as (b>a). The value in register B is subtracted from the value in register A, and the test checks for (b>a). Similarly, since a set Carry bit indicates a less than or equal to relationship the (a>=b) example is implemented as (b<=a). 2.1.d COMPILER INEFFICIENCIES Since compiler writers have no way of knowing what you are trying to accomplish with your code, a low quality compiler might generate code with a test for equality and a separate test for magnitude. This would contain extra jump instructions and would slow a program down unnecessarily.

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide A side-by-side comparison of the right and wrong ways to implement (a>=b) shows this extra code:

BASIC Code If (a >= b) then a=1 else a=2 endif

Assembly Code Implemented As (b <= a) movf _b,0 subwf _a,0 btfss status, C goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of program)

Inefficient Assembly Code movf _a,0 subwf _b,0 btfsc status, C goto $+4 movlw 0x01 movwf _a goto _endif movf _a,0 xorwf _b,0 btfss status, Z goto $+4 movlw 0x01 movwf _a goto _endif movlw 0x02 movwf _a _endif: (rest of program)

The inefficient assembly code example is implemented as two separate tests and can be written in BASIC code as:

If (a > b) then a = 1 else If (a = b) then a = 1 else a = 2 endif


The extra code is highlighted in red in the table. There are seven extra lines of code and an extra jump. This uses up the limited code space in the PIC microcontrollers and slows down your program. PIC Basic Pro LITE V7.10 from Leading Edge Technologies for example, not only generates the inefficient code for (a>=b) they also perform both tests for (a<=b). They store A in a temporary variable, using up extra code and an extra register. On top of that to test for (a>b) they subtract B from A and if the carry bit is set they assume A is greater than B. This does not rule out the possibility that A is equal to B, so your program may not work the way you intended even though YOU wrote it correctly. 2.1.e AH, SMUG MODE The previous examples have illustrated that even on a simple IF statement a human programmer could produce more efficient code than a compiler. Although, to be fair, good compilers would change (a>=b) into (b<=a) and generate the more efficient code.

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

2.2 CHAINING TOGETHER MULTIPLE EXPRESSIONS


IF statements can make more than one equality or magnitude comparison in the same code fragment. These comparisons are glued together by the logical AND and/or logical OR.

If ((a > b) AND (b < c)) OR (c <> a) then a = b endif


Complex IF statements can be decomposed into multiple simple IF statements. The previous code can be thought of as:

If (c <> a) then a = b else If (a > b) then If (c > b) then a = b endif


Notice that because the OR part of the expression contains only one test, whereas the AND part contains two tests, you should perform the OR test first. If the OR part of the expression is true you can skip over the AND part of the expression, also note that (b < c) has been changed to (c > b) to produce efficient code. Since OR statements dont depend on any other part of the expression they can become completely separate IF statements. Since AND statements must both be satisfied they form an IF then IF construct.

If (expression1 AND expression2) then statement


Is equivalent to:

If (expression1) then If (expression2) then statement


And

If (expression1) OR (expression2) then statement


Is equivalent to:

If (expression1) then statement If (expression2) then statement

Winnipeg Area Robotics Society

10

WARS PIC Assembly Programming Guide

Here is the code for the previous example:

BASIC Code If ((a > b) AND (b < c)) OR (c <> a) then a = b endif

Assembly Code Implemented As: If (c <> a) then a = b else If (a > b) then If (c > b) then a = b endif movf _a,0 xorwf _c,0 btfss status, Z goto $+9 movf _b,0 subwf _a,0 btfsc status, C goto _endif movf _c,0 subwf _b,0 btfsc status, C goto _endif movf _b,0 movwf _a _endif: (rest of program)

Once again, the colors indicate the relative sections of the code. The OR section is performed first followed by the AND section, and again (b < c) has been implemented as (c > b). 2.2.a NEGATIVE LOGIC Notice I have made a departure from the previous programming formula of:

Test if (C <> A) is TRUE Skip GOTO if TRUE Follow GOTO if FALSE


The OR section of code is implemented as:

Test if (C <> A) is FALSE Skip GOTO if FALSE Follow GOTO if TRUE


The practice of testing for the opposite result then reversing the way we perform the GOTO is called negative logic. Negative logic is perfectly acceptable. There would have been at least one extra jump instruction if we didnt use negative logic. The only problem with changing between positive and negative logic within the same piece of code is that it can make it very hard to understand what you were trying to achieve when you wrote it.

10

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide 2.2.b USING A HIGHER LEVEL LANGUAGE AS COMMENTS

11

With the color coding it is easy to tell what each piece of code does. If you wrote a program using this code, I doubt you would be color-coding it, and you would quickly forget what you were trying to accomplish. When you write out your code, comment the code with the BASIC code. BASIC is self-explanatory so you can easily remember what you were trying to do.

Winnipeg Area Robotics Society

11

12

WARS PIC Assembly Programming Guide

3 IMPLEMENTING CASE STATEMENTS


The case statement isnt usually included in the BASIC language. Pascal, C, C++, and Visual BASIC, among others, all have a version of the CASE statement. It is a useful, well-known construct so I will include it here. CASE statements are N-conditional branch statements. An Nconditional branch allows a programming language to determine one of branches in the code.

3.1 THE SWITCH STATEMENT


The SWITCH statement of the C programming language contains all of the options available to a CASE statement so I will use it as my example. The SWITCH statement has the following format:

switch(expression) { case (value1): statement_1; . . . statement_N; break; case (value2): statement_1; . . . statement_N; break; . . . case (value N): statement_1; . . . statement_N; break; default: statements; }

12

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

13

The (expression) part of the above psudo-code evaluates to an integer. This integer is used as an index to determine which case block is executed. The optional break keyword is used to terminate each case block. If the current case block does not include a break keyword, program execution continues into the next case block. The optional default section of the psudocode is executed if there is no match to the (expression). If there is no match to (expression), and the default section is omitted, then no part of the switch body is executed. 3.1.a DEALING WITH SMALL MICROCONTROLLERS Since integers are usually two bytes long, (16 bits 16,536 possibilities) and PIC microcontrollers have small program code memories, the switch (expression) is usually implemented as an 8 bit (256 possibilities) character (char) data type. You will probably never even come close to using a 256 case SELECT statement.

3.2 IMPLEMENTATION
3.2.a LINEAR SEQUENCE OF COMPARISONS The simplest way to implement the CASE statement is as a linear sequence of comparisons against each arm in the statement.

If (expression = 1) then statement1 endif If (expression = 2) then statement2 endif If (expression = 3) then statement3 endif If (expression = 4) then statement4 endif
This technique is inefficient for even a small number of CASE statements. The value of the expression is tested against a constant for each IF statement even if the expression has already matched a previous IF statement. For large numbers of IF statements this can take many clock cycles. 3.2.b THE IF-TREE A more sophisticated technique is the if-tree, where the selection is accomplished by a nested set of comparisons organized into a tree.

If (expression = 1) then statement1 else If (expression = 2) then statement2 else If (expression = 3) then statement3 else If (expression = 4) then statement4 endif

Winnipeg Area Robotics Society

13

14

WARS PIC Assembly Programming Guide This technique is more efficient then the previous technique because the comparisons stop when a match is found. This technique is efficient for a small number of CASE statements, but the number of possible comparisons increases linearly with the number of CASE statements.

3.2.c THE JUMP TABLE A more common implementation is the jump table. In this approach the

(expression) is used as an index or offset into a table containing the start


address of the code to be executed. This approach is efficient for large numbers of consecutive CASE statements.

Case_Table:

Location 0: Location 1: Location 2: Location 3: Location 4: Location 5:

goto case_0 goto case_1 goto case_2 goto case_3 goto case_4 goto case_5

In this example, if the (expression) evaluated to three, the code in location 3 would be executed. This results in a jump to case 3s code. The advantage to this approach is the execution time of this section of the code is constant regardless of the number of cases. Its main disadvantage is that the case indices must be consecutive.

14

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

15

Here is an example of how to implement the SWITCH statement in a PIC microcontroller with a 14-bit instruction set:

C Style Code switch (3) { case 0: statement1; statement2; break; case 1: statement1; statement2; break; case 2: statement; case 3: statement1; statement2; break; default: statement1; }

_case_table:

_case0:

_case1:

_case2: _case3:

_default:

Assembly Code movlw 0x03 ;Load the expression call _case_table ;value and call the table nop ;This is where the program . ;resumes execution . . . addwf PCL ;add the expression goto _case0 ;value to the program goto _case1 ;counter and go to goto _case2 ;the address of the proper goto _case3 ;case statement goto _default . . . statement1 statement2 return ;jump back to main program statement1 statement2 return ;jump back to main program statement ;Notice lack of return statement1 ;case2 continues into case3 statement2 return ;jump back to main program statement1 return ;jump back to main program

This example has the CASE table code separate in memory from the code for the case statements. If the case statements immediately followed the CASE table the goto instruction could have been changed to the goto $+(offset) form:

Winnipeg Area Robotics Society

15

16

WARS PIC Assembly Programming Guide

_case_table:

Assembly Code 0x03 ;Load the expression _case_table ;value and call the table ;This is where the program . ;resumes execution . . . addwf PCL ;add the expression goto $+5 ;value to the program goto $+7 ;counter and go to goto $+9 ;the address of the proper goto $+9 ;case statement goto $+11 statement1 statement2 return ;jump back to main program statement1 statement2 return ;jump back to main program statement ;Notice lack of return statement1 ;case2 continues into case3 statement2 return ;jump back to main program statement1 return ;jump back to main program movlw call nop

This leads to code that is fast, compact, efficient, easy to read, and almost as concise as the high-level language equivalent.

3.2.c1 BOUNDS CHECKING


There are a couple of flaws in our implementation. There is no bounds checking on the SWITCH expression, and the default action only occurs when the SWITCH expression evaluates to 4. If the SWITCH expression evaluated to 5 or greater our program would become lost. We need to test that the value of the expression is in a valid range, and if it isnt the default action should be performed. An IF statement can be used to do the range checking.

If (expression < lower_bound) OR (expression > upper_bound) then expression = 4 endif


For this example expression = 4 will result in a jump to the default action for any value outside the valid range.

16

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

17

3.2.c2 INDEX CONSIDERATIONS


All index values must start from zero. If you expected numbers in the range of 65 to 90 (0x41 to 0x5A) for example, you would have to remove the offset of 65 first. This is easily accomplished by subtracting 65 (0x41) from the proposed index value. This gives us a new index value in the range of 0 to 25 (0x00 to 0x19). Our range test is now made easier. With an 8-bit index value, any number less than zero will be a large number due to wrap-around (unsigned).

Example: Large eight bit #

0x04 0x06 0xFE

0000 0100 0000 0110 1111 1110

This automatically turns any number less than a number in the valid range into a number greater than a number in the valid range. Our range can be determined by subtracting the lower bound from the upper bound (90 65 = 25). Our range test now becomes:

If (expression > 25) then goto _default endif

Winnipeg Area Robotics Society

17

18

WARS PIC Assembly Programming Guide

Sometimes programmers are tempted to jump into the middle of a section of code to save space or time. This can help in the short term but the code ends up being very hard to follow and the structure is very hard to see. For example, look at this SWITCH statement generated by the HIGH-TEC PICC compiler:

Address 03DD 03DE 03DF 03E0 03E1 03E2 03E3 03E4 03E5 03E6 03E7 03E8 03E9 03EA 03EB 03EC 03ED 03EE 03EF 03F0 03F1 03F2 03F3 03F4 03F5 03F6 03F7 03F8 03F9 03FA 03FB 03FC 03FD 03FE 03FF

Label main case0

case1

case2 case3

default

start

end

Assembly Code goto start clrf _a clrf _b incf _b goto end clrf _a incf _a movlw 0x2 goto 0x3EC movlw 0x2 movwf _a movlw 0x3 movwf _a goto 0x3DF movlw 0x8 movwf _b goto end movf _a, W movwf _temp movlw 0x4 subwf _temp, W btfsc STATUS, C goto default movlw 0x3 movwf PCLATH movlw 0xFB addwf _temp, W btfsc STATUS, C incf PCLATH movwf PCL goto case0 goto case1 goto case2 goto case3 goto exit

Comments ;a=0 ;b=1 ;Could have jumped to exit directly ;a=1 ;Spaghetti code ;a=2 ;a=3 ;Spaghetti code ;b=8 ;Could have jumped to exit directly ;Save a in temp ; ;Perform range ;test ;

;Add index to program counter ;CASE jump table

18

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

19

Without the comments and shading you probably could have guessed it was a SWITCH statement but the two spaghetti-code goto instructions make it hard to see the individual cases. Here is the C code it was generated from:

main() { char a, b; switch (a) { case 0: a=0; b=1; break; case 1: a=1; b=2; break; case 2: a=2; case 3: a=3; b=1; break; default: b=8; } } 3.2.c3 THE WRAP-AROUND PROBLEM
The only danger to this method occurs when you are using more than 127 cases. After 127 cases, it is possible to wrap-around into the valid range. At this point, you would have to explicitly compare the number to the upper and lower bounds as in the preceding example.

If (expression < lower_bound) OR (expression > upper_bound) then goto _default endif

Winnipeg Area Robotics Society

19

20

WARS PIC Assembly Programming Guide

3.2.c4 NON-CONSECUTIVE INDICES


The previous techniques work very well with consecutive index values. Suppose you wanted something like this:

switch (expression) { case 0: statement1; break; case 20: statement2; break; case 50: statement3; break; }
Using the jump table method you would use 51 memory locations to index three statements. In this case, it is better to use an If-Tree approach:

If (expression = 0) then statement1 else If (expression = 20) then statement2 else If (expression = 50) then statement3 endif
Consider the following situation:

switch (expression) { case 0: statement1; break; case 50: statement2; break; case 51: statement3; break; case 52: statement4; break; case 53: statement5; break; case 54: statement6; break; case 55: statement7; break; }
20 Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide In this situation, a jump table would require 56 memory locations to index seven statements, and an If-Tree would be getting quite large.

21

In this situation, it is possible to combine the approaches to yield an optimal solution. Notice that cases 50 to 55 are consecutive and case 0 is the only non-consecutive case. Use an IF statement to test for case 0 and a SWITCH statement for cases 50 to 55.

If (expression = 0) then goto statement1 endif switch (expression 50) { case 0: statement2; break; case 1: statement3; break; case 2: statement4; break; case 3: statement5; break; case 4: statement6; break; case 5: statement7; break; }
Notice that in the SWITCH statement we subtracted 50 from the expression value to translate a range from 50 to 55 down to 0 to 5. Also, note that if the expression were equal to zero there would be no need to enter the SWITCH statement. You would then have to include some code to jump over the SWITCH part of the code. 3.2.d ASSEMBLER VS. COMPILER Examining the expected data and optimizing the implementation is a technique usually only found in compilers for larger more popular microprocessors. Most PIC compilers would not bother with this optimization and would implement every SWITCH statement as an If-tree. Once again, the assembly programmer has a chance to outperform the compiler by having the flexibility to choose implementations.

Winnipeg Area Robotics Society

21

22

WARS PIC Assembly Programming Guide

4 LOOPS
After the ability to make decisions, the ability to loop is the most important characteristic of a program. Loops are used for time delays, comparing strings, generating cyclic redundancy checks, shifting in serial data, and many other applications. Almost every program contains some type of loop. There are many types of loops but they all have a few elements in common. There is an optional initialization section, a loop termination test, and the body of the loop. Common types of loops in high-level languages are:

While loops DoWhile (Repeat...Until) Loops FOR Loops LOOPENDLOOP Loops

4.1 THE INFINITE LOOP


The simplest loop from an assembly programmers perspective is the infinite loop. In the C programming language it would be:

for (;;) { loop body } or while(1) { loop body }


Implementing this type of loop is trivial for the assembly programmer. It is simply a goto instruction after the loop body, which points back to the beginning of the loop body.

loop:

loopbody goto loop

A microcontroller usually performs some continuous task while it is turned on even if it is just waiting for in input. This task never ends so it is incorporated within an infinite loop.

22

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide

23

4.2 LOOPENDLOOP LOOPS


A close relative to the infinite loop is the LOOPENDLOOP structure. The keyword LOOP defines the beginning of the loop, and the keyword ENDLOOP defines the end of the loop. The keyword ENDLOOP does not cause the loop to terminate it simply marks the point where the program jumps. Without any modification, the LOOPENDLOOP structure forms an infinite loop:

LOOP loopbody ENDLOOP;


To make it more functional we can add a termination condition to the loop body:

LOOP loopbody; EXIT loop WHEN true; loopbody; ENDLOOP;


The termination condition can be anywhere inside the LOOPENDLOOP structure. This structure is used in the U.S. militarys ADA programming language but it can easily be replaced with a WHILE loop. This structure can be emulated by an infinite FOR loop with an IF (expression) THEN goto statement as the termination condition. This insight leads us to the following psudo-assembly code:

statement statement btfsc STATUS, Z goto rest_of_prog statement goto loop rest_of_prog: statement

loop:

;Test for some condition ;Break out of the loop if condition is false

4.3 THE WHILE LOOP


The WHILE loop is similar to the LOOPENDLOOP structure. Whereas the LOOPENDLOOP has its termination condition in the body of the loop, the WHILE loop has its termination condition at the beginning of the loop. The implication of this is, that while the LOOPENDLOOP structure executes once, at least partially, the WHILE loop may never execute. If the termination condition is logically FALSE before the WHILE loop executes the whole loop structure will be skipped.

Winnipeg Area Robotics Society

23

24

WARS PIC Assembly Programming Guide In the C programming language, WHILE loops take this form:

while (expression = TRUE) { statement1; statement2; statement3; }


If the (expression) part of the WHILE loop evaluates to a Boolean TRUE the loop is executed all the way through. If the (expression) part of the WHILE loop evaluates to a Boolean FALSE the loop is immediately exited. In most computer systems TRUE = 1 and FALSE = 0 by definition. The WHILE loop only exits when the expression is FALSE so any non-zero constant will cause the WHILE loop to infinitely loop.

example: while (1) { loopforever; }


This also implies that you could do something like this:

a = 5; while (a) { a = a 1; }
This loops exactly five times. On the sixth time a = 0 which is defined as FALSE so the loop immediately exits. In PIC assembly language:

C Code a = 5; while (a) { a = a 1; }

Assembly Code movlw 0x05 movwf _a _loop: movf _a, 1 btfsc STATUS, Z goto _exit decf _a goto _loop _exit: rest of program ;a=5 ;move a to itself to set or clear Z bit ;test if it was zero ;a = a 1 (loop body)

The termination conditions can be tested using the methods described in the Conditional Expressions chapter, and the loop is created by a goto instruction that jumps backwards in program memory.

4.4 THE DOWHILE LOOP


The opposite of the WHILE loop is the DOWHILE loop. The DOWHILE loop executes at least once and has its loop termination condition at the bottom of the loop. 24 Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide In the C programming language, the DOWHILE loop takes this form:

25

do { statement; . . . statement; }while (expression = TRUE);


Its about this point when most people start to say, Why bother. I can do this with a WHILE loop. 4.4.a WHY BOTHER? Suppose you wrote a program to display a menu on an LCD. The user presses buttons to select the appropriate option. With a DOWHILE loop you could do this:

do { a = read(portb); switch (a) { case 0: break; case 1; display_temperature(); break; case 2: display_humidity(); break; } }while (a < 3);

Winnipeg Area Robotics Society

25

26

WARS PIC Assembly Programming Guide With a WHILE loop you would have no idea what the value of port B was so you would have to check before you entered the loop, and again inside the loop.

a = read(portb); while (a < 3) { switch (a) { case 0: break; case 1; display_temperature(); break; case 2: display_humidity(); break; } a = read(portb); }
In PIC assembly language the DOWHILE loop is very similar to the WHILE loop, only the location of the loop termination condition changes.

C Code a = 5; do { a = a 1; } while (a);

Assembly Code movlw 0x05 movwf _a _loop: decf _a movf _a, 1 btfss STATUS, Z goto _loop rest of program ;a = 5 ;a = a 1 (loop body) ;move a to itself to set or clear Z bit ;test if it was zero

Notice that when the loop termination statement is encountered for the first time the value of a is 4. This may lead to some confusion. Does the loop execute 4 times or 5 times? The answer is that it has already executed once by the time it reaches the termination statement so it will execute 4 more times, to make 5 times in total. 4.4.b THE OPTIMAL LOOP STRUCTURE Another important thing to notice is that because the termination condition was moved to the end of the loop we could eliminate an extra goto instruction. This is the optimal loop structure. The extra goto instruction slowed down our loop by two clock cycles in every iteration of the loop. This can add up very fast in a microprocessor and lead to sluggish performance, especially in real-time applications.

26

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide 4.4.c OPTIMIZING THE WHILE LOOP

27

The WHILE loop can be optimized by converting a DOWHILE loop into a WHILE loop with an extra goto instruction:

C Code a = 5; while (a) { a = a 1; }

Assembly Code movlw 0x05 movwf _a goto _test _loop: decf _a _test: movf _a, 1 btfss STATUS, Z goto _loop rest of program ;a = 5 ;a = a 1 (loop body) ;move a to itself to set or clear Z bit ;test if it was zero

At first, this doesnt look like weve accomplished anything. The extra goto instruction is only performed once, upon entering the loop, and the rest of the loop is identical to the DOWHILE loop. The resulting code is marginally harder to follow, but if you always write your WHILE loops this way it should be easily recognized.

4.5 THE FOR LOOP


4.5.a RAISON DTRE The FOR loop is used to perform a known number of iterations. That is why it is the preferred way of implementing an infinite loop. When you program an infinite loop, you know it will be looping forever. If you do not know in advance how many times a loop will execute, you should use one of the other loop structures. In the C programming language the FOR loop takes the following form:

for (initialization ;termination condition; increment) { loopbody; }


Or in BASIC:

FOR (initialization TO condition STEP increment) loopbody NEXT loopcounter

Winnipeg Area Robotics Society

27

28

WARS PIC Assembly Programming Guide The STEP keyword is optional in the BASIC example. If STEP is not included, the default step size is +1. The entire contents of the C language FOR expression are optional. Here are two typical FOR loops:

BASIC Example FOR j =1 to 20 loopbody NEXT j


In both cases:

C Language Example for (j=1; j <= 20; ++j) { loopbody; }

the loop counter variable j is initialized to 1 the loop continues until j > 20 j increments at the end of the loop.

In PIC assembly language this can be implemented as:

BASIC Code FOR j = 1 to 20 loopbody NEXT j

Assembly Code clrf _j incf _j _Loop: movf _j, 0 xorlw 0x15 btfsc STATUS, Z goto _end loopbody incf _j goto _Loop: _end rest of program ;j = 1 ;is j decimal 21 ;if yes then end ;increment loop counter

Since we know that j is not greater than 20 the first time through the loop (because we initialized it to 1), we can move the termination test to the end of the loop:

BASIC Code FOR j = 1 to 20 loopbody NEXT j

Assembly Code clrf _j incf _j _Loop: loopbody incf _j movf _j, 0 xorlw 0x15 btfsc STATUS, Z goto _Loop: _end rest of program ;j = 1 ;increment loop counter ;is j 21 (decimal)

28

Winnipeg Area Robotics Society

WARS PIC Assembly Programming Guide 4.5.b COUNTING BACKWARDS

29

If the loop counter variable is not being used inside the loop body, you can further optimize the FOR loop by having it count backwards. This is the equivalent to: FOR j = 20 to 1 STEP 1.

BASIC Code FOR j = 1 to 20 loopbody NEXT j

Assembly Code Implemented As: FOR j = 20 to 1 STEP 1 movlw 0x14 movwf _j ;j = 20 _Loop: loopbody decf _j ;decrement loop counter movf _j, 1 btfss STATUS, Z ;is j = 0 goto _Loop: _end rest of program

The FOR loop still executes 20 times, but now it does it in reverse. Running the loop counter down to zero eliminates the need to subtract or XOR a literal value with the W register. We can now simply check the ZERO bit of the STATUS register when the loop counter decrements. By performing both optimizations, we have saved three instruction cycles per iteration. Moving the termination condition to the end of the loop saved two instruction cycles by eliminating a goto instruction, and running the counter backwards saved one instruction cycle by eliminating a subtraction. 4.5.c USING STEP SIZES OTHER THAN +/- 1 The technique of counting down to zero doesnt work with non-unity step sizes unless the loop count is evenly divisible by the step size. The increment and decrement instructions incf and decf cannot be used. A literal (the step size) must be added or subtracted from the loop counter on each iteration.

BASIC Code FOR j = 1 to 20 STEP 3 loopbody NEXT j

_Loop:

_end

Assembly Code clrf _j incf _j ;j = 1 loopbody movlw 0x03 addwf _j ;increment loop counter movf _j, 0 sublw 0x14 ;is j > 20 (decimal) btfsc STATUS, C goto _Loop: rest of program

This is the best we can do in this situation; unless we cheat.

Winnipeg Area Robotics Society

29

30 So lets cheat:

WARS PIC Assembly Programming Guide

BASIC Code FOR j = 1 to 20 STEP 3 loopbody NEXT j

Assembly Code movlw 0x15 movwf _j loopbody movlw 0x03 subwf _j movf _j, 1 btfsc STATUS, Z goto _Loop: rest of program ;j = 21

_Loop:

;decrement loop counter by 3 ;is j = 0

_end

So how did we come up with this? Heres how: The forwards loop executed until it reached (1 + (X * 3)) > 20. If you find the value for X you know how many times the loop will execute, which in this case was 7. If you start counting backwards from (X * 3) = (7 * 3) = 21 you will reach zero in the same number of iterations. Doing all this work to save one instruction cycle per iteration usually isnt worth it. In most cases, youll never see the difference between the previous two pieces of code. Chances are if youve picked a non-unity step size, you are using the loop counter value somewhere inside the loop. If that is the case, you probably cant run the counter backwards anyway.

30

Winnipeg Area Robotics Society

You might also like