pic basic vs assembly and code tutorial

30
WARS PIC Assembly Programming Guide 1 Winnipeg Area Robotics Society 1 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 microcontroller’s 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) > (expression2) THEN (DoSomething) Or IF (expression1) < (expression2) THEN (DoSomething) Or IF (expression1) >= (expression2) THEN (DoSomething) Or IF (expression1) <= (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 wouldn’t 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.

Upload: saneild

Post on 04-Mar-2015

432 views

Category:

Documents


9 download

TRANSCRIPT

Page 1: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 1

Winnipeg Area Robotics Society 1

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 microcontroller’s 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) > (expression2) THEN (DoSomething) Or IF (expression1) < (expression2) THEN (DoSomething) Or IF (expression1) >= (expression2) THEN (DoSomething) Or IF (expression1) <= (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 wouldn’t 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.

Page 2: PIC Basic vs Assembly and Code Tutorial

2 WARS PIC Assembly Programming Guide

2 Winnipeg Area Robotics Society

1.3.a SUBTRACTION

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

Example: movlw 0x03 ; Move literal value 3 to W register

Or

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 reg2,0 ; Subtract W from reg2

1.3.b THE CARRY BIT

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 you’ve 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)

Page 3: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 3

Winnipeg Area Robotics Society 3

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-OR’ed (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 ; Move reg1 to W register xorlw 0x03 ; 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 ; Move reg1 to W register xorwf reg2 ; 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 ; Move reg1 to W register xorwf reg2 ; 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.

Page 4: PIC Basic vs Assembly and Code Tutorial

4 WARS PIC Assembly Programming Guide

4 Winnipeg Area Robotics Society

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 didn’t mention how to test these bits. Once again, the PIC’s 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: btfsc status, Z ; Test the Zero bit in STATUS register goto somewhere ; Skip this line if Z=0

or

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.

Page 5: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 5

Winnipeg Area Robotics Society 5

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.

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.

BASIC Code Assembly Code

If (a = b) then a=1 movf _a,0

else a=2 xorwf _b,0

endif btfss status, Z

goto $+4

movlw 0x01

movwf _a

goto _endif

movlw 0x02

movwf _a

_endif: (rest of program)

Page 6: PIC Basic vs Assembly and Code Tutorial

6 WARS PIC Assembly Programming Guide

6 Winnipeg Area Robotics Society

2.1.a THE $ OPERATOR

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:

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.

BASIC Code Assembly Code

If (a <> b) then a=1 movf _a,0

else a=2 xorwf _b,0

endif btfsc status, Z

goto $+4

movlw 0x01

movwf _a

goto _endif

movlw 0x02

movwf _a

_endif: (rest of program)

Page 7: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 7

Winnipeg Area Robotics Society 7

2.1.c MAGNITUDE COMPARISONS

Examples of magnitude comparisons are as follows:

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.

BASIC Code Assembly Code BASIC Code Assembly Code

If (a > b) then a=1 movf _a,0 If (a < b) then a=1 movf _b,0 else a=2 subwf _b,0 else a=2 subwf _a,0 endif btfsc status, C endif btfsc status, C goto $+4 goto $+4 movlw 0x01 movlw 0x01 movwf _a movwf _a goto _endif goto _endif movlw 0x02 movlw 0x02 movwf _a movwf _a _endif: (rest of

prog.) _endif: (rest of

prog.) BASIC Code Assembly Code BASIC Code Assembly Code

If (a >= b) then movf _b,0 If (a <= b) then movf _a,0 else a=2 subwf _a,0 else a=2 subwf _b,0 endif btfss status, C endif btfss status, C goto $+4 goto $+4 movlw 0x01 movlw 0x01 movwf _a movwf _a goto _endif goto _endif movlw 0x02 movlw 0x02 movwf _a movwf _a _endif: (rest of

prog.) _endif: (rest of

prog.)

Page 8: PIC Basic vs Assembly and Code Tutorial

8 WARS PIC Assembly Programming Guide

8 Winnipeg Area Robotics Society

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

BASIC Code Assembly Code

Implemented As (b <= a) Inefficient Assembly Code

If (a >= b) then a=1 movf _b,0 movf _a,0 else a=2 subwf _a,0 subwf _b,0 endif btfss status, C btfsc status, C goto $+4 goto $+4 movlw 0x01 movlw 0x01 movwf _a movwf _a goto _endif goto _endif movlw 0x02 movf _a,0 movwf _a xorwf _b,0 _endif: (rest of program) 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.

Page 9: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 9

Winnipeg Area Robotics Society 9

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 don’t 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

Page 10: PIC Basic vs Assembly and Code Tutorial

10 WARS PIC Assembly Programming Guide

10 Winnipeg Area Robotics Society

Here is the code for the previous example:

BASIC Code

Assembly Code Implemented As: If (c <> a) then a = b else If (a > b) then If (c > b) then a = b endif

If ((a > b) AND (b < c)) OR (c <> a) then a = b movf _a,0 endif 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 didn’t 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.

Page 11: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 11

Winnipeg Area Robotics Society 11

2.2.b USING A HIGHER LEVEL LANGUAGE AS COMMENTS

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.

Page 12: PIC Basic vs Assembly and Code Tutorial

12 WARS PIC Assembly Programming Guide

12 Winnipeg Area Robotics Society

3 IMPLEMENTING CASE STATEMENTS

The case statement isn’t 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 N-conditional 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; }

Page 13: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 13

Winnipeg Area Robotics Society 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 psudo-code 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

Page 14: PIC Basic vs Assembly and Code Tutorial

14 WARS PIC Assembly Programming Guide

14 Winnipeg Area Robotics Society

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: goto case_0

Location 1: goto case_1

Location 2: goto case_2

Location 3: goto case_3

Location 4: goto case_4

Location 5: 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 3’s 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.

Page 15: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 15

Winnipeg Area Robotics Society 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 Assembly Code switch (3) movlw 0x03 ;Load the expression { call _case_table ;value and call the table case 0: nop ;This is where the program statement1; . ;resumes execution statement2; . break; . case 1: . statement1; _case_table: addwf PCL ;add the expression statement2; goto _case0 ;value to the program break; goto _case1 ;counter and go to case 2: goto _case2 ;the address of the proper statement; goto _case3 ;case statement case 3: goto _default statement1; . statement2; . break; . default: _case0: statement1 statement1; statement2 } return ;jump back to main program _case1: statement1 statement2 return ;jump back to main program _case2: statement ;Notice lack of return _case3: statement1 ;case2 continues into case3 statement2 return ;jump back to main program _default: 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:

Page 16: PIC Basic vs Assembly and Code Tutorial

16 WARS PIC Assembly Programming Guide

16 Winnipeg Area Robotics Society

Assembly Code movlw 0x03 ;Load the expression call _case_table ;value and call the table nop ;This is where the program . ;resumes execution . . .

_case_table: 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

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 isn’t 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.

Page 17: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 17

Winnipeg Area Robotics Society 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: 0x04 0000 0100 - 0x06 - 0000 0110

Large eight bit # → 0xFE 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

Page 18: PIC Basic vs Assembly and Code Tutorial

18 WARS PIC Assembly Programming Guide

18 Winnipeg Area Robotics Society

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 Label Assembly Code Comments 03DD main goto start 03DE case0 clrf _a ;a=0 03DF clrf _b 03E0 incf _b ;b=1 03E1 goto end ;Could have jumped to exit directly 03E2 case1 clrf _a 03E3 incf _a ;a=1 03E4 movlw 0x2 03E5 goto 0x3EC ;Spaghetti code 03E6 case2 movlw 0x2 03E7 movwf _a ;a=2 03E8 case3 movlw 0x3 03E9 movwf _a ;a=3 03EA goto 0x3DF ;Spaghetti code 03EB default movlw 0x8 03EC movwf _b ;b=8 03ED goto end ;Could have jumped to exit directly 03EE start movf _a, W 03EF movwf _temp ;Save a in temp 03F0 movlw 0x4 ; 03F1 subwf _temp, W ;Perform range 03F2 btfsc STATUS, C ;test 03F3 goto default ; 03F4 movlw 0x3 03F5 movwf PCLATH 03F6 movlw 0xFB 03F7 addwf _temp, W 03F8 btfsc STATUS, C 03F9 incf PCLATH 03FA movwf PCL ;Add index to program counter 03FB goto case0 03FC goto case1 ;CASE jump table 03FD goto case2 03FE goto case3 03FF end goto exit

Page 19: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 19

Winnipeg Area Robotics Society 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

Page 20: PIC Basic vs Assembly and Code Tutorial

20 WARS PIC Assembly Programming Guide

20 Winnipeg Area Robotics Society

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; }

Page 21: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 21

Winnipeg Area Robotics Society 21

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

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.

Page 22: PIC Basic vs Assembly and Code Tutorial

22 WARS PIC Assembly Programming Guide

22 Winnipeg Area Robotics Society

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

• Do…While (Repeat...Until) Loops

• FOR Loops

• LOOP…ENDLOOP 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.

Page 23: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 23

Winnipeg Area Robotics Society 23

4.2 LOOP…ENDLOOP LOOPS

A close relative to the infinite loop is the LOOP…ENDLOOP 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 LOOP…ENDLOOP 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 LOOP…ENDLOOP structure. This structure is used in the U.S. military’s 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:

loop: statement statement btfsc STATUS, Z ;Test for some condition goto rest_of_prog ;Break out of the loop if condition is false statement goto loop rest_of_prog: statement

4.3 THE WHILE LOOP

The WHILE loop is similar to the LOOP…ENDLOOP structure. Whereas the LOOP…ENDLOOP 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 LOOP…ENDLOOP 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.

Page 24: PIC Basic vs Assembly and Code Tutorial

24 WARS PIC Assembly Programming Guide

24 Winnipeg Area Robotics Society

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 Assembly Code a = 5; movlw 0x05 while (a) movwf _a ; a = 5 { _loop: movf _a, 1 ;move a to itself to set or clear Z bit a = a – 1; btfsc STATUS, Z ;test if it was zero } goto _exit decf _a ;a = a – 1 (loop body) goto _loop _exit: rest of program

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 DO…WHILE LOOP

The opposite of the WHILE loop is the DO…WHILE loop. The DO…WHILE loop executes at least once and has its loop termination condition at the bottom of the loop.

Page 25: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 25

Winnipeg Area Robotics Society 25

In the C programming language, the DO…WHILE loop takes this form:

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

It’s 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 DO…WHILE 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);

Page 26: PIC Basic vs Assembly and Code Tutorial

26 WARS PIC Assembly Programming Guide

26 Winnipeg Area Robotics Society

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 DO…WHILE loop is very similar to the WHILE loop, only the location of the loop termination condition changes.

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

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.

Page 27: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 27

Winnipeg Area Robotics Society 27

4.4.c OPTIMIZING THE WHILE LOOP

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

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

At first, this doesn’t look like we’ve accomplished anything. The extra goto instruction is only performed once, upon entering the loop, and the rest of the loop is identical to the DO…WHILE 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 D’ÊTRE

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

Page 28: PIC Basic vs Assembly and Code Tutorial

28 WARS PIC Assembly Programming Guide

28 Winnipeg Area Robotics Society

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 C Language Example FOR j =1 to 20 for (j=1; j <= 20; ++j) loopbody { NEXT j loopbody; }

In both cases:

• 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 Assembly Code FOR j = 1 to 20 clrf _j loopbody incf _j ;j = 1 NEXT j _Loop: movf _j, 0 xorlw 0x15 ;is j decimal 21 btfsc STATUS, Z goto _end ;if yes then end loopbody incf _j ;increment loop counter goto _Loop: _end rest of program

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 Assembly Code FOR j = 1 to 20 clrf _j loopbody incf _j ;j = 1 NEXT j _Loop: loopbody incf _j ;increment loop counter movf _j, 0 xorlw 0x15 ;is j 21 (decimal) btfsc STATUS, Z goto _Loop: _end rest of program

Page 29: PIC Basic vs Assembly and Code Tutorial

WARS PIC Assembly Programming Guide 29

Winnipeg Area Robotics Society 29

4.5.b COUNTING BACKWARDS

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 Assembly Code Implemented As: FOR j = 20 to 1 STEP –1

FOR j = 1 to 20 movlw 0x14 loopbody movwf _j ;j = 20 NEXT j _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 doesn’t 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 Assembly Code FOR j = 1 to 20 STEP 3 clrf _j loopbody incf _j ;j = 1 NEXT j _Loop: loopbody movlw 0x03 addwf _j ;increment loop counter movf _j, 0 sublw 0x14 ;is j > 20 (decimal) btfsc STATUS, C goto _Loop: _end rest of program

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

Page 30: PIC Basic vs Assembly and Code Tutorial

30 WARS PIC Assembly Programming Guide

30 Winnipeg Area Robotics Society

So lets cheat:

BASIC Code Assembly Code FOR j = 1 to 20 STEP 3 movlw 0x15 loopbody movwf _j ;j = 21 NEXT j _Loop: loopbody movlw 0x03 subwf _j ;decrement loop counter by 3 movf _j, 1 btfsc STATUS, Z ;is j = 0 goto _Loop: _end rest of program

So how did we come up with this? Here’s 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 isn’t worth it. In most cases, you’ll never see the difference between the previous two pieces of code. Chances are if you’ve picked a non-unity step size, you are using the loop counter value somewhere inside the loop. If that is the case, you probably can’t run the counter backwards anyway.