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

Cómo conectar dos Arduino por bus I2C

En esta entrada vamos a ver cómo conectar, enviar y recibir datos entre dos procesadores como Arduino empleando el bus de comunicación I2C.

El bus de comunicación es una de las mejores alternativas para conectar dispositivos entre sí. Como hemos visto en muchas entradas, una gran cantidad de dispositivos emplean I2C para comunicarse con Arduino.

Si aún no conocéis el bus I2C os recomendamos que veáis la entrada básica sobre el bus I2C.

Pero el bus I2C no sirve sólo para comunicarnos con todo tipo de sensores, también podemos usarlo para conectar dos o más microprocesadores usando únicamente dos cables para la comunicación.

¿Para qué nos puede servir esto? Pues para muchas cosas. La más típica, conectar entre microprocesadores distintos, como por ejemplo combinar en un proyecto un ESP8266/ESP32 + Arduino, o Raspberry Pi + Arduino.

También puede ser que en un proyecto necesitemos más pines de los que tenemos disponibles, aunque podríamos plantearnos alternativas usar multiplexores u otro tipo de placa como un mega.

Otro uso posible es en el caso de una máquina que cuenta con múltiples grupos de sensores, y sea preferible emplear varios microprocesadores locales, y coordinarlos entre sí.

Finalmente, también es útil cuando vamos justos de potencia de cálculo. Por ejemplo, en el caso de uno o varios subsistemas que requieren estar monitorizando a alta frecuencia, y pasa los datos periódicamente a un procesador jerárquicamente superior.

En cualquiera caso, conectar microprocesadores por bus de comunicación I2C es una opción muy interesante y útil, potencialmente, podríamos montar una red de ¡hasta 127 procesadores! Así que ¡vamos a ello!

Esquema de conexión

Realmente la conexión es muy sencilla. Simplemente alimentamos la alimentación (5V y GND) de ambos Arduinos, y los dos pines del I2C (SDA, SCL) de Arduinos a los pines SDA y SCL del otro.

arduino-conectar-i2c

En el caso de alimentar ambos Arduinos por el micro USB, desconectar Vcc entre ellos y dejar únicamente GND como referencia de voltaje común.

Por otro lado, si alguno de los dispositivos opera a distintas tensiones (por ejemplo, 5V y 3.3), necesitaréis interponer en medio un adaptador de nivel lógico como vimos en esta entrada.

Tener en cuenta que el bus I2C está pensado para funcionar en distancias cortas, típicamente 20-30 cm máximo. Aunque existen expansores de bus I2C que emplean cables cruzados (similar al UART sobre RS485) que consiguen más de 300 m.

Por último, el bus I2C necesita resistencias de Pull-Up. Los Arduinos disponen de resistencias internas, pero son de baja autoridad (valor alto). Si tenéis problemas de comunicación, probar a reducir la longitud de los cables o añadir resistencias externas de 4K7.

Ejemplo de código

Código Master

En cuanto al código, también es bastante sencillo. En el bus I2C uno de los dispositivos actúa como Master y puede enviar información a los Slaves.

Los Slaves no pueden iniciar una comunicación con el Master (por eso se llaman Slaves), pero el Master si puede pedir información a un Slave y este enviárselo.

Como ejemplo, simplemente vamos a en enviar y recibir una variable ‘long’, que en Arduino tiene una longitud de 4 bytes, y es buen ejemplo para enviar variables de más de un 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);
}

Usando el mismo ejemplo podríamos mostrar una estructura que contenga una agrupación de varias variables como mensaje.

Vamos a ver el código del Master que muestra ambos casos. En primer lugar, el Master inicia la comunicación I2C con ‘Wire.begin()’, sin ninguna dirección como parámetro. Esto indica que el Arduino actuará como Master.

Como vemos, la función ‘sendToSlave’ es muy sencilla. Simplemente iniciamos la comunicación con uno de los Slaves, y transmitimos los bytes del mensaje (en nuestro caso, una variable ‘long’)

La función ‘requestToSlave’ no es mucho más difícil. Pedimos a uno de los Slaves un número de bytes, y los recibimos en el bucle siguiente.

Código Slave

Por su parte, el código del Slave sería el siguiente. El Slave inicia el bus I2C con ‘begin(…ADDRESS…)’, indicando que este Arduino funcionará como Slave en la dirección pasada como parámetro.

#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;
  }
}

A continuación, asociamos las funciones de callback a los eventos ‘Wire.onReceive(…)’ y ‘Wire.onRequest(…)‘.

En la función asociada al evento ‘onReceive’ recogemos los datos enviados desde el master y los almacenamos en la variable ‘data’. En el bucle principal comprobamos si el valor de ‘data’ ha cambiado y, en ese caso, lo mostramos por puerto serie.

Por su parte, la función asociada a ‘onRequest’, simplemente devolvemos la variable ‘response’.

En un proyecto real, por supuesto, estos datos no serían valores fijos, y después haríamos algo con ellos. Pero para el ejemplo, es suficiente.

Conclusión

Como vemos, es bastante sencillo conectar dos o más procesadores como Arduino por bus I2C. En general, es útil en proyectos avanzados cuya complejidad justifica la dificultad adicional de emplear más de un microprocesador.

La mayor limitación es que ambos Arduinos tienen que conocer y compartir exactamente el tipo de mensaje que va a recibir. Típicamente, definiremos una estructura que contenga los datos que queremos intercambiar.

Otra limitación es que el envío de datos por I2C en Arduino está limitado a 32 bytes en la librería ‘Wire.h’. Puede ampliarse hasta un máximo de 64 bytes, pero en ciertos proyectos puede ser insuficiente.

Así que, por ejemplo, pensabais usarlo para recibir información codificada en formato Json desde un ESP8266, siento deciros que vais a tener dificultades.

Pero ¡no pasa nada! Precisamente en la próxima entrada veremos cómo evitar estas limitaciones para recibir una comunicación de longitud arbitraria y desconocida. ¡Hasta pronto!