Inter-Integrated Circuit (I2C)
Inter-Integrated Circuit (I2C) is a bidirectional communication protocol commonly used to connect microcontrollers (MCUs) to various integrated circuits (ICs) like IMUs, temperature sensors, multiplexers, and more. It's popular for its simple hardware interface, multi-slave communication capability, and low power consumption.
Hardware:
SDA = Serial Data = Streams/Receives binary data onto the bus
SCL = Serial Clock = Synchronizes binary data. SDA is sampled on the rising edge of SCL
Take an example waveform:
We notice that I2C uses 8-bit data frames. For every 8 bits, we receive an ACK/NACK on the 9th bit. We can also take note of the following characteristics:
START BIT = Pulling SDA low while SCL is high
END BIT = Pulling SDA high while SCL is high
ACK/NACK BIT = This is the 9th bit. ACK means the message was received properly, bit will be 0. NACK means the message was not received, bit will be 1
We define the bus in an “idle” state when both SDA and SCL are high, and we sample always on the rising edge
Standard I2C Communication Steps:
Addressing [0:8]:
The master starts by sending a 7-bit address of the slave device it wants to communicate with
Note that 10-bit addressing is available on some devices!
The 8th bit indicates the operation: 0 = Write, 1 = Read
Acknowledge [9]:
After the address byte, the slave device responds with an ACK bit if it recognizes the address.
Data Transmission [10:]:
Depending on the operation (Read or Write), data is either sent master → slave or slave → master.
The receiver sends an ACK bit for every byte of data to confirm successful reception
Stop Condition:
The master ends the communication by generating a STOP condition.
Open-Drain:
The defining characteristic of I2C buses is that both SCL and SDA are open-drain pins. Put simply, the pins are driven by a single transistor(MOSFET), When Vin is HIGH, Vout = GND. However, when Vin is LOW, we notice that Vout is left floating. That's when the pull-up resistor pulls the line HIGH
Notice that there is only 1 NMOS transistor that controls Vout. A Pull-up/Pull-down GPIO has two transistors. The drain is connected to the signal, and the source is tied to ground. This means the STM32 can only pull the line LOW. It needs a pull-up resistor to go back HIGH, as the pin is left floating. This plays a crucial role in preventing contention (Covered in arbitration and clock stretching) and leaving the output in something called “High-Z”. If you’re still confused by the purpose of Pull Up/Pull Down Resistors, see this document.
Pull-up Resistors:
The value of your pull-up resistor plays a crucial role in efficient communication. The following relations are important in choosing your pull-up resistance:
Higher supply voltage → Higher pull-up resistor (V/I = R)
More resistance → Lower power and current consumption (V2/R = Power) (V/R = I)
Lower resistance → Higher bandwidth/frequencies; faster rise time
More capacitance → Lower pull-up resistor
Generally, you should check the GPIO pins source/sinking current to determine the sizing of the pull-up/down resistor. Common values are 1k, 4.7k, 10k, but that will always depend on the scenario)
I2C Hardware & PCB Design - Phil's Lab #132
Take this example from a random forum online What happens if I omit the pullup resistors on I2C lines?
The first image captures an I2C waveform with a very high pull-up resistor. This is evident in the slow rise time and incomplete waveform. The poster was actually using the internal pull-up resistors of the ATMEGA Arduino board (20k-50k ohms)
The poster added 4.7k ohm pull-up resistors, increasing the rise time significantly. Make sure to choose the appropriate pull-up resistance for your application! Also having I2C test points can really save your sanity
Note: This also goes into a different discussion of using good practices when probing signals(close ground return paths)
Multi-Slave Busses
I2C buses can hold up to 127 slaves on a single bus. Unlike SPI, I2C does not have a chip select line to control communication. Instead, I2C relies on IC addressing (each IC on the bus has a different address). Typically each slave has alternative addresses that can be configured in the situation two slaves have the same address.
Arbitration:
Arbitration is used to resolve conflicts that arise in multi-master configurations, where multiple devices are transmitting. These concepts apply only to the SDA line. If any node drives the line low, another node cannot transmit a logical one. This makes sense because the SDA line is open-drain and cannot actively be pulled high. The device notices that it cannot transmit and delays until the SDA line is free again.
Arbitration by data: Each device monitors the SDA line and compares the data it is transmitting with the data on the bus. If a device detects a mismatch, it loses the arbitration and stops transmitting.
Example: Master transmits 0x123, but detects 0x111 on the bus. It has lost the arbitration and will delay transmission.Bit-by-bit arbitration: Each bit is arbitrated individually. To lose arbitration on a bit, SDA must remain low when trying to transmit a high. If a device loses arbitration, it stops transmitting and waits for the bus to become free again before retrying.
Example: Master 1 transmits 0b0010 and Master 2 simultaneously transmits 0b0001. For the first two bits, both masters remain in arbitration. Notice that Master 2 drives the line low on bit 3. This means Master 1 cannot transmit a high. Master 1 has lost the arbitration.Slave devices: Slave devices on the I2C bus do not participate in the arbitration process. They respond to requests initiated by the master device that wins arbitration.
Cool forum! How would you explain multimaster arbitration in I²C?
Clock Stretching:
The slave device is capable of “stretching” or slowing down SCL. This halts the masters' data transmission when the slave device is swamped with data. The slave device holds SCL low. The master can also stretch the clock which is common when handling an interrupt or high-priority task.
Bus Lock-Up
In rare cases, the I2C bus will lock up, especially if the slave device holds the SDA or SCL line low due to malfunctioning or during clock stretching. Proper error recovery mechanisms like toggling the SCL line or resetting the device are important to handle lock up.
Resources:
Basics of the I2C Communication Protocol
I²C
The I2C Bus: Hardware Implementation Details - Technical Articles