I2C or inter-integrated circuit is a widely used serial communication protocol in the electronics world. It is a multi-master multi-slave two-wire bus. In this tutorial, we are discussing about I2C module in PIC18F4550, how it can be implemented using firmware.

Serial Communication

Serial communication is the most commonly implemented method for transmitting the data between a computer and its peripheral device. In an embedded system when a microcontroller communicates with the outside world the data is transmitted in bytes. The data can be transferred in two methods: parallel and serial. In parallel data transfer, 8 or more than 8 lines are used for each line carrying a signal. This method is commonly used to communicate with devices which are few feet away. Although it seems to be faster since more number of lines are used and all bits can be transmitted simultaneously they are not used widely. Multiple lines in parallel transmission can increase the expense. Moreover, it can’t be used for larger distances and higher frequencies. For these reasons, serial communication is preferred over parallel. In serial communication, the data transfer occurs over a single cable one bit at a time.


The serial communication can be again implemented in two different ways, synchronous and asynchronous. For synchronous data transfer, both the sender and receiver access the data according to the same clock. Therefore, a special line for the clock signal is required. A master (or one of the senders) should provide the clock signal to all the receivers in the synchronous data transfer. For asynchronous data transfer, there is no common clock signal between the sender and receivers. Therefore, the sender and the receiver first need to agree on a data transfer speed. This speed usually does not change after the data transfer starts. Both the sender and receiver set up their own internal circuits to make sure that the data accessing follows that agreement. PIC microcontrollers have several peripheral modules for serial communication some of them includes USART, SPI, I2C, USB.


I2C protocol is invented by Philips. I2C bus physically consists of two lines SDA and SCL. SDA line transmits the data and SCL lines transfer the clock signal. Both SDA and SCL lines are bidirectional open-drain lines so they need to be tied to Vcc through pull-up resistors. In I2C communication there will be a master device which initiates data transfer. The master device provides a clock for communication. The communication network can have multiple masters connected with multiple slaves on the same bus. Every device in the I2C bus will have a unique address. The master device in the bus will access the slave device with this address.
The masters in the bus are generally microcontrollers which generate the clock according to the configuration register values associated with the I2C module. The generated clock will be made available to the slave device through the SCL line. Both master and slave communicate each other in synchronous with this clock. Master device initiates each communication process.
The communication in an I2C bus begins with a Start Signal generated by the master. Start signal act as an alarm signal for all the slaves indicating that a communication is going to begin. Followed by the start condition master will transmit the address of the slave it needs to communicate with. All the slaves connected to the bus will receive the address and compares with its own address. The one which matches its address with the received address will send an acknowledgement signal to the master. The LSB of the address will be write/read bit, if 0 the slave is accessed in write mode and if 1 the slave is accessed in read mode.
Initially, when no communication takes place and the I2C bus is free, both lines are high. Activating the lines means pulling it down. A high to low transition in SDA line when SCL is held high is considered as START signal. The Communication in an I2C bus is terminated with a stop condition. A low to high transition in SDA line when SCL is held high is called the STOP condition.


I2C Data transfer

Data is transmitted in an I2C bus in bytes. There is no limitation in the number of bytes that can be transmitted. However, each byte must be acknowledged with an acknowledgement bit. The acknowledgement bit signals whether the device is ready to proceed with the next byte. It is generated by the master when master receives data from the slave and is generated by the slave when slave act as the receiver. For each data bits including the acknowledgement bit, the master must generate the clock pulses.
As we have seen earlier data transmission in I2C bus begin with a START condition. After the start condition is sent, no other master device in the bus can access the bus without an occurrence of a STOP condition. Next, the master device will send the 7-bit address of the slave with its 8 bit indicating the mode of operation. The device connected to the bus which matches this address will respond with an acknowledgement bit. Once the MCU sends the address and corresponding device acknowledges, the MCU can start transmitting or receiving data.
The bus will be released only after a STOP condition is generated by the master. If the master needs to communicate with a different slave without losing the bus, it should generate a restart signal. Restart signal is a start condition which is not generated after a STOP condition.

If the master device needs to read data from the slave, it generates a start signal and then sends the address of the slave with the eighth bit as 1 to indicate the read operation.

I2C Module in PIC18F4550

Most of the PIC microcontrollers have an inbuilt I2C module. Let us take a look at important registers in PIC18F4550 to be configured to make I2C module active and running on a PIC microcontroller.


sspstat-i2c-module - I2C Module in PIC18F4550

SMP:- This bit is called the slew rate control bit Clearing this bit enables the slew rate control for high speed mode (400Khz). When the bit is set slew rate control will be disabled for low-speed mode (100Khz and 1 Mhz)
CKE:- This bit should be set to enable SMBus logic levels.
D/A:- This bit is used only in slave mode
P:- This is a read-only bit which indicates the occurrence of a stop condition. When P is set a stop condition is identified in the bus and is cleared when next start condition or RESET occurs.
S:- This is a read-only bit which indicates the occurrence of a start condition. When P is set a start condition is identified in the bus and is cleared when next stop condition or RESET occurs.
R/W:- When configured in master mode this bit is used to verify whether transmit is in progress or not. This bit is set when transmit is in progress and is cleared when no transmission not in progress.
UA:- This bit is only used in 10-bit slave mode.
BF:- This bit indicates the status of the buffer. In both transmit and receive mode a logic one in this bit indicates that the buffer register SSPBUF is full and a logic 0 indicates that the buffer is empty.



WCOL:- This bit is called write collision bit and is used only in transmit mode. When in master mode a write to SSPBUF register when I2C module is not ready for transmission will set the bit. In slave mode, this bit will be set when SSPBUF register is written when data is still being transmitted.
SSPOV:- Receive overflow indicate bit used only in receive mode, the bit will be set when a new byte is received and SSPBUF is still holding the previous data.
SSPEN:- Master Synchronous serial port enable bit setting this bit will enable the serial port and clearing the bit will disable the serial port and configures the SCL and SDA pins as IO pins.
CKP:- SCK release control bit, this bit is used only in slave mode setting the bit will release the clock and be clearing the bit will hold the clock low (clock stretch), used to ensure data setup time.
SSPM3 : SSPM0 Master synchronous serial port mode select bit
1111 = I2C Slave mode, 10-bit address with Start and Stop bit interrupts enabled
1110 = I2C Slave mode, 7-bit address with Start and Stop bit interrupts enabled
1011 = I2C Firmware Controlled Master mode (slave Idle)
1000 = I2C Master mode, clock = FOSC/(4 * (SSPADD + 1))
0111 = I2C Slave mode, 10-bit address
0110 = I2C Slave mode, 7-bit address

Note: For I2C communication both SDA and SCL pins should be configured as input pins with TRIS register.



GCEN:- This bit is used only in slave mode so no need to consider this bit when interfacing DS1307
ACKSTAT:- Acknowledge status bit used in master transmit mode only. This bit will set when an acknowledge is received from a slave.
ACKDT:-Acknowledge data bit(master receive mode only). ACKDT should be set for a non-acknowledge ACKDT should be cleared for an acknowledge.
ACKEN:-Acknowledge sequence enables bit. This bit when set enables the acknowledge sequence on SDA and SCL pins and transmit ACKDT data bit.When cleared acknowledge sequence will be disabled.
RCEN:-Receive enable bit(Master receive mode only).When set receive mode will be enabled.
PEN:- Stop Condition enable bit used to enable stop condition on the SDA and SCL lines. when this bit is cleared stop condition is idle.
RSEN: – Repeated start condition enable bit used to enable a repeated start in SCL and SDA lines. Setting this bit will enable restart condition in the bus. When cleared Restart is idle.
SEN:Start condition enable bit.setting this bit will enable a start condition on the bus. when cleared start is idle.

I2C Firmware Implementation

The I2C module should be initialized with desired configurations for communicating with the slave. Here we are also setting the SCL clock frequency according to an input parameter.

Firmware Example:

#define _XTAL_FREQ 20000000UL                   //Frequency of external oscillator in Hz

void i2c_init(unsigned long int clock_freq)
    TRISB |= 0x03;                              //Configuring SDA and SCL as input pins
    SSPCON1 = 0x28;                             //Configuring as I2C Master mode with SCL clock frequency= FOSC/(4 * (SSPADD + 1))
    SSPADD = (_XTAL_FREQ/(4*clock_freq))-1;     //Setting SCL frequency with value in clock_freq
    SSPSTAT = 0x00;                             //Resetting SSPSTAT register
    SSPIF = 0;                                  //Resetting MSSP interrupt flag

After the module gets initialized, communication can begin with a start condition in the I2C bus. Before releasing a start signal, we need to check if the I2C bus is busy with an ongoing communication in case of a multi-master I2C bus.
The SSPIF bit, called the MSSP interrupt flag bit is set to 1 in different I2C events. This bit must be cleared in software. It helps us identify the completion of an event in the I2C bus.
The start condition in the bus will act as a ready signal for all the slaves in the bus to start communication. Upon receiving the start condition, all slaves will wait for the slave address.

void i2c_idle(void)
  // Wait for Status Idle (i.e. ACKEN, RCEN, PEN, RSEN, SEN) and I2C Bus stop detection
  while (( SSPCON2 & 0x1F ) || ( SSPSTAT & 0x04 ));

void mssp_wait(void)
    while(!SSPIF);                              //wait till SSPIF flag is set

void i2c_start(void)
    i2c_idle();                                 //Check if the I2C bus is idle
    SEN=1;                                      //Initiate start condition
    mssp_wait();                                //Wait till completion of event

Followed by the start condition, the address of slave with LSB bit specifying the operation is transmitted. All slaves connected to the bus will receive the address and compares it with its own address. The slave which finds its address matching with the received address will respond with an acknowledgement signal.

int i2c_write(unsigned char data)
    L1: SSPBUF=data;                            //Input data to buffer
    mssp_wait();                                //Wait till completion of event
    while(ACKSTAT)                              //check if acknowledgement signal received 
        i2c_restart;                            //If no acknowledgement received, give restart signal and transmit data again
        goto L1;

In I2C, for receiving option the slave should be accessed in read mode. The data received from the slave is stored in the SSPBUF register.

unsigned char i2c_read(void)
    RCEN=1;                                     //Enable reception in Master device 
    while(!BF);                                 //Wait till buffer is full
    RCEN=0;                                     //Enable reception in Master device 
    return SSPBUF;                              //return received data

After receiving each byte of data, the master should Acknowledge the slave. During acknowledgement of the data, the ACKDT bit should be cleared.
If the data received is sufficient and no more data is required then master should initiate a non-acknowledgement instead of an acknowledgement. During non-acknowledgement of the data, the ACKDT bit should be set.

void i2c_ack(void)
    ACKDT=0;                                    //Set as acknowledgement
    ACKEN=1;                                    //Initiate acknowledgement signal
    mssp_wait();                                //Wait till completion of event

void i2c_nack(void)
    ACKDT=1;                                    //Set as negative acknowledgement
    ACKEN=1;                                    //Initiate negative acknowledgement signal
    mssp_wait();                                //Wait till completion of event

Both data reception and transmission operation in an I2C bus should be terminated with a stop condition. Stop condition will release the I2C bus between the master and communicating slave. The bus will now be available to other devices in the bus.

void i2c_stop(void)
    PEN=1;                                      //Initiate Stop condition
    mssp_wait();                                //Wait till completion of event
    SSPEN=0;                                    //Disable I2C operation

In cases in which the master doesn’t need to terminate the present communication, but needs to change the operation or slave device, then it should initiate a repeated start condition in the bus. A repeated condition is nothing but a start condition without a stop condition. So the bus will not be released. This is normally used in networks which have more than one master device.

void i2c_restart(void)
    RSEN=1;                                     //Initiate restart condition
    mssp_wait();                                //Wait till completion of event
Spread the love, share this