dimanche 12 janvier 2020

Analyse d'une communication I2C


Tout comme nous l'avons déjà fait pour les communications UART et SPI, nous allons aujourd'hui décrire les principales caractéristiques d'une communication I2C , et observer une transaction I2C typique au moyen d'un analyseur logique.

Mis au point par la compagnie Philips en 1982,  I2C est un protocole de communication série qui permet à un microcontrôleur d'interagir avec une grande quantité de périphériques en utilisant uniquement deux lignes de transmission (on l'appelle d'ailleurs parfois "Two Wire Interface"). Le nom " I2C " est l'acronyme de "inter-integrated circuit"; donc "IIC" apparemment transformé en "I2C" pour faire plus original.

Il s'agit d'une communication série synchrone bidirectionnelle half-duplex...

"Série" car les bits qui forment un octet sont transmis un à la suite de l'autre sur la même ligne de transmission (par opposition à une communication parallèle, dans laquelle chaque bit serait transmis sur sa propre ligne). UART et SPI sont deux autres exemples de communication série.

"Synchrone" car un signal d'horloge sert de référence à tous les appareils impliqués dans la communication. Ce signal d'horloge est géré par le "maître" et partagé sur la ligne SCL ("serial clock").

"Bi-directionnelle" car l'information peut être transmise du maître à l'esclave et de l'esclave vers le maître.

"Half-duplex" car, puisque qu'une seule ligne de transmission est utilisée pour les deux directions (la ligne SDA "serial data"), les informations ne peuvent circuler que dans une direction à la fois.

Puisqu'il peut y avoir un très grand nombre d'esclaves, chaque esclave dispose d'une adresse unique, constituée de 7 bits (ce qui donne une possibilité théorique de 128 esclaves). La présence de plusieurs maîtres est également possible.

Quelques exemples de périphériques  I2

Le nombre de capteurs et d'afficheurs qui peuvent communiquer avec l'Arduino par I2C est assez impressionnant: écran OLED SH1106,  accéléromètre MMA7455capteur de lumière TSL2561récepteur radio FM RDA5807convertisseur analogique-numérique PCF8591,  accéléromètre/gyro MPU-6050, capteur de pression atmosphérique BMP180magnétomètre/boussole HMC5883L, horloge temps réel DS1307, mémoire EEPROM, etc.

Drain ouvert 

Tous les appareils  I2C  sont connectés à la ligne SDA et à la ligne SCL par une sortie à drain ouvert (ou à collecteur ouvert, si vous préférez la terminologie des transistors bipolaires). Le circuit est schématisé ci-contre. Il s'agit d'un circuit inverseur, puisque sa sortie est au niveau logique '0' lorsque l'entrée est au niveau logique '1', et sa sortie est au niveau '1' lorsque l'entrée est au niveau '0'.

Lorsque l'entrée (la grille du transistor) est à 0 V, le trajet source-drain ne conduit pas le courant, aucun courant ne traverse la résistance et la sortie se trouve à un potentiel de 5 V. Lorsque l'entrée est à 5 V, le trajet source-drain devient conducteur et relie la sortie à la masse (GND).

Imaginez plusieurs circuits de ce genre dont la sortie est reliée à la même ligne (SDA ou SCL): il s'agit qu'un seul de ces circuits relie la ligne de transmission à la masse pour qu'elle se retrouve au niveau logique '0', même si tous les autres circuits tentent de la maintenir au niveau logique '1'.  Nous verrons un peu plus loin que cette caractéristique est utilisée, entre autres,  pour permettre à un esclave d'envoyer un acquittement (aknowledge): pendant que le maître maintient la ligne SDA à '1', l'esclave l'abaisse à '0' pour indiquer qu'il a reçu l'information.

La résistance pull-up doit être assez grande pour limiter le courant circulant à travers les lignes SDA et SCL. Par contre, plus elle est grande, plus la transition entre le niveau logique '0' et le niveau logique '1' sera longue, ce qui limite la vitesse de transmission du signal. Plus le bus  I2C comporte un grand nombre d'appareils, plus la capacité est grande , ce qui nous oblige à diminuer la valeur de la résistance pull-up.

Lorsqu'il n'y a aucune communication  I2C , les lignes SDA et SCL sont au niveau logique '1', puisque c'est dans cet état qu'aucun courant ne circule à travers la résistance pull-up.

Observation d'une communication  I2

J'ai profité de mes récentes expérimentations avec une mémoire EEPROM 24LC01B branchée à une carte Arduino pour observer une communication  I2C  typique au moyen d'un analyseur logique et du logiciel Sigrok Pulseview. La communication  I2C  étant, comme d'habitude, gérée par la bibliothèque Wire.


L'EEPROM 24LC01B était branché à l'Arduino de la façon suivante:
  • Les broches 1, 2 et 3 du 24LC01B ne sont pas branchées. 
  • Broche 4 du 24LC01B branchée à la masse (GND).
  • Broche 5 du 24LC01B (SDA) branchée à la broche A4 de l'Arduino Uno, ainsi qu'à 5 V par l'entremise d'une résistance de tirage de 4,7 kΩ.
  • Broche 6 du 24LC01B  (SCL) branchée à la broche A5 de l'Arduino Uno, ainsi qu'à 5 V par l'entremise d'une résistance de tirage de de 4,7 kΩ.
  • Broche 7 du 24LC01B (WP) reliée à la masse (GND).
  • Broche 8 du 24LC01B reliée à 5 V.

Première communication: écriture d'une valeur en mémoire

Dans un premier temps, j'ai exécuté un sketch qui écrit la valeur "44" à l'adresse "63" de l'EEPROM (dont l'adresse I2C est 0x50) .


Et j'ai obtenu ce chronogramme sur Seegrok Pulseview, qui montre l'émission de 3 octets consécutifs.

La vue d'ensemble est jolie, mais c'est tout petit: on va zoomer un peu...


Première constatation: avant l'établissement de la communication I2C, les deux signaux (SCL et SDA) étaient au niveau logique '1'.

Le maître envoie ensuite un bit "start", qui consiste à mettre la ligne SDA au niveau '0' pendant que la ligne SCL demeure au niveau '1'.

Le maître envoie ensuite un octet constitué des 7 bits de l'adresse I2C du périphérique (ici 50 en hexadécimal, ou 1010000 en binaire), et d'un huitième bit nommé R/W. Le bit R/W prend la valeur '0' si le maître s'apprête à envoyer de l'information à l'esclave, et la valeur '1' si le maître demande à l'esclave de lui envoyer de l'information. Ici, ce 8e bit est à 0: les prochains octets seront donc générés par le maître et non par l'esclave.  Chaque bit est lu sur la ligne SDA au moment où la ligne SCL passe de 0 à 1 (j'ai ajouté des points noirs pour indiquer le moment de la lecture de chacun des 8 bits).

Pour terminer l'envoi de ce premier octet, le maître laisse la ligne SDA à 1, et l'esclave force cette ligne à '0' pour indiquer qu'il a bien reçu l'information et qu'il est prêt à recevoir les données (c'est le signal d'acquittement ACK, symbolisé par le "A" rose à l'extrême droite du chronogramme). Si d'autres esclaves sont présents, ils ne tiennent pas compte du reste de cette communication, puisque l'adresse énoncée n'est pas la leur.


Suite à cette confirmation que l'esclave est prêt à recevoir les données, le maître envoie un deuxième octet, qui est l'adresse de la mémoire dont on désire modifier le contenu. Il s'agit ici de 63, qui se traduit par 00111111 en binaire. À la fin, l'esclave met la ligne SDA à '0' pour indiquer qu'il a bien reçu l'information (acquittement).

Le maître envoie ensuite un troisième octet qui contient la valeur qui désire enregistrer en mémoire. Il s'agit ici de 44, qui se traduit par 00101100 en binaire (ou par 0x2C en hexadécimal, tel qu'indiqué par le logiciel PulseView sur le chronogramme ci-dessus).

C'est suivi par un dernier signal d'acquittement de la part de l'esclave et, finalement, le maître clôt la transaction par le signal "stop" (identifié par le P à l'extrême droite du chronogramme), qui consiste à faire passer la ligne SDA de '0' à '1' pendant que le signal SCL demeure à '1'.

La communication étant terminée, les deux lignes (SDA et SCL) demeurent à '1' en attendant la prochaine transaction.

Remarque: la fréquence par défaut de la communication I2C est de 100 kHz. Au besoin, j'aurais pu modifier cette fréquence en utilisant Wire.setClock() dans mon sketch. Remarquons en passant que cette fréquence est environ 10 fois plus lente que celle d'une communication SPI typique.



Deuxième communication: lecture d'une valeur en mémoire

Dans un deuxième temps, j'ai exécuté un sketch qui lit la valeur se trouvant à l'adresse "63" de l'EEPROM:


Cette fois, la transaction implique 4 octets: le maître doit d'abord indiquer à l'esclave qu'il lui envoie de l'information (l'adresse dont il veut connaître le contenu), puis lui indiquer qu'il attend la réponse.


Faisons un zoom sur chaque octet:


Encore une fois, le signal "Start", qui consiste à faire passer la ligne SDA de '1' à '0' pendant que la ligne SCL demeure à '1'.

Ensuite, l'envoi des 7 bits de l'adresse I2C de l'esclave (1010000), suivi du bit R/W qui est de '0' car le maître devra d'abord envoyer de l'information à l'esclave.

Cette partie de la transaction se termine par l'émission du signal d'acquittement par l'esclave.


Dans un deuxième temps, le maître envoie sur la ligne SDA l'adresse de la mémoire EEPROM dont il désire connaître le contenu (00111111 qui correspond à 63 en décimal). Si à la réception de cette adresse, l'esclave génère le bit d'acquittement, et le maître clôt la transaction par un bit "stop" (la ligne SDA passe de '0' à '1' pendant que SCL reste à '1').


Le 3e octet permet au maître d'aviser l'esclave que c'est maintenant à lui, l'esclave, d'envoyer l'information (qui sera le contenu de l'adresse qu'on lui a précédemment communiqué). Ce troisième octet est identique au premier, sauf qu'il se termine par '1' plutôt que par '0'. Le bit R/W de '1' indique que le prochain échange s'effectuera de l'esclave vers le maître.


Le 4e octet est généré par l'esclave. 00101100 correspond à 44 en décimal, qui est la valeur précédemment enregistrée à l'adresse 63.

Cette fois, c'est le maître qui est responsable d'émettre un signal d'acquittement. S'il le fait, l'esclave lui enverra une autre donnée (le contenu de l'adresse 64). Sinon (signal "N" de non-acquittement), l'esclave cesse d'envoyer des données, et le maître clôt la transaction en faisant passer la ligne SDA de '0' à '1' pendant que SCL est maintenu à '1'.

En cas de mauvaise adresse

Qu'arrive-t-il si le maître envoie une instruction à une adresse qui ne correspond à aucun esclave connecté? Pour vérifier, j'ai modifié mon sketch pour qu'il tente d'écrire une valeur à un esclave (inexistant) dont l'adresse serait 0x60 plutôt que 0x50.


Cette fois, la transaction se limite à un seul octet. Le maître envoie un message constitué des 7 bits de l'adresse i2c 1100000 (qui ne correspond à aucun esclave connecté) et du bit R/W de 0. N'ayant détecté aucun signal d'acquittement, le maître met fin à la transaction.

Avantages/inconvénients

Une communication I2C est moins rapide qu'une communication SPI, entre autres parce que la fréquence de l'horloge est plus faible, parce que l'information ne peut circuler que dans une direction à la fois, parce qu'une partie de la communication consiste à transmettre l'adresse de l'esclave concerné, etc.  Par contre on peut brancher un grand nombre de périphériques en utilisant seulement 2 broches du microcontrôleur.

À lire également

Des articles similaire ont été publiés concernant la communication SPI et la communication UART.


Yves Pelletier (TwitterFacebook)

2 commentaires:

  1. tout d'abord Bravo pour la qualité des articles
    pour le sniffing I2C j'ai une carte dangrous prototype
    mais je dois avouer que j'ai jamais testé !
    c'est ici
    http://dangerousprototypes.com/blog/2010/07/13/bus-pirate-firmware-v5-3-development/
    F1CHF

    RépondreSupprimer
  2. une info en passant
    j'utilise les utilitaires develloppés par Marc Bouget
    c'est super ..
    ici :
    http://meteosat.pessac.free.fr/Cd_elect/temp/a_nettoyer/perso.club-internet.fr/mbouget/i2c.html

    RépondreSupprimer