HI-TECH C supports a number of special features and extensions to the C language which are designed to ease the task of producing ROM-based applications. This chapter documents the compiler options and special features which are available. After reading and understanding this manual you should know how to:
PIC C diverges from the ANSI C standard in one area: function recurson.
Due to the PIC's hardware limitations of no stack and limited memory, function recursion is unsupported.
PIC C supports a wide range of processors as shown in Table 3 - 3 on page 56. Additional processors may be added by editing picinfo.ini in the lib directory. This file is divided into baseline, midrange and high end sections, but user-defined processors should be placed at the end of the file. The header of the file explains how to specify a processor.
PIC C includes a number of standard libraries, each with the range of functions described in the Library Functions chapter.
Figure 4 - 1 illustrates the naming convention used for the standard libraries. The meaning of each field is described here, where:
Table 4 - 1 lists the standard libraries which are supplied.
The printf() function is provided but some features have been removed. For more details on this function, see the documentation on page 214.
The compiler is able to directly produce a number of the output file formats which are used by common PROM programmers and in-circuit emulators.
If you are using the HPDPIC integrated environment compiler driver you can select Motorola Hex, Intel Hex, Binary, UBROF, Tektronix Hex, American Automation symbolic Hex, Bytecraft .COD, or Library file using the Output file type menu item in the "Options" menu.
The default behaviour of the PICC command is to produce Bytecraft COD and Intel HEX output. If no output file name or type is specified, PICC will produce a Bytecraft COD and Intel HEX file with the same base name as the first source or object file. Table 4 - 2 on page 72 shows the output format options available with PICC. With any of the output format options, the base name of the output file will be the same as the first source or object file passed to PICC. The File Type column lists the filename extension which will be used for the output file.
In addition to the options shown, the -O option may be used to request generation of binary or UBROF files. If you use the -O option to specify an output file name with a .BIN type, for example -Otest.bin
, PICC will produce a binary file. Likewise, if you need to produce UBROF files, you can use the -O option to specify an output file with type .UBR, for example -Otest.ubr
.
The PICC -G option tell the compiler to produce a symbol file which can be used by debuggers and simulators to perform symbolic and source level debugging. This option produces symbol files which contain assembler level information and C source level information. If no symbol file name is specified, by default a file called file.sym will be produced, where file is the basename of the first source file on the command line. For example, to produce a symbol file called test.sym which includes C source level information:
PICC -16C84 -Gtest.sym test.c
The symbol files produced by these options may be used with in-circuit emulators and also the Lucifer debugger included with the compiler.
The compiler drivers define certain symbols to the preprocessor (CPP), allowing conditional compilation based on chip type, memory model etc. The symbols defined are listed in Table 4 - 3 on page 73. Each symbol, if defined, is equated to 1.
The PIC processor's configuration fuses may be set using the __CONFIG
macro as follows:
#include <pic.h>
__CONFIG(x);
where x is the word that is to be the configuration word. For convenience, the commonly-used bits are provided in the header file for the appropriate PIC processor. The code protection fuses are not always defined in the header file. Here is an example for the PIC16C5x:
__CONFIG(FOSC1|WDTE|0x0600);
The PICC compiler supports basic data types of 1, 2 and 4 byte size. All multi-byte types follow least significant byte first format, also known as little endian. Word size values thus have the least significant byte at the lower address, and double word size values have the least significant byte and least significant word at the lowest address.
Table 4 - 4 shows the data types and their corresponding size and arithmetic type.
A char is unsigned by default, and signed if the PICC
A double defaults to 24-bit, but becomes 32-bit with the PICC
Type
Size
(in bits)
Arithmetic Type
bit
1
boolean
char
8
signed or unsigned integer1
unsigned char
8
unsigned integer
short
16
signed integer
unsigned short
16
unsigned integer
int
16
signed integer
unsigned int
16
unsigned integer
long
32
signed integer
unsigned long
32
unsigned integer
float
24
real
double
24 or 322
real
1 -SIGNED_CHAR
option is used.
-D32
option.
HI-TECH C supports both signed char and unsigned char 8 bit integral types. The default char type is unsigned char unless the PICC -SIGNED_CHAR option is used, in which case it is signed char. Signed char is an 8 bit two's complement signed integer type, representing integral values from -128 to +127 inclusive. Signed char is an 8 bit unsigned integer type, representing integral values from 0 to 255 inclusive. It is a common misconception that the C char types are intended purely for ASCII character manipulation. This is not true, indeed the C language makes no guarantee that the default character representation is even ASCII. The char types are simply the smallest of up to four possible integer sizes, and behave in all respects like integers.
The reason for the name char is historical and does not mean that char can only be used to represent characters. It is possible to freely mix char values with short, int and long in C expressions. On the PIC the char types will commonly be used for a number of purposes, as 8 bit integers, as storage for ASCII characters, and for access to I/O locations. The default unsigned char type is the most efficient data type on the PIC and maps directly onto the 8 bit bytes which are most efficiently manipulated by PIC instructions. It is suggested that char types be used wherever possible so as to maximize performance and minimize code size.
HI-TECH C supports four 16 bit integer types. Int and short are 16 bit two's complement signed integer types, representing integral values from -32,768 to +32,767 inclusive. Unsigned int and unsigned short are 16 bit unsigned integer types, representing integral values from 0 to 65,535 inclusive. 16 bit integer values are represented in little endian format with the least significant byte at the lower address. Both int and short are 16 bits wide as this is the smallest integer size allowed by the ANSI standard for C. 16 bit integers were chosen so as not to violate the ANSI standard. Allowing a smaller integer size, such as 8 bits would lead to a serious incompatibility with the C standard. 8 bit integers are already fully supported by the char types and should be used in place of int wherever possible.
HI-TECH C supports two 32 bit integer types. Long is a 32 bit two's complement signed integer type, representing integral values from -2,147,483,648 to +2,147,483,647 inclusive. Unsigned long is a 32 bit unsigned integer type, representing integral values from 0 to 4,294,967,295 inclusive. 32 bit integer values are represented in little endian format with the least significant word and least significant byte at the lowest address. 32 bits are used for long and unsigned long as this is the smallest long integer size allowed by the ANSI standard for C.
Floating point is implemented using the IEEE 754 32-bit format and a modified IEEE 754 (truncated) 24-bit format.
The truncated 24-bit format is used for all float values. For double values, the truncated 24-bit format is the default, but may be explicitly invoked with the PICC -D24 option. The 32-bit format is used for doubles by using the PICC -D32 option.
Both of these formats are described in Table 4 - 5, where:
The value of this number is (-1)sign x 2(exponent-127) x 1.mantissa.
Format
Sign
biased exponent
mantissa
IEEE 754 32-bit
x
xxxx xxxx
xxx xxxx xxxx xxxx xxxx xxxx
Modified IEEE 754 24-bit
x
xxxx xxxx
xxx xxxx xxxx xxxx
Here are some examples of the IEEE 754 32-bit and modified IEEE 754 24-bit formats:
Note that the most significant bit of the mantissa column in Table 4 - 6 (that is the bit to the left of the radix point) is the implied bit, which is assumed to be 1 unless the exponent is zero (in which case the float is zero).
A global or static variable can be located at an absolute address by following its declaration with the construct @ address, for example:
volatile unsigned char Portvar @ 0x06;
will declare a variable called Portvar located at 06h. Note that the compiler does not reserve any storage, but merely equates the variable to that address, the compiler generated assembler will include a line of the form:
_Portvar equ 06h
Note that the compiler and linker do not make any checks for overlap of absolute variables with other variables of any kind, so it is entirely the programmer's responsibility to ensure that absolute variables are allocated only in memory not in use for other purposes.
HI-TECH C supports struct and union types of any size from one byte upwards. Structures and unions may be passed freely as function arguments and return values. Pointers to structures and unions are fully supported.
HI-TECH C fully supports bit fields in structures.
Bit fields are allocated starting with the least significant bit. Bit fields are allocated within 8-bit words. The first bit allocated is the least significant bit of the byte. Bit fields are always allocated in 8-bit units, starting from the most significant bit. When a bit field is declared, it is allocated within the current 8-bit unit if it will fit, otherwise a new 8-bit byte is allocated within the structure. Bit fields never cross the boundary between 8-bit units allocation unit. For example, the declaration:
struct {
unsigned hi : 1;
unsigned dummy : 6;
unsigned lo : 1;
} foo @ 0x10;
will produce a structure occupying 1 byte from address 10h. The field hi will be bit 0 of address 10h, lo will be bit 7 of address 10h. The least significant bit of dummy will be bit 1 of address 10h and the most significant bit of dummy will be bit 6 of address 10h. If a bit field is declared in a structure that is assigned an absolute address, no storage will be allocated.
Unnamed bit fields may be declared to pad out unused space between active bits in control registers. For example, if dummy is never used the structure above could have been declared as:
struct {
unsigned hi : 1;
unsigned : 6;
unsigned lo : 1;
} foo @ 0x10;
HI-TECH C supports the use of the ANSI type qualifiers const and volatile.
The const type qualifier is used to tell the compiler that an object has a constant value and will not be modified. If any attempt is made to modify an object declared const, the compiler will issue a warning. User defined objects declared const are placed in a special psect called strings in ROM. For example:
const int version = 3;
The volatile type qualifier is used to tell the compiler that an object cannot be guaranteed to retain its value between successive accesses. This prevents the optimiser from eliminating apparently redundant references to objects declared volatile because it may alter the behaviour of the program to do so. All Input/Output ports and any variables which may be modified by interrupt routines should be declared volatile, for example:
volatile unsigned char P_A @ 0x05;
HI-TECH C supports special type qualifiers, persistent, bank2 and bank3 to allow the user to control placement of static and extern class variables into particular address spaces. If the PICC -STRICT option is used, these type qualifiers are changed to __persistent, __bank2 and __bank3. These type qualifiers may also be applied to pointers. These type qualifiers may not be used on variables of class auto; if used on variables local to a function they must be combined with the static keyword. For example, you may not write:
void func(void)
{
/* WRONG! */
persistent int intvar;
.. other code ..
}
because intvar is of class auto. To declare intvar as a persistent variable local to function test(), write:
static near int intvar;
By default, any C variables that are not explicitly initialised are cleared to zero on startup. This is consistent with the definition of the C language. However, there are occasions where it is desired for some data to be preserved across resets or even power cycles (on-off-on).
The persistent type qualifier is used to qualify variables that should not be cleared on startup. In addition, any persistent variables will be stored in a different area of memory to other variables (the nvram psect).
There are some library routines provided to check and initialise persistent data - see page 212 for more information, and for an example of using persistent data.
The bank1, bank2 and bank3 type qualifiers are used to place static variables in RAM Bank 1, RAM Bank 2 and RAM Bank 3 respectively. In the baseline microprocessors, pointers are unaffected by these type qualifiers.
The format and use of pointers depend upon the range of processor.
All pointers in the Baseline range are 8-bits. This describes the pointer types used.
All pointers for the Midrange are the same as for the Baseline processors with the following exceptions:
The const, volatile, persistent and code modifiers may also be applied to pointers, controlling the behaviour of the object which the pointer addresses. When using these modifiers with pointer declarations, care must be taken to avoid confusion as to whether the modifier applies to the pointer, or the object addressed by the pointer. The rule is as follows: if the modifier is to the left of the "*" in the pointer declaration, it applies to the object which the pointer addresses. If the modifier is to the right of the "*", it applies to the pointer variable itself. Using the volatile keyword to illustrate, the declaration:
volatile char * nptr;
declares a pointer to a volatile character. The volatile modifier applies to the object which the pointer addresses because it is to the left of the "*" in the pointer declaration.
char * volatile ptr;
behaves quite differently however. The volatile keyword is to the right of the "*" and thus applies to the actual pointer variable ptr, not the object which the pointer addresses. Finally, the declaration:
volatile char * volatile nnptr;
will generate a volatile pointer to a volatile variable.
Pointers to const should be used when indirectly accessing objects which have been declared using the const qualifier. Const pointers behave in nearly the same manner as the default pointer class in each memory model, the only difference being that the compiler forbids attempts to write via a pointer to const. Thus, given the declaration:
const char * cptr;
ch = *cptr;
*cptr = ch;
is not. Const pointers always access program ROM because const declared objects are stored in ROM.
The compiler incorporates features allowing the PIC interrupt to be handled without writing any assembler code. The function qualifier interrupt may be applied to one function to allow it to be called directly from the hardware interrupt. The compiler will process the interrupt function differently to any other functions, generating code to save and restore any registers used and exit using the RETFIE instruction instead of a RETLW or RETURN instructions at the end of the function.
(If the PICC option -STRICT is used, the interrupt keyword becomes __interrupt. Wherever this manual refers to the interrupt keyword, assume __interrupt if you are using -STRICT.)
An interrupt function must be declared as type interrupt void and may not have parameters. It may not be called directly from C code, but it may call other functions itself, subject to certain limitations. An example of an interrupt function follows:
long tick_count;
void interrupt tc_int(void)
{
++tick_count;
}
As there is only a maximum of one interrupt vector in the PIC series, only one interrupt function may be used. The interrupt vector will automatically be set to point to this function.
Assembly language code can be mixed with C code using three different techniques.
Entire functions may be coded in assembly language as separate .as source files, assembled by the assembler (ASPIC) and combined into the binary image using the linker. This technique allows arguments and return values to be passed between C and assembler code. To access an external function, first include an appropriate C extern declaration in the calling C code. For example, suppose you need an assembly language function to provide access to the rotate left through carry instruction on the PIC:
extern char rotate_left(char);
declares an external function called rotate_left() which has a return value type of char and takes a single argument of type char. The actual code for rotate_left() will be supplied by an external .as file which will be separately assembled with ASPIC. The full PIC assembler code for rotate_left() would be something like:
processor 16C84
psect text0,class=CODE,local,delta=2
global _rotate_left
signat _rotate_left,4201
_rotate_left
; Fred is passed in the W register - assign it
; to ?a_rotate_left.
movwf ?a_rotate_left
; Rotate left. The result is placed in the W register.
rlf ?a_rotate_left,w
; The return is already in the W register as required.
return
FNSIZE _rotate_left,1,0
global ?a_rotate_left
end
The name of the assembly language function is the name declared in C, with an underscore prepended. The global pseudo-op is the assembler equivalent to the C extern keyword and the signat pseudo-op is used to enforce link time calling convention checking. Signature checking and the signat pseudo-op are discussed in more detail later in this chapter.
Note that in order for assembly language functions to work properly they must look in the right place for any arguments passed and must correctly set up any return values. Local variable allocation (via the fnsize directive), argument and return value passing mechanisms are discussed in detail later in the manual and should be understood before attempting to write assembly language routines.
PIC instructions may also be directly embedded in C code using the directives #asm, #endasm and asm(). The #asm and #endasm directives are used to start and end a block of assembler instructions which are to be embedded inside C code. The asm() directive is used to embed a single assembler instruction in the code generated by the C compiler. To continue our example from above, you could directly code a rotate left on a memory byte using either technique as the following example shows:
#include <stdio.h>
unsigned char var;
void main(void)
{
var = 1;
#asm
rlf _var,f
#endasm
asm("rlf _var,f");
}
When using inline assembler code, great care must be taken to avoid interacting with compiler generated code. If in doubt, compile your program with the PICC -S option and examine the assembler code generated by the compiler.
IMPORTANT NOTE: the #asm and #endasm construct is not syntactically part of the C program, and thus it does not obey normal C flow-of-control rules. For example, you cannot use a #asm block with an if statement and expect it to work correctly. If you use in-line assembler around any C constructs such as if, while, do etc. they you should use only the asm("") form, which is a C statement and will correctly interact with all C flow-of-control structures.
The compiler automatically produces signatures for all functions. A signature is a 16-bit value computed from a combination of the function's return data type, the number of its parameters and other information affecting the calling sequence for the function. This signature is output in the object code of any function referencing or defining the function.
At link time the linker will report any mismatch of signatures. Thus if a function is declared in one module in a different way (for example, as returning a char instead of short) then the linker will report an error.
It is sometimes necessary to write assembly language routines which are called from C using an extern declaration. Such assembly language functions need to include a signature which is compatible with the C prototype used to call them. The simplest method of determining the correct signature for a function is to write a dummy C function with the same prototype and compile it to assembly language using the PICC -S option. For example, suppose you have an assembly language routine called _widget which takes two int arguments and returns a char value. The prototype used to call this function from C would be:
extern char widget(int, int);
Where a call to _widget is made in the C code, the signature for a function with two int arguments and a char return value would be generated. In order to match the correct signature the source code for widget needs to contain an ASPIC SIGNAT pseudo-op which defines the same signature value. To determine the correct value, you would write the following code:
char widget(int arg1, int arg2)
{
}
and compile it to assembler code using
picc -S x.c
The resultant assembler code includes the following line:
signat _widget,8297
The SIGNAT pseudo-op tells the assembler to include a record in the .OBJ file which associates the value 8297 with symbol _widget. The value 8297 is the correct signature for a function with two int arguments and a char return value. If this line is copied into the .AS file where _widget is defined, it will associate the correct signature with the function and the linker will be able to check for correct argument passing. For example, if another .C file contains the declaration:
extern char widget(long);
then a different signature will be generated and the linker will report a signature mis-match which will alert you to the possible existence of incompatible calling conventions.
The compiler will automatically invoke the linker unless requested to stop after producing assembler code (PICC -S option) or object code (PICC -C option).
PICC and HPDPIC by default generate Intel HEX files and Bytecraft COD. If you use the -BIN option or specify an output file with a .BIN filetype using the PICC -O option the compiler will generate a binary image instead. After linking, the compiler will automatically generate a memory usage map which shows the address and size of all memory areas which are used by the compiled code. For example:
Memory Usage Map:
Program ROM: $0000 - $0000 $0001 (1) bytes
Internal RAM: $000C - $000D $0002 (2) bytes
Program ROM: $0014 - $0016 $0003 (3) bytes
More detailed memory usage information, listed in ascending order of individual psects, may be obtained by using the PICC -PSECTMAP option.
The compiler makes few assumptions about memory. With the exception of variables declared using the @address construct, absolute addresses are not allocated until link time.
The memory used is based upon information in the chipinfo file (which defaults to lib\picinfo.ini).
In the midrange processors, the W register is used for register-based function argument passing and for function return values. This register should be preserved by any assembly language routines which are called.
The method used to pass function arguments depends on the size of the argument or arguments.
If there is only one argument, and it is one byte in size, it is passed in the W register.
If there is only one argument, and it is greater than one byte in size, it is passed in the argument area of the called function. If there are subsequent arguments, these arguments are also passed in the argument area of the called function.
If there is more than one argument, and the first argument is one byte in size, it is passed in the auto variable area of the called function, with subsequent arguments being passed in the argument area of the called function.
In the case of a variable argument list, which is defined by the ellipsis symbol (...), the calling function builds up the variable argument list and passes a pointer to the variable part of the argument list in btemp. btemp is the label at the start of the temp pesect (ie. the psect used for temprorary data).
Take, for example, the following ANSI-style function:
void test(int a, int b, int c)
{
}
The function test() will receive all arguments in its function argument block. A call:
test( 0x65af, 0x7288, 0x080c);
would generate code similar to:
movlw 0AFh
movwf (((?_test))&7fh)
movlw 065h
movwf (((?_test+1))&7fh)
movlw 088h
movwf ((0+((?_test)+02h))&7fh)
movlw 072h
movwf ((1+((?_test)+02h))&7fh)
movlw 0Ch
movwf ((0+((?_test)+04h))&7fh)
movlw 08h
movwf ((1+((?_test)+04h))&7fh)
lcall (_test)
It is often helpful to write a dummy C function with the same argument types as your assembler function, and compile to assembler code with the PICC -S option, allowing you to examine the entry and exit code generated. In the same manner, it is useful to examine the code generated by a call to a function with the same argument list as your assembler function.
Function return values are passed to the calling function as follows:
For the baseline processors, 8-bit values (char, unsigned char and pointer) are returned in memory via the temp psect.
For the midrange processors, 8-bit values are returned in the W register. For example, the function:
char return_8(void)
{
return 0;
}
will exit with the following code:
movlw 0
return
16-bit and 32-bit values (int, unsigned int, short, unsigned short and some pointer values; long, unsigned long, float and double) are returned in memory, with the least significant word in the lowest memory location. For example, the function:
int return_16(void)
{
return 0x1234;
}
will exit with the following code:
movlw low 01234h
movwf btemp
movlw high 01234h
movwf btemp+1
return
Composite return values (struct and union) of size 4 bytes or smaller are returned in memory as with 16-bit and 32-bit return values. For composite return values of greater than 4 bytes in size, the structure or union is copied into the struct psect. For example:
struct fred
{
int ace[4];
}
struct fred return_struct(void)
{
struct fred wow;
return wow;
}
will exit with the following code:
movlw ?a_return_struct+0
movwf 4
movlw structret
movwf btemp
movlw 8
global structcopy
lcall structcopy
return
C supports two classes of local variables in functions: auto variables which are normally allocated in the function's auto variable block and static variables which are always given a fixed memory location.
Auto (ie. automatic) variables are the default type of local variable. Unless explicitly declared to be static a local variable will be made auto. Auto variables are allocated in the auto variable block and referenced by indexing off the start of that function's block. The variables will not necessarily be allocated in the order declared - in contrast to parameters which are always in lexical order. Note that most type qualifiers cannot be used with auto variables, since there is no control over the storage location. Exceptions are const and volatile.
The auto variable blocks for a number of functions are overlapped by the linker if those functions are never called at the same time.
Uninitialised static variables are allocated in the rbss_n psect and occupy fixed memory locations which will not be overlapped by storage for other functions. Static variables are local in scope to the function which they are declared in, but may be accessed by other functions via pointers. Static variables are guaranteed to retain their value between calls to a function, unless explicitly modified via a pointer. Static variables are not subject to any architectural limitations on the PIC.
The compiler splits code and data objects into a number of standard program sections (referred to as psects). The HI-TECH assembler allows an arbitrary number of named psects to be included in assembler code. The linker will group all data for a particular psect into a single segment.
If you are using PICC or HPDPIC to invoke the linker, you don't need to worry about the information documented here, except as background knowledge. If you want to run the linker manually (this is not recommended), or write your own assembly language subroutines, you should read this section carefully.
The compiler generated psects which are placed in ROM are:
The compiler generated psects which are placed in RAM are:
rbit_n These psects (where n is the bank number) are used to store all bit variables except those declared at absolute locations. The declaration:
static bit flag;
The starting address of a C program is usually the lowest code address (0h). The global symbol powerup is at this address. The code located at the start address may perform some initialisation, notably clearing of the bss (uninitialised data) psect and copying initialised data (which is not declared const) into RAM.
The startup code calls the function _main. Note the underscore `_' prepended to the function name - all symbols derived from external names in a C program ha > Startoff Module
The source code used to generate all of the runtime startoff modules is called picrt66x.as which is in the sources directory.
Some hardware configurations require special initialisation, often within the first few cycles of execution after reset. Rather than having to modify the run-time startoff module to achieve this there is a hook to the reset vector provided via the powerup routine. This is a user-supplied assembler module that will be executed immediately on reset. Often this can be embedded in a C module as embedded assembler code.
The powerup routine should be written assuming that little or no RAM is working and should only use system resources after it has tested and enabled them. The following example code shows the default powerup routine which are in the standard library:
global powerup,start
psect powerup,class=CODE,delta=2
powerup
ljmp start
The link address of a psect can be obtained from the value of a global symbol with name __Lname where name is the name of the psect. For example, __Lbss is the low bound of the bss psect. The highest address of a psect (i.e. the link address plus the size) is symbol __Hname. If the psect has different load and link addresses, as may be the case if the data psect is linked for RAM operation, the load address is __Bname.
There are certain compile-time directives that can be used to modify the behaviour of the compiler. These are implemented through the use of the ANSI standard pragma facility. The format of a pragma is:
#pragma keyword options
where keyword is one of a set of keywords, some of which are followed by certain options. A list of the keywords is given in Table 4 - 8 on page 91. Each keyword is discussed below.
If your code includes strings with two-byte characters in the JIS encoding for Japanese and other national characters, the #pragma jis directive will enable proper handling of these characters, specifically not interpreting a back-slash (\) character when it appears as the second half of a two byte character. The nojis directive disables this special handling. JIS character handling is disabled by default.
Certain library functions accept a format string followed by a variable number of arguments in the manner of printf(). Although the format string is interpreted at run-time, it can be compile-time checked for consistency with the remaining arguments. This directive enables this checking for the named function, e.g. the system header file <stdio.h> includes the directive #pragma printf_check(printf) to enable this checking for printf(). You may also use this for any user-defined function that accepts printf-style format strings. Note that the warning level must be set to -1 or below for this option to have effect.
Normally the object code generated by the compiler is broken into the standard psects as already documented. This is fine for most applications, but sometimes it is necessary to redirect variables or code into different psects when a special memory configuration is desired. Code and data for any of the standard C psects may be redirected using a #pragma psect directive. For example, if all the uninitialised global data in a particular C source file is to be placed into a psect called otherram,the following directive should be used:
#pragma psect rbss_0=otherram
This directive tells the compiler that anything which would normally be placed in the rbss_0 psect should now be placed in the otherram psect.
Any given psect should only be redirected once in a particular source file, and all psect redirections for a particular source file should be placed at the top of the file, below any #include statements and above any other declarations. For example, to declare a group of uninitialised variables which are all placed in a psect called otherram, the following technique should be used:
--File OTHERRAM.C
#pragma psect rbss_0=otherram
char buffer[5];
int var1, var2, var3;
Any files which need to access the variables defined in otherram.c should #include the following header file:
--File OTHERRAM.H
extern char buffer[5];
extern int var1, var2, var3;
The #pragma psect directive allows code and data to be split into arbitrary memory areas. Definitions of code or data for non-standard psects should be kept in separate source files as documented above. When linking code which uses non-standard psect names, you will need to use the PICC -L option to specify an extra linker option, or use the linker manually, or use an HPDPIC project to compile and link your code. If you want a nearly standard configuration with the addition of only an extra psect like otherram, you can use the PICC -L option to add an extra -P specification to the linker command. For example:
PICC -L-Potherram=50h/400h -16C84 test.obj otherram.obj
will link test.obj and otherram.obj with a standard configuration, and the extra otherram psect at 50h in RAM, but not overlapping any valid ROM load address. If you are using the HPDPIC integrated environment you can set up a project file by selecting Start New Project, add the names of your four source files using Source Files ... and then modify the linker options to include any new psects by selecting Linker Options ....
A number of the standard I/O functions are provided in the C library with the compiler, specifically those functions intended to read and write formatted text on standard output and input. A list of the available functions is in
Function name
Purpose
printf(char * s, ...)
Formatted printing to stdout
sprintf(char * buf, char * s, ...)
Writes formatted text to buf
Before any characters can be written or read using these functions, the putch() and getch() functions must be written. Other routines which may be required include getche() and kbhit().
You will find samples of serial code which implements the putch() and getch() functions in the samples\serial directory.