Microchip escribió notas de aplicación sobre esto:
- AN734 sobre la implementación de un esclavo I2C
- AN735 sobre la implementación de un maestro I2C
- También hay un aspecto más teórico AN736 sobre la creación de un protocolo de red para la vigilancia del medio ambiente, pero no es necesario para este proyecto.
Las notas de aplicación están trabajando con ASM pero eso puede ser portado a C fácilmente.
Los compiladores gratuitos C18 y XC8 de Microchip tienen funciones I2C. Puedes leer más sobre ellas en el Documentación de las bibliotecas del compilador sección 2.4. Aquí hay información de inicio rápido:
Configuración
Ya tienes el compilador C18 o XC8 de Microchip. Ambos tienen funciones I2C incorporadas. Para usarlas, necesitas incluir i2c.h
:
#include i2c.h
Si quieres echar un vistazo al código fuente, puedes encontrarlo aquí:
- Cabecera C18: _
installation_path
/v
x.xx
_/h/i2c.h
- Fuente de C18: _
installation_path
/v
x.xx
_/src/pmc_common/i2c/
- Cabecera XC8: _
installation_path
/v
x.xx
_/include/plib/i2c.h
- Fuente XC8: _
installation_path
/v
x.xx
_/sources/pic18/plib/i2c/
En la documentación, se puede encontrar en qué archivo del /i2c/
carpeta se encuentra una función.
Abrir la conexión
Si estás familiarizado con los módulos MSSP de Microchip, sabrás que primero tienen que ser inicializados. Puedes abrir una conexión I2C en un puerto MSSP usando la función OpenI2C
función. Así es como se define:
void OpenI2C (unsigned char sync_mode, unsigned char slew);
Con sync_mode
En el caso de que el dispositivo sea maestro o esclavo, puede seleccionar si debe utilizar una dirección de 10 o 7 bits. La mayoría de las veces se utilizan 7 bits, especialmente en aplicaciones pequeñas. Las opciones de sync_mode
son:
SLAVE_7
- Modo esclavo, dirección de 7 bits
SLAVE_10
- Modo esclavo, dirección de 10 bits
MASTER
- Modo maestro
Con slew
puede seleccionar si el dispositivo debe utilizar la velocidad de giro. Más información sobre lo que es aquí: ¿Qué es el slew rate para I2C?
Dos módulos MSSP
Hay algo especial en los dispositivos con dos módulos MSSP, como el PIC18F46K22 . Tienen dos conjuntos de funciones, uno para el módulo 1 y otro para el módulo 2. Por ejemplo, en lugar de OpenI2C()
tienen OpenI2C1()
y openI2C2()
.
Bien, ya lo has configurado todo y has abierto la conexión. Ahora vamos a hacer algunos ejemplos:
Ejemplos
Ejemplo de escritura maestra
Si estás familiarizado con el protocolo I2C, sabrás que una secuencia de escritura maestra típica tiene este aspecto:
Master : START | ADDR+W | | DATA | | DATA | | ... | DATA | | STOP
Slave : | | ACK | | ACK | | ACK | ... | | ACK |
Al principio, enviamos una condición de START. Consideremos que esto es descolgar el teléfono. Luego, la dirección con un bit de escritura - marcando el número. En este punto, el esclavo con la dirección enviada sabe que está siendo llamado. Envía un acuse de recibo ("Hola"). Ahora, el dispositivo maestro puede ir a enviar datos - empieza a hablar. Envía cualquier cantidad de bytes. Después de cada byte, el esclavo debe ACK los datos recibidos ("sí, te escucho"). Cuando el dispositivo maestro ha terminado de hablar, cuelga con la condición de STOP.
En C, la secuencia de escritura del maestro se vería así para el maestro:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe ); // Send address with R/W cleared for write
IdleI2C(); // Wait for ACK
WriteI2C( data[0] ); // Write first byte of data
IdleI2C(); // Wait for ACK
// ...
WriteI2C( data[n] ); // Write nth byte of data
IdleI2C(); // Wait for ACK
StopI2C(); // Hang up, send STOP condition
Ejemplo de lectura maestra
La secuencia de lectura del maestro es ligeramente diferente de la secuencia de escritura:
Master : START | ADDR+R | | | ACK | | ACK | ... | | NACK | STOP
Slave : | | ACK | DATA | | DATA | | ... | DATA | |
De nuevo, el maestro inicia la llamada y marca el número. Sin embargo, ahora quiere obtener información. El esclavo responde primero a la llamada y luego empieza a hablar (enviar datos). El maestro reconoce cada byte hasta que tiene suficiente información. Entonces envía un Not-ACK y cuelga con una condición de STOP.
En C, esto se vería así para la parte maestra:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 ); // Send address with R/W set for read
IdleI2C(); // Wait for ACK
data[0] = ReadI2C(); // Read first byte of data
AckI2C(); // Send ACK
// ...
data[n] = ReadI2C(); // Read nth byte of data
NotAckI2C(); // Send NACK
StopI2C(); // Hang up, send STOP condition
Código esclavo
Para el esclavo, es mejor utilizar una Rutina de Servicio de Interrupción o ISR. Puedes configurar tu microcontrolador para que reciba una interrupción cuando su dirección sea llamada. De esta manera no tienes que comprobar el bus constantemente.
En primer lugar, vamos a configurar los fundamentos de las interrupciones. Tendrás que habilitar las interrupciones y añadir un ISR. Es importante que los PIC18 tengan dos niveles de interrupciones: alta y baja. Vamos a configurar I2C como una interrupción de alta prioridad, porque es muy importante responder a una llamada de I2C. Lo que vamos a hacer es lo siguiente:
- Escribir un ISR SSP, para cuando la interrupción es una interrupción SSP (y no otra interrupción)
- Escribe un ISR general de alta prioridad, para cuando la interrupción es de alta prioridad. Esta función tiene que comprobar qué tipo de interrupción se ha disparado, y llamar al sub-ISR adecuado (por ejemplo, el ISR de la SSP)
- Añade un
GOTO
al ISR general en el vector de interrupción de alta prioridad. No podemos poner el ISR general directamente en el vector porque es demasiado grande en muchos casos.
He aquí un ejemplo de código:
// Function prototypes for the high priority ISRs
void highPriorityISR(void);
// Function prototype for the SSP ISR
void SSPISR(void);
// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code
// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
if (PIR1bits.SSPIF) { // Check for SSP interrupt
SSPISR(); // It is an SSP interrupt, call the SSP ISR
PIR1bits.SSPIF = 0; // Clear the interrupt flag
}
return;
}
// This is the actual SSP ISR
void SSPISR(void) {
// We'll add code later on
}
Lo siguiente que hay que hacer es habilitar la interrupción de alta prioridad cuando el chip se inicializa. Esto se puede hacer mediante algunas manipulaciones simples de los registros:
RCONbits.IPEN = 1; // Enable interrupt priorities
INTCON &= 0x3f; // Globally enable interrupts
PIE1bits.SSPIE = 1; // Enable SSP interrupt
IPR1bits.SSPIP = 1; // Set SSP interrupt priority to high
Ahora, tenemos interrupciones que funcionan. Si estás implementando esto, yo lo comprobaría ahora. Escriba un programa básico SSPISR()
para que empiece a parpadear un LED cuando se produzca una interrupción SSP.
Bien, entonces tienes tus interrupciones funcionando. Ahora vamos a escribir algo de código real para el SSPISR()
función. Pero primero algo de teoría. Distinguimos cinco tipos diferentes de interrupción I2C:
- El maestro escribe, el último byte fue la dirección
- El maestro escribe, el último byte era de datos
- El maestro lee, el último byte fue la dirección
- El maestro lee, el último byte era de datos
- NACK: fin de la transmisión
Puede comprobar en qué estado se encuentra comprobando los bits del SSPSTAT
registro. Este registro es el siguiente en modo I2C (se omiten los bits no utilizados o irrelevantes):
- Bit 5: D/NOT A: Datos/No dirección: se pone si el último byte era de datos, se borra si el último byte era una dirección
- Bit 4: P: Bit de parada: se pone si se ha producido una condición de STOP en último lugar (no hay ninguna operación activa)
- Bit 3: S: Bit de inicio: se establece si se ha producido una condición de INICIO en último lugar (hay una operación activa)
- Bit 2: R/NOT W: Lectura/no escritura: se establece si la operación es una lectura maestra, se borra si la operación es una escritura maestra
- Bit 0: BF: Buffer Full: se pone si hay datos en el registro SSPBUFF, se borra si no
Con estos datos, es fácil saber en qué estado se encuentra el módulo I2C:
State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1 | M write | address | 0 | 0 | 1 | 0 | 1
2 | M write | data | 1 | 0 | 1 | 0 | 1
3 | M read | address | 0 | 0 | 1 | 1 | 0
4 | M read | data | 1 | 0 | 1 | 1 | 0
5 | none | - | ? | ? | ? | ? | ?
En el software, lo mejor es utilizar el estado 5 por defecto, que se asume cuando no se cumplen los requisitos de los otros estados. De esta manera, no se responde cuando no se sabe qué está pasando, porque el esclavo no responde a un NACK.
De todos modos, echemos un vistazo al código:
void SSPISR(void) {
unsigned char temp, data;
temp = SSPSTAT & 0x2d;
if ((temp ^ 0x09) == 0x00) { // 1: write operation, last byte was address
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x29) == 0x00) { // 2: write operation, last byte was data
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x0c) == 0x00) { // 3: read operation, last byte was address
// Do something, then write something to I2C
WriteI2C(0x00);
} else if ((temp ^ 0x2c) == 0x00) { // 4: read operation, last byte was data
// Do something, then write something to I2C
WriteI2C(0x00);
} else { // 5: slave logic reset by NACK from master
// Don't do anything, clear a buffer, reset, whatever
}
}
Puede ver cómo puede comprobar el SSPSTAT
registro (primero ANDed con 0x2d
para que sólo tengamos los bits útiles) utilizando máscaras de bits para ver qué tipo de interrupción tenemos.
Es tu trabajo averiguar qué tienes que enviar o hacer cuando respondes a una interrupción: depende de tu aplicación.
Referencias
De nuevo, me gustaría mencionar las notas de aplicación que Microchip escribió sobre I2C:
- AN734 sobre la implementación de un esclavo I2C
- AN735 sobre la implementación de un maestro I2C
- AN736 sobre la creación de un protocolo de red para la vigilancia del medio ambiente
Hay documentación para las bibliotecas del compilador: Documentación de las bibliotecas del compilador
Cuando configure algo usted mismo, compruebe la hoja de datos de su chip en la sección (M)SSP para la comunicación I2C. Yo he utilizado el PIC18F46K22 para la parte principal y el PIC18F4620 para la parte esclava.