Comenzando con I2C en PIC18s

8

Para un proyecto, me gustaría que tres PIC (dos esclavos PIC18F4620, un maestro PIC18F46K22) se comuniquen a través del bus I2C. Más tarde, se pueden agregar más esclavos (como EEPROM, SRAM, ...). Estoy escribiendo el código para estos PIC en C usando el compilador C18. He buscado 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 encontrar la forma de conectar el bus.

Entonces necesito bibliotecas maestras y esclavas.

¿Que recomiendas? ¿Tienes tal biblioteca en alguna parte? ¿Está integrado en el compilador y, en caso afirmativo, dónde? ¿Hay algún buen tutorial en algún lugar de la red?


fuente
2
Tuve problemas similares hace unos meses. Puedes leer sobre ellos aquí . Aquí hay bibliotecas para C18 que funcionan con I ^ 2C, pero falta una gran cosa: debe establecer la velocidad del bus manualmente escribiendo en el registro apropiado y eso no se menciona en ninguna parte de la documentación de la biblioteca.
AndrejaKo
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. Lo siento.
AndrejaKo
2
No, está bien, ¡fue útil para la parte maestra! :-)
desactive también analógico en los puertos ANSELC = 0;

Respuestas:

22

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/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 utilizando la OpenI2Cfunció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_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í: ¿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 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 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:

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

Puede verificar en qué estado se encuentra verificando los bits en el SSPSTATregistro. 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 SSPSTATregistro (primero ANDed 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 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.

Comunidad
fuente
3

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, tengo algunas rutinas básicas muy antiguas aquí 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 Hi-Tech), pero creo que pueden funcionar bien con el PIC18, ya que creo que el periférico es el mismo. De todos modos, descubrirá muy rápidamente si todo es diferente.
Formaban parte de un archivo más grande que se utilizó con un proyecto de registro 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á tener 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 con respecto a la convención de nomenclatura de registro.

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 );
Oli Glaser
fuente
Gracias, esa es la parte maestra y, de hecho, probablemente la misma que PIC18. También gracias por la nota del compilador :-) Preguntar un poco me dio algunas notas de aplicación, así que las agregaré como respuesta.
Debería considerar agregar una sección sobre cómo configurar el generador de velocidad en baudios. El código normalmente se parece SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;a 1 KHz, etc.
Jesse Craig
1

Los compiladores XC8 y XC16 incluyen bibliotecas para I2C.

¡El problema que encontré es que la documentación no es muy buena! Si usa los ejemplos de la documentación de Microchip, no tiene suerte. Incluso el soporte de Microchip no puede ayudarte. He estado allí yo mismo.

Hace algún tiempo trabajé con el microcontrolador de la serie PIC24EP512GP y la biblioteca no funcionó para mí como lo documenta Microchip.

Chetan Bhargava
fuente
Ah, eso es una pena! Entonces que hiciste?
Improvisé la mía por desgracia.
Chetan Bhargava
1
¿Son útiles para los demás también? ¡Me gustaría verlos!