El Bus I2C, con ejemplo de DS1307

Vamos a comenzar incluyendo en nuestro programa la libreria de comunicaciones I2C de Arduino, la libreria Wire.

#include <Wire.h>                                     // Para el bus I2C

El reloj y el LCD, cada uno tiene su dirección I2C. Estas direcciones del reloj y el LCD vienen de fabrica, así que no se pueden poner mas de uno por bus, aunque algunas veces hay dispositivos que cada uno contesta cuando le parece, y se puede preguntar algo a todos y leer rápido a ver si les oyes a todos lo que responden, pero no es el caso del reloj y el LCD, que son mas complejos para eso.

Las direcciones I2C son dos cifras en Hexadecimal: Reloj 0x68. Display 0x27 (el 0x no pertenece a la cifra, es para indicar que es hexadecimal), y representa dos grupos de 4 unos y ceros ( 0110 (6) 1000 (8) el reloj y 0010 (2) y 0111 (7) el LCD ), o sea direcciones de 8 unos y ceros (8 bits o 1 Byte) que se suelen escribir así en hexadecimal, aunque si es importante para la persona saber como están los unos y ceros individualmente, también se puede escribir como B01101000 en vez de 0x68, o incluso como cifra decimal entre 0 y 255 (104 para nuestro 0x68). Todo depende del uso. La calculadora de programador de windows es muy útil para convertir entre formatos

La librería I2C de Arduino, que es la versión original del protocolo I2C, usa solo 7  de los 8 bits para la dirección , así que puede haber 2= 128 direcciones diferentes en el mismo bus. Como ves en las direcciones de antes, el bit de la izda de la dirección es un 0 en ambos casos. Ese es el bit que no se usa para la direccion. Para enviar comandos y demas si se usan los 8 bits. De todas formas hay tambien formas de controlar los dispositivos de formas diferentes, y hay juego a la imaginacion

Truco para direccionar dispositivos I2C que utilizan en el “DeLorean Time Circuit”, dejando pasar la señal de reloj solo a un grupo.

Para no tener que andar refiriéndome a las direcciones por su numero, se define un “alias” arriba y así la llamo con un nombre en principio menos “maquinero” (aunque yo no me acuerdo de esto tampoco, pero por lo menos se donde buscarlo …). Aquí esta definido como tipo Integer (int). Integer es una variable mas grande que el byte. Concretamente son 2 bytes ( 00000000 00000000 ). Puede almacenar por tanto 216=65535 valores diferentes. Esto habría que corregirlo para ahorrar memoria del arduino ya que así el byte alto no se usa para nada ( el 0x68 se guarda como 00000000 01101000 ) y se malgasta memoria. De todas formas a veces el compilador se da cuenta de estos fallos y lo corrige (el de Arduino no lo se), y ademas como en realidad es una constante (un alias digamos) esto no ira a la memoria del Arduino, pero lo normal siempre es definir cada cosa del tamaño que hace falta.

// DIRECCIONES I2C

const int DS1307_I2C_ADDRESS = 0x68;     // Direccion del reloj en el I2C
const int LCD_I2C_ADDRESS = 0x27;         // Direccion del LCD en el I2C

Nota: Aprovecho para pintar el codigo tal y como se ve en el Notepad++ si seleccionas lenguaje C, ya que es el que se usa en Arduino. Muy util el Notepad++ …. Se puede poner como editor por defecto en el software de programar el Arduino.

 

Controlador para 16 LED con direccion I2C programable.

 

El reloj que tengo yo lo uso asi: Primero en la parte SETUP del codigo de arduino (la que se ejecuta una sola vez al encender), inicializo la librería de comunicaciones I2C. Esto ya vale para el reloj y para todo así que hace solo una vez. Al hacer esto se “crea el objeto”  Wire que luego usaremos abajo, y se configura el I2C del arduino como Master o Slave. Si doy una direccion a la funcion, me configuro como Slave, como el reloj y el LCD. En la practica al meter esta linea en el programa, el compilador cargara en un área de memoria todo el código (ya traducido a lenguaje maquina) que hay en la librería. Si necesitamos mas memoria para nuestro código, podemos crear versiones personalizadas de las librerías quitando el código que no se use.

Wire.begin();                                        //INICIALIZAR BUS I2C

Despues de la parte SETUP se ejecuta la parte LOOP del código, que es la que se repite una y otra vez, Ahí se hacen todas las cosas del programa que hagas. En el reloj lo primero que se hace por primera vez normalmente es ponerlo en hora. Mediante unas cuantas funciones que tengo que escriben cosas en el el LCD y leen los botones, se puede ir poniendo una fecha y obtener 7 números (Año – Mes – Día – Hora – Minuto – Segundo – Dia de la semana), de valor menor de 255 (que caben por tanto en un Byte cada uno). Defino 7 variables globales de tamaño byte, que mantendrán almacenados en memoria estos datos a lo largo del programa. Como quiero que sean globales (accesibles por todas las funciones), las defino arriba, antes del SETUP:

// VARIABLES DE PROGRAMA. Su valor posiblemente cambie durante la ejecucion.

byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;   // Para almacenar hora y fecha ….

La función que maneja el LCD y los botones, escribe en esas variables los datos que pones tu al ajustar el reloj (menos el dia de la semana). Se va cambiando cada dato uno por uno. Ya explicare otro día esta librería del LCD y botones y la publicare por aquí en cuanto la termine de retocar:

lcd.cambiabyte (&dayOfMonth, 31, 0, 1, 2);        // Cambiar los valores
lcd.cambiabyte (&month, 12, 0, 4, 2);
lcd.cambiabyte (&year, 99, 0, 7, 2);

Y esta otra función es la que una vez termino de meter la hora en el LCD, se la envía al reloj.

 

EscribeReloj(second, minute, hour, dayOfWeek, dayOfMonth, month, year);    // Escribir en el reloj

 

¿Facil no?. ¿Y que hace esta función que envía el dato al reloj?. Pues es muy sencilla porque con el reloj solo se puede hacer 2 cosas: leer la hora que tiene o enviarle la hora para ajustarlo. Para enviarle la hora, y así ajustarlo, la función hace:

– Iniciar la comunicación con esa dirección I2C (como llamar por teléfono). El bus (la linea) entonces se queda ocupado hasta que termine esa comunicación. De todas formas, a 100Kbits por segundo del I2C standard, se manda un byte cada unos 10 microsegundos, o sea que esto va a ser como una chispa en el bus, visto y no visto. Como corren los electrones tu.

 

Wire.beginTransmission(DS1307_I2C_ADDRESS);

 

Ahora que ya tenemos al reloj al teléfono, se le envía un Cero. Con eso se pone atento y hace sus movidas. Escribo 0, y el compilador ya sabe que la función necesita un byte, y la función en la practica envía por el I2C un byte de valor cero (00000000).

 

Wire.write(0);

 

– Detrás, sin mediar mas palabra (esta comunicación es asi de seria), le envías los 8 datos byte a byte en un orden concreto. El reloj internamente ya sabe que hacer con cada dato que le llega por ese orden. La función esta decToBcd olvidar de momento. Hace una conversión del valor que luego veremos.

Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));

– Y para terminar se cierra la comunicación. El cacharro se da por enterado y ajusta la hora y se pone a funcionar. El bus queda libre de nuevo.

 

Wire.endTransmission();

 

Así de fácil es poner en hora este reloj I2C. Y para leer la hora, hacemos esto otro:

– Se abre una comunicación con el reloj como antes, se envía un cero y se cierra. Esto le espabila y le indica que le vamos a pedir algo detrás y entonces él hace sus movidas internas. Algunos sensores pueden necesitar un tiempo para hacer la medida si están en estado de bajo consumo o algo. Todo depende de cada cacharro. A este según su manual para leerlo se le envía el cero este …

 

Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();

 

Y justo detrás se le envía una solicitud de datos con otra función de la librería I2C. Le pido que me envíe 7 datos (7 Bytes) ….

 

Wire.requestFrom(DS1307_I2C_ADDRESS, 7);

 

Y detrás los leo uno por uno con la funcion wire.read, con un par de matices que explicare abajo. Se por su manual, que si le pido 7 datos me envía esto en este orden:

*second     = bcdToDec(Wire.read() & 0x7f);
*minute     = bcdToDec(Wire.read());
*hour       = bcdToDec(Wire.read() & 0x3f);  // Leer en formato 24H
*dayOfWeek  = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month      = bcdToDec(Wire.read());
*year       = bcdToDec(Wire.read());

Eso es todo para el reloj. Así de fácil es tener hora fiable en el Arduino y hacer programadores que hagan cosas a determinadas horas. Lleva una pila de botón para mantener la hora aunque este sin alimentación. Es el mismo reloj que llevan muchas placas base de los ordenadores. Puedes leerlo o escribirlo cuantas veces quieras, o incluso pararlo o arrancarlo y configurarlo para 12 o 24h. Esto se hace con algunos trucos que tiene también por ahí:

El byte que almacena cada dato en el reloj sabemos que puede ir de cero (00000000) a 255 (11111111) como todos los bytes. En este caso para el dato de segundos el valor solo ira desde cero ( 00000000 ) hasta 59 ( 00111011 ). Aunque realmente el numero en este caso el reloj no lo da así, y éste es el primer matiz. Con los números que van a interactuar con las personas, a veces en vez de su valor, se almacena lo que seria su representación de dígitos. Esta forma de guardarlo es el formato BCD, y consiste en guardar los números de la cifra por separado en grupos de 4 bits. El 59 son 2 cifras, y por tanto seria 5 (0101) y 9 (1001). 0101 1001 en formato BCD guardado en un byte que la función bcdtodec convierte en su valor “normal” 00111011.

El máximo valor que va a tener primera cifra de los segundos es 5 (0101). El bit de la izquierda no se va a poner nunca a 1, o sea que no se usa, y entonces lo aprovecha el reloj para almacenar una configuración. Este concreto sirve para controlar el paro / marcha del reloj. Parar el reloj (si esta a 1) o ponerlo en marcha ( a 0). Cuando le escribimos la hora para ajustarlo, al enviarle los segundos podemos enviarle un simple 59 (0101 1001) y se ajusta y se pone en marcha (bit 7 a 0). Si mandamos un (1101 1001), ajustaría los segundos a 59 y pararía el reloj. Al leer los segundos, ese ultimo bit no nos interesa (al leer, este bit estara a 0 si el reloj esta andando y a 1 si esta parado. En otro caso quiza podria interesar ese dato de como esta el reloj) y como no forma parte del numero y puede dar error si le leo a 1, se pone a cero haciendo un AND con el dato y 0x7F (0111 1111). Así se “limpia” el bit 7 poniéndolo a cero. En el caso del dato de la hora, que solo llega a 2 el primer numero (0010), hay 2 bits que sobran. En el bit 6 se configura si el reloj va a 12 o 24 horas, y el bit 7 no se usa para nada, así que al leer la cifra se “limpian” los dos bits que no queremos con AND 0x3f ( 0011 1111 )

La documentación del reloj es esta. Con eso y paciencia se saca como va. Hay unos cuantos datos electrónicos, y en la parte de abajo explica los registros de datos. Ahí se ve que a parte de esos 7 hay uno octavo de control, que controla una salida de señal cuadrada, y parece que se ve ahí el resto de memoria que le sobra. De la misma forma se puede usar cualquier otro dispositivo I2C. Algunos, como el LCD, tienen muchos comandos, y hay librerias ya por ahi ya hechas que lo simplifican un poco mas. Es lo bueno de arduino, que buscando un poco encuentras casi todo.

Hala, a ver si alguien se anima a hacer algo de esto y nos cuenta … Tambien se admiten preguntas …

2 pensamientos en “El Bus I2C, con ejemplo de DS1307

  1. Te lo has currado! Ya sabes que tengo un proyecto dando vueltas por la cabeza. Algún día empezaré a darte la barrila con estos cacharrillos ;-)

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>