TP 6 : I2C (Inter-Integrated Circuit)
Ce TP est consacré à la compréhension et à l’utilisation du protocole I2C sur le microcontrôleur ATmega324P. Pour aller plus loin, vous pouvez consulter la fiche technique de l’ATmega324P et l’article Wikipedia : I2C.
1. Qu’est-ce que l’I2C ?
I2C (aussi appelé IIC ou TWI – Two-Wire Interface) est un protocole de communication synchrone, série, semi-duplex, développé par Philips en 1982.
Il est couramment utilisé pour la communication entre microcontrôleurs et capteurs ou mémoires.
Un bus I2C utilise deux lignes :
- SDA – Ligne de données série
- SCL – Ligne d’horloge série
L’horloge est toujours générée par le maître. Un seul périphérique peut transmettre à la fois sur la ligne SDA, ce qui fait de l’I2C un protocole semi-duplex.
2. Fonctionnement de l’I2C
Contrairement au SPI, l’I2C ne nécessite pas de ligne Slave Select séparée.
Chaque esclave possède une adresse unique (généralement sur 7 bits), et la communication est toujours initiée par le Maître.
Une communication I2C se divise en :
- Une trame d’adresse
- Une ou plusieurs trames de données
Les messages sont encadrés par des signaux spéciaux :
- Condition de démarrage (Start)
- Condition d’arrêt (Stop)
2.1 Condition de démarrage
Avant d’envoyer l’adresse de l’esclave, le Maître doit générer une condition de démarrage :
- Pendant que SCL est à l’état HAUT, SDA passe de l’état HAUT à BAS.
Cela indique à tous les esclaves d’écouter l’adresse à venir.
2.2 Trame d’adresse
Après la condition de démarrage, le maître envoie :
- 7 bits : adresse de l’esclave (A6–A0)
- 1 bit : R/W (0 = écriture, 1 = lecture)
L’esclave qui reconnaît son adresse envoie un ACK en forçant SDA à l’état BAS pendant le 9e cycle d’horloge.
Si SDA reste à l’état HAUT pendant ce 9e cycle, c’est un NACK.
2.3 Trames de données
Si un ACK est reçu, le maître continue avec :
- Écriture : le maître envoie des octets, l’esclave acquitte chaque octet.
- Lecture : l’esclave envoie des octets, le maître acquitte chaque octet.
Pour terminer une lecture, le Maître envoie un NACK au lieu d’un ACK après le dernier octet.
Chaque octet est suivi d’un ACK/NACK selon la direction de transfert.
2.4 Condition d’arrêt
Pour terminer la communication, le maître génère une condition d’arrêt :
- SDA passe de l’état BAS à HAUT après que SCL est HAUT.
Plusieurs esclaves peuvent partager le même bus, mais chacun possède une adresse unique (généralement sur 7 bits).
Le bit de poids faible de la trame d’adresse indique s’il s’agit d’une lecture (1) ou d’une écriture (0).
Une fois l’esclave sélectionné, la transmission de données peut commencer.
3. Registres de configuration I2C
Le microcontrôleur ATmega324P supporte les modes Maître et Esclave pour l’I2C.
Registre de débit TWI (TWBR
)
- Ce registre définit le diviseur de fréquence qui détermine la vitesse de l’horloge SCL en mode Maître.
Registre de contrôle TWI (TWCR
)
- Ce registre permet d’activer la communication I2C, de générer les conditions Start/Stop, de gérer les acquittements et de détecter les collisions.
Bit | Nom | Description |
---|---|---|
7 | TWINT | Drapeau d’interruption TWI (mis à 1 par le matériel, effacé en écrivant 1) |
6 | TWEA | Activation de l’acquittement : 1 = envoie ACK, 0 = envoie NACK |
2 | TWEN | Activation du module TWI : 1 = actif, 0 = inactif |
Registre de statut TWI (TWSR
)
- Indique l’état actuel du TWI (start envoyé, adresse reconnue, etc.) et les réglages du préscaler.
Registre de données TWI (TWDR
)
- Contient l’octet à transmettre (TX) ou l’octet reçu précédemment (RX), selon le contexte.
4. Utilisation du module I2C sur AVR
La configuration du module I2C est relativement simple — il suffit principalement de définir la fréquence de l’horloge.
Exemple : Initialiser l’I2C à 100kHz
void twi_init(void)
{
// Régler le préscaler à 1 (TWPS = 00)
TWSR = (0 << TWPS0);
// Régler le registre de débit
// Formule : TWBR = (F_CPU / SCL_freq - 16) / (2 * prescaler)
TWBR = 52; // Pour une horloge CPU de 12MHz et un SCL à 100kHz
}
Cependant, effectuer une transaction I2C complète est plus complexe et nécessite les étapes suivantes :
- Configurer
TWCR
pour émettre une condition START. - Attendre la fin de l’opération (vérifier
TWCR.TWINT
). - Charger l’adresse de l’esclave dans
TWDR
et lancer la transmission. - Attendre et vérifier l’ACK via
TWSR
. - Transmettre ou recevoir les octets de données via
TWDR
, en attendant à chaque étape. - Émettre une condition STOP à l’aide d’un autre drapeau dans
TWCR
.
5. Capteur de pression – MPL3115A2
La carte de TP inclut un capteur de pression et de température MPL3115A2 qui communique via I2C.
Brochage I2C pour le capteur :
Signal I2C | Broche GPIO |
---|---|
Enable | PA6 |
SCL | PC0 |
SDA | PC1 |
La communication avec le capteur nécessite un accès en lecture/écriture à ses registres internes via I2C. Avant toute opération, l’adresse du registre doit être envoyée.
La table des registres et leurs adresses respectives se trouve dans la fiche technique du MPL3115A2, section 14.
6. Exercices
Téléchargez l’archive du squelette de code et suivez les instructions marquées avec TODO
.
Tâche 0 – Initialisation de base de l’I2C
Complétez les fonctions twi_init
, twi_start
et twi_stop
dans le fichier twi.c
.
💡 Les définitions de constantes utiles se trouvent dans avr-libc util/twi.h
Tâche 1 – Lecture et écriture
Implémentez les fonctions twi_read_ack
, twi_read_nack
et twi_write
dans twi.c
.
💡 Astuce : consultez le tableau
23-2
, page 268 de la fiche technique ATmega324P pour un exemple complet.
Tâche 2 – Découverte des périphériques sur le bus I2C
Implémentez twi_discover
dans twi.c
. Cette fonction doit envoyer une adresse en lecture (SLA_R) à toutes les adresses possibles du bus I2C (0–127) et détecter celles qui répondent avec un ACK.
- Affichez les adresses des périphériques détectés via le port série.
- Veillez à appeler cette fonction dans
main()
.
💡 Astuce : utilisez le registre
TWSR
pour vérifier si un ACK a été reçu. Consultez le tableau23-4
, page 275 de la fiche technique pour les codes de statut.
Pour envoyer une adresse de lecture ou d’écriture :
// Adresse de lecture
(ADRESSE_PERIPHERIQUE << 1) | 1
// Adresse d’écriture
ADRESSE_PERIPHERIQUE << 1
📌 L’adresse du capteur est 0x60
→ lecture = 0xC1
, écriture = 0xC0
.
Tâche 3 – Capteur de pression MPL3115A2
Nous allons maintenant configurer et lire les valeurs du capteur MPL3115A2 via I2C.
Tâche 3.1 – Initialiser le capteur
Complétez mpl3115a2_init
dans mpl3115a2.c
. Cette fonction doit être appelée une seule fois dans main()
avant la boucle principale.
📌 Réglez CTRL_REG1
pour fonctionner en mode pression (assurez-vous que le bit ALT
est à 0).
Tâche 3.2 – Lire la pression et la température
Complétez mpl3115a2_read_pressure
et mpl3115a2_read_temperature
dans mpl3115a2.c
. Appelez-les dans la boucle principale et affichez les valeurs en série.
La section 14
de la fiche technique MPL3115A2 décrit les registres.
Formule de la pression (format Q18.2) :
PRESSURE = (OUT_P_MSB << 12) | (OUT_P_CSB << 4) | (OUT_P_LSB >> 4);
Séquence de lecture d’un registre
Utilisez la séquence suivante pour lire un registre :
twi_start();
twi_write(ADRESSE_PERIPHERIQUE << 1); // SLA + W
twi_write(ADRESSE_REGISTRE); // registre à lire
twi_start(); // START répété
twi_write((ADRESSE_PERIPHERIQUE << 1) | 1); // SLA + R
uint8_t data = 0;
twi_read_nack(&data);
twi_stop();
✅ Une fois les tâches terminées, vous devriez pouvoir communiquer avec le MPL3115A2, détecter son adresse, le configurer et lire des valeurs valides de pression et de température.