De nombreux capteurs disponibles sur le marché utilisent une interface I2C. Le datasheet de ces capteurs se contente en général de rappeler le fonctionnement d'un bus I2C par un copier-coller des spécifications originales de Philips/NXP, mais pour les ingénieurs mettant en place des solutions d'automatisation basées PC ou internet aujourd'hui, ce n'est pas forcément l'explication la plus pertinente. Voici donc notre cours accéléré sur pour les nouveaux utilisateurs de I2C.
Qu'est-ce que I2C ?
I2C est un standard de transmission de données en série, comme RS232 par exemple, mais utilisant des conventions de transmission différentes, qui facilitent grandement son intégration dans un chip. I2C est l'acronyme pour "Inter IC", c'est-à-dire "entre circuits intégrés". C'est la raison pour laquelle de nombreux capteurs et autres circuits électroniques sont conçus pour être interrogés et/ou configurés par une connexion I2C.
Le protocole I2C permet de chaîner plusieurs capteurs ou circuits, aussi appelés noeuds, sur un bus électrique commun. Le maître du bus prend l'initiative d'envoyer des commandes ou de demander des mesures aux circuits auxiliaires.
Un bus I2C comprend au minimum trois fils:
- La masse (GND)
- Le signal d'horloge (SCL)
- Le signal de données (SDA)
En général, on y adjoint un ou deux fils d'alimentation fournis par le maître. On utilise souvent deux fils d'alimentation dans le cas où la tension des signaux I2C sur les lignes SCL/SDA (typiquement 1.8V ou 3.3V) est différente de celle nécessaire alimenter les circuits auxiliaires (typiquement 3.3V ou 5V).
Architecture typique d'un bus I2C
Les deux lignes de transmission SCL et SDA sont en position haute lorsque le bus est au repos, tirées à la tension du bus par des résistances fixes (appelées pull-ups). Lorsque qu'un noeud (maître ou esclave) doit transmettre un signal, il tire simplement la ligne désirée à la masse. Lorsqu'il la relâche, la ligne remonte à la tension du bus grâce au pull-up.
C'est toujours le maître qui prend l'initiative de transmettre un message, et c'est lui seul qui a le contrôle sur les descentes de la ligne d'horloge (SCL). Voyons donc comment il procède pour communiquer avec les autres circuits.
Communiquer sur un bus I2C
Adressage
Comme il peut y avoir plusieurs circuits auxiliaires, chaque circuit doit posséder une adresse unique et les messages doivent être adressés. Tous les messages envoyés par le maître commencent donc par un octet formé par l'adresse du circuit cible sur 7 bit, auquel on ajoute un bit de poids faible indiquant la direction de la transmission. On peut donc calculer ce premier octet comme suit:
- l'adresse du noeud auquel le message est destiné, multipliée par deux
- +0 si il s'agit d'envoyer une série d'octets vers le noeud spécifié
- +1 si il s'agit de recevoir une série d'octets depuis le noeud désigné
Exemples:
Lorsque le maître envoie des octets au noeud 11, le message commence par
22 ...
Lorsque le maître lit des octets du noeud 11, le message commence par
23 ...
(note: dans cet article, les octets sont toujours représentés en hexadécimal, donc avec des valeurs allant de 00 à FF)
Contenu des messages
Mis à part l'usage du premier octet décrit ci-dessus, le contenu des messages peut être défini arbitrairement par le fabricant de chaque circuit. Mais le plus souvent, la convention utilisée est celle d'un accès par registres indexés:
- le maître envoie dans le 2e octet du message l'index du registre qui l'intéresse
- pour modifier le registre (write), le maître ajoute dans le 3e octet la valeur à écrire
- pour lire le registre (read), le maître recommence un message en lecture directement après le 2e octet
Exemples:
Pour écrire FF dans le registre 05 du noeud 11, le maître enverra le message suivant:
22 05 FF
Pour lire la valeur du registre 05 du noeud 11, le maître utilisera les deux messages suivants
22 05 23 xx
On a noté par xx un octet qui n'est pas envoyé par le maître, mais reçu par le maître, et qui correspond à la valeur du registre.
Accès à des registres consécutifs
En général, il est possible d'écrire et de lire plusieurs registres consécutifs simplement en les rajoutant des octets à la suite du message, sans devoir recommencer un nouveau message pour chaque registre. C'est utile en particulier pour lire des valeurs codées sur plusieurs octets, qui sont ainsi transmises de manière atomique.
Exemples:
Pour écrire FF EE dans les registre C0 et C1 du noeud 11, le maître enverra le message suivant:
22 C0 FF EE
Pour lire d'un coup la valeur des registres C0 et C1 du noeud 11, le maître utilisera les deux messages suivants
22 C0 23 xx xx
A nouveau, les xx représentent des octets qui ne sont pas envoyés par le maître, mais reçus, et qui correspondent eux valeurs des registres.
Codage et décodage d'une transmission I2C
Jusqu'à présent, nous avons décrit les messages échangés sans nous soucier de la manière dont ils étaient délimités, ni de la manière dont le maître pouvait vérifier que le destinataire avait bien reçu chaque octet envoyé. Ces aspects font partie du codage des messages sur les lignes de données.
Délimitation des messages
Vu l'importance de reconnaître le premier octet, qui détermine le destinataire du message, le protocole I2C prévoit un mécanisme très clair pour déterminer l'état dans lequel se trouve le bus (au repos entre deux messages, en cours de transmission, etc.) basé sur des conditions détectées lors des transitions qui se produisent sur les lignes SCL et SDA:
- La condition START, notée par le symbole {S} et définie par la descente de la ligne SDA alors que SCL est encore haut, signale le début d'un message.
- La condition STOP, notée par le symbole {P} et définie par la remontée de la ligne SDA alors que SCL est encore haut, marque la fin d'un message
- Entre un START un un STOP, les bits sont transmis en baissant la ligne SCL, changeant la valeur de SDA en fonction du bit a transmettre puis en remontant la ligne SCL. Le bit n'est lu par l'autre partie qu'au moment où la ligne SCL est remontée.
- La condition RESTART, notée par le symbole {R}, consiste à ré-émettre une condition START au milieu d'un message, sans repasser préalablement par la condition STOP. Elle sert à enchaîner directement deux messages, typiquement pour lire la valeur d'un registre spécifié. Elle n'est pas supportée par tous les circuits I2C.
Quittance des octets transmis
Pour garantir la bonne réception des octets envoyés, après la transmission de chacun octet, le noeud à qui l'octet était destiné quittance la réception par un bit ACK, noté par le symbole {A}, en tirant la ligne SDA à zéro. Si le récepteur n'a pas reconnu qu'un octet lui était transmis, ou si il n'est carrément pas connecté au bus, il ne tirera pas la ligne SDA vers la terre, ce qui sera interprété par le maître comme un NAK, noté {N} et le mènera à abandonner la communication. Dans certains cas, un noeud peut envoyer volontairement un NAK pour signifier la fin des données à transmettre. C'est en général la convention utilisée par le maître pour quittancer les données lors d'un message en réception.
Exemples de codage:
Pour reprendre les exemples précédents, le codage du message
22 C0 FF EE
est codée sur le bus I2C par l'utilisation des transitions et octets suivants:
{S} 22{A} C0{A} FF{A} EE{A} {P}
La lecture d'un registre décrite par les messages
22 C0 23 xx xx
peut se coder sur le bus I2C en utilisant le RESTART de la manière suivante:
{S} 22{A} C0{A} {R} 23{A} xx{N} {P}
Envoi de messages I2C avec le Yocto-I2C
Le Yocto-I2C vous permet d'initier des transmissions sur un bus I2C en tant que maître du bus. Vous pouvez soit indiquer simplement les octets à envoyer et recevoir, et laisser le Yocto-I2C gérer automatiquement le codage des différents états du bus, soit spécifier vous-même tous les états. Quelle que soit l'option choisie, le module vous retournera les quittances et octets reçus sur le bus I2C, préfixés par l'adresse du noeud qui les a envoyés.
Pour expérimenter des transactions à l'aide de l'interface interactive du Yocto-I2C, lancez VirtualHub et cliquez sur le numéro de série du module. Vous pouvez alors tester l'envoi de messages dans l'interface web, soit par octets, soit en spécifiant tous les états, comme illustré ci-dessous:
Les deux manières d'envoyer des messages de manière interactive
Dans le cas de l'envoi par octets, notez qu'il n'est même pas nécessaire de coder vous-même l'octet d'adresse: vous préfixez simplement votre message du symbole @ suivi de l'adresse du module, et vous ne spécifiez ensuite que les octets à envoyer et à recevoir. Le Yocto-I2C se chargera d'effectuer le codage pour vous, y compris la génération de l'octet d'adresse au début de chaque message.
Pour exécuter des transactions depuis votre propre programme, utilisez la classe YI2cPort de notre librairie. Vous pouvez ensuite:
- soit construire une chaîne de caractère identique à celle utilisée de manière interactive ci-dessus et employer les méthodes writeLine ou queryLine pour l'envoyer à l'aveugle, ou l'envoyer et lire la transaction résultante
- soit employer les méthodes i2cSendBin ou i2cSendAndReceiveBin qui travaillent directement sur des tableaux d'octets
Enfin, vous pouvez configurer le Yocto-I2C pour initie lui-même périodiquement des transactions de votre choix et analyse le résultat à l'aide du système de job. Vous trouverez différents exemples dans les divers articles de blogs listés sur la page du produit du Yocto-I2C sur le site Web de Yoctopuce.
Un exemple concret
Voyons maintenant sur un exemple réel comment passer des informations qu'on trouve dans la datasheet d'un capteur à des transmissions de mesures. Prenons par exemple le capteur TAOS TCS34725 dont nous avons démontré l'utilisation dans un article précédent.
Vérification des tensions
Les premiers éléments à vérifier avant même de connecter le capteur sont la tension d'alimentation et la tension du bus I2C. Voici ce qu'on trouve à la page 3 de la datasheet:
On constate que ce chip peut donc être acheté dans plusieurs variantes, utilisant des tensions de bus I2C différentes: 3.3V ou 1.8V. Dans ce cas il faut bien être attentif au modèle qu'on utilise!
Un peu plus bas on trouve le tableau suivant:
La tension d'alimentation idéale semble donc être 3V, mais une tension régulée à 3.3V est encore dans la plage recommandée.
Si vous désirez interfacer votre capteur directement à l'aide du bus I2C intégré d'un compute board, méfiez-vous des tensions supportées sur le bus I2C: un Arduino ne peut communiquer que sur un bus I2C 5V, et un Raspberry Pi est conçu pour communiquer avec un bus I2C 3.3V. Si ce n'est pas la tension de votre capteur, il vous faudra ajouter entre les deux un circuit changeur de niveau I2C. En raison de la transmission bidirectionnelle en I2C, il n'est pas possible d'utiliser un simple diviseur de tension.
Si comme nous vous utilisez un Yocto-I2C, il suffit de prendre garde à sélectionner les bonnes tensions d'alimentation et de communication dans la configuration du module.
Registres
Comme indiqué dans la première table ci-dessus, l'adresse du capteur sur le bus dépend de la variante que vous avez, mais est le plus probablement 29. Pour envoyer des données à ce capteur, on commencera donc le message par l'octet 52 (car en hexadécimal, 29 x 2 = 52), et pour recevoir des données, on commencera un message par l'octet 53.
Ce capteur utilise le système classique de registres, comme indiqué à la page 13 de la datasheet:
On observe qu'il y a des registres de configuration (ENABLE, CONTROL, CONFIG), et des registres qui semblent destinés à contenir les mesures (RDATAL, ...). Il est très courant de devoir activer et configurer d'abord les capteurs avant de pouvoir lire les mesures. Pour en savoir plus, on ne coupe pas à une lecture plus détaillée de la datasheet, pour comprendre l'utilisation des registres.
Le premier registre décrit est appelé COMMAND, et est un peu inusuel. Il correspond à l'octet contenant l'index du registre concerné (le 2e octet des messages envoyés par le maître). A la place d'être simplement un index de registre, ce capteur combine dans cet octet des informations supplémentaires.
On constate en particulier que le bit du haut doit toujours être à 1. Donc par exemple pour accéder au registre 00, il faut écrire l'octet 80 à la place de 00.
Le premier véritable registre est l'ENABLE (index 00). C'est celui qui nous permettre d'activer le capteur.
Il est important de lire les petits caractères: pour activer le capteur, il faut d'abord activer le bit Power ON (PON), attendre au moins 2.4ms, puis activer le bit RGBC Enable (AEN). Les commandes correspondantes sont donc:
52 80 01 52 80 03
avec une petite attente entre les deux.
Une fois le capteur activé, il faut encore le configurer. Le principal paramètre décrit dans la datasheet est le temps d'intégration (RGBC, index 01), qui détermine simultanément la fréquence de mesure et la sensibilité. Pour une lecture à 10Hz, la datasheet indique qu'il faut utiliser la valeur D5. On enverra donc la commande
52 81 D5
Il ne reste alors plus qu'à lire périodiquement les registres contenant le statut du capteur, et les valeurs mesurées. Ce sont tous les registres à partir de l'index 13. Pour faire une lecture de plusieurs registres consécutifs, la description de l'octet COMMAND ci-dessus nous indique qu'il faut ajouter un bit de plus (20). La valeur de l'octet de registre sera donc B3. La structure du message sera donc
53 B3 xx xx xx ...
A votre tour: expérimentations et résolution des problèmes
Le Yocto-I2C vous permet de manière très simple de tester l'envoi de messages. Vous pouvez commencer par envoyer un message trivial comprenant juste la condition start, l'octet d'adresse du capteur, et la condition stop, et voir si le capteur la quittance bien par un {A}. Puis vous pouvez envoyer des messages plus complexes, et pour chacun voir directement la réaction constatée sur le bus sous forme des états détaillés.
Certains capteurs ont parfois tendance à planter si on leur envoie des commandes invalides. N'hésitez pas à les déconnecter puis à les reconnecter après un essai infructueux!
Pour en savoir plus
Par soucis de concision, cet article n'a pas abordé certains détails et cas particuliers prévus dans la spécification I2C. Si vous voulez en savoir plus, vous trouverez une myriade de détails sur le site https://www.i2c-bus.org.