Figure: Connectivity between a master (controller) and slaves (target). (source: https://en.wikipedia.org/wiki/I²C) |
Inter-integrated circuit (I2C) protocol is an advanced protocol, that eliminates many weaknesses in the UART and SPI protocols to provide communication between ICs using minimum number of wires. I2C consists of just two wires interconnecting one or many master devices with one or many slave devices. One wire is serial clock (SCL) that provides clock pulses across the embedded network. The other wire is serial data (SDA) that carries data between master and slave devices. Both SCL and SDA wires are pulled-up by default when the embedded network is in idle state.
When a communication occurs in an I2C environment, it goes according to the following procedure. Every communication is encapsulated by a START condition and a STOP condition. After the START condition, the master can send a address frame, which has a length of 7 or 10 bits. An address uniquely identifies a particular device (a master or slave) in the embedded network. Followed by the address bits, a read/write bit is sent, which specifies whether the sender wishes to send something to the receiver or, expecting to receive something from the receiver. Afterwards, the receiver can send a single bit ACK/NACK bit to specify that the address was receiver. There onward, the sender can send groups of 8 bits of data, each of which are followed by ACK/NACK bits from the receiver. When done, the sender sends a STOP condition to indicate that the communication is over.
Figure: Structure of an I2C message (source: https://www.circuitbasics.com/basics-of-the-i2c-communication-protocol) |
The purpose of sending a message like this would be to either write some configuration setting to a register of the recipient device, or to read some register values from a particular recipient device. The following are some key highlights about each important component of an I2C message.
START condition:
While the SCL is high, the SDA is pulled from HIGH to LOW to indicate the START condition.
Address frame:
This consists of a bit pattern of a length of 7 or 10 bits. The binary digit 1 is indicated by HIGH value in the SDA wire when SCL is HIGH. The binary digit 0 is indicated by LOW value in the SDA wire when the SCL is HIGH.
Read/Write bit:
The read operation is indicated by using a data bit 1 (SDA wire released to HIGH which SCL is HIGH). Similarly, the write operation is indicated by using a data bit 0 (SDA wire is pulled to HIGH while SCL is HIGH).
ACK/NACK:
At end of an address frame or a data frame of a length of 8 bits, the receiver is supposed to send back an ACK/NACK bit to indicate that whatever that has been sent is successfully received. When this operation is being performed, the slave controls the SDA wire. An ACK bit is indicated by SDA being pulled LOW by the receiver while the SCL is HIGH. A NACK is indicated by SDA left to be HIGH by the receiver while the SCL is HIGH.
Data frame:
Similar to the address frame, the data frame consists of a bit pattern to indicate some data to be transferred from the master to the slave or wise versa. In from which device to which device the data is transferring is decided by the read/write bit set previously by the master. Whichever the direction is, the length of a data frame is 8 bits. If the data is being sent by a slave to the master, the master will send ACK bits at the end of each 8-bit data frame from the slave to indicate that the master is ready to take more data frames. However, once the master is done with the data frames from the slave, the master sends a NACK to indicate that the slave should stop sending any subsequent data frames.
STOP condition:
While the SCL is high, the SDA is released from LOW to HIGH to indicate the STOP condition.
The following two figures illustrates two scenarios of I2C data communication between a master and a slave.
Figure: A master writing to a particular slave's a particular register. (source: TI Application Report SLVA704 https://www.ti.com/lit/an/slva704/slva704.pdf) |
Figure: A master reading from a particular slave's particular register. (source: TI Application Report SLVA704 https://www.ti.com/lit/an/slva704/slva704.pdf) |
It's time explore how I2C protocol behaves in the real world using actual hardware. We'll use two Arduino devices to communicate with I2C protocol for this demonstration. In our scenario, one Arduino device will act as the I2C master while the other device will act as the slave. The master device is programmed to request 1-byte data from a slave with an address number 8. The slave device is coded to posses address number 8, and upon a request, it sends a byte containing ASCII character A (binary: 01000001).
I2C Master Program:
I2C Slave Program:
The hardware connectivity between the master and slave devices are made simply by directly connecting SDA pin of master to SDA pin of the slave, and SCL pin of master to SCL pin of the slave. Since we are required to monitor the signals moving in these wires, I've made connections through a project board where I have connected the signal analyser's channels as well. So, when the master and slave are communicating, I should be able to see the signals going through SCL and SDA wires.
Figure: Connectivity between master and slave Adruino devices. The SDA and SCL pins are tapped for the signal analyser connectivity. |
Let's try to decode the captured signal. The SDA signal is surrounded by START and STOP conditions as expected. In between those two, the following bit pattern should exist in the SDA signal for each SCL pulse.
[ID in 7 bits] - [read bit] - [ACK] - [data frame in 8 bits] - [NACK]
The relevant values in those bits should be as follows:
ID in 7 bits: 0001000 (address 8 in binary)
read bit: 1
ACK: 0
data frame in 8 bits: 01000001 (character 'A' in ASCII)
NACK: 1
So, the bit pattern in the SDA signal should be:
[0001000] - [1] - [0] - [01000001] - [1]
If you watch carefully, you will see this pattern in the SDA signal in Channel 1. One interesting this I noticed was that the SCL clock signal has a little gap somewhere in the middle. I think, this is due to the master finishing up its job and awaiting for the slave to start its transmission. It's a way for the master to let the slave read its internal register value and prepare for a transmission back.
So, that's about it. I2C is a very important embedded communication protocol that can be used to build complex and very capable embedded system devices containing various components and modules. We can understand how the protocol works by intercepting the signals going through the wires.