Inter-Integrated Circuit (IIC or I2C) is a protocol that has gained immense popularity for its simplicity and versatility. It is commonly known as I2C or I²C.
It was developed by Philips Semiconductors in the 1980s. Due to patent issues, some microcontrollers refer to it as Two Wire Interface (TWI). However, at the protocol level, both I2C and TWI are essentially the same.
I2C is designed to enable communication between multiple integrated circuits over short distances, using just two wires (along with a common ground).
These two lines are:
- SDA (Serial Data Line)
- SCL (Serial Clock Line)
In this blog post, we will delve into the intricacies of I2C, exploring its fundamental concepts, architecture, advantages, and practical applications.
In the case of UART, there are two lines: one for transmission and one for reception. Both ends must use the same clock rate (baud rate) to ensure proper communication.
When it comes to I2C, it operates on a master-slave communication model. There must be at least one master, although multiple masters are also possible in more advanced setups.
The master is responsible for generating the clock signal, which it shares via the SCL (Serial Clock Line).
The master also initiates communication, whether it is a read or write operation, with any of the slaves connected to the network. However, at any given time, only one master can communicate.
Each slave in the I2C network has a unique address. The master sends the address of the target slave before starting communication.
There are two ways of addressing:
- 7-bit addressing allows up to 128 addresses (0-127). However the addresses 0-7 are reserved
- 10-bit addressing supports more devices, allowing for 1024 addresses. The 10-bit addressing format starts with a specific prefix to indicate it’s using a 10-bit address.
We are not going through the 10-bit addressing in this blog. We will cover it once we do the hands-on for I2C if required.
In 7-bit addressing, the next bit after the address represents the read (1) or write(0).
Eg. If the master sends the byte 0x50, it indicates the read operation from 0x28(first 7 bits) and 0x51 indicates the write operations to 0x28.
I2C Hardware setup
In an I2C hardware setup, the SCL line of all nodes that need to form a network should be connected together, and all SDA lines should also be connected together. A Pull-up resistor needs to be connected to both lines to ensure that both lines are in an active high state in idle conditions, as shown in the diagram below.
I2C Frame format
An I2C frame starts with a start condition, where the master pulls the SDA line low while SCL stays high. This signals the start of communication.
After that, the master sends the slave address (7-bit or 10-bit) along with a read/write bit. The R/W bit tells whether the master wants to read (1
) or write (0
).
The slave then responds with an ACK (Acknowledge) or NACK (Not Acknowledge) bit. This confirms whether the slave is ready for communication.
Next, the data transfer happens. Either the master or slave sends data, depending on the R/W bit.
Finally, a stop condition is sent by the master. This occurs when SDA goes high while SCL is high, marking the end of communication.
How the I2C Protocol Works
Each part of the I2C protocol has a specific role. It begins with a Start Condition, where the master pulls SDA low while SCL stays high. In some microcontrollers, a small delay after pulling SDA low is needed before pulling SCL low.
The master then sends the slave address followed by the R/W bit. A ‘0’ bit means write, and a ‘1’ bit means read.
The slave responds with an ACK bit, confirming it has received its address and is ready.
For write operations, the master sends data bytes. Each byte has 8 bits and is followed by an ACK from the slave.
For read operations, the slave sends data to the master. The master sends an ACK after each byte it receives. When done, it sends a NACK to signal the end of reading.
Finally, the master ends the session with a Stop Condition, transitioning SDA from low to high while SCL is high.
This protocol is efficient, allowing many devices to share a common data line without conflict.
Byte Transmission with I2C
Here’s a visual of how the master sends one byte. You can send multiple bytes in the same session before the stop condition.
For high-frequency I2C, some microcontrollers need a short delay between bytes to ensure reliability.
I2C in ATTiny85
The ATtiny85 doesn’t have a built-in I2C module. Instead, it uses USI (Universal Serial Interface) for communication. similar to how we implemented UART (refer to the post here). You can configure USI to work like I2C, but it’s quite complex.
An easier way is bit-banging. You manually control two GPIO pins to mimic I2C timing and logic.
ATtiny85 and AT24C04 – Practical Hands-on
The AT24C04 is an 8-pin EEPROM with 4 Kbits of memory. In our setup, the ATtiny85 acts as the master, and the AT24C04 is the slave.
You’ll need to set the correct slave address based on the AT24C04 datasheet. In this example, 0xA0
is used for writing and 0xA1
for reading.
One key thing in I2C is the bit order. You should send the most significant bit (MSB) first. To simplify this, you can reverse the bits in software and send from the least significant bit (LSB). This helps align the data correctly on the bus.
We are using the PB0 as SDA, PB2 as SCL and TIM0 in CTC mode for the required delay. On each compare match the data bits will be set in PB0 one by one from MSB to LSB. Please find the complete code in my github repository