Features Exclusive to CCS Compiler

Variable length constant strings

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

Back to top

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

#USE I2C()

Back to top

Try the powerful I2C library included in the compiler. 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);
}

#USE SPI()

Back to top

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

Back to top

The powerful RS-232 library in the compiler 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.

Automatic #fuses configuration

Back to top

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

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

Back to top

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;

Default Parameters

Back to top

Default parameters have also been borrowed from C++ and have been included in the compiler. 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

Back to top

You can use functions with a variable number of parameters in the compiler. 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 <stdarg.h>

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

Function Overloading

Back to top

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

Fixed point decimal

Back to top

A powerful feature in the compiler 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);