I2C for PIC Microcontrollers

Table of Contents

In this article, we will take a closer look at the I2C serial communication protocol. This is the second in a series of three articles covering serial communication; the others focus on USART and SPI. In this post we will explain what I2C is, how it functions, and how you can implement it in your own projects. The article begins with a brief introduction to I2C and an overview of its basics. Following that, we will delve into configuring the hardware in Microchip PIC microcontrollers. Next, we’ll explore the I2C protocols as outlined by NXP, and guide you through setting it up in both Master Mode and Slave Mode. Finally, we’ll develop a simple Point-to-Point example demonstrating how two PIC microcontrollers can communicate with each other. While we will use the PIC16F877A in this tutorial, it can easily be adapted to any other PIC MCU.

1. Introduction of I2C

The I2C (Inter-Integrated Circuit) protocol, initially developed by NXP (formerly Philips Semiconductors) in 1982, has long been a cornerstone of modern electronics. Often referred to as “Two Wire Interface (TWI)” in certain microcontroller families due to trademark considerations, this versatile serial communication standard plays a fundamental role in connecting and orchestrating communication between various electronic components on the same board.

With its hallmark features of being multi-master, multi-slave, and synchronous, I2C’s efficient use of just two wires, a data line (SDA) and a clock line (SCL), has made it indispensable for connecting sensors, EEPROMs, displays, and other peripheral devices to microcontrollers and processors. Its bidirectional, half-duplex nature ensures synchronized data transmission in both directions. Moreover, I2C’s simplicity and adaptability have led to its integration into various control architectures, further solidifying its place as a go-to solution for intra-board communication within electronic systems.

2. Physical Layer

The physical layer of the I2C (Inter-Integrated Circuit) communication protocol defines the electrical characteristics and connections between the different devices on the bus, such as the master and slaves. It encompasses aspects such as voltage levels, clock speed, and physical connections. Figure 1 gives an example of a single master connected with multiple slaves through SDA (data) and SCL (clock) lines. The pin layout on the PIC16F877A is as follows:

  • Serial clock (SCL) – RC3/SCK/SCL
  • Serial data (SDA) – RC4/SDI/SDA
Fig 1. Hardware layout of a typical I2C bus (SCL and SDA lines) with a single master and several slaves. Additional pull-up resistors are used to pull the lines high when no device is actively driving them low.
Fig 1. Hardware layout of a typical I2C bus (SCL and SDA lines) with a single master and several slaves. Additional pull-up resistors are used to pull the lines high when no device is actively driving them low.

Some of the key aspects of physical layer in I2C communication:

  • Voltage Levels: I2C supports different voltage levels, commonly 5V and 3.3V. The voltage levels must be compatible across all devices on the bus. Modern devices often support both 5V and 3.3V, but it’s crucial to verify the voltage compatibility of all connected components.

  • Open-Drain or Open-Collector Outputs: I2C devices typically use open-drain or open-collector outputs. This means that the devices can only pull the bus low (sink current), but they cannot drive it high (source current). This is why pull-up resistors are necessary.

  • Pull-Up Resistors: As mentioned earlier, pull-up resistors are required on both the SDA (data) and SCL (clock) lines. These resistors pull the lines high when no device is actively driving them low.

 

The specifications of I2C define a maximum allowable capacitance for the bus. This is important to consider when calculating the required pull-up resistor values, as excessive capacitance can lead to slow signal rise times. Increasing the length will increase this capacitance, and higher resistor values are required to maintain reliable communication. Both these impact the clock speed of the communication. Common speeds are 100 kHz (Standard Mode) and 400 kHz (Fast Mode). There are also higher speed modes like Fast Mode Plus (1 MHz) and High-Speed Mode (3.4 MHz). The resistance can be calculated, but you should know the capacitance of the wire for this. For easy reference use the following guideline:

  • 2.2kΩ: Fast Mode and beyond, 400 kHz and up. Can be used for short to moderate bus lengths.

  • 4.7kΩ: It’s often suitable for both Standard Mode (100 kHz) and Fast Mode (400 kHz).

  • 10kΩ: Standard Mode, 100 kHz. Can be used for longer bus lengths, as higher resistance values help reduce current consumption and can tolerate higher capacitance.

3. I2C Protocol

Sending a message over I2C follows a particular protocol that consists of several basic conditions. The Figure below shows an example of this protocol. It starts with a Start condition (S), followed by a 7 (or 10) bit slave adress, with a R/W bit, that determines if the master wants to read from or write to this slave device. The slave with this address will acknowledge back to the master by sending an ACK. If the slave cannot keep up with the clock speed and is still processing, it can stretch the clock, holding it down. Depending on the amount of data being send, the next 8 bits are data, followed by an ACK between every byte of data. After all the data is send, the Master can terminate the communication by the Stop condition (P). In case the communication was not received properly, a NACK (Not Ackowledged) can be sent and, depending on the program, a Repeated Start (rS) can be initiated

Animated image of I2C

3.1 Start, Stop, Restart conditions

Every transmission begins with a Start condition (S) and ends with a Stop condition (P).

  • A _Start_ condition begins with SCL line HIGH and SDA line going HIGH-to-LOW.
  • A _Stop_ condition begins with SCL line HIGH and SDA line going LOW-to-HIGH.

Both the _Start__Repeated Start_ and _Stop_ condition are initiated by the master device. The bus is considered busy (no other devices can talk) after a Start or a Repeated Start condition. The bus is released after the Stop condition. The Repeated Start condition is identical to the Start, but appears somewhere midway or at the end of the datastream. E.g. this can occur after a _NACK_ has been received by the master, or if the master wants to directly communicate with a different slave.

3.2 Address / Data transmission

3.2.1 Address first

The initial 8 bits send after the Start bit are the 7 (or 10) _address bits_, followed by 1 Reading/Writing (R/W) bit. Each slave should is designated by a unique 7 bit address name, which allows the master to communicate with it. The R/W bit indicates if the master wants to read data (“1”) from the slave, or if it has to write data (“0”) to the slave device.

In standard configurations, a slave possesses a 7-bit address, allowing for a potential of up to 128 distinct slaves. It’s worth noting that specific addresses (0000 XXX and 1111 XXX) are typically reserved for specialized functions. This still leaves a substantial number available for most applications. As a helpful suggestion, you can set or clear the bit in the SSPADD register by utilizing switches on the board connected to pins of the PIC microcontroller. This way you are able to set your own address for that particular slave microcontroller.

Following the initiation of the Start condition, the subsequent 8 bits are fed into the SSPSR shift register, and bits <7:1> are compared against the value stored in the preloaded SSPADD address register. If a match is found, the hardware will autonomously generate an ACK pulse.

3.2.2. Data second

Once the master receives the acknowledgment, it will start to transmit a _byte of data_, followed by a acknowledgement from the slave. Theoretically, there is no constraint on the volume of data that can be dispatched within a single comprehensive transfer, as long as it adheres to the format of 8 bits followed by an acknowledgment. For the data transmission, the MSB is always send first.

In the case the slave performs another action, preventing it from receiving or transmitting another data byte, it can hold the clock line (SCL) in a low state, which forces the master into a wait state. This is called _clock stretching_ and is an optional feature, although it is not commonly supported by most slave devices (e.g. temperature sensors).

3.3 Acknowledgment ACK & NACK

The acknowledgment takes place after every byte (both the address byte and data byte) on the ninth bit. There are two possible outcomes:

  • For an _ACK_, the receiver will pull the SDA line LOW during the ninth clock pulse.
  • For a _NACK_, the receiver leaves the SDA line HIGH during the ninth clock pulse.

The master is providing the clock pulses on the SCL line. After the data has been sent, the master will release the SDA line, allowing the slave to send back the (N)ACK. A NACK will be given for the following reasons:

  • No device with that address exists, so the line is automatically pulled HIGH.
  • The slave device is not ready for communication; e.g. it might be performing other tasks.
  • The slave receives data it cannot handle.
  • The slave can no longer receive more data.
  • The master in receive mode, is telling the slave to stop sending more data.

4. Modes

The Master Synchronous Serial Port (MSSP) module can operate in two modes, either in SPI or I2C; the latter is then divided into Full Master mode and Slave mode. Both master and slave mode of I2C will be covered in this section. We will go over each separately, and explain how to set it up to your liking.

The MSSP module has 6 registers for I2C operation:

Registers Description
SSPCON
Control register; enables SDA & SCL, Sets it to Master or Slave, etc.
SSPCON2
Control register; enables Start, Repeated Start & Stop conditions, enables Acknowledgments.
SSPSTAT
Status register (mainly read only); Indicator of Start/Stop bits, R/W bit information, Buffer full bit, etc.
SSPSR
Receives the incoming data in a shift register.
SSPBUF
This register can be interacted with to read from or to write to.
SSPADD
Contains the slave adress in slave mode, and baudrate in master mode.

4.1 Master mode

4.1.1. Initialization

To initialize Master mode, we need to:

  • set the SCL and SDA pins, RC3 and RC4 respectively,  as high-impedance input using the TRISC register,
  • set the baud rate register by writing to the SSPADD register,
  • Select and enable Master mode in I2C.

The Baud Rate value is loaded in lower 7 bits <6:0> of the SSPADD register of the master device. The value that you need to set a specific Baud Rate can be calculated through the following equation:

Baud Rate = Fosc / ( 4 × ( SSPADD + 1) )

				
					void I2C_Init_Master() {
    TRISC3 = 1; // SCL pin set as input
    TRISC4 = 1; // SDA pin set as input

    SSPSTAT = 0x80; // Slew Rate Control Disabled, other bits are cleared (or read only)
    SSPCON = 0x28; // Enable I2C, Master mode, clock = Fosc/(4*(SSPADD+1)), and enables SDA/SCL with SSPEN.

    SSPADD = 0x31; // For 100kHz I2C frequency with Fosc = 20MHz
}
				
			

4.1.2. Transmit sequence

As stated in the datasheet of PIC16F877A, the transmit sequence in the master mode is as is described below, and we will show how to properly address these in the program code down below.

  1. The user generates a _Start_ condition by setting the Start Enable bit, SEN (SSPCON2<0>).
  2. SSPIF is set. The MSSP module will wait the required start time before any other operation takes place.
  3. The user loads the SSPBUF with the _slave address_ to transmit.
  4. Address is shifted out the SDA pin until all 8 bits are transmitted.
  5. The MSSP module shifts in the _ACK_ bit from the slave device and writes its value into the SSPCON2 register (SSPCON2<6>).
  6. The MSSP module generates an interrupt at the end of the ninth clock cycle by setting the SSPIF bit.
  7. The user loads the SSPBUF with _eight bits of data_.
  8. Data is shifted out the SDA pin until all 8 bits are transmitted.
  9. The MSSP module shifts in the _ACK_ bit from the slave device and writes its value into the SSPCON2 register (SSPCON2<6>).
  10. The MSSP module generates an interrupt at the end of the ninth clock cycle by setting the SSPIF bit.
  11. The user generates a _Stop_ condition by setting the Stop Enable bit, PEN (SSPCON2<2>).
  12. Interrupt is generated once the Stop condition is complete.

_

Before we go into more detail on the different functions/commands, it is important for the master device to know if the bus is idle, so it can proceed with its next command. An easy method to check this is by a special function as described by the datasheet for the SSPSTAT register:

R/W: Read/Write bit information (I2C mode only)

In Master mode:
1 = Transmit is in progress
0 = Transmit is not in progress
Note: ORing this bit with SEN, RSEN, PEN, RCEN or ACK (see below for more information) will indicate if the MSSP is in idle mode.

				
					void I2C_Wait()
{
    // ORing the R/W bit in SSPSTAT with SEN, RSEN, PEN, RCEN and ACK bits in SSPCON2
    while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
				
			
Start, Stop and Repeated Start

Now that we know how to check if the bus is idle or not, we can create the _Start_, _Stop_ and _Repeated Start_ conditions; this can be done by looking at the SSPCON2<2:0> register:

SSPCON2

GCEN: General Call Enable bit (Slave mode only)
1 = Enable interrupt when a general call address (0000h) is received in the SSPSR
0 = General call address disabled

ACKSTAT: Acknowledge Status bit (Master Transmit mode only)
1 = Acknowledge was not received from slave
0 = Acknowledge was received from slave

ACKDT: Acknowledge Data bit (Master Receive mode only)
1 = Not Acknowledge
0 = Acknowledge

ACKEN: Acknowledge Sequence Enable bit (Master Receive mode only)
1 = Initiate Acknowledge sequence on SDA and SCL pins and transmit ACKDT data bit.
Automatically cleared by hardware.
0 = Acknowledge sequence Idle

RCEN: Receive Enable bit (Master mode only)
1 = Enables Receive mode for I2C
0 = Receive Idle

PEN: Stop Condition Enable bit (Master mode only)
1 = Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.
0 = Stop condition Idle

RSEN: Repeated Start Condition Enabled bit (Master mode only)
1 = Initiate Repeated Start condition on SDA and SCL pins. Automatically cleared by hardware.
0 = Repeated Start condition Idle

SEN: Start Condition Enabled/Stretch Enabled bit
In Master Mode
1 = Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware.
0 = Start condition Idle
In Slave Mode
1 = Clock stretching is enabled for both slave transmit and slave receive (stretch enabled)
0 = Clock stretching is enabled for slave transmit only (PIC16F87X compatibility)

All three conditions are similar; we first wait to see if the bus is idle or not, and then set the corresponding bit, which will initiate the condition on the SDA and SCL pins. It is also automatically cleared by the hardware. The three conditions are as follows:

				
					void I2C_Start()
{
    I2C_Wait();
    // Initiate Start condition
    SEN = 1;
}
				
			
				
					void I2C_Stop()
{
    I2C_Wait();
    // Initiate Stop condition
    PEN = 1;
}
				
			
				
					void I2C_Repeat()
{
    I2C_Wait();
    // Initiate Repeated Start condition
    RSEN = 1;
}
				
			
Writing as Master

The process of writing a byte as a Master device can involve sending a _7-bit address with a 1-bit read/write_ indication or just an _8-bit data_ payload. This operation can be separated into two distinct segments. Firstly, one must load the data onto the SSPBUF register, which will then be automatically transferred to a shift register, named SSPSR, and shifted out. Secondly, it is important to receive the corresponding _ACK/NACK_ signal sent by the slave and act accordingly, which may require resending the data. These parts can be performed when the bus is idle, which is why the I2C_Wait() function is used as well.

				
					unsigned char I2C_Write_Master(unsigned char Data)
{
  I2C_Wait(); // Wait for the I2C bus to be ready for communication
  SSPBUF = Data; // Load the data to be sent into the SSPBUF register
  I2C_Wait(); // Wait for the I2C bus to be ready again
  return ACKSTAT; // Return the acknowledgment status received from the slave (returns ACK or NACK)
}
				
			

The I2C_Write_Master function has an 8 bit char as input; and returns either a “0” or a “1”, which corresponds to an _ACK_ or a _NACK_ respectively. It is up to the user to create a response for the acknowledgment, if needed.

Receiving as Master

It is essential to note that as a Master, it may be necessary to receive data. In this case, the reading of _data_ from the SSPBUF register and the sending of _ACK/NACK_ become important. These two functions can be done as follows:

				
					unsigned char I2C_Read_Master(void)
{
  RCEN = 1;        // Enable & Start Reception (Set Receive Enable bit)
  while(!SSPIF);   // Wait Until Completion (Wait for the I2C interrupt flag to be set)
  SSPIF = 0;       // Clear The Interrupt Flag Bit (Reset the I2C interrupt flag)
  return SSPBUF;   // Return The Received Byte (Retrieve the received data from SSPBUF)
}
				
			
				
					void I2C_SendAckNack(unsigned char ackBit)
{
  I2C_Wait();
  ACKDT = ackBit; // Set the ACK = "0" or NACK = "1" bit
  ACKEN = 1;      // Send ACK/NACK Signal
}
				
			

4.2 Slave mode

4.2.1 Initialization

To initialize Slave mode, we need to:

  • set the SCL and SDA pins, RC3 and RC4 respectively,  as high-impedance input using the TRISC register,
  • set the assigned _address_ by writing it to the SSPADD register,
  • Select and enable Slave mode in I2C.
  • Enable interrupts.
				
					void I2C_Slave_Init(unsigned char SlaveAddress) {
    TRISB0 = 1; // SDA pin set as input
    TRISB1 = 1; // SCL pin set as input

    SSPADD = SlaveAddress << 1; // Set I2C Device 7-bit Address 
    SSPSTAT = 0x80;   // Disable Slew Rate Control (Standard Mode)
    SSPCON1 = 0x36;   // Enable I2C, Slave mode, other bits are cleared
    SSPCON2 = 0x01;   // Enable Clock Stretching

    // Enable interrupts
    SSPIE = 1;        // Enable I2C interrupt
    SSPIF = 0;        // Clear the I2C interrupt flag right from the start
    PEIE = 1;         // Enable Peripheral Interrupt
    GIE = 1;          // Enable Global Interrupt
}
				
			

4.2.2 Receive/transmit sequence for slaves

As stated in the datasheet of PIC16F877A, the receive sequence in the slave mode is as is described below, and we will show how to properly address these in the program code further on. The following only takes place in the hardware if the address matches the slave and both the Buffer Full bit is clear (BF = 0) and if the Overflow bit is clear (SSPOV = 0)

  1. The SSPSR register value is loaded into the SSPBUF register.
  2. The Buffer Full bit, BF, is set.
  3. An _ACK_ pulse is generated.
  4. MSSP Interrupt Flag bit, SSPIF (PIR1<3>), is set (interrupt is generated if enabled) on the falling edge of the ninth SCL pulse.

As the previous part was done mainly by hardware alone, most of the programming is on what to do when the interrupt flag is set. This can be best done using an Interrupt Service Routine, which is described here. During the ISR, you have to deal with 3 options;

  • There is a buffer overflow or bus collision (the BF and/or SSPOV was set to 1 beforehand),
  • If the R/W bit of the _address byte_ is clear, and the address matches, you can start reading data,
  • if the R/W bit of the _address byte_ is set, and the address matches, you can start writing data to the master.

For this tutorial, we will not look at the buffer overflow, but rather at reading and writing a single byte of data.

				
					void __interrupt() ISR(void)
{
    if(PIR1.SSPIF) // Check if I2C interrupt flag is set
    {
            // Check if it's a Read operation
            if (!SSPSTAT.R_nW)
            {
                while(!SSPSTAT.BF);     // Wait until buffer is full
                RX_Data = SSPBUF;       // Read The Received Data Byte
                SSPCON.CKP = 1;         // Release Clock Line SCL
            }
            else // Check if it's a Write operation
            {
                BF = 0;                 // Clear buffer full flag
                SSPBUF = TX_Data;       // Write Your Data
                SSPCON.CKP = 1;         // Release Clock Line SCL
                while(SSPSTAT.BF);      // Wait until buffer is empty
            }
        }
        PIR1.SSPIF = 0;  // Clear the I2C interrupt flag
    }
}
				
			

5. I2C example

I2C Master example

This is an example of an I2C Master device that transmits a single byte to a slave with a certain address. As such, not all functions that have been described are used.

				
					#include <pic16f877a.h>
#include <xc.h>

// Define the frequency of the external crystal oscillator
#define _XTAL_FREQ 20000000  // 20MHz external crystal oscillator frequency

void I2C_Init_Master();
void I2C_Wait();
void I2C_Start();
void I2C_Stop();
unsigned char I2C_Write_Master(unsigned char Data);

void main() 
{
    I2C_Init_Master();
    while(1)
    {
        I2C_Start();
        I2C_Write_Master(0x48);
        I2C_Write_Master(0xC7);
        I2C_Stop();
        
        __delay_ms(1000);
        
        I2C_Start();
        I2C_Write_Master(0x48);
        I2C_Write_Master(0x00);
        I2C_Stop();
        
        __delay_ms(1000);
    }
}

// end main ------------------------------------------------

void I2C_Init_Master() {
    TRISC3 = 1; // SCL pin set as input
    TRISC4 = 1; // SDA pin set as input

    SSPSTAT = 0x80; // Slew Rate Control Disabled, other bits are cleared (or read only)
    SSPCON = 0x28; // Enable I2C, Master mode, clock = Fosc/(4*(SSPADD+1)), and enables SDA/SCL with SSPEN.

    SSPADD = 0x31; // For 100kHz I2C frequency with Fosc = 20MHz
}

void I2C_Wait()
{
    // ORing the R/W bit in SSPSTAT with SEN, RSEN, PEN, RCEN and ACK bits in SSPCON2
    while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}

void I2C_Start()
{
    I2C_Wait();
    // Initiate Start condition
    SEN = 1;
}

void I2C_Stop()
{
    I2C_Wait();
    // Initiate Stop condition
    PEN = 1;
}

unsigned char I2C_Write_Master(unsigned char Data)
{
  I2C_Wait(); // Wait for the I2C bus to be ready for communication
  SSPBUF = Data; // Load the data to be sent into the SSPBUF register
  I2C_Wait(); // Wait for the I2C bus to be ready again
  return ACKSTAT; // Return the acknowledgment status received from the slave (returns ACK or NACK)
}
				
			
I2C Slave example

This is the example of an I2C Slave device that receives a single byte of data. Again, same as before, not all functions that have been described are used. But it does give you a start.

				
					#include <pic16f877a.h>
#include <xc.h>

// Define the frequency of the external crystal oscillator
#define _XTAL_FREQ 20000000  // 20MHz external crystal oscillator frequency
unsigned char RX_Data = 0x00;
unsigned char TX_Data = 0x00;

void I2C_Slave_Init(unsigned char SlaveAddress);

void main()
{
    I2C_Slave_Init(0x48); // The address should correspond to where the master is sending it to
    
    while(1)
    {
        // Do something with incoming RX_Data here
    }
}

// end main ------------------------------------------------

void I2C_Slave_Init(unsigned char SlaveAddress) {
    TRISB0 = 1; // SDA pin set as input
    TRISB1 = 1; // SCL pin set as input

    SSPADD = SlaveAddress << 1; // Set I2C Device 7-bit Address 
    SSPSTAT = 0x80;   // Disable Slew Rate Control (Standard Mode)
    SSPCON1 = 0x36;   // Enable I2C, Slave mode, other bits are cleared
    SSPCON2 = 0x01;   // Enable Clock Stretching

    // Enable interrupts
    SSPIE = 1;        // Enable I2C interrupt
    SSPIF = 0;        // Clear the I2C interrupt flag right from the start
    PEIE = 1;         // Enable Peripheral Interrupt
    GIE = 1;          // Enable Global Interrupt
}

void __interrupt() ISR(void)
{
    if(PIR1.SSPIF) // Check if I2C interrupt flag is set
    {
            // Check if it's a Read operation
            if (!SSPSTAT.R_nW)
            {
                while(!SSPSTAT.BF);     // Wait until buffer is full
                RX_Data = SSPBUF;       // Read The Received Data Byte
                SSPCON.CKP = 1;         // Release Clock Line SCL
            }
            else // Check if it's a Write operation
            {
                BF = 0;                 // Clear buffer full flag
                SSPBUF = TX_Data;       // Write Your Data
                SSPCON.CKP = 1;         // Release Clock Line SCL
                while(SSPSTAT.BF);      // Wait until buffer is empty
            }
        }
        PIR1.SSPIF = 0;  // Clear the I2C interrupt flag
    }
}

				
			

5.1 Additional implementations

Slave - Receiving multiple bytes at once

For certain programs, sending a single byte between the Start & Stop conditions isn’t practical, rather you have to send several bytes. You will have to change the code for this, similar to what was done for UART. One way to solve it, is by using a buffer array, where all data bytes can be stored. I will show you such an example here:

				
					// Should be declared somewhere at the top of your file,
// That way, they can be used anywhere in your program.

#define BUFFER_SIZE 2

unsigned char RX_Buffer[BUFFER_SIZE];
unsigned char TX_Buffer[BUFFER_SIZE] = {0xAA, 0xBB};

// This ISR can be used for 2 data bytes after the address

void __interrupt() ISR(void)
{
    if(SSPIF) // Check if I2C interrupt flag is set
    {
        if (!R_nW) // Read operation
        {
            while(!BF); // Wait until buffer is full
            RX_Data[0] = SSPBUF; // Read The Received Data Byte 1
            CKP = 1; // Release Clock Line SCL

            while(!BF); // Wait until buffer is full
            RX_Data[1] = SSPBUF; // Read The Received Data Byte 2
            CKP = 1; // Release Clock Line SCL
        }
        else // Write operation
        {
            BF = 0; // Clear buffer full flag
            SSPBUF = TX_Data[0]; // Write Your Data 1
            CKP = 1; // Release Clock Line SCL
            while(BF); // Wait until buffer is empty

            BF = 0; // Clear buffer full flag
            SSPBUF = TX_Data[1]; // Write Your Data 2
            CKP = 1; // Release Clock Line SCL
            while(BF); // Wait until buffer is empty
        }
        
        SSPIF = 0; // Clear the I2C interrupt flag
    }
}
				
			
Slave - Checking for buffer overflow, or bus collisions.
				
					void __interrupt() ISR(void)
{
    if(SSPIF) // Check if I2C interrupt flag is set
    {
        // Check for Bus Collision or Buffer Overflow
        if (SSPOV || WCOL)
        {
            SSPOV = 0;           // Clear the overflow flag
            WCOL = 0;            // Clear the collision bit
            CKP = 1;             // Release Clock Line SCL
        }
        else
        {
            // Check if it's a Read operation
            if (!R_nW)
            {
                while(!BF);          // Wait until buffer is full
                RX_Data = SSPBUF;    // Read The Received Data Byte
                CKP = 1;    // Release Clock Line SCL
            }
            else // Check if it's a Write operation
            {
                BF = 0;              // Clear buffer full flag
                SSPBUF = TX_Data;    // Write Your Data
                CKP = 1;             // Release Clock Line SCL
                while(BF);           // Wait until buffer is empty
            }
        }
        SSPIF = 0;  // Clear the I2C interrupt flag
    }
}
				
			
Share
Tweet
Share
Pin
Email
0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments