Thursday, April 19, 2012

Features of C

Prior to Version 4, the compile step and linking step were combined, and the user didn't have the ability to compile a unit into a relocatable object to be linked together with other objects. Dividing up your code into several units will not only increase the maintainability of your projects, but it can speed up compilation as only units that have been changed since last build need to be compiled again.
Version 4 has several methods of allowing you to create relocatable objects and link them together:
  1. Inside PCW, the CCS IDE, you can use the file navigator to add units to your project. When you press Build it will compile all your units and link them together.
  2. #import() and #export() preprocessor commands allow you to create units and link units at the source code level.

/*
The following preprocessor command will make the compiler compile this code into a relocatable object file (.o).

RELOCATABLE will create a CCS relocatable object file, and is the default format when #EXPORT is used.

FILE specifies the name of the output file, and is optional.

The ONLY option tells the compiler to only make the symbol GetString (which could be a function or variable) visible to modules that import/link this module.

Another option, EXCEPT, has the opposite effect - all symbols except the listed symbols are visible to modules that import/link this module.
*/
#export(RELOCATABLE, FILE=getstring.o, ONLY=GetString)/*
The following preprocessor command will make the compiler link other objects into this unit (in this example it will link unit.o).

FILE, ONLY, and EXCEPT have the same operation as detailed in the previous example.

COFF is the reciprocal of RELOCATABLE, and tells the compiler to link/import an .o created by MPASM. C18 and C30. COFF can only be used with #import, it cannot be used with #export.
*/
#import(RELOCATABLE, FILE=unit.o)As shown in the previous examples #import and #export have options for defining scope, but the standard C qualifiers static and extern can also be used for defining scope.
  1. Command-line options have been added to create units (instead of a compile/link in one step) and link all units into one final HEX. The following example shows compiling two .C files into separate .O files and then linking them into one .HEX file:

C:\project\ccsc +FH +EXPORT main.c
C:\project\ccsc +FH +EXPORT uart.c
C:\project\ccsc +FH LINK="main.hex=main.o,uart.o"
  1. Inside MPLAB® IDE, if you add more than one .C in the project manager all the .C files will be compiled seperately into relocatable objects, and in the final step all relocatable objects are linked into one HEX.
* Command-Line compiler customers can only link, they cannot create relocatable objects.
Relocatable Objects/Linker functions work only with the CCS C Windows IDE.
 

Generation of multiple hex files for chips with external memory

Two additional preprocessors are included in Version 4: #export and #import. You have may have already noticed these being used in the above examples for creating relocatable objects, but it has other options for specifying how to export and import HEX files. You will find these commands useful if creating bootloaders or using a device with external memory (CPU or EMCU mode):
/*
The compiler will create two HEX files. One contains all the ODD addressed program memory, the other contains all the EVEN addressed program memory. You will find this useful if you are using the 16-bit byte write mode on the 18F family external memory interface.
*/
#export(HEX, file=odd.hex, ODD)
#export(HEX, file=even.hex, EVEN)
/*
When the compiler creates the HEX file for this project, offset all addresses up 0x800 bytes.
*/
#export(HEX, file=application.hex, offset=0x800)
/*
The previous example was an application where the final HEX file had it's addresses offset by 0x800. If we wanted to import the loader HEX into the application this could have been done instead:
*/
#import(HEX, file=loader.hex, range=0:0x7FF)  

Variable length constant strings

Prior to Version 4, if you had an array of constant strings it was usually inefficient. Examine this example of an ineffecient array of constant strings:
const char strings[3][15] =
{
"HELLO",
"WORLD",
"EXTRALONGERWORD"
};
In the above example we had to make the maximum length of each string be 15 characters because of the length of "EXTRALONGERWORD". But since "HELLO" and "WORLD" are only 6 characters (don't forget null termination), 9 bytes are wasted for each.
To alleviate this problem, use this method for variable length constant strings:
const char strings[][*] =
{
"HELLO",
"WORLD",
"EXTRALONGERWORD"
};
Note: this is done by adding extra intelligence to the indexing of the constant data table. Because of this you cannot create a pointer to a variable length constant string.  

More flexible handling of constant data

Version 4 has a few ways of allowing pointers to constant data. First, Version 4 adds pointers to constants:
/*
A simple example showing the assignment of a pointer to a constant with the address of a constant string:
*/
const char version[] = "PRODUCT ID V1.01";
const char *ptr;

ptr = &version[0];
/*
A more complex example that creates an array of pointers to constant strings:
*/
const char *strings[] =
{
"HELLO",
"WORLD",
"CONST",
"STRINGS"
};

/*
Access the above const pointers
*/
const char *ptr;
while (i = 0; i < (sizeof(strings) / sizeof(const char *)); i++)
{
ptr = strings[i];
printf("%s", ptr);
}
In addition, constant strings can be passed to functions that are normally looking for pointers to characters:
/*
The following enables the ability for the compiler to copy constant strings into RAM when being passed as a parameter to a function:
*/
#device PASS_STRINGS=IN_RAM

/*
An example of using this new feature:
*/
if (stricmp(buffer,"ATDT\r")==0)
{
//do something
}
Note: The const qualifier in CCS always means that the data will be placed in program memory, and that the data is 'read-only'. It does not follow the ANSI definition which simply states that const is 'read-only'.  

addressmod capability to create user defined address spaces in any kind of memory device

Part of the IEEE Embedded C standard (ISO/IEC TR 18037), addressmod allows you to create custom qualifiers to create variables in any kind of memory device. The identifier can be used with any data types, including structures, unions, arrays, and pointers. Review the following example, which uses addressmod to create variables that are located in external memory:
/*
Syntax for addressmod is:
addressmod (identifier,read,write,start,end)
identifier - your new custom identifier name
read/write - the read/write functions to access the external memory
start/end - the range of addresses this identifier can access
*/
addressmod(extram, readextram, writeextram, 0, 0xFFFF)

/*
Create a large array, the actual contents of this array will be stored in the external memory.
*/
extram largeBuffer[2000]

/*
Create a pointer to the external memory. The pointer itself will be stored in the PIC's memory.
*/
extram char *extramPtr;

/*
Some examples of usage
*/
//direct access
largeBuffer[0] = 5;

//assign pointer, get address
extramPtr = &largeBuffer[0];

//access external memory indirectly
*extramPtr = 5;  

Function Overloading

Version 4 has borrowed a C++ feature called function overloading. Function overloading allows the user to have several functions with the same name, with the only difference between the functions is the number and type of parameters.
/*
Here is an example of function overloading: Two functions have the same name but differ in the types of parameters. The compiler determines which data type is being passed as a parameter and calls the proper function.
*/

void FindSquareRoot(long *n)
{
/*
This function finds the square root of a long integer variable (from the pointer), saves result back to pointer.
*/
}

void FindSquareRoot(float *n)
{
/*
This function finds the square root of a float variable (from the pointer), saves result back to pointer.
*/
}

/*
FindSquareRoot is now called. If variable is of long type, it will call the first FindSquareRoot() example. If variable is of float type, it will call the second FindSquareRoot() example.
*/
FindSquareRoot(&variable);  

Default Parameters

Default parameters have also been borrowed from C++ and have been included in Version 4. Default parameters can be specified in your functions, and if you don't pass the parameter to your function the default will be used.
int mygetc(char *c, int n=100)
{
/*
This function waits n milliseconds for a character over RS232. If a character is received, it saves it to the pointer c and returns TRUE. If there was a timeout it returns FALSE.
*/
}

//gets a char, waits 100ms for timeout
mygetc(&c);

//gets a char, waits 200ms for a timeout
mygetc(&c, 200);  

Variable number of Parameters

You can use functions with a variable number of parameters in Version 4. This is found most commonly when writing printf and fprintf libraries.
/*
stdarg.h holds the macros and va_list data type needed for variable number of parameters.
*/
#include

/*
A function with variable number of parameters requires two things. First, it requires the ellipsis (...), which must be the last parameter of the function. The ellipsis represents the variable argument list. Second, it requires one more variable before the ellipsis (...). Usually you will use this variable as a method for determining how many variables have been pushed onto the ellipsis.

Here is a function that calculates and returns the sum of all variables:
*/
int Sum(int count, ...)
{
//a pointer to the argument list
va_list al;

int x, sum=0;

//start the argument list
//count is the first variable before the ellipsis
va_start(al, count);

while(count--) {
//get an int from the list
x = var_arg(al, int);

sum += x;
}

//stop using the list
va_end(al);

return(sum);
}

/*
Some examples of using this new function:
*/
x=Sum(5, 10, 20, 30, 40, 50);
y=Sum(3, a, b, c);  

CCS Backwards Compatibility

CCS provides a method to attempt to make sure you can compile code written in older versions of CCS with minimal difficulty by altering the methodology to best match the desired version. Currently, there are 4 levels of compatibility provided: CCS V2.XXX, CCS V3.XXX, CCS V4.XXX and ANSI. Notice: this only affects the compiler methodology, it does not change any drivers, libraries and include files that may have been available in previous versions.
  • #device CCS2
    • ADC default size is set to the resolution of the device (#device ADC=10, #device ADC=12, etc)
    • boolean = int8 is compiled as: boolean = (int8 != 0)
    • Overload directive is required if you want to overload functions
    • Pointer size was set to only access first bank (PCM *=8, PCB *=5)
    • var16 = NegConst8 is compiled as: var16 = NegConst8 & 0xFF (no sign extension)
    • Compiler will NOT automatically set certain #fuses based upon certain code conditions.
    • ROM qualifier is called _rom
  • #device CCS3
    • ADC default is 8 bits (#device ADC=8)
    • boolean = int8 is compiled as: boolean = (int8 & 1)
    • Overload directive is required if you want to overload functions
    • Pointer size was set to only access first bank (PCM *=8, PCB *=5)
    • var16 = NegConst8 is compiled as: var16 = NegConst8 & 0xFF (no sign extension)
    • Compiler will NOT automatically set certain #fuses based upon certain code conditions.
    • ROM qualifier is called _rom
  • #device CCS4
    • ADC default is 8 bits (#device ADC=8)
    • boolean = int8 is compiled as: boolean = (int8 & 1)
    • You can overload functions without the overload directive
    • If the device has more than one bank of RAM, the default pointer size is now 16 (#device *=16)
    • var16 = NegConst8 is will perform the proper sign extension
    • Automatic #fuses configuration (see next section)
  • #device ANSI
    • Same as CCS4, but if there are any discrepancies are found that differ with the ANSI standard then the change will be made to ANSI
    • Data is signed by default
    • const qualifier is read-only RAM, not placed into program memory (use ROM qualifier to place into program memory)
    • Compilation is case sensitive by default
    • Constant strings can be passed to functions (#device PASS_STRINGS_IN_RAM)
 

Automatic #fuses configuration

Version 4 configures some of the configuration bits (#fuses) for you automatically based upon your code:
  • By default, the NOLVP fuse will be set (turn off low voltage programming)
  • By default, the PUT fuse will be set (turn on the power-up timer)
  • If there is no restart_wdt() in your code, it will set the NOWDT fuse. If there is a restart_wdt() fuse in your code then it will set the WDT fuse.
  • The oscillator config bits will automatically be set based upon your #use delay() (see next section)
  • If you have the debugger enabled in the PCW IDE, the DEBUG fuse will be set.

With the basic #fuses now being set automatically many programs will not need a #fuses directive.
This feature can be disabled by using the CCS3 backwards compatability (see previous section).  

#USE DELAY() Improvements


 

Commas, periods and speed postfixes now supported for delay parameter


By using commas, periods and speed prefixes, the delay parameter is now easier to read. The following speed postfixes are supported: M, MHZ, K, KHZ. See the next section for an example.  

#use delay() has new parameters: OSCILLATOR (or OSC), CRYSTAL (or XTAL), RC (or RC), INTERNAL (or INT)


By using these new parameters you can automatically configure your #fuses (configuration bits) for the proper oscillator type and PLL. Here are some examples:
/*
We are using a 12MHz crystal. This will set the HS fuse:
*/
#USE DELAY(CLOCK=12MHZ, CRYSTAL)

/*
We are using an external RC oscillator at 4000345 Hz. This will set the RC fuse:
*/
#USE DELAY(RC=4000345)

/*
We are using an external crystal at 10MHz, but because of the HS PLL the actual system will run at 40 MHz. This will set the H4 fuse:
*/
#USE DELAY(CLOCK=40MHZ, CRYSTAL=10MHZ)

/*
We are using an internal oscillator at 8 MHz. This will set the INTRC_IO fuse and configure the internal oscillator to run at 8 MHz:
*/
#USE DELAY(INTERNAL=8000000)  

#USE SPI()

Some of CCS's most powerful libraries have been the RS-232 and I2C libraries, which give users the flexibility of using multiple RS-232 and I2C ports at once using any set of general purpose I/O pins, and not tying the user to only using the hardware peripheral. In Version 4, SPI libraries are included to give the user: use of any general purpose I/O pins, clocking configuration, any number of data bits, streams, clock rate and more!
/*
The #use SPI configures the SPI port. Here is a simple configuration:
*/
#use SPI(
DO = PIN_B0,
DI = PIN_B1,
CLK = PIN_B2,
baud = 100000,
BITS = 8,
LSB_FIRST,
SAMPLE_RISE,
stream = SPI_PORT0
)

/*
Read a byte of data to a 9356 external EEPROM using this new SPI stream
*/
void Read9356(long address, int data)
{
output_high(EEPROM_9356_SELECT);
SPI_XFER(SPI_PORT0, 0x18);
SPI_XFER(SPI_PORT0, address);
data=SPI_XFER(SPI_PORT0, 0);
output_low(EEPROM_9356_SELECT);
}  

#USE RS232() upgrade

The powerful RS-232 library upgrade in Version 4 includes the following options:
  • Two additional parameters to #use rs232(): UART1 and UART2. Choosing one of these parameters will automatically set the transmit and receive pin of the CCS RS232 library to the specified hardware MSSP transmit and receive pins of the PIC® MCU.
  • A timeout parameter is included, which will cause getc() to timeout within specified number of milliseconds.
  • A clock parameter is included, so you can specify the system clock speed instead of using the clock specified in the #use delay. When the clock parameter is not specified, it will use the clock speed specified in the #use delay.
  • The number of stop bits can be defined.
  • You can use RS-232 in synchronous master or synchronous slave.
  • The baud rate option supports commas, periods, and the following prefixes: K, KHZ, M, MHZ. For example, these are now valid: BAUD=9.6k, BAUD=115,200, BAUD=115.2K, etc.
 

#USE I2C() upgrade

The powerful I2C library upgrade has been included in Version 4. First, you can give your I2C ports different stream identifiers. By giving your different I2C channels a stream identifier, it is easier to differentiate in your code which port is being used.
Second, the i2c_start() function can send an I2C start or I2C restart signal. The restart parameter for i2c_start() may be set to a 2 to force a restart instead of a start. A 1 value will do a normal start. If the restart is not specified or is 0, then a restart is done only if the compiler last encountered a i2c_start() and no i2c_stop().
/*
This configures two I2C ports, each has a different stream name.
*/
#use i2c(sda=PIN_C4, scl=PIN_C3, stream=I2C_HW)
#use i2c(sda=PIN_B1, scl=PIN_B2, stream=I2C_SW)

/*
The following function reads a data byte from a Microchip 24LC16 I2C EEPROM, using the I2C_HW stream
*/
int Read2416(int address)
{
i2c_start(I2C_HW, 1); //perform a start
i2c_write(I2C_HW, 0xA0);
i2c_write(I2C_HW, address);
i2c_start(I2C_HW, 2); //perform a restart
i2c_write(I2C_HW, 0xA1);
data=i2c_read(IC2_HW, 0);
i2c_stop(I2C_HW);
return(data);
}  

Bit Arrays

You can create an array of bits (or booleans). You cannot create a pointer to an array of bits or to a bit.
/*
This will create an array of bits, and initialize their values
*/
int1 flags[]={TRUE, TRUE, TRUE, FALSE, FALSE, FALSE};

/*
Some usages:
*/
bool = flags[1];

if ( flags[2] ) { /* do something */ }

flags[i++] = FALSE;  

Fixed point decimal

A powerful feature in Version 4 is the ability to represent decimal numbers using a new data type, the fixed point decimal. Fixed point decimal gives you decimal representation, but at integer speed. This gives you a phenomenal speed boost over using float. This is accomplished with a qualifier: _fixed(x). The x is the number of digits after the decimal the data type can hold.
/*
Creates a 16 bit variable with a range of 0.00 to 655.35
*/
int16 _fixed(2) dollars;

/*
Assign 1.23 to dollars. Internally, 123 will be saved to the int16.
*/
dollars=1.23;

/*
Add 3.00 to dollars. Internally, 300 will be added to the int16.
*/
dollars += 3;

/*
printf will display 4.23
*/
printf("%w", dollars);  

delay_us() and delay_ms() support int16 variable parameters

Prior to Version 4, delay_us() and delay_ms() could only take int16 parameters if they were constant values. Version 4 allows you to use delay_us() and delay_ms() with int16 variable parameters.
/*
This function delays s seconds.
*/
void delay_s(int s)
{
int16 ms;

ms = s * 1000;

delay_ms(ms);
}  

General Purpose I/O Improvements

 

output high(), output low(), output toggle() and input() now support variable parameters


The built in functions OUTPUT_LOW(), OUTPUT_HIGH(), OUTPUT_BIT() and INPUT() accept a variable to identify the pin. The variable must have a value equal to one of the constants (like PIN_A1) to work properly. The tristate register is updated unless the FAST_IO mode is set on port A. Note that doing I/O with a variable instead of a constant will take much longer time.
/*
This function gets bit n of port D
*/
int GetPinD(int n)
{
int16 pin;

pin = PIN_D0 + n;

return(input(pin));
}  

output_drive()


The output_drive() function sets the desired pin's tristate to output. This is the opposite of output_float().  

get_tris_x()


The get_tris_X() function returns the current tristate setting for that port (where X is the desired port, ie get_tris_e())  

Interrupts Improvements

The interrupt_active(INT_XXX) function returns TRUE if the specified interrupt flag is set, meaning the interrupt condition has triggered. INT_XXX is a valid constant, only valid interrupts on your target PIC® MCU are allowed (ie INT_TIMER0, INT_CCP, etc). This will be useful if you want to poll the flag instead of using interrupts.  

Preprocessor Improvements


 

== and != can be used to evaluate strings at the preprocessor level


The following example is now legal:
#if (getenv("DEVICE") != "PIC16F877A")
#error This target PIC is not supported!
#endif  

getenv() can return byte and bit addresses of special file registers


The getenv() function has two additional options to get the address of special function registers. The strings SFR:name and BIT:name may be used to get the address of a SFR. For example:
/*
On a PIC16F877A, returns a string of the form 0x003 (where the STATUS SFR is located)
*/
#byte status_reg = GETENV("SFR:STATUS")

/*
On a PIC16F877A, returns a string of the form 0x003.0 (where the carry bit is located)
*/
#bit carry_flag = GETENV("BIT:C") 

No comments:

Post a Comment