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 AN736 más teórico sobre la configuración de un protocolo de red para el monitoreo ambiental, pero no es necesario para este proyecto.
Las notas de la aplicación funcionan con ASM, pero eso se puede transferir a C fácilmente.
Los compiladores C18 y XC8 gratuitos de Microchip tienen funciones I2C. Puede leer más sobre ellos en la 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 usarlos, debe incluir i2c.h
:
#include i2c.h
Si desea ver el código fuente, puede encontrarlo aquí:
- Encabezado C18:
installation_path
/v
x.xx
/h/i2c.h
- Fuente C18:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- Encabezado 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, puede encontrar en qué archivo de la /i2c/
carpeta se encuentra una función.
Abriendo la conexión
Si está familiarizado con los módulos MSSP de Microchip, sabrá que primero deben inicializarse. Puede abrir una conexión I2C en un puerto MSSP utilizando la OpenI2C
función. Así es como se define:
void OpenI2C (unsigned char sync_mode, unsigned char slew);
Con sync_mode
, puede seleccionar si el dispositivo es maestro o esclavo y, si es un esclavo, si debe usar una dirección de 10 bits o de 7 bits. La mayoría de las veces, se utilizan 7 bits, especialmente en aplicaciones pequeñas. Las opciones para 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 usar la velocidad de respuesta. Más sobre lo que es aquí: ¿Cuál es la velocidad de respuesta para I2C?
Dos módulos MSSP
Los dispositivos con dos módulos MSSP tienen algo especial, como el PIC18F46K22 . Tienen dos conjuntos de funciones, una para el módulo 1 y otra para el módulo 2. Por ejemplo, en lugar de OpenI2C()
, tienen OpenI2C1()
y openI2C2()
.
Bien, así que lo configuraste todo y abriste la conexión. Ahora hagamos algunos ejemplos:
Ejemplos
Ejemplo de escritura maestra
Si está familiarizado con el protocolo I2C, sabrá que una secuencia de escritura maestra típica se ve así:
Master : START | ADDR+W | | DATA | | DATA | | ... | DATA | | STOP
Slave : | | ACK | | ACK | | ACK | ... | | ACK |
Al principio, enviamos una condición de INICIO. Considera esto levantar el teléfono. Luego, la dirección con un bit de escritura: marcar el número. En este punto, el esclavo con la dirección enviada sabe que lo están llamando. Envía un reconocimiento ("Hola"). Ahora, el dispositivo maestro puede enviar datos, comienza a hablar. Envía cualquier cantidad de bytes. Después de cada byte, el esclavo debe aceptar los datos recibidos ("sí, te escucho"). Cuando el dispositivo maestro ha terminado de hablar, cuelga con la condición STOP.
En C, la secuencia de escritura maestra 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 maestra es ligeramente diferente de la secuencia de escritura:
Master : START | ADDR+R | | | ACK | | ACK | ... | | NACK | STOP
Slave : | | ACK | DATA | | DATA | | ... | DATA | |
Nuevamente, el maestro inicia la llamada y marca el número. Sin embargo, ahora quiere obtener información. El esclavo primero responde la llamada, luego comienza a hablar (enviando datos). El maestro reconoce cada byte hasta que tenga suficiente información. Luego envía un Not-ACK y cuelga con una condición 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 usar una Rutina de servicio de interrupción o ISR. Puede configurar su microcontrolador para recibir una interrupción cuando se llama a su dirección. De esa manera no tienes que revisar el autobús constantemente.
Primero, configuremos los conceptos básicos para las interrupciones. Tendrá que habilitar las interrupciones y agregar 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 I2C. Lo que vamos a hacer es lo siguiente:
- Escriba un ISP ISR, para cuando la interrupción es una interrupción SSP (y no otra interrupción)
- Escriba un ISR de alta prioridad general, para cuando la interrupción es de alta prioridad. Esta función tiene que verificar qué tipo de interrupción se disparó y llamar al sub-ISR correcto (por ejemplo, el ISP del SSP)
- Agregue una
GOTO
instrucción 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.
Aquí hay 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 debe hacer es habilitar la interrupción de alta prioridad cuando se inicializa el chip. Esto puede hacerse mediante algunas manipulaciones de registro simples:
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 trabajando. Si está implementando esto, lo comprobaré ahora. Escriba un básico SSPISR()
para comenzar a parpadear un LED cuando se produce una interrupción de SSP.
Bien, entonces tienes tus interrupciones funcionando. Ahora escribamos un código real para la SSPISR()
función. Pero primero algo de teoría. Distinguimos cinco tipos diferentes de interrupciones I2C:
- El maestro escribe, el último byte fue la dirección
- El maestro escribe, el último byte fue datos
- El maestro lee, el último byte fue la dirección
- Lecturas maestras, el último byte fue datos
- NACK: fin de transmisión
Puede verificar en qué estado se encuentra verificando los bits en el SSPSTAT
registro. Este registro es el siguiente en modo I2C (se omiten bits no utilizados o irrelevantes):
- Bit 5: D / NO A: Datos / No dirección: se establece si el último byte era datos, borrado si el último byte era una dirección
- Bit 4: P: Bit de parada: se establece si se produjo una condición STOP por última vez (no hay operación activa)
- Bit 3: S: Bit de inicio: se establece si una condición de INICIO ocurrió por última vez (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 lleno: establecer si hay datos en el registro SSPBUFF, borrado si no
Con estos datos, es fácil ver cómo ver 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 software, es mejor usar el estado 5 como predeterminado, que se asume cuando no se cumplen los requisitos para los otros estados. De esa manera, no responde cuando no sabe lo que está sucediendo, 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 verificar el SSPSTAT
registro (primero ANDed 0x2d
para que solo tengamos los bits útiles) usando máscaras de bits para ver qué tipo de interrupción tenemos.
Es su trabajo averiguar qué tiene que enviar o hacer cuando responde a una interrupción: depende de su aplicación.
Referencias
Nuevamente, 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 configuración de un protocolo de red para monitoreo ambiental
Hay documentación para las bibliotecas del compilador: documentación de las bibliotecas del compilador
Al configurar algo usted mismo, consulte la hoja de datos de su chip en la sección (M) SSP para la comunicación I2C. Usé el PIC18F46K22 para la parte maestra y el PIC18F4620 para la parte esclava.