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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com 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 - http://www.simpopdf.com [...]... that there should only be 6 (with the 6 being the two source files and their o object files, the hex file, and a cod or coff file that would be used by the MPLAB IDE simulator or MPLAB ICD for debugging the application) The additional 17 files are used by the compiler and the linker to produce the application The files that you should be concerned with are 1 The source files (.c) 2 The object files (.o) 3 The. .. be able to understand them more effectively as they relate to the PIC and PIC microcontroller applications Creating Linked Applications Creating a linked C application (using the OIC18 or PICC compilers) is surprisingly easy in MPLAB IDE It is accomplished simply by right clicking on “Source Files” in the file application box of the MPLAB desktop and then selecting “Add Files ” and 51 9 Copyright © 2008,... label number, which is 2 for the _while and 1 for the _if These values are kept track of by the three stacks I mentioned earlier The stacks used for storing the label number and the other values are not stacks per se but actual variables that are shifted over by four and then loaded with the value This limits the total label numbers to 16 different labels, but for most small PIC microcontroller applications,... STRUCTURED PROGRAMMING MACROS 51 7 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. .. and i = i + 1 statements 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 - http://www.simpopdf.com USING DEFINES AND CONDITIONAL ASSEMBLY FOR APPLICATION DEBUG 50 7 TABLE 10 .5. .. the results of the compilation 4 The hex file (.hex) 5 The debugger file (.cod or coff), which is used to allow source-code-level sim- ulation and debugging 6 The linker file (.lkr), which may have been brought into the build folder to modify how the code is implemented 7 The application map file (.map), which outlines how the application uses data and program memory All the other files can be ignored The. .. variables), along with the file registers (gprs) that are used for variables The PIC1 6F84 cannot show an important aspect of the lkr file, and that is how code pages and register banks are handled by the linker In other devices, such as the PIC1 6C63, which has two register banks and two code pages, there are additional statements to indicate their presence: // Sample linker command file for 16C63 // $Id:... between the start of the line and the middle The instructions that Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 51 2 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... understand how the application is laid out, you will need to cross-reference this file to the listing files because the listing files have Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CREATING LINKED APPLICATIONS 52 3 the sizes needed by the different object files, and you can see how they were located in the final hex file using the map file For the most part, you won’t care how the. .. or equals Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com STRUCTURED PROGRAMMING MACROS 51 5 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 . 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. 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. 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