Diferencia entre revisiones de «Wire»

De ArduWiki
Saltar a: navegación, buscar
(Ejemplo 4)
(Referencias externas)
 
(No se muestra una edición intermedia del mismo usuario)
Línea 384: Línea 384:
 
== Referencias externas ==
 
== Referencias externas ==
 
* [https://www.arduino.cc/en/Reference/Wire Wire]
 
* [https://www.arduino.cc/en/Reference/Wire Wire]
 +
* [https://github.com/PaulStoffregen/OneWire OneWire] de Paul Stoffregen
 
* [http://www.gammon.com.au/i2c I2C] - Nick Gammon
 
* [http://www.gammon.com.au/i2c I2C] - Nick Gammon
 
* [https://aprendiendoarduino.wordpress.com/category/i2c/ Bus I2C] - Enrique Crespo
 
* [https://aprendiendoarduino.wordpress.com/category/i2c/ Bus I2C] - Enrique Crespo
 +
* [https://www.luisllamas.es/arduino-i2c/ El bus I2C en Arduino] - Luis Llamas
 
* [https://github.com/felias-fogg/SoftI2CMaster Soft I2C Master] - otra libreria I2C
 
* [https://github.com/felias-fogg/SoftI2CMaster Soft I2C Master] - otra libreria I2C
 
* [https://programarfacil.com/blog/arduino-blog/conectar-dos-arduinos-i2c/ Conectar 2 Arduino con I2C] - Luis Del Valle
 
* [https://programarfacil.com/blog/arduino-blog/conectar-dos-arduinos-i2c/ Conectar 2 Arduino con I2C] - Luis Del Valle

Revisión actual del 22:24 15 jul 2019

Descripción

La librería Wire.h estandar de Arduino le permite comunicarse con dispositivos por bus I2C (Inter-Integrated Circuit o 2 alambres). Usa dos lineas: SDA (datos) y SCL (reloj). Une los GND.

El bus I2C es extremadamente útil para conectar múltiples dispositivos, ya que todos pueden compartir los mismos dos cables (más la tierra claro). Esto se debe a que los dispositivos son "direccionables". Cada dispositivo debe tener una dirección única en el rango de 8 a 119. La dirección 0 está reservada como una dirección de "transmisión", las direcciones 1 a 7 están reservadas para otros fines y las direcciones 120 a 127 están reservadas para uso futuro.

Debido a esto puede tener una pantalla LCD en la dirección 10, otro arduino en la direccion 11 y un sensor en la direccion 12.

Nota: A partir de Arduino 1.0, la biblioteca hereda de la clase Stream, lo que la hace compatible con otras bibliotecas de lectura/escritura. Debido a esto, send() y receive() han sido reemplazados por read() y write().


Placas aplicables

Arduino Pin SDA Pin SCL
UNO, Ethernet A4 A5
MEGA 20 21
Leonardo 2 3
Debido 20 21

Nota: A diferencia de la mayoría de los Arduinos, el ESP8266 no tiene un hardware TWI, por lo que I²C esta implementado por software. Esto significa que puedes usar cualquier pin GPIO. De forma predeterminada, Wire.begin() asignara los pines 4 (SDA) y 5 (SCL), pero puede especificar otros pines usando Wire.begin(SDApin, SCLpin).


Sintaxis

#include <Wire.h>
Wire.begin();

Métodos

Metodos disponible librería Wire.h
Métodos Descripción
Wire.begin(num) o Wire.begin(SDApin, SCLpin) Inicie la libreria Wire y únase al bus I2C como maestro o esclavo. Esto normalmente debería llamarse solo una vez. En caso de varios esclavos es importante numerarlos (8~119).
Wire.beginTrasnmission(num) Comience una transmisión al dispositivo esclavo num. Posteriormente, coloque los bytes en cola para la transmisión con la función Wire.write() y transmítalos llamando a Wire.endTransmission().
Wire.read() Lee un byte de dato desde el buffer que envío un maestro.
Wire.write(dato, longitud) Pone en el buffer del maestro un byte de datos para ser transmitido a un esclavo.
Wire.onReceive(evento) Interrupción para recibir datos. Evento es una función de usuario.
Wire.onRequest(evento) Interrupción para transmitir datos. Evento es una función de usuario.
Wire.endTransmission() Termina comunicación y libera pines. Devuelve 0 si la transmisión tubo éxito.

Proceso

  1. La transmisión empieza con la "condición de inicio", esto es cuando SDA esta en LOW y SCL esta en HIGH.
  2. La direccion consta de 7 bits esta desplazada al MSB. De modo que el LSB tendra 0 = escritura o 1 = lectura (maestro a esclaveo).
  3. Luego, el maestro espera a que el esclavo tire de la línea SDA a LOW, lo cual es un ACK (confirmación) de que existe un esclavo en esa dirección y está listo para recibir datos. Si no hay ningún esclavo conectado y encendido o si no tiene la dirección requerida, ignorará la preticion, dejando la línea SDA HIGH. Esto cuenta como un NAK (reconocimiento negativo).
  4. Luego se transmite el byte de datos, el bit más significativo (MSB) primero.
  5. Después de los 8 bits de datos, el maestro verifica que el esclavo confirma tirando de la línea SDA LOW. Así, se reconoce cada byte.
  6. La transmisión finaliza con la "Condición de parada", esto es cuando SDA esta en HIGH y SCL esta en HIGH.


Velocidad de reloj

Se puede varias la velocidad de reloj así:

TWBR Pre-escaler Frecuencia Comentario
12 1 400 kHz Maxima soportada
32 1 200 kHz
72 1 100 kHz Default
152 1 50 kHz
78 4 25 kHz
158 4 12.5 kHz

Para configurar el pre-escaler en 4 (12.5 kHz):

Wire.begin(); 
TWBR = 158;
TWBR |- bit (TWPS0);

Direcciones

  • Las direcciones I2C son de 7 bits para identificar a los esclavos ya que el 8vo bit determina si se está escribiendo o leyendo.
  • Con 7 bits se puede obteniendo direcciones entre 0~127. Sin embargo, las direcciones de 0~7 y 120~127 están reservados.
  • En caso de tener mas de un esclavo se deben numerar (8~119) con Wire.begin(num_esclavo) y para transmitir el maestro debe identificar con Wire.begin Transmission(num_esclavo). 0 = todos los esclavos.
  • Si ve una dirección mayor que 127 (0x7F), entonces esa es la dirección de 8 bits, que incluye el LSB (lectura/escritura). Debe dividir esa dirección de 8 bits entre dos y desplazar una posición a la derecha () para obtener la dirección correcta para la biblioteca de Wire. Por ejemplo, si una hoja de datos dice usar la dirección 192 (0xC0) para escribir y 193 (0xC1) para leer, esa es una dirección de 8 bits. Divida 192 entre dos y la dirección "real" sera 96 (0x60).

Comentarios

La libreria estándar de I2C para Arduino es Wire. Si bien esta libreria es suficiente la mayor parte del tiempo cuando desea comunicarse con dispositivos, hay situaciones en las que no es aplicable:

  • Los pines de I2C (SDA/SCL) ya están en uso para otros fines,
  • el código se ejecutará en un procesador ATtiny con 1 MHz en pines arbitrarios,
  • tiene poca memoria (flash y RAM), o
  • no desea usar las resistencias de activación habilitadas implícitamente porque sus dispositivos se ejecutan con 3 voltios.

Se adapto la libreria I2C de Peter Fleury que está escrito en ensamblador AVR, de peso extremadamente ligero (< 500 bytes de flash) y muy rápido. Incluso con un ATtiny de 1 MHz, uno todavía puede operar el bus con 33 kHz, lo que implica que puede controlar dispositivos esclavos que usan el protocolo SMBus (tiempo de espera si la frecuencia del bus es inferior a 10 kHz).

Si desea que se ejecute una solución en una MCU ARM (Due, Zero, Teensy 3.x), desea usar los pines en el puerto H o superior en un ATmega256, o si desea usar muchos buses I2C diferentes, esta biblioteca no es la solución adecuada para usted. En estos casos recomiendo SlowSoftI2CMaster, otra libreria I2C escrita en C++.

Nota: Recuerde que Arduino UNO R3 tiene I2C dedicado adicional en los pines debajo del USB.


Advertencias

  • Las direcciones I2C son de 7 bits para identificar a los esclavos ya que el 8vo bit determina si se está escribiendo o leyendo.
  • Con 7 bits se puede obteniendo direcciones entre 0~127. Sin embargo, las direcciones de 0~7 y 120~127 están reservados.
  • En caso de tener mas de un esclavo se deben numerar (8~119) con Wire.begin(num_esclavo) y para trasnmitir el maestro debe indentificar con Wire.begin Transmission(num_esclavo). 0 = todos los esclavos.
  • Si tienes mas de un esclavo o usa cables largos, hay que poner un 4k7 como pull-up en cada linea SDA/SCL.
  • No olvide conectar también todos los GND.
  • El buffer es de 32 bytes.

Nota: Recuerde que se transmite un byte a la vez, por lo que para transmitir un int (2 byte) o un long (4 bytes) se debera hechar mano a las funciones lowByte(), highByte(), bitshift left (<<), bitshift right (<>>), asi como los operadores bit a bit compuestos como: |=, &=, >>=, <<=; tanto en el emisor como el receptor.


Ejemplo 1

#include <Wire.h> 
byte valor = 0 ; 

void setup() {
   Wire.begin();       //unirse al bus i2c (dirección opcional para el maestro) 
   TWBR = 12;          //Aumenta velocidad de reloj 4 veces
} 

void loop() {
   Wire.beginTransmission(44);    //transmitir al dispositivo N° 44 (0x2c) 
   //la dirección del dispositivo se especifica en la hoja de datos
   Wire.write(byte (0x00));       //envía un byte de instrucción
   Wire.write(valor);             //envía el valor del potenciómetro Byte
   if (Wire.endTransmission() == 0){        //deja de transmitir, 0 es exito
      val ++;                       //incrementa el valor 
      if (valor == 64){             //si se alcanza la posición 64 (max)
         valor = 0 ;                //comienza desde el valor más bajo 
      } 
   }else{
      //Error
   }
   delay(500) ; 
}

Ejemplo 2

Conectar dos Arduinos y que el maestro envie una secuencia y el esclavo la ejecute. En este caso usaremos 3 LED en los pines 11, 12 y 13 del esclavo. En el maestro ponemos este código.

#include <Wire.h>
const byte pin[] = {11, 12, 13};
byte estado = 0;
 
void setup() {
  Wire.begin();   //Iniciamos maestro
}

void loop() {
   for (byte i=0; i<3; i++){   
      Wire.beginTransmission(1);   //Nos queremos comunicar con el esclavo 1
      Wire.write(pin[i]);          //Enviamos primer byte, será el pin a encender
      Wire.write(estado);          //Enviamos segundo byte, sera el estado 0/1
      Wire.endTransmission();      //Paramos la transmisión
      delay(1000);
   }
   if (estado == 0){
     estado = 1;
   }else{
     estado = 0;
   }
}

Para el esclavo tendremos:

#include <Wire.h>
 
void setup() {
   Serial.begin(9600);
   pinMode(11, OUTPUT);
   pinMode(12, OUTPUT);
   pinMode(13, OUTPUT);
   Wire.begin(1);            //Unimos este dispositivo al bus I2C con dirección 1
   Wire.onReceive(recive);   //Evento a ejecutar al detectar recepcion
}
 
void loop() {
   delay(100);
}
 
//Función que se ejecuta cada Wire.endTransmission() del maestro para recibir lo que manda con Wire.write().
void recive() {
   byte pin = 0;
   byte estado = 0;
 
   if (Wire.available() == 2){
      pin = Wire.read();            //Leemos primer byte que sera pin
      Serial.print("LED: ");
      Serial.println(pin);
      estado = Wire.read();         //Leemos segundo byte que sera estado
      Serial.print("Estado: ");
      Serial.println(estado);
   }
   digitalWrite(pin, estado);      //Prende o apagar LED
}

Ejemplo 3

En este ejemplo ilustramos como un maestro puede solicitar datos de un esclavo.

Código maestro:

#include <Wire.h>
const byte esclavo = 42;

enum {
   CMD_ID = 1,
   CMD_READ_A0 = 2,
   CMD_READ_D13 = 3
};
 
void setup (){
   Serial.begin(9600);
   Wire.begin();   
   sendCommand(CMD_ID, 1);
   if (Wire.available()) {
      Serial.print("Id del esclavo: ");
      Serial.println(Wire.read(), DEC);
   }else{
      Serial.println ("No responde el esclavo");
   }
}

void loop(){
  int val;
  sendCommand(CMD_READ_A0, 2);
  val = Wire.read();   //Recuperamos MSB
  val <<= 8;           //Desplazamos 8 bits a la izquierda
  val |= Wire.read();  //Recuperamos LSB
  Serial.print("Valor sensor en A0 del esclavo: ");
  Serial.println(val);
  sendCommand(CMD_READ_D13, 1);
  val = Wire.read();
  Serial.print("Estado del pin D13 del esclavo: ");
  Serial.println(val);
  delay(500);   
}

void sendCommand (const byte cmd, const byte largo){
   Wire.beginTransmission(esclavo);
   Wire.write(cmd);
   Wire.endTransmission();
   Wire.requestFrom(esclavo, largo);  //No continua hasta que tenga datos en el buffer
}

Código esclavo:

#include <Wire.h>

const byte esclavo = 42;
char comando = 0;
enum {
    CMD_ID = 1,
    CMD_READ_A0  = 2,
    CMD_READ_D13 = 3
    };

void setup(){
   pinMode(13, INPUT_PULLUP); //Pulsador
   pinMode(A0, INPUT);       //Sensor analogico
   Wire.begin(esclavo);      //Numero del esclavo
   Wire.onReceive(leer);     //Cuando llega mensaje
   Wire.onRequest(pedir);    //Cuando maestro solicita mensaje 
}

void loop(){
   //Nada
}

void sensor(const byte pin){
   int val = analogRead(pin);
   byte buf[2];
   buf[0] = highByte(val);    //Capturamos MSB byte
   buf[1] = lowByte(val);     //Capturamos LSB byte
   Wire.write(buf, 2);        //Envia los 2 byte del sensor en A0
}

void leer(){
   comando = Wire.read();
}

void pedir(){
  switch (comando){
  case CMD_ID:
     Wire.write(42);   //Envia numero del esclavo
     break;
  case CMD_READ_A0: 
     sensor(A0);       //Envia dato sensor en A0
     break;
  case CMD_READ_D13: 
     Wire.write(digitalRead(13));   //Envia estado de pin 13
     break;
  } 
}

Ejemplo 4

En este ejemplo queremos saber que esclavos estan activos.

Código maestro:

#include <Wire.h>

void setup(){
   Serial.begin(115200);
   Serial.println("Escaneando bus I2C...");
   byte n = 0;
   Wire.begin();
   for (byte i=8; i<120; i++){
      Wire.beginTransmission(i);
      if (Wire.endTransmission() == 0){
         Serial.print("Encontre esclavo: ");
         Serial.println(i);
         n++;
         delay(1);
      }
   }
   Serial.println("Fin de busqueda.");
   Serial.print("Encontre ");
   Serial.print(n);
   Serial.println(" esclavo(s).");
}

void loop(){
   //Nada
}

Ejemplo 5

Como ya habras sospechado cada dispositivo en el bus I2C debe tener su propia direccion y esta no puede repetirse. Para ello los dispositivos I2C duelen tener un par de pones rotulados C1 y C2, de forma que podemos cambiar la direccion soldando o cortando las pistas, los que permite 4 direcciones distintas.

#include <Wire.h>

void setup(){
   Wire.begin();
   Serial.begin(9600);
   Serial.println("\nI2C Scanner");
}

void loop(){
   Serial.println("Escaneando...");
   byte error, address, num = 0;
   for (dir=1; dir<127; dir++ ){
      Wire.beginTransmission(dir);
      error = Wire.endTransmission();
      if (error == 0)      {
         Serial.print("Encontré modulo I2C en la dirección 0x");
         if (dir < 16){
            Serial.print("0");
         }
         Serial.println(dir, HEX);
         num++;
      }else if (error == 4){
         Serial.print("Error desconocido en dirección 0x");
         if (dir < 16){
            Serial.print("0");
         }
         Serial.println(dir, HEX);
      }    
   }
   if (num == 0){
      Serial.println("Ningún modulo I2C se encontró\n");
   }else{
      Serial.print(num);
      Serial.println(" módulos encontrados.\n");
   }
   delay(5000);
}

Vea también


Referencias externas