Para un proyecto, me gustaría que tres PIC (dos esclavos PIC18F4620, un maestro PIC18F46K22) se comuniquen a través del bus I2C. Posteriormente, se pueden agregar más esclavos (como EEPROM, SRAM, ...). Estoy escribiendo el código para estos PIC en C usando el compilador C18. Busqué mucho en Internet, pero no pude encontrar bibliotecas para manejar el periférico (M) SSP. Leí la hoja de datos de ambos PIC en el periférico (M) SSP en modo I2C, pero no pude averiguar cómo conectar el bus.
Entonces necesito bibliotecas maestras y esclavas.
¿Que recomiendas? ¿Tienes una biblioteca así en alguna parte? ¿Está integrado en el compilador y, en caso afirmativo, dónde? ¿Hay un buen tutorial en algún lugar de la red?
Microchip escribió notas de aplicación sobre esto:
Las notas de la aplicación funcionan con ASM, pero se pueden trasladar fácilmente a C.
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:
Ya tienes el compilador C18 o XC8 de Microchip. Ambos tienen funciones I2C integradas. Para usarlos, debe incluir i2c.h
:
#include i2c.h
Si quieres echar un vistazo al código fuente, puedes encontrarlo aquí:
installation_path
/v
x.xx
/h/i2c.h
installation_path
/v
x.xx
/src/pmc_common/i2c/
installation_path
/v
x.xx
/include/plib/i2c.h
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.
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 usando la OpenI2C
función. Así 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 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 bitsSLAVE_10
- Modo esclavo, dirección de 10 bitsMASTER
- Modo maestroCon slew
, puede seleccionar si el dispositivo debe usar la velocidad de respuesta. Más sobre lo que es aquí: ¿Qué es la velocidad de respuesta para I2C?
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()
.
De acuerdo, lo configuraste todo y abriste la conexión. Ahora vamos a hacer algunos ejemplos:
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. Considere esto 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: comienza a hablar. Envía cualquier cantidad de bytes. Después de cada byte, el esclavo debe acusar recibo de 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 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
La secuencia maestra de lectura 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 (enviar datos). El maestro reconoce cada byte hasta que tiene 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
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 llame a su dirección. De esa manera no tienes que revisar el autobús constantemente.
Primero, configuremos lo básico para las interrupciones. Deberá habilitar las interrupciones y agregar un ISR. Es importante que los PIC18 tengan dos niveles de interrupciones: alto y bajo. 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:
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 se puede hacer 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 funcionando. Si está implementando esto, lo comprobaría ahora. Escriba un básico SSPISR()
para comenzar a parpadear un LED cuando ocurre una interrupción 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 interrupción I2C:
Puede comprobar en qué estado se encuentra comprobando los bits en el SSPSTAT
registro. Este registro es el siguiente en modo I2C (se omiten los bits no utilizados o irrelevantes):
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 el software, es mejor usar el estado 5 como predeterminado, que se asume cuando no se cumplen los requisitos para los otros estados. De esa forma, no respondes cuando no sabes lo que 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 verificar el SSPSTAT
registro (primero con AND 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.
Nuevamente, me gustaría mencionar las notas de aplicación que Microchip escribió sobre I2C:
Hay documentación para las bibliotecas del compilador: documentación de las bibliotecas del compilador
Cuando configure algo usted mismo, verifique 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.
En primer lugar, recomendaría cambiar al compilador XC8 simplemente porque es el último. Hay bibliotecas periféricas disponibles, pero nunca las he usado mucho. Visite el sitio web de Microchips para obtener detalles y la documentación.
De acuerdo, aquí tengo algunas rutinas básicas muy antiguas para las comunicaciones eeprom I2C que usé hace mucho tiempo con un PIC16F y el antiguo compilador de rango medio Microhip (puede haber sido el de alta tecnología), pero creo que pueden funcionar bien con el PIC18, ya que creo que el periferico es el mismo. De todos modos, descubrirá muy rápidamente si todo es diferente.
Formaban parte de un archivo más grande que se usó con un proyecto de registrador de temperatura, por lo que rápidamente eliminé todas las demás funciones no relacionadas y las guardé como los archivos a continuación, por lo que es posible que haya hecho un poco de lío, pero espero que usted podrá hacerse una idea de lo que se necesita (incluso puede funcionar, nunca se sabe ;-))
Deberá asegurarse de que el archivo de encabezado principal sea correcto y verificar / modificar los pines para asegurarse de que sean los pines periféricos I2C correctos, y los nombres de registro si son del compilador de alta tecnología, que hizo las cosas un poco diferente. sobre la convención de nomenclatura de registros.
Archivo I2C.c:
#include "I2C.h"
#include "delay.h"
#include <pic.h>
#define _XTAL_FREQ 20000000
void write_ext_eeprom(unsigned int address, unsigned char data)
{
unsigned char a0 = ((address & 0x8000) >> 14);
unsigned char msb = (address >> 8);
unsigned char lsb = (address & 0x00FF);
i2c_start();
i2c_write(0xa0 | a0);
i2c_write(msb);
i2c_write(lsb);
i2c_write(data);
i2c_stop();
DelayMs(11);
}
/******************************************************************************************/
unsigned char read_ext_eeprom(unsigned int address)
{
unsigned char a0 = ((address & 0x8000) >> 14);
unsigned char data;
unsigned char msb = (address >> 8);
unsigned char lsb = (address & 0x00FF);
i2c_start();
i2c_write(0xa0 | a0);
i2c_write(msb);
i2c_write(lsb);
i2c_repStart();
i2c_write(0xa1 | a0);
data=i2c_read(0);
i2c_stop();
return(data);
}
void i2c_init()
{
TRISC3=1; // set SCL and SDA pins as inputs
TRISC4=1;
SSPCON = 0x38; // set I2C master mode
SSPCON2 = 0x00;
//SSPADD = 9; // 500kHz bus with 20MHz xtal
SSPADD = 49; // 100kHz bus with 20Mhz xtal
CKE=0; // use I2C levels worked also with '0'
SMP=1; // disable slew rate control worked also with '0'
PSPIF=0; // clear SSPIF interrupt flag
BCLIF=0; // clear bus collision flag
}
/******************************************************************************************/
void i2c_waitForIdle()
{
while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing
}
/******************************************************************************************/
void i2c_start()
{
i2c_waitForIdle();
SEN=1;
}
/******************************************************************************************/
void i2c_repStart()
{
i2c_waitForIdle();
RSEN=1;
}
/******************************************************************************************/
void i2c_stop()
{
i2c_waitForIdle();
PEN=1;
}
/******************************************************************************************/
int i2c_read( unsigned char ack )
{
unsigned char i2cReadData;
i2c_waitForIdle();
RCEN=1;
i2c_waitForIdle();
i2cReadData = SSPBUF;
i2c_waitForIdle();
if ( ack )
{
ACKDT=0;
}
else
{
ACKDT=1;
}
ACKEN=1; // send acknowledge sequence
return( i2cReadData );
}
/******************************************************************************************/
unsigned char i2c_write( unsigned char i2cWriteData )
{
i2c_waitForIdle();
SSPBUF = i2cWriteData;
//if(ACKSTAT)
{
//while(ACKSTAT);
}
return ( ! ACKSTAT ); // function returns '1' if transmission is acknowledged
}
Archivo de encabezado I2C.h:
extern void i2c_init();
extern void i2c_waitForIdle();
extern void i2c_start();
extern void i2c_repStart();
extern void i2c_stop();
extern int i2c_read( unsigned char ack );
extern unsigned char i2c_write( unsigned char i2cWriteData );
SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;
a 1 KHz, etc.Los compiladores XC8 y XC16 incluyen bibliotecas para I2C.
¡El problema que encontré es que la documentación no es muy buena! Si utiliza los ejemplos de la documentación de Microchip, no tendrá suerte. Incluso el soporte de Microchip no puede ayudarte. Yo mismo he estado allí.
Hace algún tiempo trabajé con el microcontrolador de la serie PIC24EP512GP y la biblioteca no me funcionó como lo documenta Microchip.
AndrejaKo
usuario17592
AndrejaKo
usuario17592
usuario54853