Una de las cosas más importantes que se puede hacer con tu placa Arduino, es comunicarnos con otros dispositivos. Si queremos comunicarnos, por ejemplo, con sensores y memorias, la opción más inteligente es usar el bus I2C.
Si bien, el puerto serie es uno de los medios de comunicaciones más populares, podría decirse que el bus I2C es quizá igual o más popular aún.
Los dispositivos de bus I2C abundan en forma de sensores, actuadores, temporizadores, memorias y un largo etcétera.
Con el auge actual que tiene Internet de las cosas, nuestra placa Arduino y el bus I2C es la combinación ideal para construir dispositivos que se en enmarquen dentro de la creciente tendencia del IoT.
¿Qué es el bus I2C?
El bus I2C fue diseñado por la empresa Philips, en su división Philips Semiconductors (actualmente NXP Semiconductors) a principios de los años 80, y definió las especificaciones para facilitar las comunicaciones entre los distintos componentes que residen en una misma placa de circuito.
El nombre del bus I2C se traduce en Inter IC. A veces, el bus se llama IIC o bus I²C. Este bus también es conocido como TWI (Two Wire Interface).
Este nombre alternativo fue debido únicamente por motivos de licencia, no obstante, la patente del bus I2C caducó en 2006, por lo que actualmente ya no hay restricción sobre el uso del término I2C.
La velocidad de comunicación original se definió inicialmente con un máximo de 100 kbit por segundo, ya que muchas aplicaciones no requieren transmisiones más rápidas.
Para aquellos casos en donde se necesita velocidades mayores hay un modo rápido de 400 kbit y, desde 1998, está disponible una opción de alta velocidad de 3.4 Mbit.
El bus I2C no solo se utiliza en placas individuales, sino también para comunicar componentes que están conectados mediante cables. La simplicidad y la flexibilidad son características clave que hacen que este bus de comunicaciones sea atractivo para muchas aplicaciones.
Las características más importantes incluyen:
- Solo se requieren dos líneas de conexión.
- Sin requisitos estrictos de velocidad de transmisión como, por ejemplo, con RS232, el controlador maestro genera la señal de reloj del bus.
- Relación simple entre maestro/esclavo para todos los componentes.
- Cada dispositivo conectado al bus es direccionable por software mediante una única dirección.
- El bus I2C es un verdadero bus multimaestro que proporciona arbitraje y detección de colisiones
¿Cómo funciona el bus I2C?
El bus I2C usa dos líneas para comunicarse. Transmite y recibe datos desde la línea SDA (Serial DAta), y envía señal de reloj con la línea SCL (Serial CLock).
A nivel eléctrico, se debe considerar que las líneas SDA y SCL son de tipo drenaje abierto (también conocido como colector-abierto en el mundo TTL), es decir, el maestro del bus y los dispositivos esclavos solo pueden conducir estas líneas a un nivel bajo (GND) o dejarlas abiertas.
La resistencia de terminación Rp tira de la línea hasta VCC si ningún dispositivo en el bus la está jalando hacia abajo.
Esto permite funciones como la operación simultánea de más de un maestro en el bus (si son capaces de soportar múltiples maestros) o estiramiento (los esclavos pueden ralentizar la comunicación manteniendo la línea SCL a nivel bajo).
El proceso de comunicación en el bus I2C es:
- El maestro comienza la comunicación enviando una secuencia de bits llamada start condition. Esto alerta a los dispositivos esclavos, poniéndolos en modo espera de una transmisión de datos.
- El maestro envía un byte en donde los bits (A7-A1) componen la dirección del dispositivo esclavo con el que se quiere comunicar. El octavo bit (A0) se corresponde con la operación de R/W deseada, esto es, lectura (“1” lógico, recibir datos del esclavo) o escritura (“0” lógico, enviar datos al esclavo).
- La dirección enviada es comparada por cada esclavo en el bus I2C con su propia dirección, si ambas coinciden, el esclavo se considera direccionado como esclavo-transmisor o esclavo-receptor dependiendo del bit R/W.
- Cada byte leído/escrito por el maestro debe ser reconocido en forma obligatoria mediante un bit de ACK (Acknowledge – Reconocimiento) por el dispositivo maestro/esclavo.
- Cuando la comunicación termina, el maestro transmite una señal de stop condition para dejar libre el bus.
¿Cómo usar el bus I2C en mi placa Arduino?
La placa Arduino tiene de soporte por hardware para el bus I2C en ciertos pines. Si bien es posible emplear cualquier otro par de pines como bus I2C usando una librería de control por software, la velocidad será mucho menor.
Los pines a los que están asociados varían de un modelo a otro. En los casos de las placas Arduino UNO, Arduino Pro Mini o Arduino Nano la línea SDA está en el pin A4 y la línea SCL está en el pin A5.
Para el caso de la placa Arduino Mega 2560 estos corresponden a los pines 20 y 21 respectivamente.
Para usar el bus I2C en otros modelos, debe consultar el esquema de patillaje correspondiente a la placa Arduino a usar. El Arduino IDE proporciona la librería Wire, que contiene las funciones necesarias para controlar el bus I2C.
Ejemplos de uso del bus I2C en Arduino
Gracias a la librería Wire, usar el bus I2C es realmente fácil. El único requisito es conectar el dispositivo al bus y conocer su dirección.
Puede ocurrir que no conozcamos la dirección del dispositivo a usar. En ese caso, lo que haremos es que nuestra placa Arduino lo averigüe por nosotros. Para ello, usaremos el siguiente programa, un escáner I2C.
Es un ejemplo clásico de inicio de transmisión en I2C, en este caso, es el envio desde el maestro de una dirección hacia el bus a la que consultar a un esclavo.
Si el esclavo está presente en el bus, contestara con un ACK.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/* * Escáner I2C * * https://www.proyectoarduino.com * */ #include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); // Espera a que inicie el puerto serie while (!Serial); Serial.println("\nEscaner I2C"); } void loop() { byte error, direccion; int num_dispositivos; Serial.println("Buscando..."); num_dispositivos = 0; for(direccion = 1; direccion < 127; direccion++ ) { // El escaner I2C usa el valor de retorno // de Write.endTransmisstion para ver si algún // dispositivo a respondido con ACK // a la direccion solicitada. Wire.beginTransmission(direccion); error = Wire.endTransmission(); if (error == 0) { Serial.print("Se ha encontrado dispositivo I2C en la dirección 0x"); if (direccion<16) Serial.print("0"); Serial.print(direccion,HEX); Serial.println(" !"); num_dispositivos++; } else if (error==4) { Serial.print("Error desconocido en la dirección 0x"); if (direccion<16) Serial.print("0"); Serial.println(direccion,HEX); } } if (num_dispositivos == 0) Serial.println("No se ha encontrado dispositivos I2C\n"); else Serial.println("¡Listo!\n"); // Espera 5 seg para la siguiente búsqueda delay(5000); } |
Para el caso de lectura y escritura de datos usando el bus I2C, usaremos un ejemplo en donde se necesitan dos placas Arduino UNO, aunque con pocas o ninguna modificación puede ser usado para otros modelos de placas.
Para este ejemplo, una placa Arduino actuará como maestro, y la otra placa como esclavo.
Solo serán necesarias 3 conexiones, SDA entre los pines A4, SCL entre los pines A5 y GND, en común entre las dos placas.
Programa para Arduino I2C en modo maestro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* * Arduino I2C en modo maestro * * https://www.proyectoarduino.com * */ #include <Wire.h> void setup() { // Inicializa el bus i2c (la direccion del master es opcional) Wire.begin(); } byte x = 0; void loop() { // Inicia transmisión al dispositivo #9 Wire.beginTransmission(9); // Envía 5 bytes Wire.write("x = "); // Envía un byte Wire.write(x); // Finaliza transmisión Wire.endTransmission(); x++; delay(500); } |
Programa para Arduino I2C en modo esclavo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* * Arduino I2C en modo esclavo * * https://www.proyectoarduino.com * */ #include <Wire.h> void setup() { // Inicializa bus i2c con la direccion #9 Wire.begin(9); // Manejador de eventos para I2C Wire.onReceive(recibeDatoI2C); // Inicializa el puerto serie Serial.begin(9600); } void loop() { // Hacer nada... delay(100); } // Cada vez que se reciba un dato en el bus I2C // se activa esta función void recibeDatoI2C(int num_datos) { // Recibe todos los datos excepto el último while (1 < Wire.available()) { // Recibe el dato como un caracter char c = Wire.read(); // Envia el caracter recibido al puerto serie Serial.print(c); } // Recibe el dato como un entero int x = Wire.read(); // Envia el entero al puerto serie Serial.println(x); } |