I2C - Inter-Integrated Circuit
I2C at a glance
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: SPI - Serial Peripheral Interface
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/