Last updated:

$Date: 2002/12/04 19:42:10 $

PIC are microcontroller made by Microchip, not very expensive, easy to use and quite popular.

In this document, the only PIC we present is the PIC16F877:

11. How to use a PIC

11.1 A PIC tool box

In a minimal tool chain you need:

But it's also a good idea to use a more high level programming language like C. All these tools are freely available under Linux (you could also find it under DOS, except for the C compiler that you have to pay).

Unfortunately, there is no simple and cheap way to debug on a PIC.

12. The assembler: GPASM

GPASM is a part of the gputils package. To install it under Linux download the tgz file (I use the version 0.10.2) and follow the instructions of the INSTALL file (./configure, make, and make install).

Then, to build a .hex file from an .asm file:

gpasm file.asm
the result is a file named file.hex ready to be transfer on the PIC.

There are 2 important options: -dos (if you add to produce a hex file with dos newlines), and -w1 to suppress useless warning like

(Message [302] Register in operand not in bank 0. Ensure bank bits are correct.).

13. C Compiler for PIC

13.1 Install C2C

Unfortunately, to this date, there is no efficient free and open-source C compiler for PIC. But C2C is an efficient postcardware. In fact, the new version of C2C for Linux is no more a postcardware: it's a shareware without evaluation period (44.76 Euro) available here: C2C ( I have never test this new shareware version.

To install the free postcardware version under Linux, you download a binary archive C2C Linux Version 3.27 (

This application is distributed as postcardware for non-profit use and
as shareware for commercial use.
For non-profit use you may use the compiler as long as you wish. If you
like it I ask you to send me any nice postcard.
For commercial use you have to register the compiler. To register run
the 'register.exe' application under MS Windows or go to the compiler
home page and follow the instructions.
For this Linux version there is no code size, date, or target restriction. It's a fully operational C compiler. C2C is not very big (735Ko) and could be put in /usr/local/bin to be usable for all users. It's distributed with a small help file, and some examples.

13.2 C2C limitations

C2C is not GCC, there are some major limitations that you have to known:

And as you may already known, there are also some limitations due to PIC microcontroller: There is a last point that you have to be aware of (even if it's not a limitation, more a feature): C2C will automaticly generate the necessary bank changed in your code.

13.3 using C2C

To compile a program for a PIC16F877, try for instance:

c2c -SRC -PPIC16F877 file.c
The -SRC option is used to insert C-code as comments into assembler file (file.ASM) (it's far more easy to read).

After what, to use the assembler file generate by C2C you have to add some header lines (with configuration options). For example:
        list p=16f877
        #include <>
        __config _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON & _HS_OSC & _WRT_ENABLE_ON & _LVP_ON & _DEBUG_OFF & _CPD_OFF
In an assembler file, use ; to comment a line. And every line have to start with a tabulation. Then, go to the GPASM chapter to build an hex file ready to be programmed to your PIC.

14. Picprog: A Pic programmer

The choice of a programmer closely depends on the hardware you want to use. We use Picprog1.1 to program the PIC via a PC regular serial port (see chapter 18 for the hardware design). Picprog is simple, efficient and open source. This programmer has been design for PIC16F84 but it works fine with the whole PIC16xx microcontroller family. For more informations go to Picprog 1.1 ( jaakko/pic/picprog.html).

15. Makefile

Since, you use UNIX, all these commands can be put in a Makefile like this:

# =====================================================================
# =====================================================================
ASM_FILE=$(addsuffix .asm, $(basename $(notdir $(C_FILE))))
ASM_COMPLET_FILE=$(addsuffix _complet.asm, $(basename $(notdir $(C_FILE))))
HEX_FILE=$(addsuffix _complet.hex, $(basename $(notdir $(C_FILE))))

        c2c -SRC -PPIC16F877 $(C_FILE)
        cat header.asm $(ASM_FILE) > $(ASM_COMPLET_FILE)
        gpasm --dos -w1 $(ASM_COMPLET_FILE)

        picprog --erase --burn --device=pic16f877 --input $(HEX_FILE) --pic /dev/ttyS0

        rm -f $(ASM_FILE)
        rm -f $(ASM_COMPLET_FILE)
        rm -f $(HEX_FILE)
# =====================================================================

16. PIC 16F877 tutorial

Of course, you HAVE TO read the data sheet from Microchip. But, it's not easy to understand without some examples, and unfortunately the examples provided by Microchip are poor and not very clear (and written in assembler). In this chapter, I'll just give you some tips to start.

16.1 IO

16.1.1 PORTA

Port A is a 6 bits bi-directional port. It's not the easiest IO port because pins RA0-RA3 and RA5 are multiplexed with analog inputs, and pin RA4 is multiplexed with Timer 0 module clock input.

To use IO rather than AN module use the ADCON1 register:

To switch pins as Output or Input use the TRSIA register (0 for output, 1 for input).
// Example1 from
// Code design for PIC16F877
// To compile with c2c -SRC -PPIC16F877 example1.c

char ADCON1@0x9;  //(bank 1)
// C2C automaticaly define some classical register (PORTA, TRISA,...) but not ADCON1
// so you have to define it (go to C2C doc for more informations about this syntax)

void init_portA()
   PORTA=0;//No need to switch to bank 1, C2C do it.
   TRISA=0;//using PORTA as output
   ADCON1=ADCON1|0x06;//using IOPort instead of AN module

void wait()
   char i;
     nop();// generate an empty instruction (a busy wait)

To test this program:

16.1.2 Others IO ports: PORTB, PORTC, PORTD, PORTE

For the other IO ports, it's more or less the same thing: use the TRISB, TRISC, TRISD or TRISE to choose input or output for each bits. But, PORTB pins RB7:RB4 could be use for Interrupt on change feature. This kind of interrupt inputs are very precious so keep it in stock for later. For the PORTC, pins RC4:RC3 could be use for I2C bus communication, and pins RC2:RC1 could be use for PWM commands. etc,... As you see, you always have to check the data sheet to choose the right initialize for each IO port.

16.2 PWM

An important feature of this PIC16F877 is the PWM modules. Pulse Width Modulation (PWM) is an efficient light dimmer or DC motor speed controller. In our robot, we use these 2 modules to control the two motors.

The important steps to configure the PWM modules are:

0ne problem with the PWM for speed motor control is the choice of a correct PWM period. We have obtained good results with the longest available period: 0.81ms (1220Hz).
void init_pwm_and_timer2()
   T2CON=0;// Timer2 control register (PostScale|Off|PreScale)
   set_bit(T2CON,1);//PreScale 1:16
   TMR2=0; // Reset timer2
   // set the pwm period by writing to the PR2 register
   PR2=0xFF;// period is ((PR2)+1)x(prescale)x(50x4)ns=0.81ms
   // set the PWM1 duty cycle by writing to the CCPR1L register an CCP1CON<4:5>
   // 0 -> 2 LSbs of the PWM duty cycle
   // C -> PWM Mode
   //CCPR1L=0xDF;// duty cycle -> 1101 1111 00 = 892x50xprescale=0.71ms (87%)
   //CCPR1L=0x80;// duty cycle -> 1000 0000 00 = 512x50xprescale=0.4ms (50%)
   CCPR1L=0xFF;// duty cycle -> 1111 1111 00 = 1020x50xprescale=0.8ms (100%)
   // set the PWM2 duty cycle by writing to the CCPR2L register an CCP2PON<4:5>
   // 0 -> 2 LSbs of the PWM duty cycle
   // C -> PWM Mode
   //CCPR2L=0xDF;// duty cycle -> 1101 1111 00 = 892x50xprescale=0.71ms (87%)
   //CCPR2L=0x80;// duty cycle -> 1000 0000 00 = 512x50xprescale=0.4ms (50%)
   CCPR2L=0xFF;// duty cycle -> 1111 1111 00 = 1020x50xprescale=0.8ms (100%)
   //make the CCP2, CCP1 pin an output by clearing the TRISC<1:2> bits
   set_bit(PIE1,1);//Timer2 IE
   clear_bit(PIR1,1);//Timer2 clear IF
   //set_bit(T2CON,1);// Prediviseur a 16
   set_bit(T2CON,2);// Turn ON Timer2


16.3 Interrupt

With C2C, the interrupt dispatch is done in a special function with this reserved prototype:

void interrupt(void);
The context is automaticly saved, and restored. Keep in mind that an efficient interrupt routine has to be short to avoid overflow interrupt problems. And don't forget that you often have to reset an interrupt flag (for timer, I2C, or an input interrupt).
void interrupt(void)
   // context is automaticaly saved, and restored at the end of interrupt
   if (INTCON&00000100b) timer0_interrupt();
   if (PIR1  &00000001b) timer1_interrupt();
   if (PIR1  &00000010b) timer2_interrupt();
   if (INTCON&00000001b) sensor_interrupt();
   if (PIR1  &00001000b) i2c_slave_interrupt();
void timer0_interrupt()
   clear_bit(INTCON,2);//reset the interrupt flag
void timer1_interrupt()
   clear_bit(PIR1,0);//reset the interrupt flag
void timer2_interrupt()
   clear_bit(PIR1,1);//reset the interrupt flag
void sensor_interrupt()
   clear_bit(INTCON,0);//reset the interrupt flag
void i2c_slave_interrupt()
   if (SSPSTAT&0x04)
        //I2C Slave Transmission
        set_bit(SSPCON,4);// CKP, SSPBUFF must be write before
        clear_bit(PIR1,3);//reset the interrupt flag
        //I2C Slave Reception
        clear_bit(PIR1,3);//reset the interrupt flag

16.4 I2C

As you could see in the init_i2c_slave function, to initialize I2C on the PIC, the major steps are:

There are two kinds of I2C behavior: master or slave. For the moment, I still haven't used the master mode.

Slave mode, doesn't mean that the PIC could only receive data, but that all the exchanges are initiated by an external master. In the previous example we use the slave mode. When, a master send a message to the PIC, this generate an interrupt with a flag on PIR1 register.
void init_i2c_slave()
// I2C IE : PIE1<3>
// I2C IF : PIR1<3>
   set_bit(SSPSTAT,7);// standard speed mode
   clear_bit(SSPSTAT,6);// input level conform to I2C spec
   clear_bit(SSPSTAT,0);// BF buffer full bit
   //SCL and SDA as input
   //SSPCON<5> enable bit (1)
   //SSPCON<3:0> 0110 (slave, 7 bits address)
   clear_bit(SSPCON,7);// WCOL Write Collision Detect bit
   clear_bit(SSPCON,6);// SSPOV Receive Overflow Indicator bit
   set_bit(SSPCON,5);// Synchronous Serial Port Enable
   set_bit(SSPCON,4);// CKP
   clear_bit(SSPCON,3);//0110: I2C slave mode: 7 bits address
   SSPADD=0x0E;// 0000 1110 -> 7 bits address= 0000 111
   //SSP IE
Then, we test if it's an incoming message, or a request for a transmission with SSPSTAT register. If it's a reception you have to read the SSPBUFF register (if you don't it will stop the I2C interrupt handler). If it's a transmission you have to write in the SSPBUFF register. In all the case, at the end, don't forget to reset the interrupt flag (here PIR1:3).
void i2c_slave_interrupt()
   if (SSPSTAT&0x04)
       //I2C Slave Transmission
       set_bit(SSPCON,4);// CKP, SSPBUFF must be write before
       clear_bit(PIR1,3);//reset the interrupt flag
        //I2C Slave Reception
        clear_bit(PIR1,3);//reset the interrupt flag

17. Useful links

Microchip (
For all the device data-sheets.

GPutils (

Picprog 1.1 ( jaakko/pic/picprog.html):
A simple way to program PIC microcontroller (Hardware and Open source soft for Linux).

A PIC development Tutorial (
A good way to start with some advises and examples for using libraries and Makefile.

Fribotte: PIC et Linux (
A good way to start (in French). ,