[Top] [Prev] [Next] [Bottom]

Features and Runtime Environment


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:

4.1 Divergence from the ANSI C Standard

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.

4.2 Processor Support

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.

4.3 Standard Libraries

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.

Table 4 - 1 Standard Libraries


Library

Purpose

pic200-c.lib


Baseline processor, 1 ROM bank, 1 RAM bank, 24-bit doubles

pic200dc.lib


Baseline processor, 1 ROM bank, 1 RAM bank, 32-bit doubles

pic210-c.lib


Baseline processor, 2 ROM banks, 1 RAM bank, 24-bit doubles

pic210dc.lib


Baseline processor, 2 ROM banks, 1 RAM bank, 32-bit doubles

pic211-c.lib


Baseline processor, 2 ROM banks, 2 RAM banks, 24-bit doubles

pic211dc.lib


Baseline processor, 2 ROM banks, 2 RAM banks, 32-bit doubles

pic222-c.lib


Baseline processor, 4 ROM banks, 4 RAM banks, 24-bit doubles

pic222dc.lib


Baseline processor, 4 ROM banks, 4 RAM banks, 32-bit doubles

pic401-c.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 24-bit doubles

pic401-l.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 24-bit doubles, printf supports longs

pic401-f.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 24-bit doubles, printf supports longs & floats

pic401dc.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 32-bit doubles

pic401dl.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 32-bit doubles, printf supports longs

pic401df.lib


Midrange processor, 1 ROM bank, 2 RAM banks, 32-bit doubles, printf supports longs & floats

pic411-c.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 24-bit doubles

pic411-l.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 24-bit doubles, printf supports longs

pic411-f.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 24-bit doubles, printf supports longs & floats

pic411dc.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 32-bit doubles

pic411dl.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 32-bit doubles, printf supports longs

pic411df.lib


Midrange processor, 2 ROM banks, 2 RAM banks, 32-bit doubles, printf supports longs & floats

pic412-c.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 24-bit doubles

pic412-l.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs

pic412-f.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs & floats

pic412dc.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 32-bit doubles

pic412dl.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs

pic412df.lib


Midrange processor, 2 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs & floats

pic422-c.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 24-bit doubles

pic422-l.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs

pic422-f.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs & floats

pic422dc.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 32-bit doubles

pic422dl.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs

pic422df.lib


Midrange processor, 4 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs & floats

pic432-c.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 24-bit doubles

pic432-l.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs

pic432-f.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 24-bit doubles, printf supports longs & floats

pic432dc.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 32-bit doubles

pic432dl.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs

pic432df.lib


Midrange processor, 8 ROM banks, 4 RAM banks, 32-bit doubles, printf supports longs & floats

4.3.1 Limitations of Printf

The printf() function is provided but some features have been removed. For more details on this function, see the documentation on page 214.

4.4 Output File Formats

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.

Table 4 - 2 Output File Formats


Format Name

Description

PICC Option

File Type

Motorola HEX

S1/S9 type hex file

-MOTOROLA

.HEX

Intel HEX

Intel style hex records (default)

-INTEL

.HEX

Binary

Simple binary image

-BIN

.BIN

UBROF

"Universal Binary Image Relocatable Format"

-UBROF

.UBR

Tektronix HEX

Tektronix style hex records

-TEK

.HEX

American Automation HEX

Hex format with symbols for American Automation emulators

-AAHEX

.HEX

Bytecraft .COD

Bytecraft code format (default)

n/a (default)

.COD

Library

HI-TECH library file

n/a

.LIB

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.

4.5 Symbol Files

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.

4.6 Predefined Macros

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.

Table 4 - 3 Predefined CPP Symbols


Symbol

Usage

HI_TECH_C

Always set - can be used to indicate that the compiler in use is HI-TECH C.

_MPC_

Always set - can be used to indicate the code is compiled for the Microchip PIC family

_PIC12

Set for 12-bit PIC chips.

_PIC14

Set for 14-bit PIC chips.

4.7 Configuration Fuses

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);

4.8 Supported Data Types

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.

Table 4 - 4 Data Types


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

A char is unsigned by default, and signed if the PICC -SIGNED_CHAR option is used.

2

A double defaults to 24-bit, but becomes 32-bit with the PICC -D32 option.

4.8.1 8-Bit Integer Data Types

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.

4.8.2 16-Bit Integer Data Types

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.

4.8.3 32-Bit Integer Data Types

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.

4.8.4 Floating Point

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.

Table 4 - 5 Floating Point Formats


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:

Table 4 - 6 IEEE 754 32-bit and 24-bit Examples


Format

Number

biased exponent

1.mantissa

decimal

IEEE 754 32-bit

7DA6B69Bh

11111011b
(251)

1.01001101011011010011011b
(1.302447676659)

2.77000e+37

Modified IEEE 754 24-bit

42123Ah

10000100b (132)

1.001001000111010b
(1.142395019531)

36.557

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).

4.9 Absolute Variables

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.

4.10 Structures and Unions

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.

4.10.1 Bit Fields in Structures

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;

4.11 Const and Volatile Type Qualifiers

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;

4.12 Special Type Qualifiers

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;

4.12.1 Persistent Type Qualifier

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.

4.12.2 Bank1, Bank2 and Bank3 Type Qualifiers

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.

4.13 Pointers

The format and use of pointers depend upon the range of processor.

4.13.1 Baseline Pointers

All pointers in the Baseline range are 8-bits. This describes the pointer types used.

4.13.2 Midrange Pointers

All pointers for the Midrange are the same as for the Baseline processors with the following exceptions:

4.13.3 Combining Type Qualifiers and Pointers

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.

The 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.

4.13.4 Const Pointers

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; 

the statement:

ch = *cptr; 

is legal, but the statement:

*cptr = ch; 

is not. Const pointers always access program ROM because const declared objects are stored in ROM.

4.14 Interrupt Handling in C

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.

4.15 Mixing C and Assembler Code

Assembly language code can be mixed with C code using three different techniques.

4.15.1 External Assembly Language Functions

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.

4.15.2 #asm, #endasm and asm()

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.

4.16 Signature Checking

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.

4.17 Linking Programs

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.

4.18 Memory Usage

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).

4.19 Register Usage

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.

4.20 Function Argument Passing

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.

4.21 Function Return Values

Function return values are passed to the calling function as follows:

4.21.1 8-Bit Return Values

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

4.21.2 16-Bit and 32-bit Return Values

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

4.21.3 Structure Return Values

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

4.22 Local Variables

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.

4.22.1 Auto Variables

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.

4.22.2 Static Variables

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.

4.23 Compiler Generated Psects

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:

powerup Which contains executable code for the standard or user-supplied power-up routine.
idata_n These psects (where n is the bank number) contain the ROM image of any initialised variables. These psects are copied into the rdata_n psects at startup.
textn These psects (where n is a number) contain all executable code for the Midrange and High-end processors. They also contains any exeuctable code after the first goto instruction which can never be skipped for the Baseline processors.
ctextn These psects (where n is a number) are used only in the Baseline processors. They contain executable code from the entry point of each function until the first goto instruction which can never be skipped. Further executable code is placed in the textn psects.
text Is a global psect used for executable code for some library functions.
strings The strings psect is used for all initialised variables declared code, const or code static. This also includes all unnamed string constants, such as string constants passed as arguments to routines like printf() and puts(). This psect is linked into ROM, since it does not need to be modifiable.
stringtable
The stringtable psect contains the string table which is used to access objects in the strings psect. This psect will only be generated if there is a strings psect.
jmp_tab Only for the Baseline processors, this is another strings psect used to store jump addresses and function return values.
config Used to store the config word.
intentry Contains the enrty code for the interrupt service routine. This code saves the necessary registers and parts of the temp psect.
intcode Is the psect which contains the exeutable code for the interrupt service routine.
init Used by initialisation code which, for example, clears RAM.
end_init Used by initialisation code which, for example, clears RAM.
float_text Used by some library routines, and in particular by arithmetic routines.
clrtext Used by some startup routines for clearing the rbss_n psects.

The compiler generated psects which are placed in RAM are:

rbss_n These psects (where n is the bank number) contain any uninitialised variables.
rdata_n These psects (where n is the bank number) contain any initialised variables.
nvram This psect is used to store persistent variables. It is not cleared or otherwise modified at startup.

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; 
will allocate flag as a single bit in the rbit psect.
struct Contains any structure which is returned from a function.
intsave Holds the W register saved by the interrupt service routine. If necessary, the W register will also be saved in the intsave_n psects.
intsave_n (Where n is the bank number, but not bank 0) may also hold the W register saved by the interrupt service routine. (See the description of the intsave psect.)
temp Is used to store scratch variables used by the compiler. These include function return values larger than a char and values passed to and returned from library routines.

4.24 Runtime Startoff Modules

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
Processor supported

picrt200.obj


Baseline processor, 1 ROM bank, 1 RAM bank

picrt210.obj


Baseline processor, 2 ROM banks, 1 RAM bank

picrt211.obj


Baseline processor, 2 ROM banks, 2 RAM banks

picrt222.obj


Baseline processor, 4 ROM banks, 4 RAM banks

picrt401.obj


Midrange processor, 1 ROM bank, 2 RAM banks

picrt411.obj


Midrange processor, 2 ROM banks, 2 RAM banks

picrt412.obj


Midrange processor, 2 ROM banks, 4 RAM banks

picrt422.obj


Midrange processor, 4 ROM banks, 4 RAM banks

picrt432.obj


Midrange processor, 8 ROM banks, 4 RAM banks
Table 4 - 7 on page 90.

The source code used to generate all of the runtime startoff modules is called picrt66x.as which is in the sources directory.

4.24.1 The powerup Routine

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

4.25 Linker-Defined Symbols

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.

4.26 Pragma Directives

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.

Table 4 - 8 Pragma Directives


Directive

Meaning

Example

jis

Enable JIS character handling in strings

#pragma jis

nojis

Disable JIS character handling (default)

#pragma nojis

printf_check

Enable printf-style format string checking

#pragma printf_check(printf)

psect

Rename compiler-defined psect

#pragma psect text=mytext

4.26.1 The #pragma jis and nojis Directives

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.

4.26.2 The #pragma printf_check Directive

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.

4.26.3 The #pragma psect Directive

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 ....

4.27 Standard I/O Functions and Serial I/O

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

Table 4 - 9 Supported STDIO Functions


Function name

Purpose

printf(char * s, ...)

Formatted printing to stdout

sprintf(char * buf, char * s, ...)

Writes formatted text to buf
Table 4 - 9 on page 93. More details of these functions are in the Library Functions chapter.

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.



[Top] [Prev] [Next] [Bottom]

hitech@htsoft.com
Copyright © 1997, HI-TECH Software. All rights reserved.