5.12.3.1 Writing Stand-alone Assembly Routines
It is possible to write assembly routines that can be called from C code. The following guidelines must be adhered to when writing a C-callable assembly routine.
- Include the
<xc.inc>
assembly header file if you need to use SFRs in your code. If this is included using#include
, ensure the source file are preprocessed. - Select, or define, a suitable psect for the executable assembly code (see 5.15.1 Compiler-Generated Psects for an introductory guide).
- Select a name (label) for the routine using a leading underscore character.
- Ensure that the routine’s label is globally accessible from other modules.
- Select an appropriate C-equivalent prototype for the routine on which argument passing can be modeled.
- If values need to be passed to, or returned from the routine, write a reentrant routine if possible (see 5.12.3.2 Writing Reentrant Assembly Routines With Parameters); otherwise use ordinary variables for value passing.
- Optionally, use a signature value to enable type checking when the function is called.
- Use bank selection instructions and mask addresses of any variable symbols.
The following example shows a Mid-range device assembly routine that can add an 8-bit argument with the contents of PORTB and return this as an 8-bit quantity. The code is similar for other devices.
#include <xc.inc>
GLOBAL _add ; make _add globally accessible
SIGNAT _add,4217 ; tell the linker how it should be called
; everything following will be placed into the code psect
PSECT code
; our routine to add to ints and return the result
_add:
; W is loaded by the calling function;
BANKSEL (PORTB) ; select the bank of this object
addwf BANKMASK(PORTB),w ; add parameter to port
; the result is already in the required location (W) so we can
; just return immediately
return
The code has been placed in a predefined psect, code
,
that is available after including <xc.inc>
. This section is part of
the CODE
linker class, so it will be automatically placed in the area
of memory set aside for code without you having to adjust the default linker options.
This section can be used by any device to hold executable code.
PSECT mytext,local,class=CODE,delta=2
delta
flag used with this section indicates that
the memory space in which the psect will be placed is word addressable (value of 2),
which is true for PIC10/12/16 devices. For PIC18 devices, program memory
is byte addressable, but instructions must be word-aligned, so you would instead use a
section definition such as the following, which uses a delta
value of 1
(which is the default setting), but the reloc
(alignment) flag is set
to 2, to ensure that the section starts on a word-aligned
address.PSECT text0,class=CODE,reloc=2
See 6.1.9.39 Psect Directive for detailed information on the flags used with the PSECT
assembler
directive.
The mapping between C identifiers and those used by assembly are
described in 5.12.3 Interaction between Assembly and C Code.
In assembly domain we must choose the routine name _add
as this then
maps to the C identifier add. Since this routine will be called from other modules, the
label is made globally accessible, by using the GLOBAL
assembler
directive (6.1.9.21 Global Directive).
A SIGNAT
directive (6.1.9.44 Signat Directive) was used so that
the linker can check that the routine is correctly called.
The W register will be used for passing in the argument. See 5.8.5 Function Parameters for the convention used to pass parameters.
The BANKSEL
directive and BANKMASK
macro have been used to ensure that the correct bank was selected and that all addresses
are masked to the appropriate size.
To call an assembly routine from C code, a declaration for the routine must be provided. Here is a C code snippet that declares the operation of the assembler routine, then calls the routine.
// declare the assembly routine so that the call from C code is correct
extern unsigned char add(unsigned char a);
int main(void) {
volatile unsigned char result;
result = add(5); // call the assembly routine
}