Chapter 3. C Language Features
3.2 Processor-related Features
HI-TECH C has several features which relate directly to the PIC10/12/16 architectures and instruction sets. These detailed in the following sections.
3.2.1
Stack
The hardware stack on PIC devices is limited in depth and cannot be manipulated directly. It is only used for function return address and cannot be used for program data. The compiler implements a compiled stack for local data objects, see
Section 3.4.2 “Compiled Stack Operation” for information on how this is achieved. You must ensure that the maximum stack depth is not exceeded otherwise code may fail.
A call graph is provided by the code generator in the assembler list file. This will indicate the stack levels at each function call and can be used as a guide to stack depth. The code generator may also produce warnings if the maximum stack depth is exceeded. Both of these are guides to stack usage. Optimizations and the use of interrupts can decrease or increase, respectively, the stack depth used by a program over that determined by the compiler.
3.2.2
Configuration Fuses
The PIC device processor’s configuration fuses (or configuration bits) may be set using the __CONFIG() macro as follows:
__CONFIG(x);
Note there are two leading underscore characters. The macro is defined in <htc.h>
header file, so be sure to include this into the files that uses this macro.
The x argument is the value that is to be programmed in the configuration word. The value can either be a literal or be built up from specially named quantities are defined in the header file appropriate for the processor you are using. These macro names are similar to the names as used in the PIC10/12/16 data sheets to represent the configu- ration conditions and must be bitwise ANDed together to form the configuration value. Refer to your processor’s header file for details. For example:
#include <htc.h>
__CONFIG(WDTDIS & HS & UNPROTECT);
For devices that have more than one configuration word location, each subsequent invocation of __CONFIG() will modify the next configuration word in sequence. Typically this might look like:
#include <htc.h>
__CONFIG(WDTDIS & XT & UNPROTECT); // Program config. word 1 __CONFIG(FCMEN); // Program config. word 2
3.2.3
ID Locations
Some PIC10/12/16 devices have locations outside the addressable memory area that can be used for storing program information, such as an ID number. The __IDLOC()
macro may be used to place data into these locations. The macro is used in a manner similar to:
#include <htc.h> __IDLOC(x);
where x is a list of nibbles which are to be positioned into the ID locations. Only the lower four bits of each ID location is programmed, so the following:
__IDLOC(15F0);
will attempt to fill ID locations with the values: 1, 5, F and 0.
The base address of the ID locations is specified by the idloc psect which will be auto- matically assigned as appropriate address based on the type of device selected. Some devices will permit programming up to seven bits within each ID location. To pro- gram the full seven bits, the regular __IDLOC() macro is not suitable. For this situation the __IDLOC7(a,b,c,d) macro is available. The parameters a to d are the values to be programmed. The values can be entered in either decimal or hexadecimal format, such as:
__IDLOC7(0x7f,1,70,0x5a);
It is not appropriate to use the __IDLOC7() macro on a device that does not permit seven bit programming of ID locations.
C Language Features
3.2.4
Bit Instructions
Wherever possible, HI-TECH C will attempt to use the PIC10/12/16 bit instructions. For example, when using a bitwise operator and a mask to alter a bit within an integral type, the compiler will check the mask value to determine if a bit instruction can achieve the same functionality.
unsigned int foo; foo |= 0x40;
will produce the instruction: BSF _foo,6
To set or clear individual bits within integral type, the following macros could be used: #define bitset(var, bitno) ((var) |= 1UL << (bitno))
#define bitclr(var, bitno) ((var) &= ~(1UL << (bitno)))
To perform the same operation as above, the bitset macro could be employed as follows:
bitset(foo,6);
3.2.5
EEPROM Access
For most devices that come with on-chip EEPROM, the compiler offers several meth- ods of accessing this memory. The EEPROM access methods are described in the fol- lowing sections.
3.2.5.1 THE __EEPROM_DATA() MACRO
For those PIC10/12/16 devices that support external programming of their EEPROM data area, the __EEPROM_DATA() macro can be used to place the initial EEPROM data values into the HEX file ready for programming. The macro is used as follows. #include <htc.h>
__EEPROM_DATA(0, 1, 2, 3, 4, 5, 6, 7);
The macro accepts eight parameters, being eight data values. Each value should be a byte in size. Unused values should be specified as a parameter of zero.
The macro may be called multiple times to define the required amount of EEPROM data. It is recommended that the macro be placed outside any function definitions. The macro defines, and places the data within, a psect called eeprom_data. This psect is automatically positioned by the linker.
This macro is not used to write to EEPROM locations during runtime, it is to be used for pre-loading EEPROM contents at program time only.
3.2.5.2 EEPROM ACCESS FUNCTIONS
The library functions eeprom_read() and eeprom_write(), can be called to read from, and write to, the EEPROM during program execution. For example, to write a byte-size value to an address in EEPROM and retrieve it using these functions would be:
#include <htc.h> void eetest(void) {
unsigned char value = 1; unsigned char address = 0; // write value to EEPROM address eeprom_write(address, value); // read from EEPROM at address value = eeprom_read(address); }
These functions test and wait for any concurrent writes to EEPROM to conclude before performing the required operation. The eeprom_write() function will initiate the pro- cess of writing to EEPROM and this process will not have completed by the time that
eeprom_write() returns. The new data written to EEPROM will become valid approximately four milliseconds later.
In the above example, the new value will not yet be ready at the time when
eeprom_read() is called, however because this function waits for any concurrent writes to complete before initiating the read, the correct value will be read.
It may also be convenient to use the preprocessor symbol, _EEPROMSIZE in conjunc- tion with some of these access methods. This symbol defines the number of EEPROM bytes available for the selected chip.
3.2.5.3 EEPROM ACCESS MACROS
Although these macros perform much the same service as their library function coun- terparts, these should only be employed in specific circumstances. It is appropriate to select EEPROM_READ or EEPROM_WRITE in favor of the library equivalents if any of the following conditions are true:
• You cannot afford the extra level of stack depth required to make a function call • You cannot afford the added code overhead to pass parameters and perform a
call/return
• You cannot afford the added processor cycles to execute the function call over- head
Be aware that if a program contains multiple instances of either macro, any code space saving will be negated as the full content of the macro is now duplicated in code space. In the case of EEPROM_READ(), there is another very important detail to note. Unlike
eeprom_read(), this macro does not wait for any concurrent EEPROM writes to com- plete before proceeding to select and read EEPROM. Had the previous example used the EEPROM_READ() macro in place of eeprom_read() the operation would have failed. If it cannot be guaranteed that all writes to EEPROM have completed at the time of calling EEPROM_READ(), the appropriate flag should be polled prior to executing
C Language Features
For example: #include <htc.h> void eetest(void){
unsigned char value = 1; unsigned char address = 0;
// Initiate writing value to address EEPROM_WRITE(address,value);
// wait for end-of-write before EEPROM_READ while(WR)
continue; // read from EEPROM at address value = EEPROM_READ(address);
}
3.2.5.4 EEPROM QUALIFIER
Variables may be directly qualified as eeprom. This places them in EEPROM memory of the device. For example:
eeprom long prod_ID;
This qualifier allows individual named variables to be defined and accessed in the C code. Although this is a convenient way to define EEPROM-based objects and access them in C code, access will be much slower compared to reading and writing
RAM-based variables.
3.2.6
Flash Runtime Access
HI-TECH C Compiler for PIC10/12/16 MCUs provides a number of methods to access the contents of program memory at runtime.
Particular care must be taken when modifying the contents of program memory. If the location being modified is that of the code that is currently being executed, or a region of your executable code has been used for use as non-volatile storage, the code may fail.
For those devices requiring a Flash erasure operation be performed prior to writing to Flash, this step will be performed internally by the compiler within the access routine and does not need to be implemented as a separate stage. Data within the same Flash erasure block that is unrelated to the write operation will be backed up before the block is erased and restored after the erasure.
3.2.6.1 FLASH ACCESS MACROS
Similar to the EEPROM read/write routines described above, there are equivalent Flash memory routines. For example, to write a byte-sized value to an address in Flash memory use:
FLASH_WRITE(address,value);
To read a byte of data from an address in Flash memory, and store it in a variable, use:
variable=FLASH_READ(address); 3.2.6.2 FLASH ACCESS FUNCTIONS
For the small subset of devices which allow independent control over a Flash block era- sure process, the flash_erase() function provides this service. It is not required for other devices.
3.2.7
Baseline PIC MCU special instructions
The baseline (12-bit instruction word) devices have some registers which are not in the normal SFR area and cannot be accessed using an ordinary file instruction. The HI-TECH C compiler is instructed to automatically use the special instructions intended for such cases when pre-defined symbols are accessed.
The definition of the special symbols make use of the control qualifier. This qualifier informs the compiler that the registers are outside of the normal address space and that a different access method is required.
3.2.7.1 THE OPTION INSTRUCTION
Some baseline PIC devices use an OPTION instruction to load the OPTION register. The <htc.h> header file will ensure a special definition for a C object called OPTION
and macros for the bit symbols which are stored in this register. PICC will automatically use the OPTION instruction when an appropriate processor is selected and the OPTION
register is accessed.
For example, to set the prescaler assignment bit so that prescaler is assigned to the watchdog timer, the following code can be used.
OPTION = PSA;
This will load the appropriate value into the W register and then call the OPTION
instruction.
3.2.7.2 THE TRIS INSTRUCTIONS
Some PIC devices use a TRIS instruction to load the TRIS register. The <htc.h>
header file will ensure a special definition for a C object called TRIS. PICC will auto- matically use the TRIS instruction when an appropriate processor is selected and the
TRIS register is accessed.
For example, to make all the bits on the output port high impedance, the following code can be used.
TRIS = 0xFF;
This will load the appropriate value into the W register and then call the TRIS instruction. Those PIC devices which have more than one output port may have definitions for objects: TRISA, TRISB and TRISC, depending on the exact number of ports available. This objects are used in the same manner as described above.
3.2.7.3 OSCILLATOR CALIBRATION CONSTANTS
Some PIC devices come with an oscillator calibration constant which is pre-pro- grammed into the devices program memory. This constant can be read from program memory and written to the OSCCAL register to calibrate the internal RC oscillator. On some baseline PIC devices, the calibration constant is stored as a MOVLW instruc- tion at the top of program memory, e.g. the PIC12C50X and PIC16C505 parts. On Reset, the program counter is made to point to this instruction and it is executed first before the program counter wraps around to 0x0000, which is the effective reset vector
C Language Features
For other chips, such as PIC12C67X chips, the oscillator constant is also stored at the top of program memory, but as a RETLW instruction. The compiler’s startup code will automatically generate code to retrieve this value and perform the configuration. Loading of the calibration value can be turned off via the --RUNTIME option (see
Section 2.7.50 “--RUNTIME: Specify Runtime Environment”). At runtime this calibration value may be read using the macro
_READ_OSCCAL_DATA(). To be able to use this macro, make sure that <htc.h> is included into the relevant modules of your program. This macro returns the calibration constant which can then be stored into the OSCCAL register, as follows:
OSCCAL = _READ_OSCCAL_DATA();
Note: The location which stores the calibration constant is never code protected and will be lost if you reprogram the device. Thus, if you are using a win- dowed or Flash device, the calibration constant must be saved from the last ROM location before it is erased. The constant must then be reprogrammed at the same location along with the new program and data.
If you are using an in-circuit emulator (ICE), the location used by the cali- bration RETLW instruction may not be programmed. Calling the
_READ_OSCCAL_DATA() macro will not work and will almost certainly not return correctly. If you wish to test code that includes this macro on an ICE, you will have to program a RETLW instruction at the appropriate location in program memory. Remember to remove this instruction when programming the actual part so you do not destroy the calibration value.