Descubre todo lo que necesitas saber sobre el protocolo de comunicación I2C y cómo implementarlo en microcontroladores PIC usando el compilador CCS C. En esta guía, te llevaremos paso a paso desde la teoría hasta la práctica, con un tutorial detallado que incluye ejemplos y código listo para usar.
Antes de comenzar, te invito a que veas todas las entradas de nuestro CURSO GRATUITO DE MICROCONTROLADORES PIC.
Y que te suscribas al canal para seguir aprendiendo más sobre estos temas.
Comunicacion I2C Microcontroladores PIC
Vamos a comenzar a entender inicialmente a ver la explicación del protocolo I2C el cual puede ser implementado en cualquier microcontrolador, tanto en el PIC como también se puede implementar una comunicación I2C con Arduino.
¿Que es el I2C?
El bus de comunicaciones I2C, conocido como Inter-Integrated Circuit, es un protocolo que permite la comunicación entre múltiples dispositivos mediante solo dos hilos: uno para los datos (SDA) y otro para el reloj (SCL). Es ampliamente utilizado en sistemas embebidos por su simplicidad y eficacia para conectar componentes como sensores, memorias y otros periféricos.
Aunque el I2C es muy popular en aplicaciones donde la simplicidad y la baja velocidad son primordiales, existe otro protocolo llamado SPI (Serial Peripheral Interface) que también es muy utilizado en la comunicación entre dispositivos. A diferencia del I2C, el SPI utiliza cuatro hilos y es conocido por ofrecer una comunicación más rápida y eficiente en distancias cortas.
Ejemplo Práctico: Imagina que necesitas conectar un sensor de temperatura y una memoria EEPROM a un microcontrolador PIC. Con el protocolo I2C, puedes gestionar la comunicación entre estos dispositivos de manera eficiente utilizando un solo bus compartido
SDA
SDA es la línea utilizada para transmitir los datos de manera bidireccional entre el maestro y los dispositivos esclavos conectados al bus. Esta línea se encarga de llevar la información necesaria para la comunicación, como direcciones, datos a enviar, y confirmaciones de recepción. El flujo de datos en la línea SDA es controlado por el maestro, que decide cuándo enviar o recibir información de los dispositivos esclavos.
SCL
SCL, por otro lado, es la línea que transporta la señal de reloj que sincroniza la transferencia de datos en la línea SDA. El maestro genera los pulsos de reloj en la línea SCL, y todos los dispositivos conectados al bus I2C deben seguir esta señal para coordinar cuándo deben leer o escribir datos en la línea SDA. En esencia, mientras SCL define el ritmo de la comunicación, SDA es el canal por donde fluye la información. Estos dos hilos trabajan juntos para asegurar una comunicación ordenada y sincronizada entre los dispositivos en un entorno I2C.
¿Como trabaja la comunicación I2C?
Para poder reconocer cada uno de los dispositivos conectados a los DOS hilos del bus I2C, a cada dispositivo se le asigna una dirección.
Así en este tipo de comunicaciones el MAESTRO es el que tiene la iniciativa en la transferencia y este es quien decide con quien se quiere conectar para enviar y recibir datos y también decide cuando finalizar la comunicación.
Los DOS hilos del BUS interfaz de comunicación I2C PIC son lineas de colector abierto donde una de las lineas lleva la señal de reloj y es conocida como (SCL), y la otra linea lleva los datos y es conocida como (SDA).
Comparado con el protocolo SPI, el I2C es más sencillo en cuanto al número de líneas necesarias, ya que SPI requiere dos líneas adicionales para la selección del esclavo (SS) y los datos enviados en sentido opuesto (MISO). Sin embargo, el SPI es más rápido y suele ser preferido en aplicaciones donde se necesita alta velocidad de transferencia de datos.
Los Pines SDA y SDL I2C se encuentran especificados en todos los componentes que usan este tipo de protocolo de comunicación.
Para que la comunicación funcione se deben utilizar unas resistencias PULL UP (resistencias conectadas a positivo) para asegurar un nivel alto cuando NO hay dispositivos conectados al BUS I2C.
La conexión I2C entre un maestro y vários esclavos se muestra a continuación:
El número de dispositivos que pueden conectarse de esta forma y además la longitud del BUS es limitada por la capacidad de direccionamiento (de 7 a 10 bits) y por la máxima carga del BUS (400pF). La velocidad máxima estándar es de 100Kbps.
Videos:
Funcionamiento de la Comunicación I2C PIC
La transmisión de datos se inicia con un bit de inicio (START) y termina con un bit de finalización (STOP). El bit de START se reconoce porque la linea SDA pasa de un estado lógico alto para un estado lógico de bajo cuando la linea SCL esta en nivel alto. El STOP se establece cuando hay una transición de bajo a alto en la linea SDA, cuando SCL está en un nivel alto.
Cuando comienza la transmisión de datos, el MAESTRO envía la dirección del ESCLAVO con el cual se quiere comunicar, esta dirección puede ser de 7 o 10 bits con formato de byte (uno o dos bits respectivamente) Después de la dirección se adiciona 1 bit, que indica si se desea ESCRIBIR o LEER (R/W).
Cuando el Maestro envía estos DATOS para el esclavo. El ESCLAVO debe responderle al maestro con un bit de confirmación para informarle que escucho la solicitud del maestro y que esta a disposición de lo que él necesite. Este bit de confirmación se conoce como (ACK).
Si el maestro NO recibe este bit, la comunicación se interrumpe. Por otro lado, se puede dar el caso que un ESCLAVO está mandando alguna información al maestro, entonces el maestro también generará este bit de confirmación hacia el esclavo.
Puede darse el caso también de que una vez el MAESTRO se comunica con el ESCLAVO, el MAESTRO no abandone el BUS y continúe comunicándose con el ESCLAVO, para eso el MAESTRO debe generar una nueva condición de START que se conoce en la literatura como START REPETIDA (Sr), idéntica al START anterior solo que con un pulso de reconocimiento.
Para trabajar con el modulo de comunicación 12C PIC, se deben configurar los siguientes registros: SSPCON, SSPCON2, SSPADD, SSPBUF, SSPSTAT y SSPSR.
Protocolo de Comunicación I2C con CCS C
En CCS C PIC C, existen funciones que nos facilitan la implementación de este tipo de comunicación que permiten usar el puerto I2C. Comencemos primero viendo el tipo de configuraciones que podemos llevar a cabo:
Para las configuraciones genéricas del i2c declaramos lo siguiente en el encabezado de nuestro programa:
#use i2c (opciones)
Estas opciones pueden ir separadas por comas y pueden ser cualquiera de las siguientes opciones
La directiva (#use i2c) tiene efecto en las funciones: I2C_START, I2C_STOP, I2C_READ. I2C_WRITE y I2C_POLL. Se utilizan funciones de Software a menos que se especifique FORCE_HW.
Como por ejemplo:
#USE I2C(master, sda=PIN_B0, scl=PIN_B1) #USE I2C(slave, sda=PIN_C4, scl=PIN_C3, address=0XA0, force_hw) #USE I2C(master, sda=PIN_B0, scl=PIN_B1, fast=450000)
Las funciones asociadas al i2c son:
i2c_start();
Para el modo maestro, esta función inicia la trasmisión. Después de la condición de arranque o START, el reloj es colocado en nivel lógico bajo hasta el momento en que se escriba con la función I2C_WRITE(); Si se llama otra función I2C_START antes que un I2C_STOP quiere decir que se está utilizando un START Repetido (Sr). Esta función depende de la respuesta del esclavo.
i2c_stop();
Finaliza la transmisión.
i2c_write(DATO);
En esta función «DATO» es un entero de 8 bits que se envía por el bus. Cuando un dispositivo que es MAESTRO aplica esta instrucción , se genera una señal de reloj que marca la velocidad de transmisión del dato; Cuando un ESCLAVO aplica esta instrucción se queda esperando la señal de reloj que genere el MAESTRO.
Esta función devuelve el bit de reconocimiento ACK que le envía el receptor cuando la transmisión ha terminado: 0 indica ACK; 1 indica NOACK y 2 indica una colisión en el modo MULTIMASTER.
Para saber la dirección de la transmisión, se observa el bit de menor peso (LSB) del primer dato transmitido tras un START (Si el bit es «0» quiere decir que la información se está transmitiendo de MAESTRO a ESCLAVO)
Dato=i2c_read(); Dato2=i2c_read([ack]);
Dato es un entero de 8 bits leído del BUS. Cuando un dispositivo que es MAESTRO aplica esta instrucción se genera una señal de reloj que marca la velocidad de transmisión del dato; Cuando un ESCLAVO aplica esta instrucción se queda esperando la señal de reloj que genere el MAESTRO. No hay timeout por lo que esta instrucción se utiliza junto con i2c_poll() para prevenir bloqueos.
Opcionalmente se puede incluir un ACK donde 1 indica ACK; 0 indica NOACK
valor=i2c_poll();
Se utiliza solo si el PIC tiene modulo SSP. Devuelve un TRUE(1) si se ha recibido el dato en el buffer y un FALSE(0) si no se ha recibido. Cuando devuelve el TRUE(1) la función i2c_read() guarda el dato recibido.
estado=i2c_isr_state();
Se utiliza solo si el PIC tiene modulo SSP. Devuelve el estado del bus en modo ESCLAVO después de una interrupción.
Estado es un entero de 8 bits.
i2c_slaveaddr(int8 adr);
Se especifica la dirección del dispositivo en modo esclavo.
Protocolo I2C PIC – Ejemplo
Un proceso de temperatura tiene incorporado una tarjeta electrónica encargada de realizar el monitoramiento de la variable temperatura en el interior del proceso.
Dicha tarjeta electrónica cuenta con PIC16F887, un LCD, un puerto de comunicación serial, un sensor I2C de temperatura (DS1621), cuenta con un reloj calendario de tiempo real I2C (DS1307) y cuenta además con una memoria EEPROM serie I2C (M24512).
Se solicita al ingeniero de control realizar un programa en el PIC16F887, para que por medio de un menu en el computador, el usuario pueda tener la opción de leer la temperatura y la hora de lectura de la misma almacenando estos datos en la memoria EEPROM y que estos datos también se visualicen en el LCD.
El Menú debe tener una segunda opción que le permita al usuario ver por el puerto serial los últimos datos almacenados en la EEPROM.
Circuito Electronico Comunicación I2C Microcontroladores PIC:
En el proteus vamos a necesitar los siguientes componentes: PIC16F887, RES, CAP, CRYSTAL, M24512, DS1621, DS1307 y LM016L.
Código de Implementación de Comunicación I2C PIC:
A continuación se muestra el procedimiento y todo el código de implementación del ejemplo. Recuerda que para poder ver el código es necesario que COMPARTAS el contenido de este post o te SUSCRIBAS con cualquiera de los tres botones que están abajo y automáticamente el Código de implementación queda desbloqueado para que lo pegues y lo copies en tu compilador.
En este ejemplo vamos a hacer la libreria I2C para PIC nosotros mismos, entonces es una práctica muy interesante donde vas a aprender mucho.
Para solucionar este ejemplo vamos a crear tres librerías para manejar cada uno de los 3 esclavos del ejemplo.
EEPROM
En entradas pasadas habíamos utilizado la libreria de la EEPROM del PIC para guardar datos en ella, en este ejemplo vamos a crear nuestra propia libreria I2C para PIC para entender como programarla por i2c:
La EEPROM M24512 (click para ver datasheet) tiene tres pines E2, E1, E0 que permiten establecer la dirección de este dispositivo, de esta manera el PIC se comunicara con la Memoria cuando el envíe por el i2c dicha dirección.
Esta dirección es almacenada dentro de la EEPROM en un byte de control donde la parte alta de este byte siempre es fija (1010) y la parte baja corresponde a los estados lógicos de E2, E1, E0, por ultimo viene el bit de escritura/lectura, como puede ser visto en la siguiente figura:
Lo que quiere decir que si el PIC envia 0xA0 el PIC estará escribiendo en la memoria, por otro lado si manda 0xA1h el PIC estará leyendo la memoria.
El formato de escritura de la EEPROM es mostrada en la hoja de datos en la pagina 15, donde después de la señal de START, se escribe la palabra de control para seleccionar el dispositivo y el modo de trabajo, dos bytes para la dirección de escritura y el dato a escribir.
A continuación se muestra la función de escritura de nuestra libreria I2C para PIC que guardaremos en un archivo llamado EEPROM_24512.c
void write_ext_eeprom(long int address, BYTE data) { short int status; i2c_start(); //Inicializa la Transmisión i2c_write(0xa0); //Escribe la palabra de control(direccion //0h + 0 para escritura) i2c_write(address>>8); //Parte alta de la dirección a escribir en la eeprom i2c_write(address); //Parte baja de la dirección a escribir en la eeprom i2c_write(data); //Dato a escribir i2c_stop(); //Finalización de la transmisión i2c_start(); //Reinicio status=i2c_write(0xa0); //Lectura del bit ACK, para evitar escrituras //incorrectas while(status==1) //Si es "1" se espera para responder al esclavo { i2c_start(); status=i2c_write(0xa0); } }
El proceso de escritura puede hacerse de 4 formas como mostrada en la pagina 19 del datasheet, nosotros utilizaremos la segunda forma «leer una dirección cualquiera (Random Address Read)» que es la más común y dicho proceso de lectura es muy similar con el de escritura solo que en este caso al final el MAESTRO debe enviar un NO ACK.
A continuación se muestra la segunda función de nuestra librería I2C para PIC que colocaremos en nuestra librería de EEPROM_24512.c
BYTE read_ext_eeprom(long int address) { BYTE data; i2c_start(); //Inicializa la Transmisión i2c_write(0xa0); //Escribe la palabra de control(direccion //0h + 0 para escritura) i2c_write(address>>8); //Parte alta de la dirección a escribir en la eeprom i2c_write(address); //Parte baja de la dirección a escribir en la eeprom i2c_start(); //Reinicio i2c_write(0xa1); //Escribe la palabra de control(direccion //0h + 1 para escritura) data=i2c_read(0); //Lectura del Dato i2c_stop(); //Finalización de la transmisión return(data); }
RELOJ DE TIEMPO REAL DS1307
Datasheet click aca. Este dispositivo es capaz de suministrar los segundos, minutos, horas, dia, mes y año en tiempo real (por medio de un cristal de cuarzo de 32.768Khz).
Este dispositivo posee una serie de registros donde aparecen los datos necesarios (tabla 2 de la pagina 8 del datasheet). Estos datos son suministrados en BCD.
El ciclo de escritura de este dispositivo se inicia con la palabra 0xD0 y la lectura con 0xD1 (pagina 12).
En la escritura el segundo byte es un puntero que indica la dirección del dato que queremos escribir (ver la tabla 2).
En el ciclo de lectura es igual que el de escritura solo que el ultimo byte debe indicar un NACK al Maestro. Con esto tan solo queda convertir los bytes leídos de bcd a binario.
Para el manejo del reloj de tiempo real vamos a crear una libreria I2C para PIC llamada DS1307.c con las siguientes funciones:
int BCDaBIN(BYTE bcd) { int varia; varia=bcd; varia>>=1; varia &= 0x78; return (varia + (varia>>2) + (bcd & 0x0f)); } void rtc_get_time(BYTE& hr, BYTE& min, BYTE& sec) { i2c_start(); //Escritura i2c_write(0xD0); //Codigo de escritura i2c_write(0x00); //Puntero de la primera dirección i2c_start(); //Lectura i2c_write(0xD1); //Codigo de lectura sec = BCDaBIN(i2c_read()&0x7f); //Lectura de los 7 bit de los segundos min = BCDaBIN(i2c_read()&0x7f); //Lectura de los 7 bit de los minutos hr = BCDaBIN(i2c_read(0)&0x3f); //Lectura de los 6 bit de las horas i2c_stop(); //Finaliza comunicación }
TERMOMETRO
El termometro digital i2c DS1621 (Clic aca Datasheet) permite medir una temperatura entre -55°C y 125°C como mostrado en la tabla 2 de la pagina 4 del datasheet.
Este valor de temperatura es suministrado en dos Bytes. El byte alto es el valor entero con una resolución de 1°C y el segundo byte es el valor decimal con una resolución de 0.5°C.
El termómetro tiene tres pines los cuales sirven para darle la dirección al dispositivo (A2, A1, A0). En nuestro ejemplo tenemos la dirección (0x03).
Al igual que la memoria EEPROM el termómetro tiene un byte de control donde se almacena la dirección y si se desea escribir o leer. Este byte de control posee en su parte alta un numero fijo (1001) y la parte baja viene dado por los pines de dirección (A2, A1, A0) y el ultimo bit (R/W) es de escritura o lectura.
En la pagina 10 del datasheet se muestran todos los comandos que se pueden usar con este dispositivo y la pagina 9 muestra ejemplos de como debe realizarse el envío de tramas por el i2c.
Los comandos pueden ser 0xAA para lectura de la temperatura, 0xEE para el inicio de conversión.
A continuación se muestran las funciones que vamos a colocar en nuestra librería llamada TEMP_DS1621.c
void init_temp(int address) { i2c_start(); i2c_write(0x90|(address<<1)); //Genera primer byte (1001A2A1A0W) i2c_write(0xee); //Inicia conversion i2c_stop(); } float read_full_temp(int address) { float tura; BYTE datah; BYTE datal; int data; i2c_start(); i2c_write(0x90|(address<<1)); //Genera primer byte (1001A2A1A0-W) i2c_write(0xaa); //Lee temperatura i2c_start(); i2c_write(0x91|(address<<1)); //Genera primer byte (1001A2A1A0-R) datah=i2c_read(); //Lectura parte alta datal=i2c_read(0); //Lectura parte alta y NACK i2c_stop(); data=datah; if (datal==128) tura=data+0.5; else tura=data; return(tura); }
PRINCIPAL
Dado que la temperatura es un dato tipo FLOAT, no se puede guardar este dato directamente en la EEPROM, por lo tanto vamos a utilizar una librería suministrada por CCS C llamada FLOATEE.c que nos permitirá guardar y leer datos tipo FLOAT en una EEPROM.
A continuación se muestra el programa principal el cual despliega un menú, donde si se digita «1» el pic lee la temperatura y la hora y las almacena en la memoria EEPROM mostrando estos datos en el LCD. Si el usuario presiona «2» el PIC lee la memoria y muestra los últimos 7 datos en el terminal serie.
#INCLUDE <16F887.h> #DEVICE ADC=10 #USE DELAY(CLOCK=4000000) #FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP //Configura direccion de memoria de los puertos A,B,C,D #BYTE PORTA= 5 #BYTE PORTB= 6 #BYTE PORTC= 7 #BYTE PORTD= 8 #define EEPROM_SDA PIN_C4 #define EEPROM_SCL PIN_C3 #use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, bits=8, parity=N) #use i2c(MASTER,Fast=100000, sda=EEPROM_SDA, scl=EEPROM_SCL,force_sw) #include <lcd.c> #include <EEPROM_24512.c> #include <DS1307.c> #include <TEMP_DS1621.c> #include <floatee.c> int dat_in=0, cnt, hr, min, sec; int16 address=0; int dat_serie[7]; float dato=0; //Interrupción por el puerto serial #int_rda void rda_isr(void) { dat_in=getc(); //Lee el puerto Serial printf("%c\r",dat_in); if (dat_in=='2') //Pregunta si es "2" para visualizar parametros de la EEPROM { for(cnt=address-7;cnt<=address;cnt++) //Lee los primeros 7 bytes de la EEPROM { dat_serie[cnt]=read_ext_eeprom(cnt); printf("Byte %u=%3u \r", cnt, dat_serie[cnt]); } } } void main() { lcd_init(); set_tris_c(255); enable_interrupts(int_rda); enable_interrupts(global); address=0; init_temp(0x03); //Inicializa el DS1621 //Crea un Menu para mostrar por el Serial printf("Pulse 1 para leer los datos\r"); printf("Pulse 2 para visualizar los datos\r"); while(1) { if(dat_in=='1') //Si es "1" inicie la lectura y grabado de la EEPROM { init_temp(0x03); //Inicializa el DS1621 rtc_get_time(hr,min,sec); //Lee el tiempo del DS1307 dato=read_full_temp(0x03); //Lee temperatura del DS1621 write_float_ext_eeprom(address,dato); //Guarda 4 bytes del float address=address+4; write_ext_eeprom(address++,hr); //Guarda byte de Hora write_ext_eeprom(address++,min); //Guarda byte de Minuto write_ext_eeprom(address++,sec); //Guarda byte de Segundo lcd_gotoxy(1,1); printf(lcd_putc,"Temp=%4.1f C \n",dato);//Visualiza la temperatura printf(lcd_putc,"%2u:%2u:%2u",hr,min,sec);//Visualiza la hora if (address==0xffff) address=0; //Cuando se termina la EEPROM vuelve al principio dat_in=0; } } }
Preguntas Frecuentes sobre el Potocolo I2C (FAQs)
Pregunta: ¿Qué ocurre si dos dispositivos en el bus I2C tienen la misma dirección?
Respuesta: En caso de conflicto de dirección, el bus I2C no funcionará correctamente ya que el maestro no podrá diferenciar entre los dos dispositivos. Es crucial asegurarse de que cada dispositivo tenga una dirección única.
Pregunta: ¿Cómo puedo aumentar la velocidad de comunicación en I2C?
Respuesta: Puedes ajustar la frecuencia del bus aumentando la velocidad de reloj SCL, pero ten en cuenta que algunos dispositivos pueden no soportar velocidades más altas, lo que podría causar errores de comunicación.
Bibliografia
- Compilador C CCS y Simulador Proteus para Microcontroladores PIC, Eduardo Garcia Breijo,
Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:
👉 Invitar a Sergio a un Café ☕️
Que esten muy bien, nos vemos en la siguiente entrada.
Mi nombre es Sergio Andres Castaño Giraldo, y en este sitio web voy a compartir una de las cosas que mas me gusta en la vida y es sobre la Ingeniería de Control y Automatización. El sitio web estará en constante crecimiento, voy a ir publicando material sobre el asunto desde temas básicos hasta temas un poco más complejos. Suscríbete al sitio web, dale me gusta a la página en Facebook y únete al canal de youtube. Espero de corazón que la información que comparto en este sitio, te pueda ser de utilidad. Y nuevamente te doy las gracias y la bienvenida a control automático educación.