XC8-PIC-Assembler User's Guide For Embedded Engineers
XC8-PIC-Assembler User's Guide For Embedded Engineers
Engineers
Notice to Customers
All documentation becomes dated and this manual is no exception. Microchip tools and documentation are constantly
evolving to meet customer needs, so some actual dialogs and/or tool descriptions can differ from those in this
document. Please refer to our web site (https://www.microchip.com) to obtain the latest documentation available.
Documents are identified with a “DS” number. This number is located on the bottom of each page, in front of the page
number. The numbering convention for the DS number is “DSXXXXXA,” where “XXXXX” is the document number
and “A” is the revision level of the document.
For the most up-to-date information on development tools, see the MPLAB® IDE online help. Select the Help menu,
and then Topics to open a list of available online help files.
2. Introduction............................................................................................................................................. 6
Customer Support........................................................................................................................................ 30
Trademarks.................................................................................................................................................. 32
1. Preface
Filenames autoexec.bat
Bit values 0, 1
Italic Courier New A variable argument file.o, where file can be any valid
filename
Square brackets [ ] Optional arguments mcc18 [options] file [options]
2. Introduction
This guide shows and describes example assembly programs that can be built with the MPLAB® XC8 PIC Assembler
(PIC Assembler) for a variety of 8-bit Microchip PIC device families.
The examples shown bring together various concepts, assembler directives, and operators, which you can read
about in more detail in the MPLAB® XC8 PIC Assembler User's Guide The programs themselves show how device or
assembler features can be utilized but do not represent meaningful programs.
If you are migrating programs from the Microchip MPASM™ Assembler to PIC Assembler, this guide in conjunction
with the MPLAB® XC8 PIC Assembler Migration Guide will be of great assistance.
#include <xc.inc>
// configuration word 1
CONFIG FEXTOSC=XT // crystal oscillator
CONFIG RSTOSC=EXTOSC // EXTOSC operating per FEXTOSC bits
CONFIG CLKOUTEN=OFF // CLKOUT function is disabled
CONFIG PR1WAY=ON // PRLOCK bit can be cleared and set only once
CONFIG CSWEN=ON // Writing to NOSC and NDIV is allowed
CONFIG FCMEN=ON // Fail-Safe Clock Monitor enabled
// configuration word 2
CONFIG MCLRE=EXTMCLR // If LVP=0, MCLR pin is MCLR; If LVP=1, RE3 pin function is MCLR
CONFIG PWRTS=PWRT_OFF // PWRT is disabled
CONFIG MVECEN=OFF // Vector table isn't used to prioritize interrupts
CONFIG IVT1WAY=ON // IVTLOCK bit can be cleared and set only once
CONFIG LPBOREN=OFF // ULPBOR disabled
CONFIG BOREN=SBORDIS // Brown-out Reset enabled, SBOREN bit is ignored
CONFIG BORV=VBOR_2P45 // Brown-out Reset Voltage (VBOR) set to 2.45V
CONFIG ZCD=OFF // ZCD disabled, enable by setting the ZCDSEN bit of ZCDCON
CONFIG PPS1WAY=ON // PPSLOCK cleared/set only once; PPS locked after clear/set cycle
CONFIG STVREN=ON // Stack full/underflow will cause Reset
CONFIG DEBUG=OFF // Background debugger disabled
CONFIG XINST=OFF // Extended Instruction Set and Indexed Addressing Mode disabled
// configuration word 3
CONFIG WDTCPS=WDTCPS_31 // Divider ratio 1:65536; software control of WDTPS
CONFIG WDTE=OFF // WDT Disabled; SWDTEN is ignored
CONFIG WDTCWS=WDTCWS_7 // window open 100%; software control; keyed access not required
CONFIG WDTCCS=SC // Software Control
// configuration word 4
CONFIG BBSIZE=BBSIZE_512 // Boot Block size is 512 words
CONFIG BBEN=OFF // Boot block disabled
CONFIG SAFEN=OFF // SAF disabled
CONFIG WRTAPP=OFF // Application Block not write protected
CONFIG WRTB=OFF // Configuration registers (300000-30000Bh) not write-protected
CONFIG WRTC=OFF // Boot Block (000000-0007FFh) not write-protected
CONFIG WRTD=OFF // Data EEPROM not write-protected
CONFIG WRTSAF=OFF // SAF not Write Protected
CONFIG LVP=ON // Low voltage programming enabled, MCLR pin, MCLRE ignored
// configuration word 5
CONFIG CP=OFF // PFM and Data EEPROM code protection disabled
subwf max,w,c
bc loop ;no - read again
movff tmp,max ;yes - record this new high value
goto loop ;read again
END resetVec
3.1 Comments
There are several styles of comments that can be used in assembly source code.
Assembler comments consist of a semi-colon, ;, followed by the comment text. These can be placed on a line of
their own, such as the comment:
;objects in common (Access bank) memory
as shown in 3. A Basic Example For PIC18 Devices, or they can be at the end of a line containing an instruction or
directive, as shown bolded in this example:
movff PORTA,tmp ;read the port
C-style comments can be used in assembly source code if the assembly source file is preprocessed. To run the
preprocessor over an assembly source file, name the file using a .S extension (upper case), or use the -
xassembler-with-cpp option when you build.
Single-line C-style comments begin with a double slash sequence, //, followed by the comment text, as seen bolded
in the line:
CONFIG PWRTS=PWRT_OFF // PWRT is disabled
Multi-line C-style comments begin with slash-star, /*, and terminate with a star-slash sequence, */. This is the only
comment style that allow the comment text to run over multiple lines. One is shown in the example:
/* find the highest PORTA value read, storing this into
the object max */
As shown in the example code, you typically provide setting-value pairs to these directives, setting each configuration
bit to the desired state, but an entire configuration word can also be programmed using one directive, if required. The
CONFIG directive can also be used to set the device's IDLOC bits, where relevant.
If you are not using the MPLAB X IDE, the configuration bit settings and values associated with each device can be
determined from an HTML guide. Open either the pic18_chipinfo.html guide for PIC18 devices or the
pic_chipinfo.html guide for all other devices. These are located in the docs directory in the MPLAB XC8 C
Compiler distribution that contains your PIC Assembler. Click the link to your target device and the page will show
you the settings and values that are appropriate for your directives. Note that the usage examples shown in these
HTML guides demonstrate the #pragma config directive, which is relevant only for C programs, but the setting-
value pairs used are relevant for the CONFIG assembler directive.
your assembly source files will typically always include the <xc.inc> file, which is provided by the PIC Assembler.
This file allows your source code to access special functions registers and other device features. Do not include
device-specific include files or maintain your own version of these files, as their content might change in future
versions of the assembler.
Processor Directive
The PROCESSOR directive should be used if the assembler source in the module is only applicable to one device.
The -mcpu option always specifies which device the code is being built for. If there is a mismatch between the device
specified in the PROCESSOR directive and in the option, an error will be triggered. The line of code:
PROCESSOR 18F47K42
will assemble the movwf instruction only for PIC18F47K40 devices. The _18F47K40 preprocessor macro is one that
is automatically defined by the assembler, based on the selected device. A full list of predefined macros is available
in the MPLAB® XC8 PIC Assembler User's Guide.
End Directive
Use of the END directive is not mandatory, but signifies an end to the source code in that module. There can be no
further lines of source present after an END directive, even blank ones.
If you use one or more END directives in your program, one (and only one) of those directives should specify the
program's entry point label to prevent an assembler warning being generated. This has been done in the last line of
the example program:
END resetVec
Psects—short for program sections—are containers that group and hold related parts of the program, even though
the source code for these parts might not be physically adjacent in the source file, or may even be spread over
several modules. They are the smallest entities positioned by the linker into memory.
To place code or objects into a psect, precede their definition with a PSECT directive and the name of the psect you
wish to use. The first time a psect is used in a module, you must define the psect flags that are relevant for the psect.
These flags control which memory space the psect will reside in and further refine how the psect is linked.
To assist with code migration and development, several psects are predefined with appropriate flags by the PIC
Assembler and are available once you include the <xc.inc> file into your source. A full list of these psects can be
found in the MPLAB® XC8 PIC® Assembler User's Guide.
In the examples shown in 3. A Basic Example For PIC18 Devices, the predefined code psect has been used to hold
most of the program's code. You can see it being used in the lines:
PSECT code
main:
clrf max,c
...
thereby placing the main label and the instructions that follow into this psect. The udata_acs psect has been used
to place max and tmp in the Access Bank memory, as shown in the lines:
PSECT udata_acs
max:
DS 1
tmp:
DS 1
Configuration data also needs to be in a psect; however, the CONFIG directive automatically places its data into an
appropriate psect for you, so you should not precede it with a PSECT directive.
One advantage of using predefined psects is that they are already associated with a suitable linker class, so they will
be automatically linked into an appropriate region of memory without you having to stipulate any linker options.
The psect name being defined and selected by this directive is resetVec. The label and goto instruction that follow
will be part of this psect.
A psect is typically associated with a linker class through the class psect flag. In the above example, the resetVec
psect has been associated with a linker class called CODE, which is a class predefined by the assembler. A class
defines one or more ranges of memory in which the psect can be linked. As the resetVec psect will need to be
explicitly linked to an absolute address, the class association is not actually necessary, but it is common to always
specify a psect's class, if only to make the psect's listing in the map file easier to find. Psects associated with a class
can still be placed at arbitrary locations using a linker option. The predefined classes are listed in the MPLAB® XC8
PIC® Assembler User's Guide.
The reloc flag in the resetVec psect ensures that the start of the psect is aligned to an address that is a multiple
of the flag's value. It is critical that this flag be set to 2 for any PIC18 psects that hold executable code, as the
instructions must be word aligned. Set it to 1 (the default value if no reloc flag is specified) for psects holding
instructions for other devices.
The option that can be used to link the resetVec psect at the Reset vector location is given at the end of this
section.
This command will produce (among other files) test.hex and test.elf, which can be used by the IDE and
debugger tools to execute and debug the code. Such a command assumes that pic-as is in your console's search
path. If that is not the case, specify the full path to pic-as when you run the application.
Although the above command will build with no error, there are, however, some additional options that need to be
specified before the code will run. To see why, build the code again using the -Wl driver option to request a map file,
as shown below.
pic-as -mcpu=18f47k42 -Wl,-Map=test.map test.S
Open test.map and look for the special resetVec psect that was defined in the code. You will see something
similar to the following:
The resetVec psect will be present in several lists, but you can easily find it under the CODE class, with which it was
associated. Notice that it was linked to 1FFDE, not address 0, as we require, because the linker received no explicit
placement instructions and simply linked it to a free location in the memory defined by the CODE class.
You can see the definition (-A linker option) for the CODE (and other) linker classes at the top of the map file as part of
the linker options. For this device, the definition for the CODE class is:
-ACODE=00h-01FFFFh
which shows that this class represents one large chunk of memory from address 0 thru 0x1FFFF.
Build the source file again, this time use the -Wl driver option to pass a -p option to the linker. In the command line
below, the option explicitly places the resetVec psect at address 0.
The content of the map file will now look similar to the following, showing that the reset code is in the correct location.
Notice also in the map file that the data specified by the CONFIG directives were placed into a psect called config,
which was linked to the correct address in the hex file for this device.
TOTAL Name Link Load Length Space
...
CLASS CONFIG
config 300000 300000 A 4
#include <xc.inc>
;configuration word 1
CONFIG FOSC=XT // XT Oscillator
CONFIG WDTE=OFF,PWRTE=OFF // WDT & PWRT disabled
CONFIG MCLRE=ON // MCLR/VPP pin function is MCLR
CONFIG CP=OFF,CPD=OFF // Program & data memory unprotected
CONFIG BOREN=ON // Brown-out Reset enabled
CONFIG CLKOUTEN=OFF // CLKOUT function is disabled
CONFIG IESO=ON // Internal/External Switchover mode enabled
CONFIG FCMEN=ON // Fail-Safe Clock Monitor enabled
;configuration word 2
CONFIG WRT=OFF // Write protection off
CONFIG VCAPEN=OFF // All VCAP pin functionality disabled
CONFIG PLLEN=ON // 4x PLL enabled
CONFIG STVREN=ON // Stack Over/underflow will cause a Reset
CONFIG BORV=LO // Brown-out Reset Voltage low trip point
CONFIG LVP=ON // Low-voltage programming enabled
skipnc MACRO
btfsc STATUS,0
ENDM
PSECT resetVec,class=CODE,delta=2
resetVec:
PAGESEL main ;jump to the main routine
goto main
END resetVec
The macro is used just the once in this example, shown here:
subwf max^(tmp&0ff80h),w
skipnc
goto loop
An assembler macro can have arguments, and there are several characters that have special meaning inside macro
definitions. A full description of these are presented in the MPLAB® XC8 PIC Assembler User's Guide
Notice that the reloc=2 flag that was used in the PIC18 example is not necessary with psects that hold code for
Mid-range or Baseline devices. As this flag has not be specified, the reloc value defaults to 1, which implies that
this psect will not be word aligned. Instructions on Mid-range or Baseline devices can be located at any address, so
no word alignment is necessary.
The new psect flag that has been used with resetVec is the delta flag. A delta value of 2 indicates that 2 bytes
reside at each address in the memory space where this psect will be located. This agrees with the program memory
implemented on Mid-range and Baseline devices, which is either 14 or 12 bits wide (respectively) and which requires
2 whole bytes to program. PIC18 devices, on the other hand, define 1 byte of memory at each address, hence the
delta value associated with psects holding code on those devices should be set to 1 (the default value if no delta
flag is used).
Any program memory psect holding code on a Mid-range or Baseline devices must set the delta flag to 2. Psects
destined for data memory or psects used with other devices should omit the delta flag or explicitly set the flag value
to 1.
To illustrate how banked instructions should be used, the code in this Mid-range example links the tmp and max
objects into banked rather than common memory, by placing them in the assembler-defined psect udata_bank0.
This means that the code that accesses these objects must deal with bank selection and address masking.
If all the objects in your program are located in the same bank, then only a single bank selection sequence might be
required. Similarly, address masking can omitted for objects in bank 0. However, it is good practice to always use
address masking and be particularly careful of bank selection, especially if you modify the placement of objects.
The main part of the example program begins as follows.
PSECT code
main:
BANKSEL max
clrf BANKMASK(max)
BANKSEL ANSELA
clrf BANKMASK(ANSELA)
loop:
BANKSEL PORTA
movf BANKMASK(PORTA),w
BANKSEL tmp
movwf BANKMASK(tmp)
;is PORTA larger than input?
subwf max^(tmp&0ff80h),w
...
The BANKSEL(max) directive has been used to select the bank of the object max before it is accessed by the
instruction following. Although you can manually write the instruction or instructions needed to set the bank selection
bits for the device you are using, the BANKSEL directive is more portable and is easier to interpret. On devices other
than PIC18s and Enhanced Mid-range devices, this directive can sometimes expand into more than one instruction,
you should never use it immediately after an instruction that can skip, such as the btfsc instruction.
The clrf instruction clears the object max. Here, the BANKMASK() preprocessor macro has been used with the
instruction operand to remove the bank bits contained in the address of max. This directive ANDs the address with an
appropriate mask. If this information is not removed, the linker may issue a fixup overflow error.
In the last few instructions of the code snippet shown above, the programmer has assumed that max and tmp are
defined in the same psect, and hence will be located in the same data bank. The example code selects the bank of
tmp, writes WREG into that object using a movwf instruction, and then max is accessed by the subwf instruction.
Based on the programmer's assumption, a BANKSEL directive to select the bank of max before the subwf instruction
is redundant, and it has been omitted to reduce the size of the program.
While the above assumption is valid and the code will execute correctly, it could fail if the definitions for max and tmp
were to change and the objects ended up in different banks. Such a failure would be difficult to track down.
Fortunately, there are other ways to remove the bank information from an address that can be used to your
advantage.
The last line of code in the above example:
subwf max^(tmp&0ff80h),w
contains a check to ensure that the bank of tmp and max are the same. Rather than ANDing out the bank information
in max by using the BANKMASK() macro, the operand expression XORs the full address of max with the bank bits
contained in the address of tmp (which is obtained by masking out only the bank offset from tmp using the AND
operator, &). If the bank bits in the address of max and the bank bits in the address of tmp are the same, they will
XOR to zero. If they are not the same, they will produce a non-zero component in the upper bits of the address and
trigger a fixup overflow error from the linker. This is much more desirable than the code silently failing at runtime.
The example code checks to ensure that two objects are in the same bank, but you can also use this style of check to
ensure that an object is in a known bank. If, for example, the code required that max must be in bank 2, then using
the address expression (max ^ 100h) on a Mid-range device would trigger a fixup error if that was not the case.
The section relating to file register address masking in the MPASM™ to MPLAB® XC8 PIC® Assembler Migration
Guide has more information on the format of addresses and the appropriate masks to use, should you decide to not
use the BANKMASK() macro.
The psect containing the reset code has been linked to address 0. The command also includes the options to
generate a map (test.map) and assembly list file (test.lst), so you can explore the generated code.
#include <xc.inc>
PSECT code
;read PORTA, storing the result into WREG
readPort:
BANKSEL PORTA
movf BANKMASK(PORTA),w
return
PSECT resetVec,class=CODE,delta=2
resetVec:
PAGESEL main
goto main
PSECT code
main:
BANKSEL ANSELA
clrf BANKMASK(ANSELA)
BANKSEL TRISC
movlw 0ffh
movwf BANKMASK(TRISC)
clrf count
loop:
;a call to a routine in same psect
call readPort ;value return in WREG
;a call to a routine in different module
PAGESEL storeLevel
call storeLevel ;expects argument in WREG
PAGESEL $
;wait for a few cycles
movlw 3
delay:
decfsz WREG,f
goto delay
;increment the array index, count, and stop iterating
;when the final element is reached
movlw 100
incf count,f
xorwf count,w
btfss ZERO
goto loop
goto main
END resetVec
#include <xc.inc>
PSECT udata_shr
tmp:
DS 1
PSECT code
;store byte passed via WREG into the count-th element of the
;linear memory array, levels
storeLevel:
movwf tmp ;store the parameter
movf count,w ;add the count index to
addlw low(levels) ;the base address of the
movwf FSR1L ;array, levels, storing
movlw high(levels) ;the result in FSR1
clrf FSR1H
addwfc FSR1H
movf tmp,w ;retrieve the parameter
movwf INDF1 ;access levels in linear memory
return
END
As count should be accessible from other modules, a GLOBAL count directive was used in addition to the symbol's
definition, as shown above, to tell the linker that this symbol may be accessed from other modules.
In file_2.S, another GLOBAL count directive was used to declare the symbol and link this with the definition of
the symbol that the linker will ultimately find in the other module. You may prefer to use the EXTRN count directive
to perform this function. This directive performs a similar task, but it will trigger an error if the symbol is defined in the
same module.
The same mechanism is used for symbols used by routines that need to be accessible from more than one module.
In the example code , the code in file_2.S contains a GLOBAL storeLevel directive to allow the routine to be
called from outside that module, and file_1.S also contains a GLOBAL storelevel directive to link in with this
global symbol. Again, an EXTRN storeLevel directive could have been used in file_1.S to ensure the symbol is
defined in the module you expect.
The program memory on Baseline and Mid-range devices is paged, and your device data sheet will indicate the page
arrangements for your device. The way psects are concatenated has consequences for how code written for these
devices must call or jump to labels. The program memory on PIC18 devices is not paged. All addresses in their
program memory are reachable by call and jump instructions, so the following discussion does not apply to programs
written for these devices.
Before making a call or jump on Baseline and Mid-range devices, the PCLATH register must contain the value to
select the page of the destination. The PAGESEL directive can be used to initialize the PCLATH register for you.
You can see a PAGESEL directive being used before the call storeLevel in the example code, repeated here:
loop:
call readPort
PAGESEL storeLevel
call storeLevel
PAGESEL $
...
goto delay
Note that it is used again after the call using the location counter, $, as its argument to ensure that PCLATH is again
pointing to the current page. This allows the goto instructions following the call to work as expected.
As shown above, the call to readPort did not use a PAGESEL directive. The PCLATH register did not need to be
updated in this case because of two conditions. First, as mentioned earlier, the code psect that contains the
readPort routine will concatenate with the code psect that holds the main routine, so these two routines (the caller
and callee) will be in the same concatenated psect; and second, the CODE linker class associated with the code
psect is defined in such a way that psects placed in its memory ranges can never cross a page boundary.
The linker options used when you build are shown in the map file. For the 16F1937 device used in this example, the
top of the map file is as follows:
Linker command line:
The CODE class is defined by the linker option: -ACODE=00h-07FFhx4. This option indicates that the memory
associated with the CODE class consists of 4 consecutive pages, the first starting at address 0, and each being 0x800
words long. These ranges correspond to the page addresses on the 16F1937 device.
The linker will never allow a psect placed into the memory associated with a class to cross boundaries in that class's
memory ranges, which implies in this example that a psect placed in the CODE class will be wholly contained in a
device page. You will receive a 'can't find space' error if a psect linked into this class exceeds the size of a page. If
the class had instead been defined using -ACODE=0-01FFFh, it would cover exactly the same memory, but the
boundaries in that memory would not exist. Psects placed in a class such as this could be linked anywhere,
potentially straddling a device page boundary.
It is common to restrict where the linker can place psects so that assumptions can then be made in the source code
that improve efficiency. In this case, the page boundaries in the CODE class, has meant that calls or jumps to a label
that is defined in the same psect and in the same module do not need to first select the destination page (assuming
that PCLATH already points to that page).
Page selection must be considered for any Baseline and Mid-range non-relative control instruction, those being the
goto, call, and callw instructions. It is not needed prior to relative branch instructions, but as these instructions
modify PC once the branch has been taken, you will need to assess whether page selection is required for calls and
jumps made after the branch. Page selection may also be required should you use any instructions that specify the
PCL register as the destination, as these also use the PCLATH register to form the destination address.
There are two pseudo instructions that you can use to ensure that page selection always takes place. They are ljmp
and fcall. These expand to a goto and call, respectively, with the necessary page selection before the goto or
call instruction, then page selection of the current page after the instruction. As the ljmp and fcall mnemonics
can expand to more than one PIC instruction, they should never be used immediately after any instruction that skips,
such as the btfsc instruction. You can see the opcodes generated for the ljmp or fcall pseudo instructions in the
assembly list file.
The above code snippet could be rewritten:
loop:
call readPort
fcall storeLevel
...
goto delay
If you are not sure whether page selection is required before a call or jump, the safest approach is to use the
PAGESEL directive or use the fcall and ljmp instructions, but just remember that doing so might unnecessarily
increase the size of your code and slow its execution.
The above considerations might tempt you to place most of your code in the same psect in the one module so that
you can avoid page selection instructions, but remember that once a psect grows larger than the size of a page, it will
no longer fit in the CODE class and you'll receive 'can't find space' error message from the linker. Consider having
frequently called routines and the routines that call them in psects with the same name and in the same module.
Move other routines to other modules, or place them in psects with different names so that they are linked separately
and can fill other device pages.
If you decide to create your own psects and linker classes to position code, keep in mind that how you define the
linker classes might affect how your code needs to be written. If routines can straddle a bank boundary, they are
more likely to fit in the program memory, but your program will require more page selection instructions. If you
manually position psects (rather than have them linked anywhere in a linker class) to control where page selection
instructions are needed, this will require a lot of maintenance as the code is debugged and developed.
This directive takes several arguments. The first is the address space in which the memory will be defined. This
should be the value 1 for objects to be placed in data memory. The second argument is the starting address for the
storage. This can be specified as either a linear or banked address. The equivalent linear address for the banked
address 0x120 is 0x20A0, for example. The following argument is the number of bytes that is to be reserved, and the
final argument is a symbol. The symbol is optional and creates a label for the object at the indicated address.
Unlike most directives, the DLABS directive does not produce output that is part of the current psect, so it can appear
inside any psect. This also means that there should not be a label placed immediately before the directive. If a label
is required for the object you are defining, then specify it as the last argument of the directive, as shown above.
For this example, the compiler will reserve storage for the levels object in banks 2 and 3. Using banked instructions
to access it, however, is problematic, because it may not be clear which bank to select beforehand. The Enhanced
Mid-range devices, however, allow you to use the FSR register to indirectly read and write data memory, and
accessing levels in this way is shown in the example and is repeated here.
movf count,w ;add the count index to
addlw low(levels) ;the base address of the
movwf FSR1L ;array, levels, storing
movlw high(levels) ;the result in FSR1
clrf FSR1H
addwfc FSR1H
movf tmp,w ;retrieve the parameter
movwf INDF1 ;access levels in linear memory
The psect containing the reset code has been linked to address 0. The command also includes the options to
generate a map (test.map) and assembly list file (test.lst), so you can explore the generated code.
If you prefer to build each file separately (for example, from a make file) then you could also use the commands
below:
pic-as -mcpu=16f1937 -c file_1.S
pic-as -mcpu=16f1937 -c file_2.S
pic-as -mcpu=16f1937 -Wl,-presetVec=0h -Wa,-a -Wl,-Map=test.map file_1.o file_2.o
Here, the -c option has been used to generate an intermediate file for each source file. The intermediate (object)
files will have a .o extension. The final command links the object files and produces the final output. Note that the
options that are passed to the linker and those that create the list and map files are only needed in the final build
step. The -mcpu option, which indicates the target device, must be used with every command.
PSECT resetVec,class=CODE,reloc=2
resetVec:
goto main
PSECT code
;add needs 4 bytes of parameters, but no autos
FNSIZE add,0,4 ;two 2-byte parameters
GLOBAL ?pa_add
;add the two 'int' parameters, returning the result in the first parameter location
add:
movf ?pa_add+2,w,c
addwf ?pa_add+0,f,c
movf ?pa_add+3,w,c
addwfc ?pa_add+1,f,c
return
END resetVec
The function of the above assembly code is similar to that of the below C code, which is less complex and will be
more familiar to C programmers.
int add(int a, int b) {
return a + b;
}
void main(void) {
result = 0;
incr = 2;
while(1) {
result = add(result, incr);
write(result);
incr++;
}
}
which indicates that the udata_acs psect (Access bank data memory) will be used to hold all the objects on the
stack. The psect you choose will affect how the stack objects must be accessed. If there are a large number of stack-
based objects and particularly if they are accessed often, placing the stack in the PIC18 Access bank will mean they
can be accessed without any bank selection instructions. If the stack becomes too large, however, it will need to be
placed in a data bank. Mid-range and Baseline devices have little common memory, and this might be needed for
other purposes, so banked memory is often used for the compiler stack on these devices.
The ?au_ symbol specified as the second directive argument will be prefixed to the name of each routine that uses
the stack to create the base symbol for their auto-style objects. The ?pa_ prefix will be used to form the base symbol
for each routine's parameter objects. These symbols are illustrated below.
The add routine begins with the following code:
PSECT code
FNSIZE add,0,4
GLOBAL ?pa_add
add:
movf ?pa_add+2,w,c
addwf ?pa_add+0,f,c
The FNSIZE directive takes three arguments, those being the name of a routine, the total number of bytes required
for that routine's auto-like objects, and the total number of bytes for its parameter-like objects. In this case, the
FNSIZE directive indicates that the add routine needs no auto-style objects and 4 bytes of parameters. The directive
can be placed anywhere in your code, but it is typical to have it located near the routine it configures.
The linker will automatically create a symbol associated with the block of 4 bytes used by the routine's parameters. In
this case, that symbol will be ?pa_add, based on the prefix used with the FNCONF directive. Although this symbol is
defined by the linker, it still needs to be declared in each module that needs it. This has been done in the above code
by the GLOBAL ?pa_add directive. Each byte of the parameter memory can be accessed by using an offset from
the ?pa_add symbol. The code above shows the first and third bytes of this memory being accessed. What these 4
bytes represent is entirely up to you. In the example, the parameter memory is used to hold two, 2-byte-wide objects,
but the same FNSIZE arguments could be used to create one, 4-byte-wide object if required.
Later in the example code, the following code begins the definition of the main routine.
FNROOT main
FNSIZE main,4,0
FNCALL main,add
FNCALL main,write
GLOBAL ?au_main
PSECT code
main:
clrf ?au_main+0,c
The main routine requires four bytes of auto objects and so the FNSIZE directive has again been used to indicate
this. You can see the first byte of main's auto area accessed by the crlf instruction, using the symbol ?au_main.
To be able to allocate memory on the stack, the linker needs to know how the program is structured in terms of calls.
To allow it to form the program's call graph, several other directives are used.
The FNROOT directive, shown above, indicates that the specified routine forms the root node in a callgraph. The
memory allocated to stack objects can be overlapped with that of other routines within the same callgraph, but no
overlapping will take place between the stack objects of routines that are in different callgraphs. Typically you will
define one callgraph root for the main part of your program and then one for each interrupt routine. This way, the
stack memory associated with interrupt routines is kept separate and no data corruption can occur.
The FNCALL directive is used as many times as required to indicate which routines are called and from where. From
this, the linker can form the callgraph nodes. In the above code sequence, the FNCALL directive was used twice. The
first indicates that the main routine calls add; the second that main calls write. As you develop your program, you
need to ensure that there is an FNCALL directive for each unique call that takes place in the code. If the called routine
does not define any compiled stack objects, the directive is not required, but it is good practice to include it anyway, in
case there are changes made to the program.
You may devise whatever convention you like to pass arguments to routines. In the example, the LSB of the first
parameter has an offset of 0; the MSB of the first parameter an offset of 1, etc., thus the expressions ?pa_add+0
and ?pa_add+1 represent the two bytes of the first 'int' parameter and ?pa_add+2 and ?pa_add+3 represent the
two bytes of the second parameter. The first auto-style object defined by main is referenced using the expressions ?
au_main+0 and ?au_main+1. You may, of course, define macros to make these expressions more readable, for
example:
#define add_b ?pa_add+2
Typically, routines that need to return a value do so by storing that value into the memory taken up by their
parameters. As the parameters should no longer be used once the routine returns, this reuse is not normally an
issue, but you do need to consider the routine’s return value when you allocate memory for the routine's parameters.
The parameter memory you request for a routine using the FNSIZE directive must be the larger of the total size of the
routine's parameters and the size of the routine's return value.
Indentation is used to indicate call depth. In the above, you can see that main calls add and main also calls write.
The size of the memory allocated for the routine's parameter and auto-style objects is printed, followed by the auto-
parameter block (APB) offset into the compiled stack. Note that the offset is not an address; just a relative position in
the stack.
Notice in the call graph that the offset of the APB for add and write are identical. This implies that the memory they
use is shared, which is possible because add and write do not call each other in the code and so are never active
at the same time.
A star, *, before a routine name indicates that the APB memory used by that routine is at a unique location and
contributes to the total size of the program's RAM usage. These routines are critical path nodes in the call graph.
Reducing the size of the APB used by these routines will reduce the total amount of data memory used by the
program. The memory blocks used by unstarred routines totally overlap with blocks from other routines and do not
contribute to the program's total memory usage.
The -mcallgraph option can be used to customize what call graph information in displayed in the map file. Using -
mcallgraph=crit, for example, will display only the nodes on critical paths, that is, all of the routines in the graph
that are starred.
The advantage of using a compiled stack is obvious in this example: Although the program needed a total of 10 bytes
of local storage, only 8 bytes needed to be allocated memory, with 2 bytes being reused. And this memory reduction
was done without the programmer having to employ the dangerous practice of sharing objects between routines.
It is important to note that corruption of data due to incorrect overlapping of APBs can occur if the information in the
call graph is not accurate because of missing or erroneous FN-type directives in your program. Consider placing the
FNCALL directive associated with a call immediately before the call instruction itself, so you can clearly see if one is
missing. The FN-type directives can, however, be placed anywhere in the file, as they do not contribute to the hex
file, and there is no harm in the same directive being repeated.
You will also see in the symbol table, printed at the end of the map file, the addresses assigned to the special linker-
generated symbols. For this program, it might look something like the following.
Symbol Table
Unlike the values printed for the offset in the call graph, the values in the symbol table are always absolute
addresses. In this example, the udata_acs psect just happened to be linked to address 0, so the APB used by
main for auto-style objects begins at address 0 (the same as its offset) and is shown to be in the udata_acs psect
(as we specified). The APB used by add for its parameters begins at address 4, as does the block used by write.
No special options are needed to build code that uses a compiled stack, but the above command uses the -
mcallgraph option to request that a full callgraph be printed. The callgraph is shown in the map file, requested by
the option -Wl,-Map=test.map in the above.
As with the previous examples, the psect containing the reset code has been linked to address 0. The command also
includes the option to generate an assembly list file (test.lst), so you can explore the generated code.
#include <xc.inc>
PSECT udata_bank1
count: DS 1
END resetVec
PSECT intCodeLo,class=CODE,reloc=2
intVecLo:
movff BSR,shadow ;save used registers
btfsc TMR0IE ;did the timer generate this interrupt?
btfss TMR0IF
goto exitInt ;no - ignore
bcf TMR0IF ;yes - clear the interrupt flag
movlw 0FEh ;reload the timer
movwf TMR0
;set a flag to say that the port should be incremented
BANKSEL (needCopy/8)
bsf BANKMASK(needCopy/8),needCopy&7
exitInt:
movff shadow,BSR ;restore context
retfie
Interrupt routines must save and restore the state of any registers that they (and any routine they call) use and that
are also used in main-line code. With some devices, commonly-used registers are automatically saved into shadow
registers when the interrupt occurs, but this is not always the case. Check your device datasheet to see how
interrupts are processed on your device. In this example, the interrupt routine itself needs to save context, as it will be
linked to the PIC18 low priority interrupt vector and the fast register stack was not used.
In this example, the only register used by both interrupt and main-line code is the BSR register, which holds the
selected data bank. That register is modified by the BANKSEL directives. This register, therefor needs to be saved on
entry to the interrupt routine and restored on exit. The WREG and STATUS registers usually need to be saved;
however, in this example, they are not used by both routines.
Registers that must be saved should be copied to objects that you must define. In this example, an object called
shadow was created for that purpose. Note that it was written and read using a movff instruction, which does not
affect the STATUS or BSR registers. Be careful that your context save code itself does not clobber registers that have
not yet been saved, and that your context restore code does not clobber register that have already been restored.
A retfie instruction was used to return from the interrupt.
The inclusion of the bit flag with the psect definition instructs the linker that the units of address within this psect are
bits, not bytes. This means that the DS directive, which allocates one unit of storage, is reserving a single bit, not a
single byte. The needCopy object, then, is an object that can only hold a single bit.
Any label defined within a bit psect represents a bit address, not a byte address, and this affects how these labels
should be used in instructions. The PIC instructions that work with bits require a byte address operand followed by a
bit offset within that byte. A bit object that is located at (bit) address 0x283, for example, is located at byte address
0x283/8, which is 0x50, and at bit position 0x283%7, which is position #3. If you are using a bit object with a PIC bit
instruction, such as bcf or btfss, then you will need to divide the bit address by 8 to obtain the byte address used
in the instruction, and you will need to take the modulus of 7 to obtain the bit offset. For example, to set needCopy,
the example code used:
BANKSEL (needCopy/8)
bsf BANKMASK(needCopy/8),needCopy&7
Note that the BANKSEL directive also requires a byte address argument, so the bit address of needCopy was divided
by 8 for this instruction as well.
You can, if desired, create a preprocessor macro to make this bit object more readable, for example
#define NEEDCOPY BANKMASK(needCopy/8),needCopy%7
You will notice that bits with SFRs are defined and accessed in just this way, for example in the instruction:
btfsc TMR0IE
the TMR0IE macro expands to the byte address and bit offset. All the SFR bit symbols are actually preprocessor
macros, which are defined in a similar way to the NEEDCOPY macro above. These SFR bits are available once you
include <xc.inc> into your source file.
Note also that the addresses of any symbol defined in a bit psect is printed in the list and map files as a bit address.
Do not compare such addresses to other byte addresses when checking for memory allocation. You can confirm
which psects used the bit flag in the map file by looking for the Scale value. For bit psects, this will indicate a value of
8 and be left empty for non-bit psects. In this example, the myFlags psect was shown:
Name Link Load Length Selector Space Scale
interrupts_pic18.o
intCodeLo 18 18 1C C 0
resetVec 0 0 2 0 0
myFlags 308 61 1 60 1 8
A bit psect can be linked anywhere in the data memory with no restriction. In this example, it was associated with the
BANK0 linker class, which means that it was placed somewhere in the bank 0 data memory. If you need to fast
access bit objects, try to place them in the Access bank on PIC18 devices, or in common memory on other devices.
The psect containing the reset code has been linked to address 0 and in addition, the psect holding the low priority
interrupt code was linked to the device's vector location 0x18. The command also includes the options to generate a
map (test.map) and assembly list file (test.lst), so you can explore the generated code.
Customer Support
Users of Microchip products can receive assistance through several channels:
• Distributor or Representative
• Local Sales Office
• Embedded Solutions Engineer (ESE)
• Technical Support
Customers should contact their distributor, representative or ESE for support. Local sales offices are also available to
help customers. A listing of sales offices and locations is included in this document.
Technical support is available through the website at: http://www.microchip.com/support
Examples:
• PIC16LF18313- I/P Industrial temperature, PDIP package
• PIC16F18313- E/SS Extended temperature, SSOP package
Note:
1. Tape and Reel identifier only appears in the catalog part number description. This identifier is used for ordering
purposes and is not printed on the device package. Check with your Microchip Sales Office for package
availability with the Tape and Reel option.
2. Small form-factor packaging options may be available. Please check http://www.microchip.com/packaging for
small-form factor package availability, or contact your local Sales Office.
Trademarks
The Microchip name and logo, the Microchip logo, Adaptec, AnyRate, AVR, AVR logo, AVR Freaks, BesTime,
BitCloud, chipKIT, chipKIT logo, CryptoMemory, CryptoRF, dsPIC, FlashFlex, flexPWR, HELDO, IGLOO, JukeBlox,
KeeLoq, Kleer, LANCheck, LinkMD, maXStylus, maXTouch, MediaLB, megaAVR, Microsemi, Microsemi logo, MOST,
MOST logo, MPLAB, OptoLyzer, PackeTime, PIC, picoPower, PICSTART, PIC32 logo, PolarFire, Prochip Designer,
QTouch, SAM-BA, SenGenuity, SpyNIC, SST, SST Logo, SuperFlash, Symmetricom, SyncServer, Tachyon,
TempTrackr, TimeSource, tinyAVR, UNI/O, Vectron, and XMEGA are registered trademarks of Microchip Technology
Incorporated in the U.S.A. and other countries.
APT, ClockWorks, The Embedded Control Solutions Company, EtherSynch, FlashTec, Hyper Speed Control,
HyperLight Load, IntelliMOS, Libero, motorBench, mTouch, Powermite 3, Precision Edge, ProASIC, ProASIC Plus,
ProASIC Plus logo, Quiet-Wire, SmartFusion, SyncWorld, Temux, TimeCesium, TimeHub, TimePictra, TimeProvider,
Vite, WinPath, and ZL are registered trademarks of Microchip Technology Incorporated in the U.S.A.
Adjacent Key Suppression, AKS, Analog-for-the-Digital Age, Any Capacitor, AnyIn, AnyOut, BlueSky, BodyCom,
CodeGuard, CryptoAuthentication, CryptoAutomotive, CryptoCompanion, CryptoController, dsPICDEM,
dsPICDEM.net, Dynamic Average Matching, DAM, ECAN, EtherGREEN, In-Circuit Serial Programming, ICSP,
INICnet, Inter-Chip Connectivity, JitterBlocker, KleerNet, KleerNet logo, memBrain, Mindi, MiWi, MPASM, MPF,
MPLAB Certified logo, MPLIB, MPLINK, MultiTRAK, NetDetach, Omniscient Code Generation, PICDEM,
PICDEM.net, PICkit, PICtail, PowerSmart, PureSilicon, QMatrix, REAL ICE, Ripple Blocker, SAM-ICE, Serial Quad
I/O, SMART-I.S., SQI, SuperSwitcher, SuperSwitcher II, Total Endurance, TSHARC, USBCheck, VariSense,
ViewSpan, WiperLock, Wireless DNA, and ZENA are trademarks of Microchip Technology Incorporated in the U.S.A.
and other countries.
SQTP is a service mark of Microchip Technology Incorporated in the U.S.A.
The Adaptec logo, Frequency on Demand, Silicon Storage Technology, and Symmcom are registered trademarks of
Microchip Technology Inc. in other countries.
GestIC is a registered trademark of Microchip Technology Germany II GmbH & Co. KG, a subsidiary of Microchip
Technology Inc., in other countries.
All other trademarks mentioned herein are property of their respective companies.
© 2020, Microchip Technology Incorporated, Printed in the U.S.A., All Rights Reserved.
ISBN: 978-1-5224-6126-5
AMBA, Arm, Arm7, Arm7TDMI, Arm9, Arm11, Artisan, big.LITTLE, Cordio, CoreLink, CoreSight, Cortex, DesignStart,
DynamIQ, Jazelle, Keil, Mali, Mbed, Mbed Enabled, NEON, POP, RealView, SecurCore, Socrates, Thumb,
TrustZone, ULINK, ULINK2, ULINK-ME, ULINK-PLUS, ULINKpro, µVision, Versatile are trademarks or registered
trademarks of Arm Limited (or its subsidiaries) in the US and/or elsewhere.