I2C communication between PICs

From Mech
Jump to navigationJump to search


I2C is pronounced "I squared C" and stands for Inter-Integrated Circuit. This protocol was designed by Phillips Semiconductors around 1992 to allow easy communication between components on the same circuit board and can achieve transfer rates of up to 400 kbit/sec. Is is a 2 line (plus common ground) communication method for one master device to control up to 112 slave devices. While it is possible to have multiple masters on the same I2c bus, this page will only deal with a one master configuration.

The two lines are named SCL and SDA where SCL is the CLock line and SDA is the DAta line. The PIC 4520 is designed to use pin 18 as SCL and pin 23 as SDA for hardware I2C. Note that pin 18 is one of the only pins not accessible on the prototyping board. An additional wire can be added as shown in the example image. It is possible to use lines accessible from the prototyping board to communicate via software I2C, but this has data rates of less than one fourth those of hardware I2C and will therefore be omitted. I2C requires the lines to be high unless the master or the slave is pulling the line down, so you will need to use pull up resistors on both the clock and data lines.

An communication diagram is shown:

I2C Data Transfer.jpg
  • S - As the master pulls down the data line, the falling edge of the data line signifies the start condition.
  • P - As the master allows the data line to rise, the rising edge of the data line signifies the stop condition.
  • B - Individual bits being transferred.

The data line can only change while the clock line is low. The data line is only read when the clock line is high and therefore 1 bit can be transferred per cycle of the clock line.

The master is initially sends a start bit followed by the 7-bit address of the slave it wishes to communicate with. The next bit represents whether it wishes to write(0) to or read(1) from the slave.

The slave then responds with an acknowledge bit. The transmission continues according to whether the master is attempting to read to or write from the device.

The master then ends the transmission with a stop bit. Another option is to send another start bit to continue the transfer in a combined message.


Below is a simple circuit diagram showing the master PIC with 2 close button switches. Note that pin 18 (RC3) is not brought out to the circuit board, but a wire can be soldered next to the PIC itself. Both SDA and SCL need pull up resistors as either the master or the slave can hold the line low to stop communication.

I2C Circuit Diagram.jpg

Here is an example image of how to wire this.

I2C Wiring Image.jpg


Code for master to slave transfer of one byte with switch de-bouncing can be found here:

Master Code

Slave Code


For the Master PIC, we first need to define the output pins and force hardware I2C communication.

  #use I2C(FAST, SCL=PIN_C3, SDA=PIN_C4, FORCE_HW)  //using hardware I2C, built into the PIC, make sure to include this line in any master I2C program

To communicate with a device, we use the following lines of code where 0x14 is the device name. The first line is the issuing of the start command and this forces all slave devices on the bus to wait for their address. The next write command is the address of the slave. The following byte is the data being sent to the slave. All communication must terminate with a stop command or the line will remain open and no future communication is possible until the command is given.

  i2c_start();               //begin transmission
  i2c_write(0x14);           //select address of device to communicate with
  i2c_write(data);           //send actual data
  i2c_stop();                //terminate communication


For the Slave PIC, we first need to define the output pins, define the name of the device and force hardware I2C communication. This device was randomly assigned name 0x14, but could have been just about any even hex number with a few exceptions noted below code.

  #use i2c(SLAVE, FAST, SCL=PIN_C3, SDA=PIN_C4, address=0x14, FORCE_HW) //make sure to include this line in any Slave I2C program

The SSP interrupt is used for I2C communication. When the master begins to transmit data, the PIC goes into this interrupt.

  #INT_SSP                      //Interrupt for I2C activity
  void sspinterupt()

The definition of state is to determine whether or not the master is requesting or sending data.

     state = i2c_isr_state();   //Reading the type of transmission

State is less than 0x80 for all read operations.

     if(state < 0x80)           //Master is sending data
        data = i2c_read();      //An array will be needed to store data if more than one byte is transferred

State will be equal to 0x80 for write commands.

     if(state == 0x80)          //Master is requesting data
     output_d(data);            //Output data to port D to visualize

Somewhere in the main section of the program, probably right away, the SSP interrupt will need to be enabled.


Address exceptions:

  0000 000 1 START byte - for slow micros without I2C h/w
  0000 001 X CBUS address - a different bus protocol
  0000 010 X Reserved for different bus format
  0000 011 X Reserved for future purposes
  0000 1XX X Hs-mode master code
  1111 1XX X Reserved for future purposes
  1111 0XX X 10-bit slave addressing

Two Way Communication

An alternate circuit diagram is shown below with an analogue input on the slave and three switches.

I2C Alternate Circuit Diagram

Changes are made to the code to allow more than one bit of data as well as data transferred from the slave to the master.

Alternate Master

Alternate Slave


An alternate address of 0xa0 is used to demonstrate any address can be used.

To read from the slave, the first byte sent is the address of the slave. The second byte is the location of the data on the slave being requested. The communication is terminated before being restarted.

  i2c_start ();           //begin communication
  i2c_write (0xa0);       //send slave address
  i2c_write (0x02);       //request slave internal memory address for analogue data
  i2c_stop();             //stop write cycle to shift to read cycle
  i2c_start ();           //send repeated start command to begin read cycle

Immediately after restarting, the address plus one read bit added is sent. The data requested previously is read and the communication is terminated.

  i2c_write (0xa1);       //add 1 to the address to send a read bit
  result = i2c_read(0);   //read analogue information from the slave
  i2c_stop ();            //terminate communication
  output_d(result);       //display analogue information from the slave


In this example a larger information buffer is used, an array of 0x03 bytes, but this can be as large as needed.

  int address, buffer[0x03]; 

In the interrupt, most of the same code is used as before.

  void ssp_interupt ()
     state = i2c_isr_state();
     if(state < 0x80)                 //master is sending data

The first command bit is empty, so nothing is done.

        if(state == 0)

The first data byte returns state 1 and is the address to be written to.

        if(state == 1)                   //first received byte is address
           address = i2c_read();

The second data byte is the information being received from the master and is stored in the address sent in the first data byte.

        if(state == 2)                   //second received byte is data
           buffer[address] = i2c_read();

If the master is requesting data, the slave sends data from the requested address.

     if(state == 0x80)                //master is requesting data
        i2c_write (buffer[address]);  //send requested data

More Information

Phillips Brief on I2c: http://www.nxp.com/acrobat_download/literature/9398/39340011.pdf

An I2C Primer: http://www.i2c-bus.org/I2C-Primer.520.0.html