Microcontroller Tutorials

Arduino, Raspberry Pi, PIC, Beaglebone Resources

PIC16 I2C Tutorial

To continue with our tutorial on synchronous serial with PICs, we will know look at I2C, another popular protocol used by sensors, displays and memory devices.

I2C vs. SPI

I2C answers some of the problems of SPI including no multi-master mode, no slave flow control and more pins to use. But in terms of speed, SPI is still faster due to its push-pull driver compared to the open-collector driver for I2C. In fact the fastest I2C mode, Ultra Fast, can reach only up to 5 MHz while some SPI busses can reach 50 MHz.

Read the I2C Protocol Guide for more information on I2C.

The PIC16F877A MSSP, which is responsible for both I2C and SPI, allows only up to 400 kHz on the I2C bus which is the Fast-mode rate. This rate is only achievable if an oscillator of at least 10 MHz is used. [From Section 30 p 29 of Mid-range Family Technical Manual]. The 100 kHz standard mode is also supported needing at least 1.5 MHz of clock speed.

The MSSP in I2C Mode

In I2C mode, the MSSP module use the SSPCON, SSPCON2 and SSPSTAT registers to configure both master and slave devices. Here they are again:


Configuring MSSP to I2C Mode

Here’s a typical sequence in configuring I2C:

  • Enable the SDA and SCL pins by setting SSPEN in SSPCON and select master or slave mode through bits SSPM3:SSPM0:

PIC16F877A I2C modes

  • If in master, configure the ACKSTAT, ACKDT, ACKEN, RCEN, PEN, RSEN and SEN bits on SSPCON2. Normally, these bits are just cleared.
  • If in slave, set CKP in SSPCON and configure the GCEN bit in SSPCON2 which is also normally cleared.
  • If in master or slave, configure SMP, CKE, and UA (if 10-bit addressing) in SSPSTAT. Note that the master and slave must have the same settings for these three.
  • If in master, calculate the correct SSPADD for the transmission speed using the formula:

For example, if 100 kHz speed is desired using a 4 MHz oscillator then

  • There is also a need to set RC3 (SCL) and RC4 (SDA) as input.
  • An interrupt in the slave device is needed to signal if data is received from the I2C bus. This is done by setting SSPIE and PEIE on the INTCON and PIE1 registers accordingly.

Sending I2c Data

The normal sequence in sending data in an I2C bus is like this:

START -> SLAVE ADDRESS -> REGISTER ADDRESS (if applicable) -> DATA (to be written or read) -> STOP

To send data as master, a start condition is signalled by setting SEN bit. Then the address of the slave device is sent, followed by the register address (most I2C sensors have this) where the data is to be sent or read followed by the data to be sent or read on that device.

The stop condition is then signaled by setting PEN bit. Note that the MSSP can be overwhelmed if the mentioned sequence are done in succession. Checking if R/W bit is set or any of the bits ACKEN, RCEN, PEN, RSEN and SEN has been set is a good way to avoid overwhelming.  

To receive data as slave, the SSPIF bit is checked upon interrupt. If this bit is set then data has been received from the I2C bus. The data is then read from SSPBUF provided that the data was received not address (checked using the D/A bit of SSPSTAT) and that the bus is in read mode (set using the R/W bit of SSPSTAT).

Example Implementation using XC8

PIC to PIC communication using I2C would look like this:

Note the 4.7 kΩ pull-up resistor which is typical in I2C.

Here’s how to implement I2C on PIC16F877A using XC8:

Master Mode:

Slave Mode:

Here, the i variable is incremented and sent to the slave via the I2C bus. When the data is received by the slave, it is moved to the PORTB register.

Once you’re familiar with how the I2C protocol works with PICs, you can now use I2C devices like MPU6050 accelerometer or the BMP280 barometric pressure and altitude sensor.