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
/v
x.xx
/h/i2c.h
- Source C18:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- En-tête XC8:
installation_path
/v
x.xx
/include/plib/i2c.h
- Source XC8:
installation_path
/v
x.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 OpenI2C
fonction. 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_mode
sont:
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
GOTO
instruction à 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:
- Le maître écrit, le dernier octet était l'adresse
- Le maître écrit, le dernier octet était des données
- Le maître lit, le dernier octet était l'adresse
- Le maître lit, le dernier octet était des données
- NACK: fin de transmission
Vous pouvez vérifier dans quel état vous êtes en vérifiant les bits dans le SSPSTAT
registre. 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 SSPSTAT
registre (d'abord ANDed avec 0x2d
pour 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.