Language: EN

como-conectar-dos-arduino-por-bus-i2c

How to connect two Arduino by I2C bus

In this post we are going to see how to connect, send and receive data between two processors like Arduino using the I2C communication bus.

The communication bus is one of the best alternatives to connect devices to each other. As we have seen in many posts, a large number of devices use I2C to communicate with Arduino.

But the I2C bus is not only useful for communicating with all kinds of sensors, we can also use it to connect two or more microprocessors using only two wires for communication.

What can this be used for? Well, for many things. The most typical is to connect different microprocessors, such as combining an ESP8266/ESP32 + Arduino, or Raspberry Pi + Arduino in a project.

It may also be the case that in a project we need more pins than we have available, although we could consider alternatives such as using multiplexers or other types of boards such as a mega.

Another possible use is in the case of a machine that has multiple groups of sensors, and it is preferable to use several local microprocessors, and coordinate them with each other.

Finally, it is also useful when we are short of computing power. For example, in the case of one or several subsystems that require monitoring at high frequency, and periodically pass the data to a hierarchically superior processor.

In any case, connecting microprocessors by I2C communication bus is a very interesting and useful option, potentially, we could set up a network of up to 127 processors! So let’s get to it!

Connection diagram

Actually, the connection is very simple. We simply power the power (5V and GND) of both Arduinos, and the two I2C pins (SDA, SCL) of Arduinos to the SDA and SCL pins of the other.

arduino-conectar-i2c

In the case of powering both Arduinos by micro USB, disconnect Vcc between them and leave only GND as a common voltage reference.

On the other hand, if any of the devices operates at different voltages (for example, 5V and 3.3), you will need to interpose a level shifter in between as we saw in this post.

Keep in mind that the I2C bus is intended to operate over short distances, typically 20-30 cm maximum. Although there are I2C bus expanders that use crossed cables (similar to UART over RS485) that can achieve more than 300 m.

Finally, the I2C bus requires Pull-Up resistors. The Arduinos have internal resistors, but they are of low authority (high value). If you have communication problems, try reducing the length of the cables or adding external resistors of 4K7.

Code example

Master code

As for the code, it is also quite simple. In the I2C bus, one of the devices acts as Master and can send information to the Slaves.

The Slaves cannot initiate communication with the Master (that’s why they are called Slaves), but the Master can request information from a Slave and receive it.

As an example, we are simply going to send and receive a ‘long’ variable, which in Arduino has a length of 4 bytes, and is a good example for sending variables of more than one byte.

#include "Wire.h"

const byte I2C_SLAVE_ADDR = 0x20;

long data = 100;
long response = 0;

void setup()
{
  Serial.begin(115200);
  Wire.begin();
}

void loop()
{
  sendToSlave();
  requestToSlave();
  delay(2000);
}

void sendToSlave()
{
  Wire.beginTransmission(I2C_SLAVE_ADDR);
  Wire.write((byte*)&data, sizeof(data));
  Wire.endTransmission();
}

void requestToSlave()
{
  response = 0;
  Wire.requestFrom(I2C_SLAVE_ADDR, sizeof(response));

  uint8_t index = 0;
  byte* pointer = (byte*)&response;
  while (Wire.available())
  {
    *(pointer + index) = (byte)Wire.read();
    index++;
  }

  Serial.println(response);
}

Using the same example we could show a structure containing a grouping of several variables as a message.

Let’s see the Master code that shows both cases. Firstly, the Master initiates the I2C communication with ‘Wire.begin()’, without any address as a parameter. This indicates that the Arduino will act as Master.

As we can see, the ‘sendToSlave’ function is very simple. We simply initiate communication with one of the Slaves, and transmit the bytes of the message (in our case, a ‘long’ variable).

The ‘requestToSlave’ function is not much more difficult. We request a number of bytes from one of the Slaves, and receive them in the next loop.

Slave code

For its part, the Slave code would be as follows. The Slave initiates the I2C bus with ‘begin(…ADDRESS…)’, indicating that this Arduino will work as Slave at the address passed as a parameter.

#include "Wire.h"

const byte I2C_SLAVE_ADDR = 0x20;

void setup()
{
  Serial.begin(115200);

  Wire.begin(I2C_SLAVE_ADDR);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

long data = 0;
long response = 200;

void receiveEvent(int bytes)
{
  data = 0;
  uint8_t index = 0;
  while (Wire.available())
  {
    byte* pointer = (byte*)&data;
    *(pointer + index) = (byte)Wire.read();
    index++;
  }
}

void requestEvent()
{
  Wire.write((byte*)&response, sizeof(response));
}

void loop() {

  if (data != 0)
  {
    Serial.println(data);
    data = 0;
  }
}

Next, we associate the callback functions with the events ‘Wire.onReceive(…)’ and ‘Wire.onRequest(…)‘.

In the function associated with the ‘onReceive’ event, we collect the data sent from the master and store it in the ‘data’ variable. In the main loop, we check if the value of ‘data’ has changed and, in that case, we show it by serial port.

For its part, the function associated with ‘onRequest’, we simply return the ‘response’ variable.

In a real project, of course, these data would not be fixed values, and then we would do something with them. But for the example, it is sufficient.

Conclusion

As we can see, it is quite simple to connect two or more processors like Arduino by I2C bus. In general, it is useful in advanced projects whose complexity justifies the additional difficulty of using more than one microprocessor.

The major limitation is that both Arduinos have to know and share exactly the type of message that will be received. Typically, we will define a structure containing the data we want to exchange.

Another limitation is that the data transmission via I2C in Arduino is limited to 32 bytes in the ‘Wire.h’ library. It can be expanded up to a maximum of 64 bytes, but in certain projects it may be insufficient.

So, for example, if you were thinking of using it to receive information encoded in Json format from an ESP8266, I am sorry to say that you will have difficulties.

But it’s okay! Precisely in the next post we will see how to overcome these limitations to receive communication of arbitrary and unknown length. See you soon!