I2C communication between Arduino

Arduino I2C communication

In this tutorial we will show how two Arduino can be communicated using I2C communication protocol. This is useful if we want to transfer data from one Arduino to another or control sensors or actuator connected to one Arduino with another Arduino. Hence it is useful if I/O capability is to be increased or if more processing is required. One Arduino is configured as a master and the other is configured as slave.

What is I2C?

I2C is acronym for Inter-Integrated Circuit which is a serial communication standard developed by Philips Semiconductor in 1982. It is also called Two Wire Interface (shortened to TWI) because it uses two wire when the two I2C devices are within the same system. If two devices of different system like two Arduino are used for I2C communication another wire which is the ground wire is required. I2C serial communication is designed to connect number of integrated circuit which is cable of I2C communication. Example of such I2C capable system are LCD, EEPROM, different types of sensors such as temperature sensor, pressure sensors, accelerometers and others. Arduino USART can only communicate with one device, SPI(Serial Peripheral Interface) can be used to communicate upto 3 devices but I2C can be used to communicate hundreds of devices.

 How I2C works?

I2C is a serial communication standard which uses Master Slave system. The master sends commands and information request to Slave address and the Slave replies with information required and keeps quit until another information is requested by the Master. The information exchange between Master and Slave happens with two communication wires called SDA(Serial Data) and SCL(Serial Clock) and a common ground wire. All the slave devices are connected to master device on these SDA and SCL wires as illustrated below.

I2C devices interface diagram

The I2C connection is very easy. The master SDA and SCL are connected to devices SDA and SCL respectively. The wires is open drain which means that the slave devices can provide low value but not high. So pullup resistor like 4.7KOhm are required so that the wires have power or active high. In case of Arduino communication, Arduino have internal pull-up and the I2C wire library puts it into high state so that external pull-up resistor are not required.

I2C protocol

The I2C protocol works on master slave networking. The slave devices have address and the master communicates with a specific slave with the slave address. The original address bits was 7 bits and later 10 bits address are also used. In 7 bit addressing, the 7 bits are for address and the last bit is for read or write. This 7 bit addressing(from A6 to A0) is shown below.

In 10 bit addressing, two bytes which is 16-bits are required. The first 5 bits is 11110 and is used to tell that next coming byte contains address bits. The next 2 bits are address bits A9 and A8. The next bit is Read/Write bit. Then the next byte contains other address bits which are A7 to A0. This is shown below.

Most of the device manufacturer specifies I2C address. In I2C network two sensors can have the same address and therefore to avoid addressing conflict, manufacturer also allows developers to change the address by allowing connection to +5V or ground. An example of this is shown below where the temperature sensor LM75A device pins can be connected in several ways to give it specific address.

Once the address of the slave is fixed, the master sends information request from slaves devices connected to the I2C network. Each of the slave receives compares the slave address with its own address. The slave which has the same address then replies to the information request by the master.The slave cannot initiate information exchange. Only when master device request information, slave can respond. Also the I2C communication is not full duplex meaning that request and respond from slave cannot happen at the same time otherwise there is collision. The slave device such as temperature sensor stores their values in register and master device request the information stored in that register.

All Arduino boards have I2C pins which are listed below for some Arduino boards.

different Arduino board I2C pins

Arduino Wire or I2C library

To use the I2C serial communication with Arduino there is a standard library called Wire.h. This needs to be imported when working with Arduino.

#include <Wire.h>

 If the Arduino is to made master then we declare it as I2C master device using the following statement in the setup() function.

Wire.begin()

If the Arduino is to made a slave then we write the following statement in the setup() function.

Wire.begin(SLAVE_ADDRESS);

where we define the SLAVE_ADDRESS as follows:

#define SLAVE_ADDRESS 0x08

 Master Communication

The master can send information to slave device, request information from slave device.

To start sending message from the master we need to begin the transmission, write the data and then end the transmission. This creates I2C message with address and data. The following statement begins the transmission.

Wire.beginTransmission(SLAVE_ADDRESS);

The next step is to queue data for transmission. This is done using the following command.

Wire.write()

This function takes parameters which can be a byte, a string(number of bytes), an array in which case second parameter is the length of the bytes, 

Wire.write(value); // append a byte
Wire.write(string); // append a string
Wire.write(data, length); // append an array with a specified number of bytes

Finally Wire.write() can also return the amount of bytes appended to the message using the statement like the following.

number =Wire.write(string); // store the number of bytes appended in a variable

 The transmission from master ends using either of the following statement.

Wire.endTransmission(); // send the message

The above statement can have optional bus release parameter which can be TRUE and FALSE. If TRUE then a stop message is sent which frees the I2C bus. If FALSE is specified then a restart message is sent, the I2C bus is not released. 

A stop message is sent using the following statement.

Wire.endTransmission(stop); // send the message and close the connection

The response to the Wire.endTransmission() is a status byte which can be the followings:

The master device can also request information from the slave device. This is done using the following statement:

Wire.requestFrom()

It can take upto 3 parameters. The first parameter is the address of the slave device, the second parameter is the data size in number of bytes and the third which is optional is whether to release the I2C bus.

Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);

After sending the information request, the master can then wait and read the response from the slave. The master can store the information from the slave in a variable as illustrated below.

data = Wire.read(); // store the information in a variable

This will read single byte from the input buffer. For multiple bytes the buffer is checked for any bytes in the buffer which is done using the following statement.

number = Wire.available();

This when executed returns the number of bytes remaining in the buffer. Using the following statement we can continuously check whether there is any data in the buffer and save it in variable and print it.

while(Wire.available()) // Repeat as long as there is data waiting
{
char c = Wire.read(); // Read in one byte
Serial.print(c); // Print the byte
}

Slave Communication

Here Arduino is used as Slave device. This is useful in projects where number of Arduino have to connected and communicated. When using Arduino as Slave device any address can be specified which is an advantage and upto 128 Arduino can be connected as slave devices to a main master Arduino. 

A slave can respond to master request when it receives message or when it is requested for information. These responses are done with callback functions. There are two callback functions: Wire.onReceive() and Wire.onRequest().

The Wire.onReceive() callback is used to serve or take action when master sends information. This callback function requires a function in its argument whose name can be anything. For example, the following Wire.onReceive() callback function has receiveData() function which is called.

Wire.onReceive(receiveData); // Create the callback

The function receiveData() can accept only parameter of data type int. This is illustrated below.

void receiveData(int byteCount)
{
// Put your code here
}

To receive individual bytes, we use the function Wire.read() and save in variable here called data as follows:

data = Wire.read();

The Wire.read() reads 1 byte from the I2C buffer and returns that data. To find out remaining bytes in the I2C buffer we use the function Wire.available().

number = Wire.available();

We can use Wire.read() within a while loop with Wire.available() for condition testing to read in data whenever there is data in the buffer.

while(Wire.available()){
data = Wire.read();
// Do something with data
}

The next callback function important for slave is the Wire.onRequest(). A function is created which is passed into the Wire.onRequest() callback function. The name of the function which is passed as argument can be anything such as sendData() which is as follows.

void sendData(void){

//put your code here

}

Once created it is passed in the callback function:

Wire.onRequest(sendData); // Create the callback 

I2C communication between Arduino

In this tutorial we will use two Arduino to communicate over I2C network. Arduino Uno is used as master and Arduino Mega 2560 is used as slave. The Arduino Uno has a push button and a LED connected to it. The Arduino Mega is connected to a LED.

The schematic is shown below.

Arduino I2C communication circuit diagram

As you can see in the schematic diagram above, the SDA pin which is A4 pin on Arduino Uno is connected to Arduino Mega SDA pin which is the pin number 20. Similarly Arduino Uno SCL pin which is A5 is connected to the Arduino Mega SDA pin which is the pin number 21.

When the push button on the Arduino Uno is pressed, the LED connected to it and the LED connected to the Arduino Mega are turned on. When push button is pressed, the state of the push button is sent to Arduino Mega via the I2C communication line. 

Below picture shows how the two Arduino along with switch and LEDs on breadboards.

Arduino I2C communication

The following video demonstrates how I2C communication between Arduino works.


Program Codes

I2C Master program

The following is the program code for the I2C Master device which is the Arduino Uno in this tutorial. This should thus be uploaded into Arduino Uno.

// Arduino I2C Master

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
#define SW 7
#define LED 3

int data = 0;

void setup(){
  pinMode(7, INPUT_PULLUP);  //Switch
  pinMode(6, OUTPUT); // LED
  Serial.begin(9600);
  Wire.begin();       // Initialize as I2C master
}

void loop(){
  uint8_t sw = digitalRead(SW);

  if(sw == LOW){
      Wire.beginTransmission(SLAVE_ADDRESS); // Prepare message to slave
      Wire.write(1); // Send one byte, LED ON
      Wire.endTransmission(); // End message, transmit
      digitalWrite(LED, HIGH); // Turn the LED on
      delay(10); // Give the slave time to react
      slaveResponse(); // What is the slave's status?
    }
  else if(sw == HIGH){
      Wire.beginTransmission(SLAVE_ADDRESS); // Prepare message to slave
      Wire.write(0); // Send one byte, LED ON
      Wire.endTransmission(); // End message, transmit
      digitalWrite(LED, LOW); // Turn the LED off 
      delay(10); // Give the slave time to react
      slaveResponse(); // What is the slave's status?
    }
  else{
    slaveResponse(); // What is the slave's status?
    }
  
  delay(200);
}

void slaveResponse()
{
  Wire.requestFrom(SLAVE_ADDRESS, 1); // Request 1 byte from slave device
  
  data = Wire.read(); // Receive a byte af data
  switch (data)
  {
    case 0:
      Serial.println("LED is OFF");
      break;
    case 1:
      Serial.println("LED is ON");
      break;      
    default:
      Serial.println("Unknown status detected");
      break;
  }
}

In the above program code we have included the wire library by importing wire.h header file. We have defined 0x04 as the slave address which is required later in the program. Alias name LED and SW are given to pin 3 and 7 respectively. A variable called data is created which is initialized to 0. 

In the setup() function, we have set the LED pin as output and SW as the input. Serial is set up with baud rate of 9600. The wire.begin() without any parameter is used to initialize Arduino Uno as I2C Master. 

In the loop() function, we read the switch state and store it in variable sw. Next we compare whether the switch state sw is LOW or HIGH. When it is LOW then we send 1 using I2C protocol, turn the LED connected to the master Arduino Uno HIGH and also invoke the function slaveResponse(). When the switch state sw is HIGH, which is the normal position, we send 0 using I2C communication protocol, turn the LED connected to the master Arduino Uno LOW and invoke the function slaveResponse(). 

In both cases of the switch state, the I2C protocol begins with Wire.beginTransmission(SLAVE_ADDRESS) then writing the message with Wire.write(1) and finally end the I2C communication with Wire.endTransmission(). The slaveResponse() function is used to request data from the slave device. To request data inside the slaveResponse() function, Wire.requestFrom(SLAVE_ADDRESS, 1) function is used which ask for 1 byte of information from slave with slave address SLAVE_ADDRESS. The requested 1 byte of information is stored in the Master buffer which is read using the Wire.read() and stored in local variable data. This data variable is then compared to 1 and 0 and print the data otherwise unknown status detected is printed on serial monitor.

I2C Slave program

The following is the program code fro the I2C Slave device which is the Arduino Mega in this tutorial. This code should be uploaded into Arduino Mega.

// Arduino I2C Slave

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
#define LED 8

int data = 0;
int state = 0;

void setup(){
  pinMode(LED, OUTPUT); // Internal LED
  Serial.begin(9600);
  Wire.begin(SLAVE_ADDRESS); // Initialize as I2C slave

  // Register I2C callbacks
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
}

void loop(){
  // Nothing to do
  delay(100);
}

// Callback for data reception
void receiveData(int byteCount){
  while(Wire.available())  {
    data = Wire.read();
    Serial.print("Data received: ");
    Serial.println(data);

    if (data == 1){
      digitalWrite(LED, HIGH); // Turn the LED on
      state = 1;
    }
    else{
      digitalWrite(LED, LOW); // Turn the LED off
      state = 0;
    }
  }
}

// Callback for sending data
void sendData(){
    Wire.write(state); // Send the LED state
}

In the above code, the wire library is imported for the I2C communication. Some variables required in the program are declared which are the  SLAVE_ADDRESS which is 0x04 and LED variable which is alias for pin 8. Other variable required later in the program are data and state. 

In the setup() function we have made the LED pin as an output. Initiated the serial monitor with baud rate of 9600. The statement Wire.begin(SLAVE_ADDRESS) is required to make this Arduino Mega as I2C slave device. The Wire.onReceive(receiveData) and Wire.onRequest(sendData) statement sets up I2C slave callback functions which are later used to invoke the function receiveData() and sendData() upon receiving data request from Arduino Uno master.

In the loop() function we do nothing as the request from master is handled by the callback functions. 

The receiveData() which takes in int paramenter byteCount is used to handle Wire.onReceive() callback function. Whenever, slave receives data from master this Wire.onReceive() callback is invoked. Once invoked, receiveData() function is executed. In this tutorial, the master sends 1 or 0 depending upon the switch state. This received 1 or 0 message is stored in the slave buffer which is read whenever it is available(while(Wire.available())) using the Wire.read() function and stored in local data variable. This received data is then sent to serial monitor and the value is stored in variable state.

The sendData() is called and executed whenever the callback function Wire.onRequest(sendData) is invoked. The Wire.onRequest() callback is invoked whenever the slave receives data request as sent by the master using the Wire.requestFrom() function. The data requested is the value stored in the state variable when is sent by the slave to master.

Conclusion & Further Work

In this way we can use I2C communication protocol to transfer data from one Arduino to another Arduino or microcontroller. But one can also use I2C sensor devices such as distance measurement sensor HC-SR04, alcohol sensor, motors, humidity sensor etc. instead of simple LED. The sensor data can then be sent from slave to the master upon request. An example is provided in the tutorial Write Read Sensor Data to External EEPROM with Arduino where temperature sensor is acquired and saved into I2C EEPROM. Another example of I2C device that is regularly used is I2C LCD. The tutorial LCD I2C NodeMCU beginner tutorial shows how to use I2C LCD with NodeMCU and the tutorial Arduino Nano I2C LCD Interfacing & Programming shows how to use I2C LCD with Arduino.

Post a Comment

Previous Post Next Post