I2C Communication Protocol Tutorial

Previous Tutorial Previous Tutorial Tutorial 26 Next Tutorial Next Tutorial
I2C Communication Protocol With PIC MCUs
Intermediate Level ★★☆☆☆

I2C Tutorial With PIC Microcontrollers Thumbnail

 

In this article, we’ll deep dive into I2C serial communication protocol. What is I2C communication? And how I2C works? And where I2C bus is used? And much more questions will be answered. This article will start by defining the I2C communication basics and show you the I2C bus specifications and standards as published by NXP the originator of I2CTM. Then we’ll discuss specifically the I2C hardware in Microchip PIC microcontrollers, and see how it works and how to configure it. We’ll then develop a simple driver code to test it out. And finally, do a couple of LAB projects to put it all together and get it to work practically.

It’s going to be a very long read, so stick around and let’s get started!

Tutorial Contents


   Required Components For This Tutorial   

 

Qty. Component Name Buy On Amazon.com
2 PIC16F877A or PIC18F2550 or anyother Add
2 BreadBoard Add
8 LED Add    Add
1 Resistors Kit Add    Add
1 Capacitors Kit Add    Add
1 Jumper Wires Pack Add    Add
2 LM7805 Voltage Regulator (5v) Add
2 Crystal Oscillator Add
1 PICkit2 or 3 Programmer Add
2 9v Battery or DC Power Supply Add    Add    Add

The Prototyping Board Setup

Prototyping Board - Embedded Systems Tutorials With PIC MCUs

Debugging Tools:

 


   Introduction To I2C   

Up till now, we’ve introduced UART, SPI serial communication ports. We’ve also done a handful of practical LABs using both of them. Before introducing the I2C bus, let’s just quickly review the previous serial ports.

UART

Universal Asynchronous Receiver/Transmitter or UART for short represents the hardware circuitry (module) being used for serial communication. UART is sold/shipped as a standalone integrated circuit (IC) or as an internal module within microcontrollers. There is a couple of io pins dedicated to the UART serial communication module highlighted in the following figure. Namely, RX (data input – receiving end) & TX (data output – transmitting end).

There are actually two forms of UART Hardware as follows:

  • UART – Universal Asynchronous Receiver/Transmitter
  • USART – Universal Synchronous/Asynchronous Receiver/Transmitter

The Synchronous type of transmitters generates the data clock and sends it to the receiver which works accordingly in a synchronized manner. On the other hand, the Asynchronous type of transmitter generates the data clock internally. There is no incoming serial clock signal, so in order to achieve proper communication between the two ends, both of them must be using the same baud rate. The baud rate is the rate at which bits are being sent bps (bits per second).

SPI

SPI is an acronym for (Serial Peripheral Interface) pronounced as “S-P-I” or “Spy”. Which is a serial, synchronous, single-master, multi-slave, full-duplex interface bus typically used for serial communication between microcomputer systems and other devices, memories, and sensors. Usually used to interface Flash Memories, ADC, DAC, RTC, LCD, SDcards, and much more.

The SPI was originally developed by Motorola back in the 80s to provide full-duplex serial communication to be used in embedded systems applications. Specifically for very short distance communications (ob-board).

In typical SPI communication, there should be at least 2 devices attached to the SPI bus. One of them should be the master and the other will essentially be a slave. The master initiates communication by generating a serial clock signal to shift a data frame out, at the same time serial data is being shifted-in to the master. This process is the same whether it’s a read or write operation.

I2C

I2C (i-square-c) is an acronym for “Inter-Integrated-Circuit” which was originally created by Philips Semiconductors (now NXP) back in 1982. I2CTM is a registered trademark for its respective owner and maybe it was the reason they call it “Two Wire Interface (TWI)” in some microcontrollers like Atmel AVR. The I2C is a multi-master, multi-slave, synchronous, bidirectional, half-duplex serial communication bus. It’s widely used for attaching lower-speed peripheral ICs to processors and microcontrollers in short-distance, intra-board communication.

The I2C-bus is used in various control architectures such as System Management Bus (SMBus), Power Management Bus (PMBus), Intelligent Platform Management Interface (IPMI), Display Data Channel (DDC) and Advanced Telecom Computing Architecture (ATCA).

 

Definitions of I2C Bus Terminologies

 


   Features Of I2C Bus   

 

Here are some of the most important features of I2C Bus:

  • Only two bus lines are required; a serial data line (SDA) and a serial clock line (SCL).
  • Each device connected to the bus is software addressable by a unique address and simple master/slave relationships exist at all times; masters can operate as master-transmitters or as master-receivers.
  • It is a true multi-master bus including collision detection and arbitration to prevent data corruption if two or more masters simultaneously initiate data transfer.
  • Serial, 8-bit oriented, bidirectional data transfers can be made at up to 100 kbit/s in the Standard-mode, up to 400 kbit/s in the Fast-mode, up to 1 Mbit/s in Fast-mode Plus, or up to 3.4 Mbit/s in the High-speed mode.
  • Serial, 8-bit oriented, unidirectional data transfers up to 5 Mbit/s in Ultra Fast-mode
  • On-chip filtering rejects spikes on the bus data line to preserve data integrity.
  • The number of ICs that can be connected to the same bus is limited only by a maximum bus capacitance. More capacitance may be allowed under some conditions.
  • Supports various control architectures such as System Management Bus (SMBus), Power Management Bus (PMBus), Intelligent Platform Management Interface (IPMI), Display Data Channel (DDC) and Advanced Telecom Computing Architecture (ATCA).

I2C Applications Examples Diagram

I2C Applications Examples Diagram

 


   I2C Bus Protocol   

 

In this section, we’ll start discussing the I2C Bus protocol in detail. What are the essential elements of I2C transactions? And how does the physical layer of I2C look like? And the modes, speeds of I2C, and much more. This is going to be the densest part of this tutorial and will be divided into many sub-sections for each topic. One at a time to successively build a very deep understanding of every little detail in the working mechanics of I2C communication.

I2C Modes & Bus Speeds

Originally, the I2C-bus was limited to 100 kbit/s operation. Over time there have been several additions to the specification so that there are now five operating speed categories. Standard-mode, Fast-mode (Fm), Fast-mode Plus (Fm+), and High-speed mode (Hs-mode) devices are downward-compatible. Which means any device may be operated at lower bus speed. Ultra Fast-mode devices are not compatible with previous versions since the bus is unidirectional.

Bidirectional bus:

  • Standard-Mode (Sm), with a bit rate up to 100 kbit/s
  • Fast-Mode (Fm), with a bit rate up to 400 kbit/s
  • Fast-Mode Plus (Fm+), with a bit rate up to 1 Mbit/s
  • High-speed Mode (Hs-mode), with a bit rate up to 3.4 Mbit/s.

Unidirectional bus:

  • Ultra Fast-Mode (UFm), with a bit rate up to 5 Mbit/s

Note: You have to refer to the specific device datasheet to check the typical details for the i2c hardware specifications that have actually been implemented on-chip.

I2C Physical Layer (Hardware)

Have a quick look at this short demo figure. Then continue reading and let me explain what’s going on over there.

I2C Bus Hardware - Physical Layer Demo

The I2C bus uses what’s known as an open-drain (open-collector) output driver. You should also note that the I2C pins (SDA & SCL) both should be isolated from the IO pins driver (PORT Register), so these pins must be configured as input which makes the TRIState in high-impedance mode. As if they aren’t connected to the IO output driver and now both of SCL, SDA line are under the control of the I2C open-drain output driver.

As the name suggests, the open-drain driver transistor connects the SDA or SCL pin to ground if it’s activated (input=1), if not the pins will be floating as they are connected to open drains. To eliminate this floating state, we must pull the I2C bus line up with external resistors, so its IDLE state is HIGH. When an open drain driver gets activated, the line will go down (LOW). And that’s how open drain drivers work.

When the driver transistor is ON, the line is held LOW.

When the driver transistor is OFF, the line is released and pulled to HIGH (by the effect of external pull-up resistors).

A typical I2C message has a format like the shown above in the figure. Starting with a start condition, ending by a stop condition. This format will be discussed in more detail hereafter. But the thing to note is as follows.

PCBgogo Ad

If 2 master MCUs (MCU1 And MCU2) are sending data to the same slave (motor driver) at the same time. This will cause no problem, and here is why I2C bus is immune to such conditions. Both messages are identical in the first part (address) as they are actually sending to the same slave with the same address. But the data byte will be definitely different. At a moment, one of them will put 1 to the bus and the other will put 0.

This will not cause a short! instead, the master who puts 1 is actually releasing the SDA line while the other is holding it low. So, the MCU1 master chip will lose arbitration and will have to wait until MCU2 finished the communication so it can start once again!

This great feature in the I2C bus is called “Arbitration”. And will be discussed in detail hereafter in the article.

SDA & SCL, Data Validity

Both SDA and SCL are bidirectional lines, connected to a positive supply voltage via a current-source or pull-up resistor. When the bus is free, both lines are HIGH. The output stages of devices connected to the bus must have an open-drain or open-collector to perform the wired-AND function. Data on the I2C-bus can be transferred at rates of up to 100 kbit/s in the Standard-mode, up to 400 kbit/s in the Fast-mode, up to 1 Mbit/s in Fast-mode Plus, or up to 3.4 Mbit/s in the High-speed mode. The bus capacitance limits the number of interfaces connected to the bus.

Due to the variety of different technology devices (CMOS, NMOS, bipolar), that can be connected to the I2C-bus, the levels of the logical ‘0’ (LOW) and ‘1’ (HIGH) are not fixed and depend on the associated level of VDD. Input reference levels are set as 30 % and 70 % of VDD; VIL is 0.3VDD and VIH is 0.7VDD.

The data on the SDA line must be stable during the HIGH period of the clock. The HIGH or LOW state of the data line can only change when the clock signal on the SCL line is LOW. One clock pulse is generated for each data bit transferred.

I2C Data Validity Timing Diagram

Elements of I2C Transactions

A typical I2C message consists of some basic elements (conditions) that take place on the I2C bus sequentially and it always starts with a start condition (s). Followed by the desired slave device address (7-Bits or 10-Bits), then a R/W bit to determine whether the master (who initiated the S condition for communication) wants to read or write to this slave having that address. Then if the slave exists and works OK, it’ll acknowledge back to the master by sending an Acknowledge bit ACK otherwise, it’s considered a Negative Acknowledge NACK.  Afterward, the byte of Data is sent, followed by acknowledging from the slave. And finally, the master can terminate the communication by sending the Stop Condition (P) sequence.

I2C_write

We can summarize these conditions (elements) of I2C bus signaling as follows:

  • Start Condition (S)
  • Stop Condition (P)
  • Repeated Start (Restart) Condition (Sr)
  • Acknowledge ACK (A)
  • Not Acknowledge NACK (~A)
  • Address + R/W
  • Data Byte

( Start, Stop, Restart ) Conditions

All transactions begin with a START (S) and are terminated by a STOP (P).
> A HIGH-to-LOW transition on the SDA line, while SCL is HIGH, defines a START condition.
> A LOW-to-HIGH transition on the SDA line, while SCL is HIGH, defines a STOP condition.

I2C Start And Stop Conditions

START and STOP conditions are always generated by the master. The bus is considered to be busy after the START condition. The bus is considered to be free again a certain time after the STOP condition. The bus stays busy if a repeated START (Sr) is generated instead of a STOP condition. In this respect, the START (S) and repeated START (Sr) conditions are functionally identical.

i2c_restart_condition

Detection of START, STOP, and RESTART conditions by devices connected to the bus is easy if they incorporate the necessary interfacing hardware. However, microcontrollers with no such interface have to sample the SDA line at least twice per clock period to sense the transition.

Acknowledge ACK & NACK

ACK

The acknowledge takes place after every byte. The acknowledge bit allows the receiver to signal the transmitter that the byte was successfully received and another byte may be sent. The master generates all clock pulses, including the acknowledge ninth clock pulse. The Acknowledge signal is defined as follows: the transmitter releases the SDA line during the acknowledge clock pulse so the receiver can pull the SDA line LOW and it remains stable LOW during the HIGH period of this clock pulse.

NACK

When SDA remains HIGH during this ninth clock pulse, this is defined as the Not Acknowledge signal. It’s also called a Negative Acknowledge or simply NACK. The master can then generate either a STOP condition to abort the transfer, or a repeated START condition to start a new transfer.

I2C ACK NACK

There are five conditions that lead to the generation of a NACK:

  1. No receiver is present on the bus with the transmitted address so there is no device to respond with an acknowledge.
  2. The receiver is unable to receive or transmit because it is performing some real-time function and is not ready to start communication with the master.
  3. During the transfer, the receiver gets data or commands that it does not understand.
  4. During the transfer, the receiver cannot receive any more data bytes.
  5. A master-receiver must signal the end of the transfer to the slave transmitter.

I2C Byte Format

Every byte put on the SDA line must be eight bits long. The number of bytes that can be transmitted per transfer is unrestricted. Each byte must be followed by an Acknowledge bit. Data is transferred with the Most Significant Bit (MSB) first. If a slave cannot receive or transmit another complete byte of data until it has performed some other function, for example servicing an internal interrupt, it can hold the clock line SCL LOW to force the master into a wait state. Data transfer then continues when the slave is ready for another byte of data and releases clock line SCL.

I2C Data Transaction Format

I2C Clock Synchronization

Two masters can begin transmitting on a free bus at the same time and there must be a method for deciding which takes control of the bus and complete its transmission. This is done by clock synchronization and arbitration. In single master systems, clock synchronization and arbitration are not needed.

Clock synchronization is performed using the wired-AND connection of I2C interfaces to the SCL line. This means that a HIGH to LOW transition on the SCL line causes the masters concerned to start counting off their LOW period and, once a master clock has gone LOW, it holds the SCL line in that state until the clock HIGH state is reached. However, if another clock is still within its LOW period, the LOW to HIGH transition of this clock may not change the state of the SCL line. The SCL line is therefore held LOW by the master with the longest LOW period. Masters with shorter LOW periods enter a HIGH wait-state during this time.

I2C Clock Synchronization

When all masters concerned have counted off their LOW period, the clock line is released and goes HIGH. There is then no difference between the master clocks and the state of the SCL line, and all the masters start counting their HIGH periods. The first master to complete its HIGH period pulls the SCL line LOW again. In this way, a synchronized SCL clock is generated with its LOW period determined by the master with the longest clock LOW period, and its HIGH period determined by the one with the shortest clock HIGH period.

I2C Bus Arbitration

Arbitration, like synchronization, refers to a portion of the protocol required only if more than one master is used in the system. Slaves are not involved in the arbitration procedure. A master may start a transfer only if the bus is free. Two masters may generate a START condition within the minimum hold time (tHD;STA) of the START condition which results in a valid START condition on the bus. Arbitration is then required to determine which master will complete its transmission.

Arbitration proceeds bit by bit. During every bit, while SCL is HIGH, each master checks to see if the SDA level matches what it has sent. This process may take many bits. Two masters can actually complete an entire transaction without error, as long as the transmissions are identical. The first time a master tries to send a HIGH but detects that the SDA level is LOW, the master knows that it has lost the arbitration and turns off its SDA output driver. The other master goes on to complete its transaction.

No information is lost during the arbitration process. A master that loses the arbitration can generate clock pulses until the end of the byte in which it loses the arbitration and must restart its transaction when the bus is free. If a master also incorporates a slave function and it loses arbitration during the addressing stage, it is possible that the winning master is trying to address it. The losing master must, therefore, switch over immediately to its slave mode.

Consider The Following Example For 2 I2C Masters Arbitration

I2C Bus Arbitration Example

The moment there is a difference between the internal data level of the master generating DATA1 and the actual level on the SDA line, the DATA1 output is switched off. This does not affect the data transfer initiated by the winning master. Since control of the I2C-bus is decided solely on the address and data sent by competing masters, there is no central master, nor any order of priority on the bus. There is an undefined condition if the arbitration procedure is still in progress at the moment when one master sends a repeated START or a STOP condition while the other master is still sending data.

Clock Stretching

Clock stretching pauses a transaction by holding the SCL line LOW. The transaction cannot continue until the line is released HIGH again. Clock stretching is optional and in fact, most slave devices do not include an SCL driver so they are unable to stretch the clock.

On the byte level, a device may be able to receive bytes of data at a fast rate, but needs more time to store a received byte or prepare another byte to be transmitted. Slaves can then hold the SCL line LOW after reception and acknowledgment of a byte to force the master into a wait state until the slave is ready for the next byte transfer in a type of handshake procedure.

On the bit level, a device such as a microcontroller with or without limited hardware for the I2C-bus can slow down the bus clock by extending each clock LOW period. The speed of any master is adapted to the internal operating rate of this device.

I2C Clock Synchronization

I2C Addressing & Direction Control

Data transfers follow the format shown below. After the START condition (S), a slave address is sent. This address is seven bits long followed by an eighth bit which is a data direction bit (R/W) — a ‘zero’ indicates a transmission (WRITE), a ‘one’ indicates a request for data (READ). A data transfer is always terminated by a STOP condition (P) generated by the master. However, if a master still wishes to communicate on the bus, it can generate a repeated START condition (Sr) and address another slave without first generating a STOP condition. Various combinations of read/write formats are then possible within such a transfer.

I2C Complete Data Transfer

In general settings, the default address length is 7-bits. Which means a master can, theoretically, address up to 128 different slaves. But in fact, many slave devices gives you only 3 bits to change. And there are some other addresses reserved by the hardware of the I2C-Bus itself as we’ll see hereafter. However, the I2C-Bus can be also configured to operate in 10-Bit Addressing mode, which will significantly raise up the fundamental limitation of the number of slaves to communicate with. Where it becomes up to the limit of total bus capacitance.

I2C Master Addressing 7-Bit Slaves

 

10-Bit Addressing

10-bit addressing expands the number of possible addresses. Devices with 7-bit and 10-bit addresses can be connected to the same I2C-bus, and both 7-bit and 10-bit addressing can be used in all bus speed modes. Currently, 10-bit addressing is not being widely used.

The 10-bit slave address is formed from the first two bytes following a START condition (S) or a repeated START condition (Sr). The first seven bits of the first byte are the combination 1111 0XX of which the last two bits (XX) are the two Most-Significant Bits (MSB) of the 10-bit address; the eighth bit of the first byte is the R/W bit that determines the direction of the message.

Although there are eight possible combinations of the reserved address bits 1111 XXX, only the four combinations 1111 0XX are used for 10-bit addressing. The remaining four combinations 1111 1XX are reserved for future I2C-bus enhancements.

I2C Master Addressing 10-Bit Slave

I2C General Call & Reserved Addresses

Two groups of eight addresses (0000 XXX and 1111 XXX) are reserved for the purposes shown in the table below.

Reserved Addresses In I2C

The general call address is for addressing every device connected to the I2C-bus at the same time. However, if a device does not need any of the data supplied within the general call structure, it can ignore this address by not issuing an acknowledgment. If a device does require data from a general call address, it acknowledges this address and behaves as a slave-receiver. The master does not actually know how many devices acknowledged if one or more devices respond. The second and following bytes are acknowledged by every slave-receiver capable of handling this data. A slave who cannot process one of these bytes must ignore it by not acknowledging. Again, if one or more slaves acknowledge, the not-acknowledge will not be seen by the master. The meaning of the general call address is always specified in the second byte.

Applicability Of I2C-Bus Protocol Features

Applicability Of I2C Bus Protocol Features

 


   I2C In Microchip PIC MCUs   

 

I2C Mode For MSSP

The MSSP module in I2C mode fully implements all master and slave functions (including general call support) and provides interrupts on Start and Stop bits in hardware to determine a free bus (multi-master function). The MSSP module implements the standard mode specifications as well as 7-bit and 10-bit addressing. Two pins are used for data transfer:

  • Serial clock (SCL) – RB1/AN10/INT1/SCK/SCL
  • Serial data  (SDA) – RB0/AN12/INT0/FLT0/SDI/SDA

The user must configure these pins as inputs or outputs through the TRISB<1:0> bits.

I2C Block Diagram

I2C Block Diagram In PIC Microcontrollers

MSSP I2C Registers

The MSSP module has six registers for I2C operation. These are:

  • MSSP Control Register 1 (SSPCON1)
  • MSSP Control Register 2 (SSPCON2)
  • MSSP Status Register (SSPSTAT)
  • Serial Receive/Transmit Buffer Register (SSPBUF)
  • MSSP Shift Register (SSPSR) – Not directly accessible
  • MSSP Address Register (SSPADD)

SSPCON1, SSPCON2, and SSPSTAT are the control and status registers in I2C mode operation. The SSPCON1 and SSPCON2 registers are readable and writable. The lower six bits of the SSPSTAT are read-only. The upper two bits of the SSPSTAT are read/write.
SSPSR is the shift register used for shifting data in or out. SSPBUF is the buffer register to which data bytes are written to or read from. SSPADD register holds the slave device address when the SSP is configured in I2C Slave mode. When the SSP is configured in Master mode, the lower seven bits of SSPADD act as the Baud Rate Generator reload value.
In receive operations, SSPSR and SSPBUF together create a double-buffered receiver. When SSPSR receives a complete byte, it is transferred to SSPBUF and the SSPIF interrupt is set. During transmission, the SSPBUF is not double-buffered. A write to SSPBUF will write to both SSPBUF and SSPSR.

 

I2C Module Operation

 

The MSSP module functions are enabled by setting MSSP enable bit, SSPEN (SSPCON1<5>). The SSPCON1 register allows control of the I2C operation. Four mode selection bits (SSPCON1<3:0>) allow one of the following I2C modes to be selected:

  • I2C Master mode
  • I2C Slave mode (7-bit address)
  • I2C Slave mode (10-bit address)
  • I2C Slave mode (7-bit address) with Start and Stop bit interrupts enabled
  • I2C Slave mode (10-bit address) with Start and Stop bit interrupts enabled
  • I2C Firmware Controlled Master mode, the slave is Idle

Selection of any I2C mode with the SSPEN bit set forces the SCL and SDA pins to be open-drain, provided these pins are programmed to inputs by setting the appropriate TRISB bits. To ensure proper operation of the module, pull-up resistors must be provided externally to the SCL and SDA pins.

 

Slave Mode Operation

 

In Slave mode, the SCL and SDA pins must be configured as inputs (TRISB<1:0> set). The MSSP module will override the input state with the output data when required (slave-transmitter). The I2C Slave mode hardware will always generate an interrupt on an address match. Through the mode select bits, the user can also choose to interrupt on Start and Stop bits.

When an address is matched or the data transfer after an address match is received, the hardware automatically will generate the Acknowledge (ACK) pulse and load the SSPBUF register with the received value currently in the SSPSR register. Any combination of the following conditions will cause the MSSP module not to give this ACK pulse:

  • The Buffer Full bit, BF (SSPSTAT<0>), was set before the transfer was received.
  • The overflow bit, SSPOV (SSPCON1<6>), was set before the transfer was received.

In this case, the SSPSR register value is not loaded into the SSPBUF, but bit SSPIF (PIR1<3>) is set. The BF bit is cleared by reading the SSPBUF register, while bit SSPOV is cleared through software.

Slave Addressing

Once the MSSP module has been enabled, it waits for a Start condition to occur. Following the Start condition, the eight bits are shifted into the SSPSR register. All incoming bits are sampled with the rising edge of the clock (SCL) line. The value of register SSPSR<7:1> is compared to the value of the SSPADD register. The address is compared on the falling edge of the 8th clock (SCL) pulse. If the addresses match and the BF and SSPOV bits are clear, the following events occur:

  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 9th SCL pulse.

Slave Reception

When the R/W bit of the address byte is clear and an address match occurs, the R/W bit of the SSPSTAT register is cleared. The received address is loaded into the SSPBUF register and the SDA line is held low (ACK).

When the address byte overflow condition exists, then the no Acknowledge (ACK) pulse is given. An overflow condition is defined as either bit, BF (SSPSTAT<0>), is set or the bit, SSPOV (SSPCON1<6>), is set.

An MSSP interrupt is generated for each data transfer byte. Flag bit, SSPIF (PIR1<3>), must be cleared in software. The SSPSTAT register is used to determine the status of the byte. If SEN is enabled (SSPCON2<0> = 1), RB1/SCL will be held low (clock stretch) following each data transfer. The clock must be released by setting bit, CKP (SSPCON1<4>).

Slave Reception Timing Diagram

Slave Transmission

When the R/W bit of the incoming address byte is set and an address match occurs, the R/W bit of the SSPSTAT register is set. The received address is loaded into the SSPBUF register. The ACK pulse will be sent on the ninth bit and pin RB1/SCL is held low regardless of SEN. By stretching the clock, the master will be unable to assert another clock pulse until the slave is done preparing the transmit data. The transmit data must be loaded into the SSPBUF register which also loads the SSPSR register.

Then pin RB1/SCL should be enabled by setting bit, CKP (SSPCON1<4>). The eight data bits are shifted out on the falling edge of the SCL input. This ensures that the SDA signal is valid during the SCL high time. The ACK pulse from the master-receiver is latched on the rising edge of the ninth SCL input pulse. If the SDA line is high (not ACK), then the data transfer is complete. In this case, when the ACK is latched by the slave, the slave logic is reset (resets SSPSTAT register) and the slave monitors for another occurrence of the Start bit. If the SDA line was low (ACK), the next transmit data must be loaded into the SSPBUF register. Again, pin RB1/SCL must be enabled by setting bit CKP (SSPCON1<4>).

I2C Slave Transmission Timing Diagram

Open In New Tab Or Look it up in the datasheet

An MSSP interrupt is generated for each data transfer byte. The SSPIF bit must be cleared in software and the SSPSTAT register is used to determine the status of the byte. The SSPIF bit is set on the falling edge of the 9th clock pulse.

Slave Clock Stretching

Both 7 and 10-bit Slave modes implement automatic clock stretching during a transmit sequence. The SEN bit (SSPCON2<0>) allows clock stretching to be enabled during receives. Setting SEN will cause the SCL pin to be held low at the end of each data receive sequence.

In 7-bit Slave Receive mode, on the falling edge of the 9th clock at the end of the ACK sequence, if the BF bit is set, the CKP bit in the SSPCON1 register is automatically cleared, forcing the SCL output to be held low. The CKP bit being cleared to ‘0’ will assert the SCL line low. The CKP bit must be set in the user’s ISR before the reception is allowed to continue. By holding the SCL line low, the user has time to service the ISR and read the contents of the SSPBUF before the master device can initiate another receive sequence. This will prevent buffer overruns from occurring.

7-bit Slave Transmit mode implements clock stretching by clearing the CKP bit after the falling edge of the 9th clock if the BF bit is clear. This occurs regardless of the state of the SEN bit. The user’s ISR must set the CKP bit before transmission is allowed to continue. By holding the SCL line low, the user has time to service the ISR and load the contents of the SSPBUF before the master device can initiate another transmit sequence.

Slave Clock Synchronization & CKP Bit

When the CKP bit is cleared, the SCL output is forced to ‘0’. However, setting the CKP bit will not assert the SCL output low until the SCL output is already sampled low. Therefore, the CKP bit will not assert the SCL line until an external I2C master device has already asserted the SCL line. The SCL output will remain low until the CKP bit is set and all other devices on the I2C bus have deasserted SCL. This ensures that a write to the CKP bit will not violate the minimum high time requirement for SCL.

 

Master Mode Operation

 

Master mode is enabled by setting and clearing the appropriate SSPM bits in SSPCON1 and by setting the SSPEN bit. In Master mode, the SCL and SDA lines are manipulated by the MSSP hardware. Master mode operation is supported by interrupt generation on the detection of the Start and Stop conditions. The Stop (P) and Start (S) bits are cleared from a Reset or when the MSSP module is disabled. Control of the I2C bus may be taken when the P bit is set or the bus is Idle, with both the S and P bits clear.

In Firmware Controlled Master mode, user code conducts all I2C bus operations based on Start and Stop bit conditions. Once Master mode is enabled, the user has six options:

  1. Assert a Start condition on SDA and SCL.
  2. Assert a Repeated Start condition on SDA and SCL.
  3. Write to the SSPBUF register initiating transmission of data/address.
  4. Configure the I2C port to receive data.
  5. Generate an Acknowledge condition at the end of a received byte of data.
  6. Generate a Stop condition on SDA and SCL.

Note That

The MSSP module, when configured in I2C Master mode, does not allow queueing of events. For instance, the user is not allowed to initiate a Start condition and immediately write the SSPBUF register to initiate transmission before the Start condition is complete. In this case, the SSPBUF will not be written to and the WCOL bit will be set, indicating that a write to the SSPBUF did not occur.

The following events will cause SSP Interrupt Flag bit, SSPIF, to be set (SSP interrupt if enabled):

  • Start condition
  • Stop condition
  • Data transfer byte transmitted/received
  • Acknowledge transmit
  • Repeated Start (Restart) Condition

Operation

The master device generates all of the serial clock pulses and the Start and Stop conditions. A transfer is ended with a Stop condition or with a Repeated Start condition. Since the Repeated Start condition is also the beginning of the next serial transfer, the I2C bus will not be released.

In Master Transmitter mode, serial data is output through SDA, while SCL outputs the serial clock. The first byte transmitted contains the slave address of the receiving device (seven bits) and the Read/Write (R/W) bit. In this case, the R/W bit will be logic ‘0’. Serial data is transmitted eight bits at a time. After each byte is transmitted, an Acknowledge bit is received. Start and Stop conditions are output to indicate the beginning and the end of a serial transfer.

In Master Receive mode, the first byte transmitted contains the slave address of the transmitting device (7 bits) and the R/W bit. In this case, the R/W bit will be logic ‘1’ Thus, the first byte transmitted is a 7-bit slave address followed by a ‘1’ to indicate receive bit. Serial data is received via SDA, while SCL outputs the serial clock. Serial data is received eight bits at a time. After each byte is received, an Acknowledge bit is transmitted. Start and Stop conditions indicate the beginning and end of the transmission.

The Baud Rate Generator used for the SPI mode operation is used to set the SCL clock frequency for either 100 kHz, 400 kHz or 1 MHz I2C operation. Here is the formula to calculate the I2C Baud Rate given a specific Fosc frequency and SSPADD register. And note that you’ve to find out the value of SSPADD that you’ve to write into this register in order to obtain a specific I2C Baud Rate.

I2C Baud Rate Formula Equation

Baud Rate Generation

In I2C Master mode, the Baud Rate Generator (BRG) reload value is placed in the lower seven bits of the SSPADD register. When a write occurs to SSPBUF, the Baud Rate Generator will automatically begin counting. The BRG counts down to ‘0’ and stops until another reload has taken place. The BRG count is decremented twice per instruction cycle (TCY) on the Q2 and Q4 clocks. In I2C Master mode, the BRG is reloaded automatically. Once the given operation is complete (i.e., the transmission of the last data bit is followed by ACK), the internal clock will automatically stop counting and the SCL pin will remain in its last state.

Baud Rate Generator For I2C

Clock Arbitration

Clock arbitration occurs when the master, during any receive, transmit or Repeated Start/Stop condition, deasserts the SCL pin (SCL allowed to float high). When the SCL pin is allowed to float high, the Baud Rate Generator (BRG) is suspended from counting until the SCL pin is actually sampled high. When the SCL pin is sampled high, the Baud Rate Generator is reloaded with the contents of SSPADD<6:0> and begins counting. This ensures that the SCL high time will always be at least one BRG rollover count in the event that the clock is held low by an external device.

Clock Arbitration

Start Condition

To initiate a Start condition, the user sets the Start Enable bit, SEN (SSPCON2<0>). If the SDA and SCL pins are sampled high, the Baud Rate Generator is reloaded with the contents of SSPADD<6:0> and starts its count. If SCL and SDA are both sampled high when the Baud Rate Generator times out (TBRG), the SDA pin is driven low. The action of the SDA being driven low while SCL is high is the Start condition and causes the S bit (SSPSTAT<3>) to be set. Following this, the Baud Rate Generator is reloaded with the contents of SSPADD<6:0> and resumes its count. When the Baud Rate Generator times out (TBRG), the SEN bit (SSPCON2<0>) will be automatically cleared by hardware, the Baud Rate Generator is suspended, leaving the SDA line held low and the Start condition is complete.

Stop Condition

A Stop bit is asserted on the SDA pin at the end of a receive/transmit by setting the Stop Enable bit, PEN (SSPCON2<2>). At the end of a receive/transmit, the SCL line is held low after the falling edge of the ninth clock. When the PEN bit is set, the master will assert the SDA line low. When the SDA line is sampled low, the Baud Rate Generator is reloaded and counts down to ‘0’. When the Baud Rate Generator times out, the SCL pin will be brought high and one TBRG (Baud Rate Generator rollover count) later, the SDA pin will be deasserted. When the SDA pin is sampled high while SCL is high, the P bit (SSPSTAT<4>) is set. A TBRG later, the PEN bit is cleared and the SSPIF bit is set.

Repeated Started (Restart) Condition

A Repeated Start condition occurs when the RSEN bit (SSPCON2<1>) is programmed high and the I2C logic module is in the Idle state. When the RSEN bit is set, the SCL pin is asserted low. When the SCL pin is sampled low, the Baud Rate Generator is loaded with the contents of SSPADD<5:0> and begins counting. The SDA pin is released (brought high) for one Baud Rate Generator count (TBRG). When the Baud Rate Generator times out, if SDA is sampled high, the SCL pin will be deasserted (brought high). When SCL is sampled high, the Baud Rate Generator is reloaded with the contents of SSPADD<6:0> and begins counting. SDA and SCL must be sampled high for one TBRG. This action is then followed by assertion of the SDA pin (SDA = 0) for one TBRG while SCL is high.

Following this, the RSEN bit (SSPCON2<1>) will be automatically cleared and the Baud Rate Generator will not be reloaded, leaving the SDA pin held low. As soon as a Start condition is detected on the SDA and SCL pins, the S bit (SSPSTAT<3>) will be set. The SSPIF bit will not be set until the Baud Rate Generator has timed out. Immediately following the SSPIF bit getting set, the user may write the SSPBUF with the 7-bit address in 7-bit mode or the default first address in 10-bit mode. After the first eight bits are transmitted and an ACK is received, the user may then transmit an additional eight bits of address (10-bit mode) or eight bits of data (7-bit mode).

Acknowledge Sequence

An Acknowledge sequence is enabled by setting the Acknowledge sequence enable bit, ACKEN (SSPCON2<4>). When this bit is set, the SCL pin is pulled low and the contents of the Acknowledge data bit are presented on the SDA pin. If the user wishes to generate an Acknowledge, then the ACKDT bit should be cleared. If not, the user should set the ACKDT bit before starting an Acknowledge sequence. The Baud Rate Generator then counts for one rollover period (TBRG) and the SCL pin is deasserted (pulled high). When the SCL pin is sampled high (clock arbitration), the Baud Rate Generator counts for TBRG. The SCL pin is then pulled low. Following this, the ACKEN bit is automatically cleared, the Baud Rate Generator is turned off and the MSSP module then goes into Idle mode.

Master Mode Transmission

Transmission of a data byte, a 7-bit address or the other half of a 10-bit address is accomplished by simply writing a value to the SSPBUF register. This action will set the Buffer Full flag bit, BF and allow the Baud Rate Generator to begin counting and start the next transmission. Each bit of address/data will be shifted out onto the SDA pin after the falling edge of SCL is asserted. SCL is held low for one Baud Rate Generator rollover count (TBRG). Data should be valid before SCL is released high.

When the SCL pin is released high, it is held that way for TBRG. The data on the SDA pin must remain stable for that duration and some hold time after the next falling edge of SCL. After the eighth bit is shifted out (the falling edge of the eighth clock), the BF flag is cleared and the master releases SDA. This allows the slave device being addressed to respond with an ACK bit during the ninth bit time if an address match occurred, or if data was received properly. The status of ACK is written into the ACKDT bit on the falling edge of the ninth clock. If the master receives an Acknowledge, the Acknowledge Status bit, ACKSTAT, is cleared. If not, the bit is set. After the ninth clock, the SSPIF bit is set and the master clock (Baud Rate Generator) is suspended until the next data byte is loaded into the SSPBUF, leaving SCL low and SDA unchanged.

After the write to the SSPBUF, each bit of address will be shifted out on the falling edge of SCL until all seven address bits and the R/W bit are completed. On the falling edge of the eighth clock, the master will deassert the SDA pin, allowing the slave to respond with an Acknowledge. On the falling edge of the ninth clock, the master will sample the SDA pin to see if the address was recognized by a slave. The status of the ACK bit is loaded into the ACKSTAT status bit (SSPCON2<6>). Following the falling edge of the ninth clock transmission of the address, the SSPIF is set, the BF flag is cleared and the Baud Rate Generator is turned off until another write to the SSPBUF takes place, holding SCL low and allowing SDA to float.

Master Mode Reception

Master mode reception is enabled by programming the Receive Enable bit, RCEN (SSPCON2<3>). The Baud Rate Generator begins counting and on each rollover, the state of the SCL pin changes (high-to-low/ low-to-high) and data is shifted into the SSPSR. After the falling edge of the eighth clock, the receive enable flag is automatically cleared, the contents of the SSPSR are loaded into the SSPBUF, the BF flag bit is set, the SSPIF flag bit is set and the Baud Rate Generator is suspended from counting, holding SCL low.

The MSSP is now in Idle state awaiting the next command. When the buffer is read by the CPU, the BF flag bit is automatically cleared. The user can then send an Acknowledge bit at the end of the reception by setting the Acknowledge sequence enable bit, ACKEN (SSPCON2<4>).

 


   How To Configure I2C Master   

 

Steps To Configure I2C Master Mode Transmitter

Down below are the typical steps for a master transmission sequence, as stated in the datasheet.

Setup I2C Master

1. Set the I2C Pins into input mode (High-Impedance). SCL, SDA pins are set to input.
2. Set The Desired Baud Rate For The I2C. By writing to SSPADD.
3. Select And Enable I2C Master Mode Operation For The MSSP module.

4. Enable interrupt signals, if needed.

Transmit Sequence

(Some of them are done automatically by hardware)

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. The address is shifted out the SDA pin until all eight 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 eight 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. An interrupt is generated once the Stop condition is complete.

Implementing I2C Master Mode Transmitter Driver

I know that the amount of details mentioned earlier makes it seem kind of intimidating but in fact most of the pre-described mechanics work automatically in the background by the hardware support. Driving the hardware is way easier than what you might be thinking of. Down below are the exact steps with Embedded-C code snippets to implement each step, one at a time.

First of all, the I2C_Wait() function which makes your master MCU sure that the previous state has reached completion successfully and the bus is on IDLE, so it can proceed to the desired command.

void I2C_Wait()
{
  while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}

1.  Setup I2C (For Master Mode)
void I2C_Master_Init()
{
  SSPCON = 0x28;
  SSPCON2 = 0x00;
  SSPSTAT = 0x00;
  SSPADD = ((_XTAL_FREQ/4)/I2C_BaudRate) - 1;
  TRISC3 = 1;
  TRISC4 = 1;
}

2.  Initiate An I2C Start Condition
void I2C_Start()
{
  //---[ Initiate I2C Start Condition Sequence ]---
  I2C_Wait();
  SEN = 1;
}

3.  Initiate An I2C Stop Condition
void I2C_Stop()
{
  //---[ Initiate I2C Stop Condition Sequence ]---
  I2C_Wait();
  PEN = 1;
}

4.  Initiate An I2C Restart Condition
void I2C_Restart()
{
  //---[ Initiate I2C Restart Condition Sequence ]---
  I2C_Wait();
  RSEN = 1;
}

5.  Send ACK Signal (For Master Receiver Mode Only)
void I2C_ACK(void)
{
  //---[ Send ACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 0; // 0 -> ACK, 1 -> NACK
  ACKEN = 1; // Send ACK Signal!
}

6.  Send NACK Signal (For Master Receiver Mode Only)
void I2C_NACK(void)
{
  //---[ Send NACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 1; // 1 -> NACK, 0 -> ACK
  ACKEN = 1; // Send NACK Signal!
}

7.  Send Byte Via I2C Bus, And Return The ACK/NACK From The Slave
unsigned char I2C_Write(unsigned char Data)
{
  //---[ Send Byte, Return The ACK/NACK ]---
  I2C_Wait();
  SSPBUF = Data;
  I2C_Wait();
  return ACKSTAT;
}

8.  Receive And Return A Byte From The I2C Bus
unsigned char I2C_Read_Byte(void)
{
  //---[ Receive & Return A Byte ]---
  RCEN = 1;        // Enable & Start Reception
  while(!SSPIF);   // Wait Until Completion
  SSPIF = 0;       // Clear The Interrupt Flag Bit
  return SSPBUF;   // Return The Received Byte
}

 

Let’s put it all together and write a simple example I2C master mode that transmits a single byte 0x52 to a slave I2C device with an address of 0x42 .. It’s going to be as shown below in the main.c file snippet.

void main(void) 
{
  I2C_Init();

  I2C_Start();
  I2C_Write(0x42); // Slave I2C Device Address + Write
  I2C_Write(0x52); // Data To Be Sent
  I2C_Stop();
  
  while(1)
  {

  }
  return;
}

 


   How To Configure I2C Slave   

 

Steps To Configure I2C Master Mode Transmitter

Down below are the steps to follow in order to configure your microcontroller to be an I2C slave device. And how to receive data from the I2C bus.

Setup I2C Slave

1. Set the I2C Pins To Input Mode (High-Impedance). Both SCL, SDA are set to input.
2. Set the address you want to assign for this slave by writing it to the SSPADD register.
3. Disable Slew Rate Control (For Standard Mode Operation).
4. Select And Enable I2C Slave Mode Operation For The MSSP module.
5. Enable The Interrupts Signals.

Data Reception Sequence

( Automatic or Done By Hardware )

Once the MSSP module has been enabled, it waits for a Start condition to occur. Following the Start condition, the eight bits are shifted into the SSPSR register. All incoming bits are sampled with the rising edge of the clock (SCL) line. The value of register SSPSR<7:1> is compared to the value of the SSPADD register. The address is compared on the falling edge of the eighth clock (SCL) pulse. If the addresses match and the BF and SSPOV bits are clear, the following events occur:

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.

( Steps To Do In Software, typically in the ISR )

In The ISR For SSPIF

1. Clear The CKP bit (Clock Stretching). To stop the master, no data will be received until the recent byte is read.
2. Check for the source of the interrupt signal. Whether it’s data read, data write, or bus collision.
3. Read The SSPBUF buffer. If it’s a write signal, then write your data to the buffer after reading it.
4. Set The CKP bit to release the SCL clock line.

5. Clear The Interrupt flag bit and it’s the end of ISR.

Implementing I2C Slave Mode Receiver Driver

1.  Setup I2C (For Slave Mode + Assign It An Address ), Enable Interrupts

void I2C_Slave_Init(void)
{
  //---[ Configures The I2C In Slave Mode]---
  TRISB0 = 1;     // Set As Input - SDA
  TRISB1 = 1;     // Set As Input - SCL
  SSPADD = Address;  // Set I2C Device Address
  SSPSTAT = 0x80; // Disable Slew Rate Control (Standard Mode)
  SSPCON1 = 0x36; // Select & Enable I2C (Slave Mode)
  SSPCON2 = 0x01; // Enable Clock Stretching
  SSPIF = 0;      // Enbable Interrupts
  SSPIE = 1;
  PEIE = 1;
  GIE = 1;
}

2.  Write The ISR Handler For I2C Slave

(General ISR For I2C Slave. it could be for write, read, or overflow error)

void __interrupt() ISR(void)
{
  if(SSPIF)
  {
    CKP = 0; // Hold (Stretch) The Clock Line LOW
    if (SSPOV || WCOL) // Bus Collision or Buffer Overflow
    {
      char Dummy = SSPBUF; // Read The Last Byte To Clear The Buffer
      SSPOV = 0;           // Clear the overflow flag
      WCOL = 0;            // Clear the collision bit
      CKP = 1;             // Release Clock Line SCL
    }
    if(!R_nW) // Read
    {
      char Dummy = SSPBUF; // Read The Last Byte To Clear The Buffer
      while(!BF);
      RX_Data = SSPBUF;    // Read The Received Data Byte
      CKP = 1; // Release Clock Line SCL
    }
    else if(!R_nW) // Write
    {
      char Dummy = SSPBUF; // Read The Last Byte To Clear The Buffer
      BF = 0;
      SSPBUF = dummy;      // Write Your Data (REMOVE THE DUMMY)
      CKP = 1; // Release Clock Line SCL
      while(BF);
    } 
    SSPIF = 0;
  }
}

 


  μC-To-μC I2C Communication – LAB1   

 

Lab Name MCU-To-MCU I2C Basic Communication Demo
Lab Number 30
Lab Level Intermediate
Lab Objectives Learn how to use I2C Communication works. And create an I2C Communication network. A very simple one, in fact, consisting of a master transmitter and a slave receiver I2C devices and making sure everything is running correctly.

 

       1. Coding       

 

Open the MPLAB IDE and create a couple of new projects and name them “I2C_Master_Transmitter”, and “I2C_Slave_Receiver”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.

Create New Project With MPLAB IDE

Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.

Now, open the main.c file and let’s start developing the firmware for our project.

The master I2C Transmitter initiates the I2C Communication and starts sending some bytes sequentially. Let’s send a running counter from 0 upto 255.

The salve will setup the I2C slave receiver port and respond to data reception interrupt signals. Each time a byte of data is received via the I2C bus, the slave device will output it to PORTD in order to check it out for validation.

Yes, it’s not something useful at all, but this lab will make you confident that the system is working as it should be. Then, we can interface some modules and sensors over the I2C bus in the upcoming tutorials. So, stay tuned for that!

The Full Code Listing For This Lab

I2C Master Transmitter Firmware (main.c)

/*
* File: main.c
* Author: Khaled Magdy
* I2C Master Transmitter
* PIC16F877A - Or Any Other Similar PIC MCU
* Tweak The Code To Get It Working With Other MCU
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 4000000 // Fosc
#define I2C_BaudRate 100000 // I2C Baud Rate = 100 Kbps
//----------------------------------------------------------
//-----------------[ Functions' Prototypes ]----------------
void I2C_Master_Init(void);
void I2C_Wait(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_Restart(void);
void I2C_ACK(void);
void I2C_NACK(void);
unsigned char I2C_Write(unsigned char Data);
//----------------------------------------------------------
void main()
{
  TRISB = 0xFF;
  TRISD = 0x00;
  PORTD = 0x00;
  unsigned char i=1;
  I2C_Master_Init();
  while(1)
  {
    I2C_Start(); // I2C Start Sequence
    I2C_Write(0x40); // I2C Slave Device Address + Write
    I2C_Write(i++); // The Data To Be Sent
    I2C_Stop(); // I2C Stop Sequence
    __delay_ms(500);
    RD3 = ~RD3; // Toggle LED Each Time A Byte Is Sent!
  }
}
//------------[ END OF MAIN ]--------------
//-----------------------------------------
void I2C_Master_Init()
{
  SSPCON = 0x28;
  SSPCON2 = 0x00;
  SSPSTAT = 0x00;
  SSPADD = ((_XTAL_FREQ/4)/I2C_BaudRate) - 1;
  TRISC3 = 1;
  TRISC4 = 1; 
}
void I2C_Wait()
{
  while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
void I2C_Start()
{
  //---[ Initiate I2C Start Condition Sequence ]---
  I2C_Wait();
  SEN = 1;
}
void I2C_Stop()
{
  //---[ Initiate I2C Stop Condition Sequence ]---
  I2C_Wait();
  PEN = 1;
}
void I2C_Restart()
{
  //---[ Initiate I2C Restart Condition Sequence ]---
  I2C_Wait();
  RSEN = 1;
}
void I2C_ACK(void)
{
  //---[ Send ACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 0; // 0 -> ACK, 1 -> NACK
  ACKEN = 1; // Send ACK Signal!
}
void I2C_NACK(void)
{
  //---[ Send NACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 1; // 1 -> NACK, 0 -> ACK
  ACKEN = 1; // Send NACK Signal!
}
unsigned char I2C_Write(unsigned char Data)
{
  //---[ Send Byte, Return The ACK/NACK ]---
  I2C_Wait();
  SSPBUF = Data;
  I2C_Wait();
  return ACKSTAT;
}

 

I2C Slave Receiver Firmware (main.c)

/*
* File: main.c
* Author: Khaled Magdy
* I2C Slave Receiver
* PIC16F877A - Or Any Similar PIC MCU
* Different Parts Will Need Few Tweaking Before Running
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 4000000 // Fosc
unsigned char RX_Data = 0x00;
//-----------[ Functions' Prototypes ]--------------
void I2C_Slave_Init(unsigned char);
//==================================================
void main(void) 
{
  TRISD = 0x00;
  PORTD = 0x00;
  I2C_Slave_Init(0x40); // Initiate I2C Slave With Address = 64 or 0x40
  while(1)
  {

  }
  return;
}
//----------------[ END OF MAIN ]-------------------
//==================================================
void __interrupt() ISR(void)
{
  if(SSPIF)
  {
    if(!R_nW) // Slave Write (Receive)
    {
      char Dummy = SSPBUF; // Dummy Read
      CKP = 1; // Release The SCL Clock Line
      while(!BF); // Wait Until Completion
      RX_Data = SSPBUF; // Read The Buffer Data
      PORTD = RX_Data;  // Output The Received Byte
    }
  CKP = 1;   // Release The SCL Clock Line
  SSPIF = 0; // Clear The Interrupt Flag
  }
}
//---------------[ I2C Routines ]-------------------
void I2C_Slave_Init(unsigned char Address)
{
  //---[ Configures The I2C In Slave Mode]---
  SSPADD = Address; // Set The I2C Slave Device Address
  SSPSTAT = 0x80;
  SSPCON = 0x36;
  SSPCON2 = 0x01;
  TRISC3 = 1; // SCL Set To Input
  TRISC4 = 1; // SDA Set To Input
  GIE = 1;    // Enable Interrupts
  PEIE = 1;
  SSPIF = 0;
  SSPIE = 1;
}

 

       2. Simulation       

 

Here is an animation for the running simulation results. The master MCU (on the left) is sending bytes of data (0 to 255) one byte per second. And the receiver is forwarding the received data to PORTD so you can see the data bits on the LEDs. Moreover, the I2C Bus debugger shows you the timestamps and exact format of each byte of data being transferred over the I2C bus.

 

I2C_LAB1_Animation

 

       3. Prototyping       

 

Down below is the real-life running test for this LAB on real boards. It’s very easy to hook everything up in this LAB. And it might be challenging to get it to work in case you MCU has different hardware implementation that needs a little bit of tweaking in code to get it to work well.

Play Video On YouTube

 

Download I2C LAB1 Project (Code+Simulation)

 


   μC-To-μC I2C Communication – LAB2   

 

Lab Name MCU-To-MCU I2C Basic Communication Demo
Lab Number 31
Lab Level Intermediate
Lab Objectives Learn how to use I2C Communication works. And create an I2C Communication network. A very simple one, in fact, consisting of a master transmitter and a slave receiver I2C devices and making sure everything is running correctly.

 

       1. Coding       

 

Open the MPLAB IDE and create a couple of new projects and name them “I2C_Master_Transmitter”, and “I2C_Slave_Receiver”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.

Create New Project With MPLAB IDE

Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.

Now, open the main.c file and let’s start developing the firmware for our project.

The master I2C receiver initiates the communication by actively sending data read requests to the slave device. The slave reads the DIP switches on PORTx and then report the state of this register (byte) to the master each time it requests data.

You can think of it like the slave I2C device is acting as an io expansion for the master I2C device which reads the IO pins states from the slave device periodically. You can also combine this lab with the previous one in order to send data from the master that will be transferred to the slave’s IO ports or whatever.

The Full Code Listing For This Lab

I2C Master Receiver Firmware (main.c)

/*
* File: main.c
* Author: Khaled Magdy
* I2C Master Receiver
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 16000000 // Fosc
#define I2C_BaudRate 100000 // I2C Baud Rate = 100 Kbps
//----------------------------------------------------------
//-----------------[ Functions' Prototypes ]----------------
void I2C_Master_Init(void);
void I2C_Wait(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_Restart(void);
void I2C_ACK(void);
void I2C_NACK(void);
unsigned char I2C_Read(void);
unsigned char I2C_Write(unsigned char Data);
//----------------------------------------------------------
void main()
{
  TRISD = 0x00;
  PORTD = 0x00;
  I2C_Master_Init();
  while(1) 
  {
    I2C_Start(); // I2C Start Sequence
    I2C_Write(0x41); // I2C Slave Device Address 0x40 + Read
    PORTD = I2C_Read(); // Read Data From Slave
    I2C_Stop(); // I2C Stop Sequence
    __delay_ms(100);
  }
}
//------------[ END OF MAIN ]--------------
//-----------------------------------------
void I2C_Master_Init()
{
  SSPCON = 0x28;
  SSPCON2 = 0x00;
  SSPSTAT = 0x00;
  SSPADD = ((_XTAL_FREQ/4)/I2C_BaudRate) - 1;
  TRISC3 = 1;
  TRISC4 = 1;
}
void I2C_Wait()
{
  while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
void I2C_Start()
{
  //---[ Initiate I2C Start Condition Sequence ]---
  I2C_Wait();
  SEN = 1;
}
void I2C_Stop()
{
  //---[ Initiate I2C Stop Condition Sequence ]---
  I2C_Wait();
  PEN = 1;
}
void I2C_Restart()
{
  //---[ Initiate I2C Restart Condition Sequence ]---
  I2C_Wait();
  RSEN = 1;
}
void I2C_ACK(void)
{
  //---[ Send ACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 0; // 0 -> ACK, 1 -> NACK
  ACKEN = 1; // Send ACK Signal!
}
void I2C_NACK(void)
{
  //---[ Send NACK - For Master Receiver Mode ]---
  I2C_Wait();
  ACKDT = 1; // 1 -> NACK, 0 -> ACK
  ACKEN = 1; // Send NACK Signal!
}
unsigned char I2C_Write(unsigned char Data)
{
  //---[ Send Byte, Return The ACK/NACK ]---
  I2C_Wait();
  SSPBUF = Data;
  I2C_Wait();
  return ACKSTAT;
}
unsigned char I2C_Read()
{
  unsigned char Data;
  I2C_Wait();
  RCEN = 1;
  I2C_Wait();
  Data = SSPBUF;
  I2C_NACK();
  return Data;
}

 

I2C Slave Transmitter Firmware (main.c)

/*
* File: main.c
* Author: Khaled Magdy
* I2C Slave Transmitter
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 4000000 // Fosc
//-----------[ Functions' Prototypes ]--------------
void I2C_Slave_Init(unsigned char);
//==================================================
void main(void) {

  TRISB = 0xFF;
  nRBPU = 0;
  I2C_Slave_Init(0x40); // Initiate I2C Slave With Address = 64 or 0x40
  while(1) 
  {

  }
  return;
}
//----------------[ END OF MAIN ]-------------------
//==================================================
void __interrupt() ISR(void)
{
  if(SSPIF)  
  {
    if(!D_nA && R_nW)
    {
      char Dummy = SSPBUF;
      SSPBUF = PORTB ;
      CKP = 1;
      while(BF);
    }  
    CKP = 1;
    SSPIF = 0;
}
}
//---------------[ I2C Routines ]-------------------
void I2C_Slave_Init(unsigned char Address)
{
  //---[ Configures The I2C In Slave Mode]---
  SSPADD = Address; // Set The I2C Slave Device Address
  SSPSTAT = 0x80;
  SSPCON = 0x36;
  SSPCON2 = 0x01;
  TRISC3 = 1; // SCL Set To Input
  TRISC4 = 1; // SDA Set To Input
  GIE = 1;    // Enable Interrupts
  PEIE = 1;
SSPIF = 0;
SSPIE = 1;
}

 

       2. Simulation       

 

I2C_LAB2_Animation

 

       3. Prototyping       

 

Down below is the real-life running test for this LAB on real boards. It’s very easy to hook everything up in this LAB. And it might be challenging to get it to work in case you MCU has different hardware implementation that needs a little bit of tweaking in code to get it to work well.

Play Video On YouTube

 

Download I2C LAB1 Project (Code+Simulation)

 


   Serial I2C EEPROM Interfacing – LAB3   

 

Click Here To GoTo This Tutorial!

 


   I2C Concluding Remarks   

 

  1  

Is This The Best I2C Driver?

Well, obviously, not! It’s not the best I2C driver that you can possibly create. However, it just works! and gives you a real hands-on experience with the I2C Bus interfaces and how to set up and run an I2C master, salve, and all this stuff. The drivers shown in this article are the bare-minimum C-Code you’ll need to in order to get something to work. But, you’ll definitely need to perform some more modifications to meet your need and make your system more reliable.

The I2C module’s state must be checked before and after each and every single process. Some functions should actually return the status of the bus, ACK, NACK, etc. This is critical for many situations. Another thing to note is the bus collision, which happens sometimes, is actually checked for in the code above but it’s not handled. These exceptions of a collision, overflow, etc should also be considered and well-handled.

The use of while(condition); statements for waiting to check a specific condition until it’s met is NOT recommended at all. Almost in all situations, we call this “Blocking Code”. If something goes wrong on the I2C bus, which is very probable, maybe all your microcontroller using the blocking code style will FREEZE forever. Bad clock stretching implementations can easily lead to such situations or any bus contention issue.

I had to disclaim the intent I had while creating this material so you don’t expect something that it’s actually not fit for the scope of these tutorials. And you can still adopt the examples and code to help you in a lot of situations and projects. And of course, I’ll be here to help if you need to. Just drop me a comment below.

  2  

Briefly Describe The I2C Bus

  • Inter-Integrated Circuit (I2C) is a Synchronous Serial Communication Port (Protocol)
  • Multi-Master Multi-Slave Bus. Only one conversation may occur at a time.
  • I2C is a Two-wire interface (TWI).
  • Bi-directional open-drain hardware pins.
  • Half-Duplex Communication. One way of data transfer at a time. Direction can be reversed but only after terminating the current stream and initiating a new start condition.
  • The data bits are only valid when the clock line is HIGH. Data must be stable only then.
  • I2C bus allows for hot-swapping. This means I2C devices could be easily hooked to and out of the bus without the need to restart devices on the bus.
  • I2C Bus supports arbitration and clock synchronization.
  • I2C supports clock stretching. This enables all slaves to hold the clock line low while servicing other interrupts until reading the incoming data, then it can release the SCL clock line and conversation continues.

  3  

I2C Bus Standards

Originally, the I2C-bus was limited to 100 kbit/s operation. Over time there have been several additions to the specification so that there are now five operating speed categories. Standard-mode, Fast-mode (Fm), Fast-modeand High-speed mode (Hs-mode) devices are downward-compatible. Which means any device may be operated at lower bus speed. Ultra Fast-mode devices are not compatible with previous versions since the bus is unidirectional.

Bidirectional bus:

  • Standard-Mode (Sm), with a bit rate up to 100 kbit/s
  • Fast-Mode (Fm), with a bit rate up to 400 kbit/s
  • Fast-Mode Plus (Fm+), with a bit rate up to 1 Mbit/s
  • High-speed Mode (Hs-mode), with a bit rate up to 3.4 Mbit/s.

Unidirectional bus:

  • Ultra Fast-Mode (UFm), with a bit rate up to 5 Mbit/s

  4  

How Many Devices Can You Hook To The I2C Bus?

Theoretically speaking, the limit for 7-Bit address mode is the addressable count of 7-bit and similarly for 10-Bit address mode. However, in practice, we’re limited by the total bus capacitance which deforms the clock and data signals.

  5  

I2C Bus Conditions (Elements)

  • Start Condition (S)
  • Stop Condition (P)
  • Repeated Start (Restart) Condition (Sr)
  • Acknowledge ACK (A)
  • Not Acknowledge NACK (~A)
  • Address + R/W
  • Data Byte

  6  

I2C Bus Arbitration

Arbitration, like synchronization, refers to a portion of the protocol required only if more than one master is used in the system. Slaves are not involved in the arbitration procedure. A master may start a transfer only if the bus is free. Two masters may generate a START condition within the minimum hold time (tHD;STA) of the START condition which results in a valid START condition on the bus. Arbitration is then required to determine which master will complete its transmission.

Arbitration proceeds bit by bit. During every bit, while SCL is HIGH, each master checks to see if the SDA level matches what it has sent. This process may take many bits. Two masters can actually complete an entire transaction without error, as long as the transmissions are identical. The first time a master tries to send a HIGH but detects that the SDA level is LOW, the master knows that it has lost the arbitration and turns off its SDA output driver. The other master goes on to complete its transaction.

No information is lost during the arbitration process. A master that loses the arbitration can generate clock pulses until the end of the byte in which it loses the arbitration and must restart its transaction when the bus is free. If a master also incorporates a slave function and it loses arbitration during the addressing stage, it is possible that the winning master is trying to address it. The losing master must, therefore, switch over immediately to its slave mode.

Consider The Following Example For 2 I2C Masters Arbitration
I2C Bus Arbitration Example

The moment there is a difference between the internal data level of the master generating DATA1 and the actual level on the SDA line, the DATA1 output is switched off. This does not affect the data transfer initiated by the winning master. Since control of the I2C-bus is decided solely on the address and data sent by competing masters, there is no central master, nor any order of priority on the bus. There is an undefined condition if the arbitration procedure is still in progress at the moment when one master sends a repeated START or a STOP condition while the other master is still sending data.

  7  

I2C Clock Synchronization

Two masters can begin transmitting on a free bus at the same time and there must be a method for deciding which takes control of the bus and complete its transmission. This is done by clock synchronization and arbitration. In single master systems, clock synchronization and arbitration are not needed.

Clock synchronization is performed using the wired-AND connection of I2C interfaces to the SCL line. This means that a HIGH to LOW transition on the SCL line causes the masters concerned to start counting off their LOW period and, once a master clock has gone LOW, it holds the SCL line in that state until the clock HIGH state is reached. However, if another clock is still within its LOW period, the LOW to HIGH transition of this clock may not change the state of the SCL line. The SCL line is therefore held LOW by the master with the longest LOW period. Masters with shorter LOW periods enter a HIGH wait-state during this time.I2C Clock Synchronization

When all masters concerned have counted off their LOW period, the clock line is released and goes HIGH. There is then no difference between the master clocks and the state of the SCL line, and all the masters start counting their HIGH periods. The first master to complete its HIGH period pulls the SCL line LOW again. In this way, a synchronized SCL clock is generated with its LOW period determined by the master with the longest clock LOW period, and its HIGH period determined by the one with the shortest clock HIGH period.

  8  

I2C Clock Stretching

Clock stretching pauses a transaction by holding the SCL line LOW. The transaction cannot continue until the line is released HIGH again. Clock stretching is optional and in fact, most slave devices do not include an SCL driver so they are unable to stretch the clock.

On the byte level, a device may be able to receive bytes of data at a fast rate, but needs more time to store a received byte or prepare another byte to be transmitted. Slaves can then hold the SCL line LOW after reception and acknowledgment of a byte to force the master into a wait state until the slave is ready for the next byte transfer in a type of handshake procedure.

On the bit level, a device such as a microcontroller with or without limited hardware for the I2C-bus can slow down the bus clock by extending each clock LOW period. The speed of any master is adapted to the internal operating rate of this device.I2C Clock Synchronization
  9  

I2C Bus Debugging

Just make sure that you’ve got the right tools before starting to debug your I2C, You’ll need for this task a decent logic analyzer (on Amazon.com) or even a DSO with decode feature enabled like my Siglent SDS1104 (on Amazon.com). You can use the simulation tools available in proteus if you don’t have any of the hardware tools available.

Then start your debugging session by stepping through your code especially the suspicious parts of it. If you’ve got a hardware debugger tool (on Amazon.com) it would be great and highly recommended. If not, then you might use some LEDs, serial terminal, and a lot of artificial delay insertion.

  10  

Tell Me A Nightmare For I2C Debugger

Well, there are many situations in which you’ll probably feel like in a real nightmare! But I’ll mention one of them as I’ve seen repeatedly happening with practitioners over and over again. Hook your DSO, logic analyzer, and all the stuff and get ready to see the I2C signal to get an idea on what’s really going on wrong. Power the system up! Et voila! Nothing happens

The master attempts to send data and suddenly the clock line goes DOWN forever and everything stops forever. It’s going to be a frustrating issue to fix. And most of the time it’s going to be one of the slaves that implements Clock Stretching but it never releases the clock line. I know your code does release the SCL line, but it’s probably not in the correct timing so your MCU may get stuck even before it’s able to release the clock line.

Just be careful when implementing this part of the I2C interface. And get to know when and how to release the clock line properly. And use maybe an LED to figure out where the CPU gets stuck! This issue arises when you’re using blocking check techniques while enabling clock stretching.

 

 


 

 

 

Finally, it’s done! You might not be able to imagine how much time and hard-working did it take me to craft these tutorials and make them available for beginners. Help me keep it up by sharing it on social networks! So, please SHARE IT! and good luck with your projects. Stay tuned for the next tutorials!

Regards,

Share This Page With Your Network!

Khaled Magdy

I'm an embedded systems engineer doing both Software & Hardware. I'm an EE guy who studied Computer Engineering, But I'm also passionate about Computer Science. I love reading, writing, creating projects and Technical training. A reader by day a writer by night, it's my lifestyle. You can view my profile or follow me via contacts.

You may also like...

%d bloggers like this: