Read the small book that comes with the compiler & ICD device: PICmicro MCU C, An Introduction to Programming the Microchip PIC in CCS C by Nigel Gardner. It's short and makes a good refresher for C programming.
Chapter 9 has some i/o specifics. Such information is on these wiki pages too, but another rendition can be useful.
- Inputs and Outputs
- Mixing C and Assembler
- Bit manipulation
- A/D Conversion
- Data Communication: RS232, I2C, SPI
- Pulse width modulation (PWM)
- LCD display driving
- Include libraries
The CCS C Compiler Reference Manual is awful, even as user manuals go. When reading the function descriptions, it helps to look at the relevant section of the hardware datasheet for the PIC chip itself, and try to figure out what the reference manual is talking about.
CCS C compared to vanilla C
CCS's PIC C is a pretty standard C with a few notable exceptions:
- You have to be very explicit about the size and type of variables. The PIC has limited memory for variables (1536 bytes). Its instruction set natively handles operations only for 8-bit integers, and all the rest is done in software. So think carefully about which variables need which data type. Adding two floats takes 150 times longer than adding two int8s.
- The native type is the int8 which is stored in one 8-bit byte. Its value can range 0 to 255. If you add an int8 with value 250 to one with value 13, the result will be 7, because it rolls over at 256. This will not generate an error. (There are no run-time error messages.)
- For maximum storage efficiency you can use an int1, which can only have values of 0 or 1. This is not fast however (check this). Use int8s for speed.
- Larger integers can be stored as int16 or int32.
- Any of the int types (except int1) can be signed; by default they are unsigned (positive only.) Operations on signed ints are just as fast as on default (unsigned) ints. (check this)
- Ideally most of your variables will be defined like int8 myvariable; or signed int8 myvariable;
- Addition and subtraction are much faster than multiplication and division. When multiplying or dividing an int8, int16, or int32 by a power of two, use the left-shift and right shift operators, e.g. result = bignumber>>3; // divide by 8; right-shift by 3 bits
- Watch out for type conversion. When in doubt force the conversion the way you expect it to be. An expression is fully evaluated before being type converted for assignment. For instance if (int8) value = 100, then bignumber = value << 3 will be wrong; what you probably want is bignumber = (int16) value << 3
- It's also useful to be able to select (mask) one or more bits from an int, using the bitwise operators & (logical and), | (logical or), and ! (invert). Make order of evaluation unambiguous with parentheses. Example: if msecs is an int32 that is incremented by a 1KHz interrupt routine, then (msecs & 255) will be 0 every 256 milliseconds.
- Because float operations are so slow it's often better to use fixed point arithmetic; e.g. a continuous value between 0 and 1 can be represented as an int16 between 0 and 1000.