8 votos

Empezando con I2C en PIC18s

Para un proyecto quiero que tres PICs (dos esclavos PIC18F4620, un maestro PIC18F46K22) se comuniquen a través del bus I2C. Más adelante, se pueden añadir más esclavos (como EEPROM, SRAM, ...). Estoy escribiendo el código para estos PICs en C usando el compilador C18. He buscado mucho en internet, pero no he podido encontrar librerías para manejar el periférico (M)SSP. He leído el datasheet de ambos PICs sobre el periférico (M)SSP en modo I2C pero no he podido encontrar cómo interconectar el bus.

Así que necesito un maestro y bibliotecas de esclavos.

¿Qué recomienda? ¿Tiene una biblioteca de este tipo en algún lugar? ¿Está incorporada en el compilador y si es así, dónde? ¿Hay algún buen tutorial en la red?

22voto

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/vx.xx_/h/i2c.h
  • Fuente de C18: _installation_path/vx.xx_/src/pmc_common/i2c/
  • Cabecera XC8: _installation_path/vx.xx_/include/plib/i2c.h
  • Fuente XC8: _installation_path/vx.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:

  1. El maestro escribe, el último byte fue la dirección
  2. El maestro escribe, el último byte era de datos
  3. El maestro lee, el último byte fue la dirección
  4. El maestro lee, el último byte era de datos
  5. 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.

3voto

SandeepJ Puntos 1339

En primer lugar, recomendaría cambiar al compilador XC8 simplemente porque es el más reciente. Hay librerías de periféricos disponibles, pero nunca las he usado mucho. Comprueba en la web de Microchips los detalles y la documentación.

Bien, tengo algunos muy antiguo Rutinas básicas aquí para comunicaciones I2C eeprom que usé hace mucho tiempo con un PIC16F y el viejo compilador de gama media de Microhip (puede ser el de Hi-Tech), pero creo que pueden funcionar bien con el PIC18, ya que creo que el periférico es el mismo. De todas formas lo descubrirás muy rápido si todo es diferente.
Eran parte de un archivo más grande que se utilizó con un proyecto de registro de la temperatura, por lo que rápidamente arrancó todas las otras funciones no relacionadas y guardado como los archivos de abajo, así que es posible que he hecho un poco de un lío de ella, pero espero que usted será capaz de obtener una idea de lo que se necesita (puede incluso sólo funciona, nunca se sabe ;-) )

Tendrás que asegurarte de que el archivo de cabecera principal es correcto, y comprobar/alterar los pines para asegurarte de que son los pines periféricos I2C correctos, y los nombres de los registros si son del compilador Hi-Tech, que hizo las cosas un poco diferentes en cuanto a la convención de nomenclatura de los 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 cabecera 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 );

1voto

Chetan Bhargava Puntos 3758

Los compiladores XC8 y XC16 incluyen bibliotecas para I2C.

El problema que he encontrado es que la documentación no es muy buena. Si utilizas los ejemplos de la documentación de Microchip, no tienes suerte. Ni siquiera el soporte de Microchip puede ayudarte. Yo mismo he pasado por eso.

Hace un tiempo trabajé con el microcontrolador de la serie PIC24EP512GP y la librería no me funcionó tal y como la documenta Microchip.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X