Tải bản đầy đủ (.pdf) (130 trang)

PROGRAMMING AND CUSTOMIZING THE PIC MICROCONTROLLER 3rd phần 5 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.5 MB, 130 trang )

THE DIFFERENCE BETWEEN DEFINES AND MACROS 493
which is not very helpful or easy to understand without a reference to how the pins are
organized. In comparison, the same instruction with the Serflag define, i.e.,
bsf Serflag
is much easier to understand without the aid of documentation or even descriptive com-
ments. The single define eliminates remembering (or looking up) where the bit is located
and which bit it is. When reading the code, using the define directive enhances the read-
ability of the purpose of the instruction over the actual source information.
Defines work because they directly copy in the string and let the assembler evaluate
it. This is different from equates, which evaluate a string and store it as a constant ref-
erenced to the label. This can cause some subtle differences that can be a problem if
you don’t know what you are doing.
For example, if you had the code
variable A = 37 ; ”Variable” is a run time variable
Test1 equal A * 5 ; Test1 = 185
#define Test2 A * 5 ; Test2 is Evaluated when it is used
:
A = A + 5 ; A = 42
:
movlw Test1 ; “Test1” is replaced with 185
:
movlw Test2 ; “Test2” is replaced with A * 5
; = 42 * 5
; = 210
in this case, even though Test1 and Test2 are declared at the same point in the code iden-
tically, they are evaluated differently and will be different values in different locations of the
application. This is a very useful capability, but one that can be a problem elsewhere.
In the sample code above, I declared the variable to be A. Variable is an MPASM
directive to create a temporary storage value during the application’s assembly and will
be discussed in the later sections of this chapter. When it is used in Test1, the value of
A when the assembler processor encounters the statement is used, multiplied by 5 and


assigned to Test1. After Test1 and Test2 are defined in the code, A is modified, which
results in a different value being calculated for Test2 when it is used later in the application.
A useful function that defines can provide is that they can provide constant strings
throughout an application. I use this ability to keep track of my “version number” of an
Simpo PDF Merge and Split Unregistered Version -
494 MACRO DEVELOPMENT
application. In many of the example application programs presented in this book, you’ll
see that the second or third line of an application is
#define _version ”1.00”
Because it is at the top of the source, I can see the version number as soon as the source
code is brought up in an editor. This define can be used throughout the code to provide
the version information without me having to update when I come up with a new ver-
sion. Often I will output the version information to indicate what source code is burned
into the PIC microcontroller. This #define statement above can be put into a dt state-
ment and read out of a table using conventional table read code:
dt “Version: ”, _version, 0
In this code, each byte of the _version define is added to the string of characters used
with the dt statement as if the code was entered as
dt “Version: 1.00”, 0
Define labels do not have to have strings associated with them. This may seem
unusual (and seem to defeat the purpose of defines), but this allows them to be used as
assembly-time execution control flags that I will show later in this chapter. This func-
tion allows fast customization of an application modification for debug.
Defines can be used for many different purposes. While macros can be used only for
replacing full lines of text, defines can simplify instruction programming and provide
common information or text strings. Neither macros nor defines can replace each other,
but their functions are complementary and allow you to create richer, more easily pro-
grammed and understood applications.
The Assembler Calculator
While not really part of the macro processor, the ability of the MPASM assembler

(and most compilers) to do constant calculations during assembly or compilation can
make applications easier to develop and avoid your having to spend time with a
handheld calculator working out the constant values needed in your application. The
assembler calculator provides capabilities for both your macros and regular pro-
gramming that can be taken advantage of in a variety of situations.
The assembler calculator works on algebraic equations, similar to how they’re
used in high level languages. It is important to remember that the calculator works
as part of the last pass of the assembler—to allow the insertion of data generated
during the build cycle, such as the address of an application variable. This can be
confusing because variables available for use by the assembler calculator are
declared within the source in a manner similar to that of variables used in the
application.
Simpo PDF Merge and Split Unregistered Version -
THE ASSEMBLER CALCULATOR 495
So far in this book you have seen the assembler calculator in operation calculating
constant-value arguments for instructions such as
movlw (1 << GIE) | (1 << T0IE)
This instruction loads w register with a byte, destined for the INTCON register, which
has the GIE (7) and T0IF (5) bits set. In this case, the assembler calculator is used to
change bit numbers to actual values to be loaded into a byte. The trick in this statement
is knowing that shifting one by the bit number converts the bit number into a constant
value that will set the bit when loaded into a register.
This is useful and avoids having to figure out what value is used for specific bits being
set. In the preceding example, if this trick had not been used, I would have to remem-
ber (or generate on a calculator) that bit 7 being set is the same as adding 128 and that
bit 5 being set is the same as adding 32. The result of these two values is 160 decimal
or 0x0A0. Using the assembler calculator, I didn’t have to worry about any of this.
To reset specific bits, the same trick can be used, but the bits have to be reset, which is
done by a bitwise inversion of the bits and then ANDing the result with the current value.
XORing the set bit value with 0x0FF accomplishes the bitwise inversion. For example,

to clear bits 4, 2, and 1 in the w register, the following instruction could be used:
andlw 0x0FF ^ ((1 << 4) | (1 << 2) | (1 << 1))
If you were to do this manually, you would have to follow these steps:
1 Calculate the values for bits 4, 2, and 1 being set:
(1 << 4) = 16
(1 << 2) = 4
(1 << 1) = 2
which translates to
(1 << 4) | (1 << 2) | (1 << 1) = 16 | 4 |2
= 22
= 0x016
2 Calculate the inverse (XOR with 0x0FF):
0x0FF ^ 0x016 = 0x0E9
3
Put the value into the andlw instruction:
andlw 0x0E9
If you go through the manual process, you can see that there are more than seven
opportunities for you to calculate constant values incorrectly or copy down the wrong
value. Avoiding these manual calculations with their inherent opportunities for error is
Simpo PDF Merge and Split Unregistered Version -
496 MACRO DEVELOPMENT
what I meant when I said the assembly calculator is easier and less prone to mistakes.
Table 10.1 lists the calculator’s arithmetic operators. All the operators have two param-
eters, except for when “−” negates a value or the complement (“~”) operator, which only
have one parameter.
In the clear bits example, I could have used the equation format
andlw ~((1 << 4) | (1 << 2) | (1 << 1))
instead of adding the 0x0FF ^ characters in the preceding instruction.
For 16-bit values, you can use the “low” and “high” assembler directives. For
example, if you wanted to jump to a specific address in another page, you could use

the code
movlw HIGH Label ; ”Label“ is the
movwf PCLATH ; Destination
movlw LOW Label
movwf PCL
TABLE 10.1 OPERATORS AVAILABLE TO THE ASSEMBLER CALCULATOR
OPERATOR DESCRIPTION COMMENTS
+ Addition
– Subtraction/negation If no first parameter, then negation
* Multiplication
/ Division
% Modulus Return remainder from divide operation
<< Shift left Shift the first parameter to the left by
second parameter number of bits
>> Shift right Shift the first parameter to the right by
second parameter number of bits
& Bitwise AND AND together the parameter’s bits
| Bitwise OR OR together the parameter’s bits
^ Bitwise XOR XOR together the parameter’s bits
~ Complement XOR the parameter with 0xFF to get the
complemented or “inverted” value
LOW Low 8 bits of a AND the parameter with 0xFF
constant value
HIGH High 8 bits of a 16-bit AND the parameter with
constant value 0xFF00 and then shift right 8 bits
$ Current address Current address of the instruction
Simpo PDF Merge and Split Unregistered Version -
THE ASSEMBLER CALCULATOR 497
which is the same as
movlw ((label && 0x0FF00) >> 8)

movwf PCLATH
movlw LABEL && 0x0FF
movwf PCL
In this example, the function of the first four instructions (which use HIGH and LOW)
is much clearer to somebody reading the code than the second four instructions, which
require the reader to evaluate what the arithmetic operations are doing.
As has been discussed earlier in this book, the $ operator returns the current program
counter, which is a 16-bit value that can be manipulated using the assembler calculator’s
operators as if it were a constant.
Along with the arithmetic operations, parentheses (the “(” and “)” characters) can be
used in the expressions to make sure that the operation is executed in the correct order.
In the preceding examples I have used parentheses to make sure that the correct order
of operations takes place for these instructions.
Variables that are only used in assembly can be declared using the format
variable label [ = constant][, ]
These variables are 32 bits in size and can be set to any value using the operators listed
above and employing the “=” operator to make an assignment statement such as
LABEL1 = LABEL1 * 2
It is important to remember that the label is not an application variable (i.e., it
cannot be modified by the PIC microcontroller as it is running), and when it is assigned
a new value, it must be in the first column of the assembly-language source. When it
is being read in another statement, it can appear in any column (except for the first)
in the line.
Taking a cue from C, assembler variable assignment statements can be simplified if
the destination is one of the source parameters. These operations can be confusing to
use and read unless you are familiar with C. Table 10.2 lists the combined assignment
statements.
The assembler calculator also can do comparisons between two parameters using the
operators listed in Table 10.3. If the comparison is true, a 1 is returned; otherwise, a 0
is returned. The comparison operators are required for the “conditional” assembly oper-

ations presented in the next section.
These comparisons can be compounded with || and &&, which are the logical OR
and logical AND operators, respectively. || returns 1 if either of its two parameters are
not equal to 0. && returns a 1 if both parameters are equal to 0. This operation brings
up an important point: In the assembler calculator, a “true” condition is any nonzero
value. The variable A, after executing
A = 7 && 5
Simpo PDF Merge and Split Unregistered Version -
498 MACRO DEVELOPMENT
TABLE 10.2 COMBINED ASSIGNMENT OPERATORS
AVAILABLE TO THE ASSEMBLER CALCULATOR
OPERATOR EQUIVALENT OPERATION
+= Parm1 = Parm1 + Parm2
–= Parm1 = Parm1 – Parm2
*= Parm1 = Parm1 * Parm2
/= Parm1 = Parm1 / Parm2
%= Parm1 = Parm1 % Parm2
<<= Parm1 = Parm1 << Parm2
>>= Parm1 = Parm1 >> Parm2
&= Parm1 = Parm1 & Parm2
!= Parm1 = Parm1 ! Parm2
^= Parm1 = Parm1 ^ Parm2
TABLE 10.3 COMPARISON OPERATORS AVAILABLE
TO THE ASSEMBLER CALCULATOR
OPERATOR OPERATION
== Return 1 if two parameters are equal
!= Return 1 if two parameters are different
> Return 1 if the first parameter is greater
than the second parameter
>= Return 1 if the first parameter is greater

than or equal to the second parameter
< Return 1 if the first parameter is less
than the second parameter
<= Return 1 if the first parameter is less
than or equal to the second parameter
|| Return 1 if either of the two parameters
is not zero
&& Return 1 only if both of the two parameters
are not zero
! Toggle the logical value of a single
parameter
Simpo PDF Merge and Split Unregistered Version -
MULTILINE C MACROS 499
will be loaded with 1 because 7 and 5 are not 0, and both are assumed to be “true.”
This operation of logical values is not unique to the MPASM assembler calculator; most
languages use this convention for “true” and “false.”
The last operator is !, which toggles the logical state of a value, for example,
A = !4 ; 4 != 0 and is “true”
; A = not true
; = false
; = 0
C = !0 ; 0 == 0 and is “false”
; C = not false
; = true
; = 1
The comparison and logical operators may seem unnecessary for arithmetic calcu-
lations, but there are cases where they can be useful.
Multiline C Macros
If you are a new C language programmer, you might be wondering how to implement macros
similar to the ones shown in this chapter. Macros are not only for assembly language—they

can be used effectively for high level languages as well. If you are learning to program in
C, you may have noticed that there isn’t a “macro” directive as there is in assembly lan-
guage, but you can use defines as macros, and using C’s ability to concatenate the text on
the next line to the current line, you can produce your own multiline macros.
The #define directive in C is similar to the define directive in assembly language;
when it is encountered, it replaces the label with the string following the define and
replaces any parameters with the arguments of the define. For example, if you had a cir-
cular buffer 20 entries in size and you wanted to increment the indexes to the buffer, you
could use the code
buffindex++; // Increment the buffer index
if (19 < buffindex) // If the index is 20 or greater, reset
buffindex = 0;
If the code were used often and for different index variables, you might want to consider
turning it into a single line of code and using it as the basis for a define. If you are familiar
with C, you would know that you could reduce the three lines above to the single line
if (19 < ++buffindex) buffindex = 0; // keep ++index in buffer range
Despite being difficult to read, this single line will increment buffindex and ensure
that it is within range for the 20-element circular buffer. The line then could be turned
Simpo PDF Merge and Split Unregistered Version -
500 MACRO DEVELOPMENT
into the define (which can be used as a macro within C):
#define buffinc(indexvar) if (19 < ++indexvar) indexvar = 0;
and each time it is invoked, the code buffinc(buffindex) would be replaced with the
single-line buffer increment and tested to ensure that it is with the index range for the
20-element circular buffer.
The problem is that the operation of the define is not easily read, and if there were
an error in the code, you would not be able to see it easily. What is needed is a way to
format the define so that the code can be seen easily.
Fortunately, C has the backslash character (\) directive, which appends (or concate-
nate) the next line of text onto the end of the current line. By using this character direc-

tive, you can create a multiline define (or macro) that is much easier to read and
understand, minimizing the opportunity for errors to come into your application. For
example, using the backslash character, the buffinc define could be rewritten as
#define buffinc(indexvar) \
indexvar++; \
if (19 < indexvar) \
indexvar = 0; // keep ++index within buffer range
When the backslash is used, the four lines are concatenated together, providing a similar
form to the C compiler as the previous define. However, it’s in a format that is much easier
for you to read or understand.
Note that when using the backslash character, comments to line end (using the //
format) cannot be used. Instead, you must either restrict your comments to the last line
of the macro or use the /
*

*
/ comment form.
Reading over this section, you might be tempted to ignore using multiline defines as
macros as I’ve shown here and just create functions for the code. This is possible, but
it is a much less efficient way of implementing short pieces of code, such as the exam-
ple shown here. Implementing the code here as a function will require saving the param-
eter in a temporary area and then saving it in a destination variable when the function
has completed. Depending on the PIC MCU you are working with and the size and com-
plexity of the function you wish to implement, the overhead of saving the data, calling
the function, returning from the function, and restoring the data can take more instruc-
tions and instruction cycles than what is required for the macro.
Conditional Assembly/Compilation
If you have taken a look at some of the more complex macros presented in this book,
you probably will be surprised to see that there are “structured language” statements
(if, else, endif, while, and endw, as listed in Table 10.4). At first glance, these

statements are providing high level language capabilities to the PIC microcontroller
assembly code. Unfortunately, they’re not; these statements provide you with the
Simpo PDF Merge and Split Unregistered Version -
CONDITIONAL ASSEMBLY/COMPILATION 501
capability of conditionally including statements in your application. Conditional assem-
bly statements are not part of the macro processor; they can be used outside macros and
can be used anywhere in application code. Unlike the operation of if and while that
you are used to, allowing condition execution of the application, they are actually exe-
cuted when the source code is being assembled and can be used to conditionally change
constant values or to add or delete sections of code.
Conditional assembly statements are actually “directives,” and they are processed
along with other directives (such as EQU, dt, and so on). For example, if an applica-
tion were to be run on two different PIC microcontrollers, each with different built-in
hardware, the ifdef (“execute if define label is found”) conditional assembly could
be used in the following manner:
#define USARTPres
:
ifdef USARTPres
: ; Put in USART Handler Code
else
: ; Put in Non-USART Serial Handler
endif
In this case, the #define statement creates a label in the application code that can be
tested. Later in the code, when the ifdef statement is encountered, if the USARTPres
label is present, the first block of code is put into the assembler source code, and the
TABLE 10.4 CONDITIONAL ASSEMBLY DIRECTIVES AVAILABLE
IN MPASM
CONDITIONAL
ASSEMBLY DIRECTIVE FUNCTION
if Return “true” if both parameters are the same

ifdef Return “true” if parameters are not the same
ifndef Return “true” if the first parameter is greater
than the second
else Return “true” if the first parameter is greater
than or equal to the second
endif Return “true” if the first parameter is less than
the second
while Return “true” if the first parameter is less than
or equal to the second
endw Return “true” if both parameters are “true”
Simpo PDF Merge and Split Unregistered Version -
502 MACRO DEVELOPMENT
second is ignored. If the USARTPres label is not defined, then the first block of code
is ignored, and the second block is assembled as source code.
For all if conditional assembly directives (including ifdef and ifndef), endif
is required, and else is an optional conditional assembly directive that will include the
code if the original condition was not true. You may see a null statement after an if,
to have the else execute like
ifdef USARTPres ; If USART Present, don’t add any code
else
: ; Put in Non-USART Code
endif
It can be somewhat difficult to understand what is happening. Instead of using else
to provide conditional execution in the case of the label not being present, the absence
of the define should be checked for using the ifndef directive.
There are a number of tricks that you can use with ifdef and ifndef conditional
assembly statements that can make your code development easier and more flexible. The
first is conditionally deleting code. As you work through an application, often you will
want to remove some code to test out different aspects of it. Elsewhere in the book I
talk about the idea of “commenting out the code” simply by putting the comment direc-

tive (the “;” or semicolon character) before the statement such as
; addlw 0x0FF ; #### - Instruction not needed, but kept
For single instructions, this is easy to do and easy to keep track of. For many instructions,
it can be difficult to keep track of everything that has to be removed (but kept). An easy way
of doing this is to put an ifdef and endif statement before and after the code, as in
ifdef Used ; #### - Ignore following
: ; Block of Code NOT to be part of application
endif ; #### - Ignore above
It takes literally just a few seconds to remove the code and can be disabled just as
quickly (by defining Used or deleting the ifdef and endif statements).
The second trick is to allow the logic of an application to be used in multiple PIC micro-
controllers that may have different built-in hardware features. In the preceding example,
by using the ifdef directive, I can have the code that takes advantage of the built-in serial
port hardware of a specific PIC microcontroller or insert bit banging code in its place if the
selected PIC MCU does not have a built-in serial port. I should point out that when an
MPLAB IDE application is built, a define label is created indicating the PIC MCU part
number. For example if you were assembling an application for the the PIC16F84, the
__16F84 label is available for testing by the ifdef and ifndef directives.
I have taken advantage of this feature in some applications where the code can run
in different devices. For example, the following code will allow an application to run
Simpo PDF Merge and Split Unregistered Version -
CONDITIONAL ASSEMBLY/COMPILATION 503
on either PIC16F84 or PIC16F877 MCUs or generate an error indicating that a supported
PIC microcontroller has not been selected for the application:
ifdef __16F84A
include “p16f84a.inc”
else
ifdef __16F877
include “p16f877.inc”
else

error “Code is Not Designed for Specified Processor”
endif
endif
In this example, note that I have put in “nested” if statements. Up to eight levels of
nesting are possible in MPASM, although this can be very confusing to read. I would
recommend that no more than two levels be used, as in the preceding example.
Along with conditional assembly based on the presence or absence of labels, constant
and variable condition testing also can be done with conditional assembly statements.
For example, tests against addresses could be performed for interpage jumping in mid-
range PIC MCUs:
if ((($ & 0x01800) ^ (Label & 0x01800)) != 0)
movlw HIGH Label ; Different Pages - Update PCLATH
movwf PCLATH
endif
goto Label & 0x07FF ; Jump to Label
In this example, if the destination is in a different page from the current location (which
is returned by the $ directive in MPLAB), then PCLATH is updated before the goto
statement.
The preceding example is suboptimal for four reasons. The first is that whether or not
PCLATH has to be updated is variably based on the address of the goto statement. A
more accurate way of doing this would be
if (((($ + 2) & 0x01800) ^ (Label & 0x01800)) != 0)
movlw HIGH Label ; Different Pages - Update PCLATH
movwf PCLATH
endif
goto Label & 0x07FF ; Jump to Label
In this case, the possible address of the goto is checked rather than the current address.
There is the possibility that the current address will be in a different page than the goto,
and PCLATH may or may not be updated correctly.
The second problem is that this code takes up a different amount of space depend-

ing on which path is taken. Doing this can result in a address “phase” error that indi-
cates that during the different passes in the assembler, required addresses change in a
way that makes correct assembly impossible. These different addresses are caused when
Simpo PDF Merge and Split Unregistered Version -
504 MACRO DEVELOPMENT
the conditional code executes for a second time, and addresses come out differently.
Phase errors are very hard to find, and chances are that if you have one in one location,
there will be a number of them.
The best way to avoid phase errors is to always make sure that the same number of instruc-
tions are used no matter what path is taken in the conditional assembly. For the preceding
code, I can add two nops as the code is inserted if else (assembled if the condition is
not true) is active to make sure that no addresses in the application will change:
if (((($ + 2) & 0x01800) ^ (Label & 0x01800)) != 0)
movlw HIGH Label ; Different Pages - Update PCLATH
movwf PCLATH
else
nop ; Add Two instructions to prevent
nop ; “Phase” Errors
endif
goto Label & 0x07FF ; Jump to Label
The third problem with this code is that a message may be produced indicating that
the jump is to a different page. To avoid this, the goto address should have the current
page bits added to it. This changes the code to
if (((($ + 2) & 0x01800) ^ (Label & 0x01800)) != 0)
movlw HIGH Label ; Different Pages - Update PCLATH
movwf PCLATH
else
nop ; Add Two instructions to prevent
nop ; “Phase” Errors
endif

goto (Label & 0x07FF) | ($ & 0x01800) ; Jump to Label
The next problem with this code is that it changes the w register. This means that the
preceding code cannot be used if the contents of the w register are going to be passed
to the destination Label. Instead of explicitly loading PCLATH with the destination,
the bits can be changed individually using the code
if (((($ + 2) & 0x01000) ^ (Label & 0x01000)) != 0)
if ((($ + 2) & 0x01000) == 0)
bsf PCLATH, 5 ; Label in Pages 2 or 3
else
bcf PCLATH, 5 ; Label in Pages 0 or 1
endif
else
nop ; No Difference in High Pages
endif
if (((($ + 2) & 0x00800) ^ (Label & 0x00800)) != 0)
if ((($ + 2) & 0x00800) == 0)
bsf PCLATH, 4 ; Label in Pages 1 or 3
Simpo PDF Merge and Split Unregistered Version -
CONDITIONAL ASSEMBLY/COMPILATION 505
else
bcf PCLATH, 4 ; Label in Pages 0 or 2
endif
else
nop ; No Difference in Low Pages
endif
goto (Label & 0x07FF) | ($ & 0x01800) ; Jump to Label
Looking at this mess of conditional assembly statements, it is starting to look a
lot like a macro, and this is the reason why I have included conditional assembly
statements in this chapter. Conditional assembly statements, while simplifying your
applications in some ways, will result in fairly complex applications in others. The

preceding code has really become the lgoto macro:
lgoto Macro Label
if (((($ + 2) & 0x01000) ^ (Label & 0x01000)) != 0)
if ((($ + 2) & 0x01000) == 0)
bsf PCLATH, 5 ; Label in Pages 2 or 3
else
bcf PCLATH, 5 ; Label in Pages 0 or 1
endif
else
nop ; No Difference in High Pages
endif
if (((($ + 2) & 0x00800) ^ (Label & 0x00800)) != 0)
if ((($ + 2) & 0x00800) == 0)
bsf PCLATH, 4 ; Label in Pages 1 or 3
else
bcf PCLATH, 4 ; Label in Pages 0 or 2
endif
else
nop ; No Difference in Low Pages
endif
goto (Label & 0x07FF) | ($ & 0x01800); Jump to Label
endm
which can be placed anywhere in your mid-range PIC MCU application, with three
instructions replacing the macro each time it is encountered. Similar macros for low-
end PIC MCU architectures can be created to allow jumping and calling subroutines (this
is not an issue in the PIC18 processor architecture because there are instructions that
allow movement to any address that the processor can execute).
Along with program constants, you also can declare integer variables that can
be updated during assembly of the application. The variable directive, appro-
priately enough, is used to declare the variables with optional initial values, as

shown below:
variable i, j=7
Simpo PDF Merge and Split Unregistered Version -
506 MACRO DEVELOPMENT
The variables that can be used as constants in the application or as parts of labels. As I
pointed out in the preceding section, variables can be used to avoid having to calculate
your own constant values.
I often use variables as counters for use with the while conditional assembly state-
ment. For example, if I wanted to loop six times, I could use the code
variable i=0 ; Declare the Counter
while (i < 6)
: ; Put in Statements to be repeated six times
i = i + 1 ; Increment the Counter
endw
Note that when the variable i is updated, the statement starts at the first column of the
line. The MPLAB assembler requires this.
In the preceding code, the statements within the while and endw statements are
inserted into the assembly-language source file each time the condition for while is true.
Looking at how while has executed in the list file can be a bit confusing. For the code
goto $ + 7 ; Put in Patch Space
variable i = 0
while (i < 6)
dw 0x03FFF ; Add Dummy Ins
i = i + 1
endw
which puts in a jump over six instructions of “patch” space, the listing file looks like
0000 2807 00036 goto $ + 7 ; Put in Patch Space
0000 00037 variable i = 0
00038 while (i < 6)
0001 3FFF 00039 dw 0x03FFF ; Add Dummy Ins

00000001 00040 i = i + 1
0002 3FFF 00039 dw 0x03FFF ; Add Dummy Ins
00000002 00040 i = i + 1
0003 3FFF 00039 dw 0x03FFF ; Add Dummy Ins
00000003 00040 i = i + 1
0004 3FFF 00039 dw 0x03FFF ; Add Dummy Ins
00000004 00040 i = i + 1
0005 3FFF 00039 dw 0x03FFF ; Add Dummy Ins
00000005 00040 i = i + 1
0006 3FFF 00039 dw 0x03FFF ; Add Dummy Ins
00000006 00040 i = i + 1
00041 endw
This listing looks more like six dw 0x03FFF instructions and i=i+1statements
rather than the two of them being repeated six times.
The conditional assembly instructions if and while use the same condition test format
as the if and while statements of the C language. The condition tests can take place only
Simpo PDF Merge and Split Unregistered Version -
USING DEFINES AND CONDITIONAL ASSEMBLY FOR APPLICATION DEBUG 507
on constant values of up to 32 bits in size. Like C’s if and while, the MPASM assem-
blers conditional assembly statements use the two parameter conditions listed in Table 10.5.
When the statements are “true,” a nonzero value is returned. If the statements are
“false,” then 0 is returned.
Using Defines and Conditional
Assembly for Application Debug
In this book I talk a lot about the need for simulating applications before they can be
burned into actual PIC microcontroller hardware. For many applications, this isn’t an
issue, but for applications that have very long delays built into them, this can be a very
significant issue because the time required for external hardware initializations actually
can take many minutes in MPLAB because the simulation has to go through long delay
loops. Another situation could be for hardware that is nonessential to the task at hand

but that requires a relatively complex interaction with simulated hardware or uses built-
in PIC microcontroller interfaces that are not simulated in MPLAB. An example of the
latter situation is a PIC microcontroller application that uses the ADC for testing battery
voltage levels, but if the hardware registers are accessed in the simulator, then chances
are the operation complete flag and interrupt will not work properly, and even if they
did, the value returned by the ADC would be invalid.
Dealing with this problem is relatively simple with use of the conditional assembly
directives built into the MPASM assembler. These instructions will allow execution to
skip over problem code areas simply by specifying a flag using the #DEFINE directive.
TABLE 10.5 COMPARISON OPERATORS AND RETURN
VALUES
CONDITION FUNCTION
== Return “true” if both parameters are the same
!= Return “true” if parameters are not the same
> Return “true” if the first parameter is greater
than the second
>= Return “true” if the first parameter is greater
than or equal to the second
< Return “true” if the first parameter is less than
the second
<= Return “true” if the first parameter is less than
or equal to the second
& Return “true” if both parameters are “true”
| “True” if either one of the parameters is “true”
Simpo PDF Merge and Split Unregistered Version -
508 MACRO DEVELOPMENT
The state of the #DEFINE flags is generally defined by their presence or absence. The
most common one that you will see in my code is the Debug label that is defined for
simulation by using the statement
#DEFINE Debug

You usually will see this statement on the second or third line of my applications,
although when I am ready to burn an application into a PIC microcontroller, I change
the statement to
#DEFINE nDebug
to disable any changes to the source code and to make sure that the object file is cre-
ated with the correct code.
Use of the Debug label to replace blocks of code that would be a problem in the
simulator (such as code that will take a long time to execute and is not germane to the
testing at hand) with nops (to ensure that the overall addresses aren’t changed) is as
follows:
ifndef Debug
call UnreasonablyLongDlay ; Wait for External Hardware
else ; “Debug” #DEFINEd
nop ; Put in Instruction Anywise
endif
In this code, if Debug has not been set in a #DEFINE statement, the instructions that
are to be skipped over are built into the application. If Debug has been defined, then
the nop is put into the application in the place of the call instruction. If the number
of instructions is not matched with an equal number of nops, you could run into the sit-
uation where the code assembles in different sizes for the different execution paths, which
results in the phase error, which is very hard to debug.
Along with being used for taking out lengthy delays or hardware inconsistencies in the
MPLAB simulator, Debug conditional assembly code can be used to put the application
into a specific state. This can save quite a bit of time during simulation and also can be
used to initialize the state of the application before simulation begins. For example, when
I was creating the EMU-II emulator application code, I used the Debug label to enter in
the following commands:
ifdef Debug ; Clear simulated application program Flash
movlw ‘P’ ; Start a program memory Program/Clear Flash
call BufferAdd

movlw 0x00D
call BufferAdd
movlw 0x003 ; Stop the transfer after the data is cleared
call BufferAdd
endif
Simpo PDF Merge and Split Unregistered Version -
DEBUGGING MACROS 509
This code initiates the command to load a new application into the Flash (and in prepa-
ration for the code, the Flash is cleared), followed by a Ctrl-C to stop the expected
transfer. The reason for requiring this code is that any writes to the simulated Flash would
not be reset when I rebuilt the application. These commands “clear” the simulated Flash
so that each time the application code is reset, I would be working with cleared Flash
instead of something that I would have to simulate the operation of downloading code
into it. This simulation isn’t possible because of my use of the USART in the applica-
tion, which is not simulated within MPLAB.
When the EMU-II application was being debugged, I placed a breakpoint at a loca-
tion where I wanted to start debugging, knowing that the program memory was cleared
and I was ready to start looking at how the application executed.
Multiple Debug statements could be put into the application code, but I would not
recommend that you do this. Multiple statements get confusing very quickly, as well as
often becoming incompatible. Instead of using multiple Debug labels, I would rec-
ommend that you just use one, and when you have fixed the current problem you are
working on, then you can change the ifdef Debug and ifndef Debug statements
to target the next problem.
Debugging Macros
In the preceding pages I’ve given you a lot of information on how to create com-
plex macros; now I want to spend a few pages discussing how to debug these mon-
sters. As the macro processor executes, it is probably going to put in some code that
will be surprising to you. I should point out that normally a C compiler will not
include a listing showing the inserted statements, the same way an assembly-

language program will. It is not unusual to discover that the application does not
work as you expect because the macro processor has inserted some code that does
not work as you would expect, and now you are left with the task of determining
where the problem lies. In this section I will pass along a few tricks that I have dis-
covered that make the debugging of macros easier and allow you to use them with
greater confidence.
The first trick is something that a surprising number of people do not think of: before
using a macro in their applications: Spend some time testing your macro in all possi-
ble ways to ensure that it does work as you expect. Earlier I showed the rather complex
macro that I have developed for producing code that will allow a mid-range PIC MCU
to jump to addresses outside the current execution page:
lgoto Macro Label
if (((($ + 2) & 0x01000) ^ (Label & 0x01000)) != 0)
if ((($ + 2) & 0x01000) == 0)
bsf PCLATH, 5 ; Label in Pages 2 or 3
else
bcf PCLATH, 5 ; Label in Pages 0 or 1
endif
Simpo PDF Merge and Split Unregistered Version -
510 MACRO DEVELOPMENT
else
nop ; No Difference in High
Pages
endif
if (((($ + 2) & 0x00800) ^ (Label & 0x00800)) != 0)
if ((($ + 2) & 0x00800) == 0)
bsf PCLATH, 4 ; Label in Pages 1 or 3
else
bcf PCLATH, 4 ; Label in Pages 0 or 2
endif

else
nop ; No Difference in Low Pages
endif
goto (Label & 0x07FF) | ($ & 0x01800) ; Jump to Label
endm
Before using this macro, I would create a simple application that would test it in a vari-
ety of different situations to ensure that it worked as I expect. When designing this appli-
cation, I would spend some time creating a table listing the different test cases with
expected outcomes such as the ones in Table 10.6.
This table only lists a few of the possible starting points and destinations that are pos-
sible. Note that the second case should produce an error because the starting address is
one address away from the destination, and the macro takes up three instructions. In this
case, the macro will attempt to write instructions over the instructions already placed
at address 0x800.
The more time you can spend debugging your macros, the better. In Table 10.6, I have
only listed a few of the different test cases (ideally, cases should be created in which each
of the PCLATH bits is set and reset), and no registers or bits that aren’t part of the jumps
are not affected. The macro is designed to work without changing the w register or the
W, C, or DC bits—these values should be checked before and after each invocation of
the lgoto macro to make sure that this is the case. Spending time up front debugging
TABLE 10.6 TABLE DEVELOPED TO TEST THE OPERATION OF THE lgoto MACRO
STARTING VALUES FINAL VALUES
TEST DESTINATION STARTING
CASE ADDRESS PC PCLATH ADDRESS PC PCLATH
1 0x0800 0x000 0b00 0x0800 0x000 0b01
2 0x0800 0x7FF 0b00 Error Error Error
3 0x0800 0x7FD 0b00 0x0800 0x000 0b01
4 0x0010 0x200 0b01 0x0010 0x010 0b00
5 0x10AA 0x010 0b00 0x10AA 0x0AA 0b10
Simpo PDF Merge and Split Unregistered Version -

DEBUGGING MACROS 511
the macros will avoid the need to go back later when your code isn’t behaving properly
and you are trying to understand why some cases work properly and others don’t.
The key to debugging macros is being able to observe what changes the macro code
made and whether or not they are appropriate for use in the application. This may seem
to be identical to the testing done on the macro, but there is a subtle and very important
difference: In macro testing, you are creating test cases and comparing the changes made
to specific registers to verify the operation of the macro. When you are observing the
changes made by a macro in an application, you have to understand what the purpose
of the macro was and whether or not the changes that were generated were appropriate
for that point in the application.
If you were debugging the lgoto macro, you might want to check to see what the
execution address is after the macro’s code has executed and if any other registers
changed inadvertently. Ideally, you should not be setting a breakpoint at the expected
destination (because there is a chance that execution would end up there at some point)
but instead single-stepping through the macro’s statements to make sure that there is no
jump to an unexpected address. It can take some ingenuity to test the operation of the
macro by seeing how it works in the application.
An important key to debugging macros is being able to read what has been added to
the application. When macros are expanded into the source code, the new instructions
can be difficult to see and work through. For example, when macros execute, the con-
ditional statements (if, ifdef, ifndef, and while) may or may not be displayed
depending on the operation. I think of the regular instructions of a macro statement to
be print or echo statements, and they are copied into the source as is (except in the
case where one of the parameter strings is present in the instruction or on the line or the
assembler calculator is used to produce a specific value). To illustrate these points, I have
created conditional assembly statements for a macro:
variable i = 0
if (i == 0)
addlw 0 - i

else
sublw i
endif
When the macro is “expanded” (or executed), the following information will be displayed
in the listing file:
00000000 M variable i = 0
M if (i == 0)
Addr 3E00 M addlw 0 - i
M else
M sublw i
M endif
As this example Illustrates, the setting of the assembly variable (i) results in a constant value
being placed somewhere between the start of the line and the middle. The instructions that
Simpo PDF Merge and Split Unregistered Version -
512 MACRO DEVELOPMENT
are actually added to the listing file are identified by the address of the instruction and its bit
pattern broken up as I have shown for the addlw 0 – 1 instruction. In this example, only
addlw 0 – 1 is inserted into the source code; the directives (variable, if, else, and
endif), as well as the sublw i instruction, are all ignored.
While directives are somewhat unusual to follow because the code is repeated within
them, and there is no start and end reference information that can be easily seen. For
the example
variable i = 0
while (i < 2)
movlw i
i = i + 1
endw
the following listing file information is generated:
00000000 M variable i = 0
M while (i < 2)

Addr 3000 M movlw i
00000001 M i = i + 1
Ad+1 3001 M movlw i
0000002 M i = i + 1
M endw
In this case, the while and endw directives are not repeated as you would expect. Again,
to really understand what is happening, you have to go back and look at the instructions
entered into the application code. These instructions are displayed to the left of the
column of M’s along with the variable updates.
Probably the best way to debug a macro is to single-step in the simulator through the
macro. If you are doing this in the source file, you will find execution jumps to where
the macro is defined that can be quite disconcerting and confusing because the param-
eters are displayed, not what the parameters actually are. These problems can be avoided
by simulating from the listing file instead of from the source file.
Breakpoints cannot be placed in MPLAB at a macro invocation for a source file. When
I want to set a breakpoint at a macro invocation, I will put a nop instruction before it
to give the MPLAB simulator somewhere on which to hang the breakpoint. This also
works with a listing file, but in this case you do not have to add the breakpoint because
breakpoints can be put at instruction statements within the macro-generated code.
Debugging macros is really the same as debugging a standard application, except
that there is no simulator or debugger. This is an important point to remember when
you are creating the macro. You may want to “flag” where the code is executing using
messages (using the messg directive), which is the same as printf debugging in
C. For example, if you have the code
if (A == B)
messg “A == B”
; put in code for A == B
Simpo PDF Merge and Split Unregistered Version -
STRUCTURED PROGRAMMING MACROS 513
else

messg “A != B”
; put in code for A != B
endif
the messg directives will flag the path execution takes through the macro’s condi-
tional assembly. This (and the preceding) tricks can be used with conditional code
external to macros to help you follow their execution and find any problems within them.
Structured Programming Macros
To finish off this chapter, I wanted to leave you with a few macros that should give you
some ideas on how powerful macros are when you need some processing to be done
when the application is being created, as well as give you a tool for making your PIC
microcontroller assembly-language code easier. The structre.inc file contains
nine macros that you can use for your own applications to add structured programming
conditional execution.
The macros are in the format
“_”
Test
Const/Var
where
Test
can be do, until, while, if, else, and end, and Const/Var spec-
ifies whether or not the second parameter is a constant or a variable. The different
macros are listed in Table 10.7.
When conditional macros are invoked, the parameters passed to the macro include
the two conditions for testing as well as the condition to test for. The standard C test
conditions are used, as listed in Table 10.8.
Only one condition can be accessed within a macro at any time. There is no ability
to AND or OR conditions together. Using these macros in your application is quite
straightforward, and if you are new to PIC microcontroller programming, you might want
to take a look at using these macros for your conditional programming. Executing some
code conditionally if two variables are equal would use the following macro invocations:

_ifv VariableA, ==, VariableB
: ; Code to Execute if VariableA == VariableB
_end
Using these macros can be expanded to include an else condition for code that exe-
cutes if the condition is not true:
_ifv VariableA, ==, VariableB
: ; Code to Execute if VariableA == VariableB
_else
: ; Code to Execute if VariableA != VariableB
_end
Simpo PDF Merge and Split Unregistered Version -
514 MACRO DEVELOPMENT
TABLE 10.7 STRUCTURED PROGRAMMING MACRO INVOCATIONS
INVOCATION DESCRIPTION
_ifv Parm1, Execute the following code if the two variables (Parm1 and
Condition, Parm2) operated on with the Condition are “true.” _else
Parm2 following is optional, and the code following it will execute if the
condition is “false.” Must have _end following.
_ifc Parm1, Execute the following code if the variable (Parm1) and constant
Condition, (Parm2) operated on with the condition is “true.” _else following
Parm2 is optional, and the code following it will execute if the condition
is “false.” Must have _end following.
_else Execute the following code if the result of the previous _if macro
was “false.” Execution before the _else jumps to the next _end
macro.
_end End the _if or _
while macro invocation. If previous operation was
“_while”, then jump back to “_while” macro.
_whilev Parm1, Execute the following code while the two variables (Parm1 and
Condition, Parm2) operated on with the Condition are “true.” Must have

Parm2 _end following, which will cause execution to jump back to the
_while macro.
_whilec Parm1, Execute the following code while the variable (Parm1) and
Condition, Parm2 constant (Parm2) operated on with the Condition are “true.”
Must have _end following, which will cause execution to jump
back to the _while macro.
_do Start of
do/until loop.
_untilv Parm1, Jump to previous _do if the variables (Parm1 and Parm2)
Condition, Parm2 operated on with Condition are “false.”
_untilc Parm1, Jump to previous _do if the variable (Parm1) and the constant
Condition, Parm2 (Parm2) operated on with Condition are “false.”
TABLE 10.8 COMPARISON OPERATORS USED IN
STRUCTURED PROGRAMMING MACROS
CONDITION DESCRIPTION
== Equals
!= Not equals
> Greater than
>= Greater than or equals
< Less than
<= Less than or equals
Simpo PDF Merge and Split Unregistered Version -
STRUCTURED PROGRAMMING MACROS 515
For these macros, I decided to use as close a label to actual programming structures
as possible, which is why I used the standard names with the underscore character
before them.
There are five aspects and features of the macro processor that have influenced how
these structured conditional execution macros were created. The first is that you cannot
distinguish between constants and variable addresses in a macro. When the macro is
invoked, a variable address is passed to the macro instead of the variable label. This was

probably done to simplify the effort in writing the macro processor. For this applica-
tion, it means that either what the value types are must be specified explicitly or there
must be a way of declaring variables so that they can be differentiated from constants.
I looked at a number of different ways to tell the two types of values apart and found
that I could not do it without changing how variables were declared, which would make
the application code more complex. Because I could not tell the two different types of
data apart, I decided to always make the first parameter a variable and the second one
a variable (v) or constant (c) depending on the character at the end of the macro.
Interestingly enough, this conversion of data does not extend to nonalphanumeric char-
acters. In the macros, the condition test is specified, and this is passed into the macro.
I take advantage of this to avoid having to create multiple macros each with a different
condition test. Inside the macro, I use this test condition parameter to determine what
it actually is (there is no way of comparing nonnumeric values in the conditional assem-
bly functions of the macro processor). For example, in the _ifv macro, to find out what
is the condition, I test the specified condition against different cases:
if (1 test 1) ; Check for “==”
movf a, w
subwf b, w
btfss STATUS, Z ; Zero Flag Set if True
else
if (1 test 0) ; Check for “!=”/”>”/”>=”
if (0 test 1) ; Check for “!=”
movf a, w
subwf b, w
btfsc STATUS, Z ; Zero Flag Reset if True
else ; Else “>”/”>=”
if (1 test 1) ; Check for “>=”
movf b, w
subwf a, w
btfss STATUS, C ; Carry Set, “>=”

else
movf a, w
subwf b, w
btfsc STATUS, C ; Carry Reset, “>”
endif
endif
else
if (0 test 1) ; Check for “<”/”<=”
if (1 test 1 )
Simpo PDF Merge and Split Unregistered Version -
516 MACRO DEVELOPMENT
movf a, w
subwf b, w
btfss STATUS, C
else
movf b, w
subwf a, w
btfsc STATUS, C
endif
else
error Unknown “if” Condition
endif
endif
endif
To help determine if there is an error in the code, note that if no Condition test is
“true,” an “error” is forced, indicating that the input condition is unknown. This can be
another technique for debugging macros: If conditional code ends up somewhere where
it shouldn’t be, an error message will alert you to the situation and help you to debug
the application.
The macros themselves use conditional code to produce simple code for the actual

functions. For example, in the code
_ifv Parm1, >, Parm2
: ; Code to Execute if Parm1 > Parm2
_else
: ; Code to Execute if Parm1 <= Parm2
_end
the best-case assembler would be
; _ifv Parm1, >, Parm2
movf b, w
subwf a, w
btfss STATUS, C
goto _ifelse1 ; Not True, Jump to “else” code
: ; Code to Execute if Parm1 > Parm2
; _else
goto _ifend1 ; Finished with “true” code, Jump to “_end”
_ifelse1:
: ; Code to Execute if Parm1 <= Parm2
; _end
_ifend1:
If you try out the macros in structre.inc, you will find that this is the exact code
that is created for this example. To do this, I had to create three stacks to keep track of
where I was. The first stack records what is the nesting level of the structured condi-
tional statements. For these macros, I only allow four nesting levels deep. The next stack
Simpo PDF Merge and Split Unregistered Version -
STRUCTURED PROGRAMMING MACROS 517
records what was the previous operation. This is important for _else, _end, and
_until to make sure that they are responding correctly. The last stack records the “label
number” for the previous operation. These stacks are combined with a label number to
keep track of what is the correct label to use and jump to.
The label number is appended to the end of the label using the #v(Number) feature

of the MPASM macro assembler. When a label is encountered with this string at the end,
the Number is evaluated and concatenated to the end of the string. For the label
_test#v(123)
the actual label recognized by MPASM would be
_test123
I use this feature to keep track of which label should be jumped to. After every state-
ment, I increment this counter for the next statement to use for its labels. Expanding the
preceding example to
_ifv Parm1, >, Parm2
_whilec ParmA == ParmB
: ; Code to Execute if Parm1 > Parm2
: ; while ParmA == Constant ParmB
_end ; End the “_while”
_else
: ; Code to Execute if Parm1 <= Parm2
_end
the structured programming macros will push the appropriate label number onto the stack
and retrieve it when necessary. Thus, for the expanded example, the actual PIC micro-
controller code would be
; _ifv Parm1, >, Parm2
movf b, w
subwf a, w
btfss STATUS, C
goto _ifelse1 ; Not True, Jump to “else” code
; _whilec ParmA == ParmB
_ifwhile2:
movf ParmA, w
subwf ParmB, w
btfss STATUS, Z
goto _ifend2

: ; Code to Execute if Parm1 > Parm2
: ; while ParmA == Constant ParmB
; _end ; End the “_while”
goto _ifwhile2
_ifend2:
Simpo PDF Merge and Split Unregistered Version -

×