I2C - Inter-Integrated Circuit

I2C at a glance

Parameter

Description

Parameter

Description

Brief Intro

I2C is a protocol used to communicate between IC’s

Logic level, signals

Typically implanted using TTL logic levels

2 signals: SDA, SCL

Duplex & Speed

Half-duplex

Among the slower protocols (max ~400kHz)

Timing & multiplexing

Synchronous protocol

Addressing required to multiplex between IC’s

Note - SPI is frequently compared to I2C. I would recommend reading it: https://uwarg-docs.atlassian.net/wiki/spaces/ZP/pages/1995964446

Signals Overview

Note, the terms ‘master’ and ‘slave’ are slowly being phased out due to their origins. However, the terms ‘master’ and ‘slave’ are still used in industry at the time of writing. This guide will use the terms ‘controller' to refer to what was conventionally known as ‘master’, while ‘peripheral' will refer to what was conventionally known as ‘slave’.

  • SDA - Serial Data

    • This signal line transmits serial data bi-directionally from both the controller and peripheral

  • SCL - Serial Clock

    • This signal line transmits a clock signal from the controller.

    • Note that only a controller can generate a clock signal, never a peripheral.

Theory of operation

I2C is intended for communicating with a bunch of IC’s efficiently in a simple and well-defined protocol. Thus, only 2 wires are needed to talk to any number of devices. I2C is a synchronous protocol, hence the addition of a clock. I2C also defines a specific frame format.

Frame Format

There are 2 frames, an address frame and a data frame. The address frame is required to multiplex between the devices on the bus. From left to right, the frame format is as follows:

  • Start condition

    • The bus is pulled low and then set back high. This is done to alert the devices that a transaction is beginning.

  • Address Frame

    • 7 (or 10, depending on your IC’s) address bits are sent to the bus

      • 7 bits means that there are only 127 possible addresses and thus address conflicts are possible. The methods to resolve them are as follows:

        • Some IC’s offer the ability to change their address by setting an external pin high or low

        • Multiplex the SDA and SCL lines between the conflicting peripherals by either using the internal MCU mux or an I2C mux

        • Use an I2C address translator

        • Power down a conflicting device when wanting to talk to another one

        • Change IC’s (alternate part numbers from the same manufacturer could work)

    • Read/Write bit

      • Indicates where the controller wants to send or receive data

  • ACK bit

    • Confirms that the IC has acknowledged the address frame and will listen/respond

  • Data frame(s)

    • Contains the 8-bit data frames to transmit the data as desired.

    • No limit on the number of data frames

  • Stop condition

    • Similar to start condition

Note that while a peripheral is not supposed to generate a clock signal, if more processing time is required by a peripheral it may pull the clock signal low. It is the controller’s responsibility to recognize this situation and set an appropriate timeout. This is referred to as clock stretching.

Hardware specifics

Unlike SPI which is a push-pull protocol, I2C is an open-drain protocol. This means that instead of the pins being explicitly driven high or low, the bus is permanently pulled high by pull-up (https://learn.sparkfun.com/tutorials/pull-up-resistors/all ) resistors and devices can pull the bus low to transmit information.

  • Note: ‘Pulling the bus low’ is accomplished by shorting that line to GND. This results in a small amount of current flowing from the pull-up resistor to the device that pulls the bus low.

The primary advantages of this are as follows:

  • Since the bus can only ever be shorted to ground, bus contention issues will not fry a board

    • Fancy talk for saying “You cannot short the IC out as a result of an I2C fault”. The same cannot be said for SPI.

  • Eliminates the need for a peripheral to include the circuitry to drive the bus high, it only needs to pull it low.

The disadvantages, however, can be summed up in the following bullet point: It’s finicky as ****, as pull-up resistor sizing is crucial as you cannot ignore rise and fall time. Parasitic inductance and capacitance have a direct impact on how fast data can be transferred and result in weird bugs. Consider the following cases:

In the above snippet, the I2C signal is clean. All looks good here.

The above snippet shows the effects of a weak pull-up resistor. In this scenario, 2.3V is the logic high voltage. However, due to the weak pull-ups, it takes a long time for the bus voltage to rise. In this situation, the time taken for the voltage to rise is so long that another signal to pull the bus low was sent before the voltage ever rose above logic high (2.3V). Meaning, the rise time of the signal was so slow that it could not even register a logic high before it was pulled low again.

A similar problem exists with fall-time on a strong pull-up, where the fall time is so slow that it never reaches logic low before it was pulled high again, as shown in the blue signal.

How to determine pull-up resistor sizing?

Start with 4.7k, observe the waveform, and go up/down if required. Oversize these footprints and make them easy to swap out for this exact reason.

https://learn.sparkfun.com/tutorials/i2c/i2c-at-the-hardware-level has some good info as well.

Application Notes

  • Oversize pull-up resistor footprints

  • Keep I2C traces short and clean

  • Write out an ‘address table' of all peripherals to ensure address conflicts are avoided

  • Ask your EE to put test points on SDA and SCL, they will be valuable in debugging

References

https://learn.sparkfun.com/tutorials/i2c/all

https://uworbital.notion.site/I2C-6d2f049215084222ac8b7f30d83beaae

https://embeddedartistry.com/blog/2021/08/02/resolving-i2c-address-conflicts/