Comenzando con I2C en PIC18s

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?

Tuve problemas similares hace unos meses. Puedes leer sobre ellos aquí . Aquí hay bibliotecas para C18 que funcionan con I ^ 2C, pero falta una cosa importante: debe configurar la velocidad del bus manualmente escribiendo en el registro apropiado y eso no se menciona en ninguna parte en la documentación de la biblioteca.
¡Gracias, eso fue útil! Sin embargo, solo hizo la parte maestra, no la parte esclava.
Sí, no necesitaba trabajar con esclavos en ese entonces, así que no hay ejemplos de esclavos. Perdón.
No, está bien, ¡fue útil para la parte maestra! :-)
deshabilite también el analógico en los puertos ANSELC=0;

Respuestas (3)

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 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:

configurando

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í:

  • Encabezado C18:installation_path/vx.xx/h/i2c.h
  • Fuente C18:installation_path/vx.xx/src/pmc_common/i2c/
  • Encabezado XC8:installation_path/vx.xx/include/plib/i2c.h
  • Fuente XC8:installation_path/vx.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 usando la OpenI2Cfunció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_modeson:

  • 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í: ¿Qué es la velocidad de respuesta 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().

De acuerdo, lo configuraste todo y abriste la conexión. Ahora vamos a hacer 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. 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

Ejemplo de lectura maestra

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

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 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:

  • Escriba un SSP ISR, para cuando la interrupción sea una interrupción SSP (y no otra interrupción)
  • Escriba un ISR general de alta prioridad, para cuando la interrupción sea 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 SSP ISR)
  • Agregue una GOTOinstrucció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:

  1. El maestro escribe, el último byte era la dirección
  2. El maestro escribe, el último byte era datos
  3. El maestro lee, el último byte era la dirección
  4. El maestro lee, el último byte era datos
  5. NACK: fin de la transmisión

Puede comprobar en qué estado se encuentra comprobando los bits en el SSPSTATregistro. Este registro es el siguiente en modo I2C (se omiten los bits no utilizados o irrelevantes):

  • Bit 5: D/NO A: Datos/No dirección: se establece si el último byte era un dato, se borra si el último byte era una dirección
  • Bit 4: P: Bit de parada: se establece si se produjo una condición de PARADA en último lugar (no hay ninguna operación activa)
  • Bit 3: S: Bit de inicio: se establece si se produjo 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: Búfer lleno: se establece si hay datos en el registro SSPBUFF, se borra 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 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 SSPSTATregistro (primero con AND 0x2dpara 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 el monitoreo ambiental

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 );
Gracias, esa es la parte maestra y, de hecho, probablemente sea la misma que PIC18. También gracias por la nota del compilador :-) Preguntar un poco me dio algunas notas de la aplicación, así que las agregaré como respuesta.
Debería considerar agregar una sección sobre la configuración del generador de velocidad en baudios. El código normalmente se parece 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.

¡Es una pena! Entonces que hiciste?
Improvisé el mío por desgracia.
¿Son útiles para los demás también? ¡Me gustaría verlos!