Ch-6 I2C Bus Based Serial Data Communication

1. Inside ATmega328P MCU, there is hardware "I2C Interface" (Fig-6.1) to exchange data bit-by-bit with another MCU/Sensor. I2C stands for "Inter-Integrated Circuit". I2C is called bus; because, we can connect more than one MCU/sensor in parallel (Fig-6.2).
I2CBusFormation.png
Figure-6.1: Formation of I2C bus and its connection between two Arduino

rtc-lcd-bme280.png
Figure-6.2: Three I2C Bus compatible sensors/devices are connected in parallel

2. The I2C Interface has two lines: SDA (Serial Data Line) and SCL (Serial Clock Line) which form the I2C Bus. The SDA line is bi-directional and caries serial data between UNO (the Master) and NANO (the Slave). The SCL line is uni-directional and carries serial clock from UNO to NANO to push-in and push-out data. The I2C bus system is also known as TWI (Two Wire Interface) bus.

3. (I2C bus formation for Master) SDA and SCL lines of Master will be connected with DPin-A4 and A5 respectively when we include the following lines in Master sketch:
#include<Wire.h> //Library file put in Global space
Wire.begin(); //K1 switch of Fig-6.1 will be closed; put this line in etup() function

4. (I2C bus formation for Slave) SDA and SCL lines of NANO Slave will be connected with DPin-A4 and A5 respectively when we include the following lines in Slave sketch:
#include<Wire.h>
Wire.begin(0x13);//put 7-bit slave address as argument

5. In I2C Bus system, there is one Master which generates SCL signal. The other MCU/sensor devices are Slaves with their own 7-bit addresses. The address range is 0x00 – 0x7F (0000000 – 1111111 = 128) of which 0x00 – 0x07 (0000000 – 000111 = 8) are reserved. The slaves are expected to be within 30 cm to ensure reliable operation.

6. The SDA and SCL lines are terminated at 5V by 2.2k – 10k pull-up resistors.

7. I2C bus is a byte (8-bit) oriented system; where, data is sent and received byte-by-byte. The Slave always sends ACK (acknowledgement = 0) signal to Master after receiving a data byte. As a result, the Master sends the next byte to Slave.

8. The possible data transfer rates (speed) of the I2C bus are:
(1) 100 kbits/s (default Standard Mode)
(2) 400 kbits/s (Fast Mode)
(3) 1 Mbits/s (Fast Mode Plus = Fm+)
(4) 3.4 Mbits/s (High Speed Mode)

9. The following instruction is used to change the data transfer rate of I2C bus.

Wire.setClock(clockFrequency);  //clockFrequency is in Hz. 400 kHz 400x1000 = 400000

10. (Checking the presence of Slave) The Master/UNO of Fig-6.1 checks the presence of the Slave/NANO by executing the following codes: (This is known as Roll Calling.)

Wire.beginTransmission(7-bitSlaveAddress); //START command; roll call; ACK = 0 comes
byte busStatus = Wire.endTransmission(); //STOP command to get ACK in busStatus 
if(busStatus == 0x00) //busStatus = 0x00 means Slave is found on the I2C bus
{
  Serial.print(“Slave is found on the I2C Bus!”);
}
else
{
   Serial.print(“Slave is not found…!”);
   while(1);    //wait here for ever
}

Note: We have said that I2C bus is a byte oriented system. In Roll Calling, the Slave address is only 7-bit data (0010011). The Master makes it 8-bit by putting “zero (0)” or “one (1)” at the right-most position of the address. Zero is appended in “Write Mode”; where, Master sends/writes something to Slave. One is appended in “Read mode”; where, Master wants to read something from Slave.

11. (Data Write Mode) The Master executes the following codes to write/send a data byte (say: 0x32) into the Slave/NANO:

Wire.beginTransmission(0x13); //7-bitSlaveAddress; START command; roll call, ACK = 0 comes
Wire.write(0x32); //0x32 goes to NANO and automatically saved in FIFO BUFFEer, ACK=0 comes
byte busStatus = Wire.endTransmission(); //STOP command, and I2C bus is free

Note: Two ACK signals are sent by Slave one after another after receiving “address bits +R-W/” and 0x32. If UNO receives these two ACK signals correctly, it puts 0x00 in the busStatus variable.

12. (Collection of data byte 0x32 by Slave) When the above data byte (0x32) of Step-11 reaches to Slave and enters into BUFFer, the Slave is automatically interrupted. The Slave goes to receiveEvent() Interrupt Sub Routine (ISR), reads data from BUFFER, and sets a flag to true state. The flag is tested in loop() function (Step-13) to show data onto Serial Monitor. Note that print() and write() methods are not allowed in ISR; perform them in loop() functio. The codes are:

volatile bool flag = false;       //put in Global Space; why volatile keyword is here?
byte y;    //declare in Global space
Wire.onReceive(receiveEvent);  //put this code in setup() function; declaration of ISR

void receiveEvent(int howMany) //put this Interrupt Sub Routine (ISR) in User Space   
{ //howMany holds a number that is equal to data bytes received
 y = Wire.read();     //bring data from BUFFer and keep in y; do print() in loop()
flag = true; //a data byte has been stored in BUFFer
}

13. (Slave shows data in Serial Monitor) The Slave/NANO performs the following codes in the loop() function to read the data byte (0x32) of Step-11 from the I2C BUFFer and show it on Serial Monitor. It is not allowed to give print() or write() ommand in ISR routine; always, do it in the loop() function.

void loop()
{
 if(flag == true)    //to be sure that there is a data byte in BUFFer
 {
 Serial.println(y, HEX); //show the data byte on Serial Monitor
 flag = false; //reset the flag
 }
}

14. (Send 2-byte data) Class Work: (a) Write just the necessary codes for Master/UNO of Fig-6.1 to send int y = 0x1234 to Slave/NANO.

Wire.beginTransmission(0x13);  //Slave address
Wire.write(highByte(y)); //MSByte of data goes first
Wire.write(lowByte(y));  
byte busStatus = Wire.Wire.endTransmission();

(b) Write just the necessary codes for Slave/NANO to receive the data bytes of Step-14(a) and show it on Serial Monitor.
(c) Write codes for Master to send “OK” to Slave. Slave will receive it and show on SM2.

15. (Master requests Slave to give data (Read Mode)) Master/UNO of Fig-6.1 executes the following code to get 1-byte data (say: 0x45) from Slave.

byte m = Wire.requestFrom(7-bitSlaveAddress, n); //addr is made 8-bit with 1 at the right

Where:
n = number of bytes requested by Master
m = number of bytes actually received

... see next post.

I2CBusFormation.png

rtc-lcd-bme280.png

1 Like

16. (Slave is interrupted) In response to the command of Step-15, the Slave is automatically interrupted and goes to sendEvent() ISR routine; where, it sets a flag to true state. The MCU returns to loop() function, tests the flag, and then puts data on I2C bus. The codes are:

volatile bool flag = false;   //declare in Global space
Wire.onRequest(sendEvent);  //put this line in the setup() function of Slave

void sendEvent()  //place in User Space
{
 flag = true;    //flag will be tested in lop() function
}

17. (Slave puts data (0x45) on I2C bus) The Slave puts data byte 0x45 on the I2C bus in loop() function.

void loop()
{
     if(flag == true)    //true means Slave has received a request from Master
{
 Wire.write(0x45);
 flag = false; //flag reset
}
}

18. (Master reads data that has arrived from Slave) The Master executes the following code after the code of Step-15 to collect data and print on SM1 that has arrived from Slave in Step-17.

//(byte m = Wire.requestFrom(7-bitSlaveAddress, n); //code of Step-15; repeated)

byte y = Wire.read();     //y holds 0x45
Serial.print(y, HEX);      //show value in SM1

19. (Steps to get data from Slave)
(1) Master sends Roll Call and request command to Slave for 1-byte data (Step-15). //byte m=Wire.requestFrom(0x13,1);
(2) Slave is interrupted. //no code; it’s automatic
(3) Slave goes to sendEven() ISR and sets flag. (Step-16). //flag = true
(4) Slave goes back to loop() function. Tests flag and put data on I2C Bus. //if(flag == true) {Wire.write(0x45);}
(5) Data enters into BUFFer of Master. //no code; it’s automatic
(6) Master reads data from its BUFFer and shows on SM1 (Step-18). // y = Wire.read(); Serial.println(y, HEX);

20. (Home work) Write the steps and codes for Master to send data to Slave.
(1) Master makes a Roll Call to see that Slave is present. //Wire.beginTransmission(0x13);
(2) ACK comes from Slave. //no code; it’s automatic[/code]
(3) Master puts data on I2C bus. //Wire.write(0x32);
(4) Data enters into the BUFFer of Slave. //no code; it’s automatic
(5) Slave is interrupted and goes to receiveEvent() IRS routine. Reads data from its BUFFer and then sets a flag. //y = Wire.read(); flag = true;

(6) Slave goes to loop() function, tests the flag and then shows data on SM2. //if(flag == true { Serial.println(y, HEX);}

21. Write code so that Slave puts the following data onto I2C Bus assuming that request has already arrived from Master.

char myData[]  = “AUST”; 
//Serial.print(myData); 
for(int i = 0: i<sizeof(myData); i++)  
{ 
    Serial.print(myData[i]);
}

21. (Home Work and Assignment) Connect LM35 sensor with NANO as per Fig-6.1. Write sketch for Slave to acquire temperature signal from the sensor and save it in variable float myTemp and show it on SM2 with 2-digit precision at 1-sec interval. Write codes for Master/UNO to acquire temperature value from NANO at 2-sec interval and show it on SM1 at 2-sec interval.

(1) Master Sketch:

#include<wire.h> //contains I2C bus related ready-made functions/routines
Void setup()
{
 Serial.begin(9600);
 Wire.begin();
}

Void loop()
{
 byte m = Wire.requesFrom(0x13, 5) // request for 5-byte ASCII coded temperature
 //-------------------------------------------------
 byte y1 = Wire.read(); 
 Serial.print(y1, DEC); //SM1 shows 1st digit of integer part of temp
 //---------------------------------------------------
 byte y2 = Wire.read(); 
 Serial.print(y2, DEC); //SM1 shows 2nd digit of integer part of temp
 //---------------------------------------------------
 byte y3 = Wire.read(); 
 Serial.print(y3, DEC); //SM1 shows 3rd symbol (the decimal point) of temp
 //---------------------------------------------------
 byte y4 = Wire.read(); 
 Serial.print(y54, DEC); //SM1 shows 1st digit of fractional part of temp
 //---------------------------------------------------
 byte y5 = Wire.read(); 
 Serial.print(y5, DEC); //SM1 shows 2nd digit of fractional part of temp
 //---------------------------------------------------\
 Delay(2000)
}

(2) Slave Sketch:

#include<Wire.h>
float myTemp;

void setup()
{
 Serial.begin(9600);
 Wire.begin(0x13);
 Wire.onRequest(sendEvent);  //declaration of sendEvent() ISR
 analogReference(INTERNAL); //1.1V Full Scale for the ADC of NANO
}

Void loop()
{
 myTemp = (float)(1.1/1023.0)*analogRead(A3);
 Serial.print(myTemp, 2);
 //------------------------------------------------
 delay(1000);
}

void sendEvent()
{
 Wire.print(myTemp, 2);    //5 byte myTemp data (xx.xx) would be sent as ASCII codes 
}

22. (Home Works)
(1) Replace codes of loop() function of Q-18 using for() loop.
(2) Write codes to save in an array, the 5-byte ASCII codes being received by the master of Q-18.

char recvArray[6]; //place it in Global area   
 for(int I = 0; i<m; i++)
 {
 recvArray[i] = Wire.read();
 }
 recvArray[m] = ‘\0’;    //insert null-byte

... this is reserved.

I2C stands for "Inter-inter Circuit Serial Data Communication Network"

No, I2C stands for "Inter-Integrated Circuit"

Whence I2C (with super-scripted 2) can stand for Inter-Integrated Circuit, thence I2C (baseline 2) may stand for Inter-inter Circuit. :slight_smile: . Anyway, I have edited my post to include your point.

GolamMostafa:
Whence I2C (with super-scripted 2) can stand for Inter-Integrated Circuit, thence I2C (baseline 2) may stand for Inter-inter Circuit. :slight_smile:

Dunno. Why not ask Koninklijke Philips?

TheMemberFormerlyKnownAsAWOL:
Dunno. Why not ask Koninklijke Philips?

This 51 page official document (the I2C Manual) of Philips on I2C Bus has not included the "full name" for the term I2C. The Manual always talks I2C Bus, ..., I2C Bus.

2003? 20 years after the bus was first formulated, they'd probably forgotten why they'd called it that. :smiley:

"Inter-inter" doesn't make a whole lot of sense.

TheMemberFormerlyKnownAsAWOL:
2003? 20 years after the bus was first formulated, they'd probably forgotten why they'd called it that. :smiley:

"Inter-inter" doesn't make a whole lot of sense.

"Inter-inter Circuit" is not actually my creation; I can't figure out now where I met it or I have shortened unconsciously "Inter-Integrated Circuit" to "Inter-inter Circuit". I have encountered Inter-IC (Inter- Integrated Circuit) like you and others in the net. As the slaves are located within very close proximity (perhaps within in a single PCB), "Inter-Integrated Circuit" is sensible/acceptable in all measures.

This is better than explaining it in every thread about I2C or in a document as attachment :smiley:

I did not check everything, but I noticed a few things.

no. 1
The SCL is also bi-directional. That is not used for "dumb" sensors but when a Arduino is used as a Slave, then it used clock pulse stretching by keeping SCL low. That will halt the Master. The Slave uses that to run the onRequest handler to be able to gather data to send back.

no. 2
When a Slave is turned off while sending data to the Master it is hard to detect, and the result is trivial.
That's why I advice to first check for errors and if there are no errors, then read all the data.

Suppose the Slave is taken from the bus, then the Master sends the SCL, reads the SDA (always high) and sends the ACK and a STOP at the end. The Master receives 0xFF, 0xFF, 0xFF and does not know that it is not real data.
Suppose the Slave is powered down and keeps SDA and SCL low (for a short time or forever). That might be seen as a bus collision or the timeout will kick in (for the AVR library). The Wire.endTransmission() will return an error and the Wire.requestFrom() will return zero.

no. 3
Oops:

if(flag == true);  //MCU has been interrupted because data byte has arrived from Master

Yes, that really is a ';' and I prefer not to use "true" or "false" to compare a bool variable.

if(flag)  //MCU has been interrupted because data byte has arrived from Master

I am extremely sorry for the late to intimate one of the best mates of the Forum.

I extend my humble gratitude towards you for going through these notes which I have hurriedly prepared to conduct online classes for my pandemic affected varsity pupils.

Your meticulous observation has caught the semicolon (;) after the if() structure, which I have corrected.

About the bi-directional nature of the SCL line of the I2C Bus, I have some reservation to agree with you as I understand very clearly that the SCL pulses are only generated when the Master writes something into the TWDR Register. A slow slave might take a bit longer time to create the ACK signal by modulating the SDA line and in that case the Master inserts wait states on the SCL line and the line remains at LOW state until the slave generates ACK signal. If the slave fails to create ACK signal at all, the time out logic will bring the Master from the infinite loop.

Thanks once again for the constructive feed back and hope the same comments may be offered for my other lecture notes on Ch-5 to Ch-13 of Education and Teaching Section.

The official I2C pdf-document: https://www.nxp.com/docs/en/user-guide/UM10204.pdf.
Parts of it as a webpage: https://i2c.info/i2c-bus-specification

In the pdf-document, page 8, paragraph 3.1.1: "Both SDA and SCL are bidirectional lines".
In that same paragraph: "For a single master application, the master’s SCL output can be a push-pull driver design if there are no devices on the bus which would stretch the clock".

So the only purpose of SCL being bidirectional is for stretching the SCL clock by the Slave.
Since sensor have the I2C interface in hardware, they don't pull the SCL low. Only an Arduino (and other microcontrollers and processors) as a Slave pull the SCL low when they need some extra time to do things in software.