El protocolo I2C
Diseñado por Philips, este sistema de intercambio de información a
través de tan solo dos cables permite a circuitos integrados y
módulos interactuar entre sí a velocidades relativamente lentas.
Emplea comunicación serie, utilizando un conductor para manejar el
reloj y otro para intercambiar datos.
Este bus se basa en tres señales:
• SDA (System Data) por la cual viajan los datos entre los dispositivos.
• SCL (System Clock) por la cual transitan los pulsos de reloj que sincronizan el sistema.
• GND (Masa) Interconectada entre todos los dispositivos "enganchados" al bus.
Las líneas SDA y SCL son del tipo drenador abierto, similares a
las de colector abierto pero asociadas a un transistor de efecto
de campo (ó FET). Se deben poner en estado alto (conectar a la
alimentación por medio de resistores Pull-Up) para construir una
estructura de bus tal que se permita conectar en paralelo
múltiples entradas y salidas.
En el diagrama se observa la configuración eléctrica básica del
bus.
Las dos líneas de comunicación disponen de niveles lógicos altos
cuando están inactivas.
Inicialmente el número de dispositivos que se puede conectar al
bus es ilimitado, pero observe que que las líneas tienen una
especificación máxima de 400pF en lo que respecta a capacidad de
carga.
La máxima velocidad de transmisión de datos que se puede obtener
es de aproximadamente 400 Kbits por segundo. Normalmente se habla
de 100 Kbit´s para mantener la tasa de errores baja.
1
Las definiciones o términos utilizados en relación con las
funciones del bus I2C son las siguientes:
• Maestro (Master): Dispositivo que determina la temporización y la dirección del tráfico de
datos en el bus. Es el único que aplica los pulsos de reloj en la línea SCL. Cuando se
conectan varios dispositivos maestros a un mismo bus la configuración obtenida se
denomina "multi-maestro".
• Esclavo (Slave): Cualquier dispositivo conectado al bus incapaz de generar pulsos de reloj.
Reciben señales de comando y de reloj proveniente del dispositivo maestro.
• Bus Desocupado (Bus Free): Estado en el cual ambas líneas (SDA y SCL) están inactivas,
presentando un estado lógico alto. Unicamente en este momento es cuando un dispositivo
maestro puede comenzar a hacer uso del bus.
• Comienzo (Start): Sucede cuando un dispositivo maestro hace ocupación del bus,
generando esta condición. La línea de datos (SDA) toma un estado bajo mientras que la línea
de reloj (SCL) permanece alta.
• Parada (Stop): Un dispositivo maestro puede generar esta condición dejando libre el bus.
La línea de datos toma un estado lógico alto mientras que la de reloj permanece también en
ese estado.
• Dato Válido (Valid Data): Sucede cuando un dato presente en la línea SDA es estable
mientras la línea SCL está a nivel lógico alto.
• Formato de Datos (Data Format): La transmisión de datos a través de este bus consta de 8
bits de datos (ó 1 byte). A cada byte le sigue un noveno pulso de reloj durante el cual el
dispositivo receptor del byte debe generar un pulso de reconocimiento, conocido como ACK
(del inglés Acknowledge). Esto se logra situando la línea de datos a un nivel lógico bajo
mientras transcurre el noveno pulso de reloj.
• Dirección (Address): Cada dispositivo diseñado para funcionar en este bus dispone de su
propia y única dirección de acceso, que viene pre-establecida por el fabricante. Hay
dispositivos que permiten establecer externamente parte de la dirección de acceso. Esto
permite que una serie del mismo tipo de dispositivos se puedan conectar en un mismo bus
sin problemas de identificación. La dirección 00 es la denominada "de acceso general", por
la cual responden todos los dispositivos conectados al bus.
• Lectura/Escritura (Bit R/W): Cada dispositivo dispone de una dirección de 7 bits. El
octavo bit (el menos significativo ó LSB) enviado durante la operación de direccionamiento
corresponde al bit que indica el tipo de operación a realizar. Si este bit es alto el dispositivo
maestro lee información proveniente de un dispositivo esclavo. En cambio, si este bit fuese
bajo el dispositivo maestro escribe información en un dispositivo esclavo.
Funcionamiento del protocolo I2C.
Como es lógico, para iniciar una comunicación entre dispositivos
conectados al bus I2C se debe respetar un protocolo.
Tan pronto como el bus esté libre, un dispositivo maestro puede
ocuparlo generando una condición de inicio. El primer byte
transmitido después de la condición de inicio contiene los siete
bits que componen la dirección del dispositivo de destino
seleccionado y un octavo bit correspondiente a la operación
deseada (lectura o escritura).
2
Si el dispositivo cuya dirección se apuntó en los siete bits está
presente en el bus éste responde enviando el pulso de
reconocimiento ó ACK y seguidamente puede comenzar el intercambio
de información entre los dispositivos.
Cuando la señal R/W está previamente a nivel lógico bajo, el
dispositivo maestro envía datos al dispositivo esclavo hasta que
deja de recibir los pulsos de reconocimiento, o hasta que se hayan
transmitido todos los datos.
En el caso contrario, es decir cuando la señal R/W estaba a nivel
lógico alto, el dispositivo maestro genera pulsos de reloj durante
los cuales el dispositivo esclavo puede enviar datos. Luego de
cada byte recibido el dispositivo maestro (que en este momento
está recibiendo datos) genera un pulso de reconocimiento.
El dispositivo maestro puede dejar libre el bus generando una
condición de parada (Stop). Si se desea seguir transmitiendo, el
dispositivo maestro puede generar otra condición de inicio el
lugar de una condición de parada. Esta nueva condición de inicio
se denomina "inicio repetitivo" y se puede emplear para
direccionar un dispositivo esclavo diferente ó para alterar el
estado del bit de lectura/escritura (R/W).
Memoria 24LC256.
La memoria 24LC256 fabricada por Microchip tiene una capacidad de
almacenamiento de 256Kbits (32 Kbytes). Sobre el mismo bus pueden
conectarse hasta ocho memorias 24LC256, permitiendo alcanzar una
capacidad de almacenamiento máxima de 256 Kbytes.
Como en todas las memorias de la familia 24XXXXX, la dirección de
identificación o byte de control comienza con 1010 (0xA0).
Seguidamente, tres bits llamados A2, A1 y A0 permiten seleccionar
una de las ocho posibles memorias conectadas en el bus.
La memoria se compone de 512 páginas de 64 bytes cada una.
Cuando se requiere transferir volúmenes considerables de datos, la
escritura de bytes resulta ser bastante ineficiente, pero la
3
escritura por páginas mejora notablemente el rendimiento. Para
escribir varios bytes en localidades consecutivas de la memoria,
bastará con enviarlos uno a continuación del otro antes de
producir la condición de STOP.
Algunas características adicionales de la memoria son:
• Tiempo de escritura = 5ms.
• Ciclos de lectura/escritura =1.000.000.
• Velocidad máxima de operación = 400 KHz.
• Consumo durante la escritura = 3mA @ 5.5V
• Consumo durante la lectura = 400uA @ 5.5V
• Consumo en estado inactivo = 100nA @ 5.5V
• Capacidad de retención mayor a 200 años.
Para conectar dos memoria en un mismo
bus debemos cambiar la dirección de hardware
actuando sobre los pines A0, A1, A2.
Si bien es posible “apilar” memorias en un bus en la
práctica no es recomendable, de la misma forma que no es
recomendable apilar discos duros (físicos) en una computadora, es
preferible conseguir una única memoria de la capacidad necesaria.
C18 contiene la librería i2c.h para el manejo del bus I2C lo que
simplifica bastante las cosas aportando varias funciones para el
manejo del bus.
Teniendo como soporte estas funciones nativas de C8 resulta
relativamente simple crear funciones para el manejo de la memoria
24LC256 o cualquier dispositivo I2C.
Veamos un ejemplo de una función básica para escribir un Byte en
una posición de memoria.
Para no complicar el ejemplo no se ha tomado en cuenta considerar
4
los valores de retorno de varias funciones que nos informar del
estado de algunas acciones, capturando estos retornos podemos
evitar que nuestro programa se quede esperando respuestas que
nunca llegan.
void Escribe_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion, unsigned char DataI2C )
{
IdleI2C(); // El modulo esta desocupado?
StartI2C(); // Condicion de START
while ( [Link] );// Espera a que la condición de inicio termine
WriteI2C( ByteControl ); // Envia el Byte de control
WriteI2C( HighDireccion );// Escribe la parte alta de la dirección
WriteI2C( LowDireccion ); // Escribe la parte baja de la dirección
WriteI2C ( DataI2C ); // Guarda Data en Eeprom en la dirección establecida.
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que la condición de stop termine
while (EEAckPolling(ByteControl)); //Espera por el ACK.
}
Puede ver aquí de que forma se envía en la trama el byte de
control, la parte alta y baja de la dirección y por último el dato
a escribir en la memoria, todos argumentos de la función que
dentro de la misma son enviados en la secuencia correcto con las
funciones nativas de C18.
Un ejemplo para leer un vector de la memoria 24LC256.
unsigned char Lee_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion )
{
unsigned char Valor;
IdleI2C(); // El modulo esta activo?
StartI2C(); // Condición de START
while ( [Link] ); // Espera a que la condición de inicio termine
WriteI2C( ByteControl );
WriteI2C( HighDireccion );
WriteI2C( LowDireccion );
RestartI2C(); // Envía ReStart
while ( [Link] ); // Si se ha recibido el byte sigue
WriteI2C( ByteControl | 0x01 ); // bit0 de ByteControl a 1 para leer.
Valor=ReadI2C(); // Realiza lectura.
NotAckI2C(); // Envía NACK
while ( [Link] ); // Espera a que de ASK termine
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que stop termine
return ( Valor ); // Retorna Lectura
}
La función lleva como argumento el Byte de control, parte alta y
baja de la dirección y retorna el dato leído en la posición
referida.
Necesitamos enviar parte alta y baja de la dirección porque
estamos tratando con una memoria que puede almacenar 32000 Bytes y
no es posible cubrir estas direcciones con un solo Byte.
5
/* *****************************************************************************
** Descripción : 24LC256.C
** Controlador : 40PIN PIC18F4620
** Compilador : Microchip C18
** IDE : Microchip MPLAB
** XTAL : 20MHZ
** Autor : Firtec
**
*******************************************************************************/
#include <p18f4620.h>
12
#pragma config OSC=HS,PWRT=ON,MCLRE=OFF,LVP=OFF,WDT=OFF
#include <stdio.h>
#include <delays.h>
#include <i2c.h>
void Escribe_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion, unsigned char DataI2C );
unsigned char Lee_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion );
unsigned char Dato=32; // Dato a Guardar en Memoria (0 a FF).
FUNCIÓN PRINCIPAL DEL PROGRAMA
void main(void){
lcd_init();
Delay1KTCYx(25);
OpenI2C(MASTER,SLEW_OFF); // Modo Master
SSPADD = 49; // (Fos/Fck*4)-1 donde Fos 20Mhz (20000Kc) y Fck 100Kz (Ck I2c)
// (20000Kc/400kc)-1 = 49 para 20Mhz y Ck I2c 100K
Escribe_Byte(0xA0,0x00,0x00,Dato); // Escribe la memoria en vector 0
Delay1KTCYx(5);
Dato=Lee_Byte(0xA0,0x00,0x00); // Lee la memoria en vector 0
lcd_putrs(" Memoria 24C256");
lcd_gotoxy(1,2);
stdout =_H_USER; // Necesario para usar printf() con el LCD
printf("Dato:%u ",Dato); // Muestra el dato en pantalla
while(1);
}
DEFINE LA FUNCIÓN DE ESCRITURA
void Escribe_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion, unsigned char DataI2C )
{
IdleI2C(); // El modulo esta activo?
StartI2C(); // Condición de START
while ( [Link] );// Espera a que la condición de inicio termine
WriteI2C( ByteControl ); // Envia el Byte de control
WriteI2C( HighDireccion );// Escribe la parte alta de la dirección
WriteI2C( LowDireccion ); // Escribe la parte baja de la dirección
WriteI2C ( DataI2C ); // Guarda Data en Eeprom en la dirección establecida.
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que la condición de stop termine
while (EEAckPolling(ByteControl)); //Espera que se complete escritura.
}
DEFINE LA FUNCIÓN DE LECTURA
unsigned char Lee_Byte( unsigned char ByteControl, unsigned char HighDireccion,
unsigned char LowDireccion )
{
unsigned char Valor;
6
IdleI2C(); // El modulo esta activo?
StartI2C(); // Condición de START
while ( [Link] ); // Espera a que la condición de inicio termine
WriteI2C( ByteControl );
WriteI2C( HighDireccion );
WriteI2C( LowDireccion );
RestartI2C(); // Envía ReStart
while ( [Link] ); // Si se ha recibido el byte sigue
WriteI2C( ByteControl | 0x01 ); // bit0 de Byte Control a 1 para leer.
Valor=ReadI2C(); // Realiza lectura.
NotAckI2C(); // Envía NACK
while ( [Link] ); // Espera a que de ASK termine
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que stop termine
return ( Valor ); // Retorna Lectura
}
Circuito de la aplicación.
Dos detalles del programa para comentar.
La función OpenI2C(MASTER,SLEW_OFF) es la encargada de configurar el
módulo I2c y puede tomar los siguientes valores:
• SLAVE_7 I2C Slave mode, 7-bit address.
• SLAVE_10 I2C Slave mode, 10-bit address.
• MASTER I2C Master mode.
7
Los dispositivos como memorias son siempre esclavos pero es
posible conectar dos micros entre si mediante I2C y forzosamente
uno de ellos será esclavo y el otro maestro.
SLEW define la velocidad en el bus.
• SLEW_OFF Modo 100 kHz.
• SLEW_ON Modo 400 kHz.
SSPADD es el registro donde se guarda el dato que determina la
velocidad de comunicación a partir de la fórmula indicada en la
hoja de datos del micro.
(Fos/Fck*4)-1
Donde Fos 20Mhz (20000Kc) y Fck 100Kz (Ck I2c)
Lo que nos da:
(20000Kc/400kc)-1 = 49 para 20Mhz y Ck I2c 100K
Si necesito acceder a una memoria no tan grande como la 24LC256,
digamos la popular 24LC04 de tan solo 512 Bytes puedo usar el
siguiente código. Un enfoque similar usaremos en el siguiente
capítulo para controlar un reloj de tiempo real (RTC) mediante el
bus I2C.
/* *****************************************************************************
** Nombre : 24LC04.c
** Descripción : Control de una memoria 24LC04
** Target : 40PIN PIC18F4620
** Compilador : Microchip C18
** XTAL : 20MHZ
** ****************************************************************************/
#include <p18f4620.h>
#pragma config OSC=HS,PWRT=ON,MCLRE=OFF,LVP=OFF,WDT=OFF
#include <stdio.h>
#include <delays.h>
#include <i2c.h>
void escribe_eeprom(unsigned int address,unsigned char data);
unsigned char lee_eeprom(unsigned int address);
unsigned char Dato=4 ; // <<<<<<< Dato a Guardar en Memoria
//********************* FUNCIÓN PRINCIPAL DEL PROGRAMA *************************
void main(void){
unsigned int contador;
TRISBbits.TRISB5=0;
lcd_init();
Delay1KTCYx(25);
OpenI2C(MASTER,SLEW_OFF); // Modo Master
SSPADD = 49; // 100KHz para 20MHz
escribe_eeprom(25,Dato); // Escribe la memoria en vector 25
Delay1KTCYx(5);
lcd_putrs(" Memoria 24LC04");
escribe_eeprom(1,4);
lcd_gotoxy(1,2);
8
stdout =_H_USER; // Necesario para usar printf() con el LCD
printf("Dato:%X ",lee_eeprom(25)); // Muestra el dato en pantalla
while(1);
}
//******************************************************************************
// Escribe una posición de memoria.
//******************************************************************************
void escribe_eeprom(unsigned int address,unsigned char data) {
IdleI2C(); // El modulo esta libre?
StartI2C(); // Condición de START
while ( [Link] ); // Espera a que la condición de inicio termine
WriteI2C((0xa0|(unsigned char)(address>>7))&0xfe);
WriteI2C(address);
WriteI2C(data);
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que la condición de stop termine
while (EEAckPolling(0xA0)); //Espera que se complete escritura.
}
//******************************************************************************
// Leer una posición de memoria.
//******************************************************************************
unsigned char lee_eeprom(unsigned int address) {
unsigned char data;
IdleI2C(); // El modulo esta libre?
StartI2C(); // Condición de START
while ( [Link] ); // Espera a que la condición de inicio termine
WriteI2C((0xa0|(unsigned char)(address>>7))&0xfe);
WriteI2C(address);
StartI2C();
WriteI2C((0xa0|(unsigned char)(address>>7))|1);
data=ReadI2C();
NotAckI2C(); // Envía NACK
while ( [Link] ); // Espera a que de ASK termine
StopI2C(); // Condición de STOP
while ( [Link] ); // Espera a que la condición de stop termine
return(data);
}
El manejo de esta memoria es un poco mas complejo ya que tiene su
capacidad dividida en dos páginas de 256 Bytes cada una lo que
permite acceder estas páginas con un solo Byte de
direccionamiento.
Se recomienda descargar las hojas de datos de estos dispositivos
directamente desde Internet.
Componentes y partes para la aplicación.
• 1 Microcontrolador PIC18F4620.
• 1 Cristal de 20Mhz.
• 2 Condensadores de 18pF o 20pF.
• 1 Pantalla LCD 16x2 Hitachi 44780 o compatible
• 1 Resistencia de 270 Ohms.
• 2 Resistencia de 4,7K
• 1 Condensador de ,01uF y 10uF
• 1 Pre-Set de 5K
9
• 1 Memoria EEPROM 24LC256 o 24LC04
• Protoboard.
• Cables de conexión.
• Placa programadora de PIC´s.
• Fuente de alimentación de 5 voltios.
10