Démarrage avec I2C sur PIC18

8

Pour un projet, j'aimerais que trois PIC (deux esclaves PIC18F4620, un maître PIC18F46K22) communiquent via le bus I2C. Plus tard, d'autres esclaves peuvent être ajoutés (comme EEPROM, SRAM, ...). J'écris le code de ces PIC en C en utilisant le compilateur C18. J'ai beaucoup regardé sur Internet, mais je n'ai pas trouvé de bibliothèques pour gérer le périphérique (M) SSP. J'ai lu la fiche technique des deux PIC sur le périphérique (M) SSP en mode I2C mais je n'ai pas trouvé comment interfacer le bus.

J'ai donc besoin de bibliothèques maîtres et esclaves.

Que recommandez-vous? Avez-vous une telle bibliothèque quelque part? Est-il intégré au compilateur et si oui, où? Existe-t-il un bon tutoriel quelque part sur le net?


la source
2
J'ai eu des problèmes similaires il y a quelques mois. Vous pouvez en lire plus ici . Voici les bibliothèques pour C18 qui fonctionnent avec I ^ 2C, mais il y a une grande chose qui manque: vous devez définir la vitesse du bus manuellement en écrivant dans le registre approprié et cela n'est mentionné nulle part dans la documentation de la bibliothèque.
AndrejaKo
Merci, c'était utile! Il ne s'agissait cependant que de la partie maître, pas de la partie esclave.
Ouais, je n'avais pas besoin de travailler avec des esclaves à l'époque, donc pas d'exemples d'esclaves. Désolé.
AndrejaKo
2
Non ça va, c'était utile pour la partie master! :-)
veuillez également désactiver l'analogique sur les ports ANSELC = 0;

Réponses:

22

Microchip a écrit des notes d'application à ce sujet:

  • AN734 sur l'implémentation d'un esclave I2C
  • AN735 sur l'implémentation d'un maître I2C
  • Il existe également une AN736 plus théorique sur la mise en place d'un protocole réseau pour la surveillance de l'environnement, mais elle n'est pas nécessaire pour ce projet.

Les notes d'application fonctionnent avec ASM mais elles peuvent être portées facilement en C.

Les compilateurs gratuits C18 et XC8 de Microchip ont des fonctions I2C. Vous pouvez en savoir plus à leur sujet dans la documentation des bibliothèques du compilateur , section 2.4. Voici quelques informations de démarrage rapide:

Mise en place

Vous disposez déjà du compilateur C18 ou XC8 de Microchip. Ils ont tous deux des fonctions I2C intégrées. Pour les utiliser, vous devez inclure i2c.h:

#include i2c.h

Si vous souhaitez consulter le code source, vous pouvez le trouver ici:

  • En-tête C18: installation_path/vx.xx/h/i2c.h
  • Source C18: installation_path/vx.xx/src/pmc_common/i2c/
  • En-tête XC8: installation_path/vx.xx/include/plib/i2c.h
  • Source XC8: installation_path/vx.xx/sources/pic18/plib/i2c/

Dans la documentation, vous pouvez trouver dans quel fichier du /i2c/dossier se trouve une fonction.

Ouverture de la connexion

Si vous connaissez les modules MSSP de Microchip, vous savez qu'ils doivent d'abord être initialisés. Vous pouvez ouvrir une connexion I2C sur un port MSSP à l'aide de la OpenI2Cfonction. Voici comment cela est défini:

void OpenI2C (unsigned char sync_mode, unsigned char slew);

Avec sync_mode, vous pouvez choisir si l'appareil est maître ou esclave et, s'il s'agit d'un esclave, s'il doit utiliser une adresse 10 bits ou 7 bits. La plupart du temps, le 7 bits est utilisé, en particulier dans les petites applications. Les options pour sync_modesont:

  • SLAVE_7 - Mode esclave, adresse 7 bits
  • SLAVE_10 - Mode esclave, adresse 10 bits
  • MASTER - Mode maître

Avec slew, vous pouvez sélectionner si l'appareil doit utiliser la vitesse de balayage. En savoir plus sur ce que c'est ici: Quel est le taux de balayage pour I2C?

Deux modules MSSP

Il y a quelque chose de spécial avec les appareils avec deux modules MSSP, comme le PIC18F46K22 . Ils ont deux ensembles de fonctions, un pour le module 1 et un pour le module 2. Par exemple, au lieu de OpenI2C(), ils ont OpenI2C1()et openI2C2().

D'accord, vous avez donc tout configuré et ouvert la connexion. Faisons maintenant quelques exemples:

Exemples

Exemple d'écriture maître

Si vous connaissez le protocole I2C, vous saurez qu'une séquence d'écriture maître typique ressemble à ceci:

Master : START | ADDR+W |     | DATA |     | DATA |     | ... | DATA |     | STOP
Slave  :       |        | ACK |      | ACK |      | ACK | ... |      | ACK |

Dans un premier temps, nous envoyons une condition START. Pensez à décrocher le téléphone. Ensuite, l'adresse avec un bit d'écriture - composer le numéro. À ce stade, l'esclave avec l'adresse envoyée sait qu'il est appelé. Il envoie un accusé de réception ("Bonjour"). Maintenant, l'appareil maître peut envoyer des données - il commence à parler. Il envoie n'importe quelle quantité d'octets. Après chaque octet, l'esclave doit ACK les données reçues ("oui, je vous entends"). Lorsque l'appareil maître a fini de parler, il raccroche avec la condition STOP.

En C, la séquence d'écriture principale ressemblerait à ceci pour le maître:

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

Exemple de lecture maître

La séquence de lecture principale est légèrement différente de la séquence d'écriture:

Master : START | ADDR+R |     |      | ACK |      | ACK | ... |      | NACK | STOP
Slave  :       |        | ACK | DATA |     | DATA |     | ... | DATA |      |

Encore une fois, le maître lance l'appel et compose le numéro. Cependant, il veut maintenant obtenir des informations. L'esclave répond d'abord à l'appel, puis commence à parler (envoi de données). Le maître reconnaît chaque octet jusqu'à ce qu'il dispose de suffisamment d'informations. Il envoie ensuite un Not-ACK et raccroche avec une condition STOP.

En C, cela ressemblerait à ceci pour la partie maître:

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

Code esclave

Pour l'esclave, il est préférable d'utiliser une routine de service d'interruption ou ISR. Vous pouvez configurer votre microcontrôleur pour recevoir une interruption lorsque votre adresse est appelée. De cette façon, vous n'avez pas à vérifier constamment le bus.

Tout d'abord, configurons les bases des interruptions. Vous devrez activer les interruptions et ajouter un ISR. Il est important que les PIC18 aient deux niveaux d'interruptions: haut et bas. Nous allons définir I2C comme une interruption de haute priorité, car il est très important de répondre à un appel I2C. Ce que nous allons faire est le suivant:

  • Écrire un ISR SSP, lorsque l'interruption est une interruption SSP (et non une autre interruption)
  • Écrivez un ISR général de haute priorité, lorsque l'interruption est de haute priorité. Cette fonction doit vérifier quel type d'interruption a été déclenchée et appeler le bon sous-ISR (par exemple, le SSP ISR)
  • Ajoutez une GOTOinstruction à l'ISR général sur le vecteur d'interruption de haute priorité. Nous ne pouvons pas mettre l'ISR général directement sur le vecteur car il est trop grand dans de nombreux cas.

Voici un exemple de code:

// 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
}

La prochaine chose à faire est d'activer l'interruption de haute priorité lors de l'initialisation de la puce. Cela peut être fait par quelques manipulations de registre 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

Maintenant, nous avons des interruptions de travail. Si vous implémentez cela, je le vérifierais maintenant. Écrivez une base SSPISR()pour commencer à faire clignoter une LED lorsqu'une interruption SSP se produit.

D'accord, vous avez donc fait fonctionner vos interruptions. Écrivons maintenant du vrai code pour la SSPISR()fonction. Mais d'abord une théorie. Nous distinguons cinq types d'interruption I2C différents:

  1. Le maître écrit, le dernier octet était l'adresse
  2. Le maître écrit, le dernier octet était des données
  3. Le maître lit, le dernier octet était l'adresse
  4. Le maître lit, le dernier octet était des données
  5. NACK: fin de transmission

Vous pouvez vérifier dans quel état vous êtes en vérifiant les bits dans le SSPSTATregistre. Ce registre est le suivant en mode I2C (les bits inutilisés ou non pertinents sont omis):

  • Bit 5: D / NOT A: Data / Not address: défini si le dernier octet était des données, effacé si le dernier octet était une adresse
  • Bit 4: P: Bit d'arrêt: défini si une condition STOP s'est produite en dernier (aucune opération active)
  • Bit 3: S: Bit de démarrage: défini si une condition START s'est produite en dernier (il y a une opération active)
  • Bit 2: R / NOT W: lecture / non écriture: activé si l'opération est une lecture principale, effacé si l'opération est une écriture principale
  • Bit 0: BF: Buffer Full: défini s'il y a des données dans le registre SSPBUFF, effacé sinon

Avec ces données, il est facile de voir comment voir l'état du module 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      | -         |   ?   |   ?   |   ?   |   ?   |   ?

Dans le logiciel, il est préférable d'utiliser l'état 5 par défaut, ce qui est supposé lorsque les exigences pour les autres états ne sont pas remplies. De cette façon, vous ne répondez pas lorsque vous ne savez pas ce qui se passe, car l'esclave ne répond pas à un NACK.

Quoi qu'il en soit, jetons un œil au code:

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

Vous pouvez voir comment vous pouvez vérifier le SSPSTATregistre (d'abord ANDed avec 0x2dpour que nous ayons seulement les bits utiles) en utilisant des masques de bit afin de voir quel type d'interruption nous avons.

Il vous appartient de savoir ce que vous devez envoyer ou faire lorsque vous répondez à une interruption: cela dépend de votre candidature.

Références

Encore une fois, je voudrais mentionner les notes d'application que Microchip a écrites sur I2C:

  • AN734 sur l'implémentation d'un esclave I2C
  • AN735 sur l'implémentation d'un maître I2C
  • AN736 sur la mise en place d'un protocole de réseau pour la surveillance environnementale

Il y a de la documentation pour les bibliothèques du compilateur: Documentation des bibliothèques du compilateur

Lorsque vous configurez quelque chose vous-même, consultez la fiche technique de votre puce dans la section (M) SSP pour la communication I2C. J'ai utilisé le PIC18F46K22 pour la partie maître et le PIC18F4620 pour la partie esclave.

Communauté
la source
3

Tout d'abord, je recommanderais de passer au compilateur XC8 simplement parce que c'est le dernier. Il y a des bibliothèques périphériques disponibles, mais je ne les ai jamais beaucoup utilisées. Consultez le site Web de Microchips pour plus de détails et la documentation.

D'accord, j'ai ici de très vieilles routines de base pour les communications eeprom I2C que j'ai utilisées il y a longtemps avec un PIC16F et l'ancien compilateur de milieu de gamme Microhip (c'était peut-être celui de haute technologie), mais je pense qu'elles peuvent fonctionner correctement avec le PIC18, car je pense que le périphérique est le même. Vous découvrirez très vite de toute façon si tout est différent.
Ils faisaient partie d'un fichier plus grand qui a été utilisé avec un projet d'enregistreur de température, donc j'ai rapidement déchiré toutes les autres fonctions non liées et enregistré en tant que fichiers ci-dessous, il est donc possible que j'en ai fait un peu gâchis, mais j'espère que vous sera en mesure de se faire une idée de ce qui est nécessaire (cela peut même fonctionner, on ne sait jamais ;-))

Vous devrez vous assurer que le fichier d'en-tête principal est correct et vérifier / modifier les broches pour vous assurer qu'il s'agit des broches périphériques I2C correctes et des noms de registre s'ils proviennent du compilateur Hi-Tech, qui a fait les choses un peu différemment. concernant la convention de dénomination des registres.

Fichier 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
}

Fichier d'en-tête 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
la source
Merci, c'est la partie maître et en fait probablement la même chose que PIC18. Merci également pour la note du compilateur :-) Demander un peu m'a donné quelques notes d'application, donc je vais les ajouter comme réponse moi-même.
Vous devriez envisager d'ajouter une section sur la configuration du générateur de vitesse de transmission. Le code ressemble généralement SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;à 1KHz, etc.
Jesse Craig
1

Les compilateurs XC8 et XC16 incluent des bibliothèques pour I2C.

Le problème que j'ai rencontré est que la documentation n'est pas très bonne! Si vous utilisez les exemples de la documentation Microchip, vous n'avez pas de chance. Même le support Microchip ne peut pas vous aider. Je m'y suis rendu moi-même.

Il y a quelque temps, j'ai travaillé avec le microcontrôleur de la série PIC24EP512GP et la bibliothèque ne fonctionnait pas pour moi, comme indiqué par Microchip.

Chetan Bhargava
la source
Ah, c'est dommage! Alors qu'as-tu fait?
J'ai improvisé le mien malheureusement.
Chetan Bhargava
1
Sont-ils également utiles aux autres? J'aimerais les voir!