Logiciels > Programmation PIC > Bases > MP > Lecture / écriture sur bus I2C

Dernière mise à jour : 24/07/2011

Présentation

Cette page présente les principes de base pour écrire ou lire des données sur un bus I2C. Le nombre de périphériques I2C existants est très grand (convertisseurs A/N, tuners, amplis BF, horloges, mémoires, etc) et tous ne seront évidement pas abordés ici. Le but de cet article est juste de faire un rapide tours du propriétaire de ce bus au nom mystérieux. Des exemples pratiques seront donnés par la suite :
Exemple avec horloge temps réel RTC PCF8583
Exemple avec expandeur de port PCF8574
Exemple avec convertisseur A/N et N/A PCF8591
Exemple avec mémoire EEProm 24C02, 24C04 et 24C16

Présentation du bus I2C

I2C = Inter Integrated Circuit - bus appelée également "interface deux fils".
La liaison I2C est une liaison série de type bidirectionnelle "Half-duplex" sur laquelle on peut raccorder en même temps plusieurs élements qui répondent au standard I2C, selon le principe d'un réseau parallèle. Sur ce réseau, un des élements est désigné comme maître et tous les autres sont désignés comme esclaves. L'écriture ou la lecture de données sur un bus I2C s'effectue de façon synchrone avec deux fils seulements, quelque soit la quantité d'informations (nombre de bits ou d'octets) à transmettre. Ces deux fils (lignes) s'appellent SDA et SCL :

Remarque : attention quand on parle de liaison deux fils : il existe forcement un troisième fil qui est la masse et qui sert de référence de potentiel. Ce troisième fil de référence est souvent sous-entendu et n'apparait pas forcement sur les schémas électroniques, où les divers points d'alimentation (Masse et VDD) des circuits intégrés sont souvent commun.

Le synoptique qui suit montre un exemple d'un réseau I2C dans lequel un élement maître peut s'adresser à trois élements esclaves différents (le nombre d'esclaves peut être différent de 3 : 1 ou 5 ou 8 par exemple).

pic_tuto_base_i2c_001a

La transposition de ce synoptique fonctionnel dans le monde réel pourrait déboucher sur le schéma électronique suivant (c'est un exemple entre autres) :

pic_tuto_base_i2c_001a_ex_001a

Echange des données
On peut envoyer des données tout comme on peut en recevoir, mais sur un seul et même fil de liaison de données SDA. Il ne s'agit donc pas d'une liaison de type "Full duplex" dans laquelle on aurait un premier fil pour l'envoi de données et un second fil pour la réception des données. La différentiation émission / réception se fait au niveau logiciel et au niveau matériel, puisqu'il faut bien qu'une ligne d'entrée / sortie soit configurée dans le bon sens selon la direction à faire prendre aux données (cela est vrai pour le maître et les esclaves). Dans la pratique, et grâce aux routines de lecture / écriture sur bus I2C, nous verrons que tout ce qui touche à la configuration matérielle est réduite à sa plus simple expression, et qu'il n'y a rien besoin de faire de particulier (tout est fait dans les coulisses). La gestion de la communication se fait par un seul des élements du bus I2C, qui est celui qu a été désigné comme maître. Les autres éléments branchés sur le bus - les esclaves - se contentent d'obéir au maître. C'est le maitre qui génère et fournit les signaux d'horloge transmis sur la ligne SCL, et c'est également lui qui gère l'envoi des données sur la ligne SDA. A noter que la ligne SCL n'est pas le siège permanent de changement d'états logiques, les signaux d'horloges ne sont transmis que quand il doit y avoir des échanges avec au moins un périphérique. On peut dire que la ligne SCL indique à l'esclave comment "interprêter" les données transmises sur la ligne SDA. L'esclave est relativement "neutre" dans la gestion d'envoi des données, mais il fait tout de même savoir au maître s'il reçoit bien les données qu'il est censé recevoir. Pour cela, l'esclave produit un signal dit d'acquitement ACK (pour Aknowledge). L'absence de signal d'acquitement (si toutes les données n'ont pas été reçues) se traduit par un "message" de type NACK (No Acknowledge). Le signal ACK est produit par la mise à l'état logique bas (0) de la ligne SDA par l'esclave, alors que le signal NACK est reconnu par l'état logique haut (1) de la ligne SDA (état logique maintenu grâce à la présence de la résistance de rappel au +Valim - pullup, résistance R2 sur le schéma précédent).

Types de messages
Les messages que l'on envoie à un périphérique I2C peuvent grosso-modo être classés dans deux catégories :
Comme les lignes SDA et SCL font référence à deux lignes d'entrée / sortie du PIC et que généralement ces dernières doivent être configurées en entrée ou en sortie, on pourrait penser qu'il est nécessaire de spécifier dès le début si les lignes SDA et SCL doivent être configurées en entrée ou en sortie. Dans les faits, la configuration (orientation) de ces deux lignes est assurée dès le départ grâce à la routine d'initialisation I2C1_Init, et les changements de direction sont automatiquement gérés dans les routines de lecture ou d'écriture. Il est donc inutile d'en rajouter à ce propos dans les lignes de votre programme (cela sera vu au moment des exemples pratiques).

Adressage
Un bus I2C est conçu pour pouvoir y connecter plusieurs périphériques de façon simultanée, c'est ce que l'on a vu avec le schéma précédent. Pour que les dialogues puissent rester digeste sur cet unique bus, il faut que les périphériques ne causent pas tous en même temps. Imaginez une réunion de 10 personnes où tout le monde prend la parole en même temps, ça fait du bruit et on ne comprend rien. Chaque périphérique doit donc être adressable de façon individuelle, et quand on en questionne un il est hors de question qu'un autre réponde à sa place. Pour cela, chaque périphérique dispose d'une adresse unique, et ne répond que si les question qu'il reçoit intègre un numéro d'adresse qui correspond à son adresse propre. S'il s'agit d'une autre adresse (celle de son voisin par exemple), il se contente d'écouter mais ne prend pas la parole car il est bien élevé et qu'il a compris que ce n'était pas à son tour de se manifester. Ce point est facile à comprendre, mais il faut bien s'en souvenir car une erreur commune consiste à envoyer des messages sur le bus avec une adresse erronnée, et on se demande pourquoi le circuit auquel on s'adresse ne répond pas. Dans le schéma d'exemple montré ci-avant, les trois périphériques PCF8574, PCF8583 et 24C02 possèdent chacun une adresse différente.

Nombre d'octets transmis
Un message complet peut comporter plusieurs octets dont le nombre exact dépend du composant périphérique I2C utilisé. Le premier octet correspond toujours à l'adresse de l'esclave avec lequel on souhaite communiquer. A noter que l'adresse n'est pas codée sur 8 bits mais sur 7 bits seulement. Le huitième bit permet de définir si on souhaite se positionner en mode lecture (READ, valeur du bit à 1) ou en mode écriture (WRITE, valeur du bit à 0). Le second octet à transmettre dépend du type de périphérique I2C auquel on s'adresse. Il peut s'agir d'un octet de contrôle, tout comme il peut s'agir d'un octet de donnée. Le graphe qui suit montre un exemple de données transitant sur la ligne SDA, au rythme des impulsions d'horloge présentes sur la ligne SCL (les échanges de données ont été "provoqués" par un PIC maître communiquant avec un expandeur de port PCF8574).

pic_tuto_base_i2c_pcf8574_001a_graphe_001a

Chronologie d'une communication
Comme la liaison est de type sérielle, les données qui se présentent sous forme d'octet sont transmises bit après bit. Pour un octet, il y a huit informations binaires à transmettre à la queue-leu-leu.
Ce procédé se déroule de façon répétée pour chaque bit de l'octet à transmettre, soit huit fois.
Une fois les huit bits de l'octet transmis, la ligne SDA, qui était jusqu'alors configurée en sortie logique, est configurée en entrée logique par le maître. Ce dernier surveille dès cet instant l'état de la ligne SDA. Si la transmission des données s'est déroulée sans erreur, l'esclave positionne la ligne SDA à l'état logique bas (ACK) et la suite des opérations n'est pas compromise. Si l'esclave ne fait rien et que la ligne SDA reste à l'état logique haut, c'est qu'une erreur s'est produite. Dans un tel cas de figure, on considère qu'il y a fin de transmission, il est inutile de continuer à envoyer le reste des données. La fin de transmission est initiée par le maître par un bit de STOP, qui correspond à un passage à l'état haut de la ligne SDA (repassée en mode sortie côté maître) alors que la ligne SCL est toujours à l'état logique haut.

Exemple pratique de communication I²C

Le graphe que l'on a vu précédement et qui est rappelé ci-après montre un exemple de données transitant sur la ligne SDA, au rythme des impulsions d'horloge présentes sur la ligne SCL et "provoqués" par un PIC maître communiquant avec un expandeur de port PCF8574.

pic_tuto_base_i2c_pcf8574_001a_graphe_001a

Le code logiciel MikroPascal qui a été mis en oeuvre pour établir cet échange de données est visible ci-après.

procedure Init_I2C_materiel;
begin
I2C1_Init(100000);
end.

procedure PCF8574_Read;
begin
I2C1_Start; // signal START
I2C1_Wr($41); // addresse lecture PCF8574
Values := I2C1_Rd(0); // lecture port PCF8574
I2C1_Stop; // signal STOP
end;

procedure PCF8574_Write;
begin
I2C1_Start; // signal START
I2C1_Wr($70); // addresse ecriture PCF8574A
I2C1_Wr(Values); // ecriture valeurs
I2C1_Stop; // signal STOP
end;


Remarque : dans ce cas précis, le fait d'aller lire ou écrire des données dans le PCF8574 conduit à la circulation d'une même quantité de données. Sur les deux procédures de lecture et d'écriture ci-avant, une seule (écriture ou lecture) a conduit aux données visibles sur le graphe.

Initilisation
La routine d'initialisation (Init_I2C_materiel) a été appelée une seule fois au tout début du programme, il n'est pas nécessaire de la répeter ensuite. Elle est indispensable, la liaison I2C ne fonctionnera pas si elle n'est pas correctement préparée.

Lecture et écriture
Pour la lecture (procédure PCF8574_Read), deux octets sont transmis. Le premier octet est envoyé par le maître (le PIC) et correspondant à l'adresse de base d'un PCF8574. Le second octet est envoyé par l'esclave, qui répond au maître.
Pour l'écriture (procédure PCF8574_Write), deux octets sont également véhiculés, mais cette fois dans un sens unique : c'est le maître (le PIC) qui envoie les deux octets l'un après l'autre en direction de l'esclave. Le premier octet correspondant à l'adresse d'écriture d'un PCF8574A. Le second octet correspond à la valeur à écrire dans le PCF8574A.
Notez comme l'écriture de ces procédures est simple par rapport à ce qu'il faudrait faire si on voulait exploiter le bus I2C à partir de rien, c'est à dire en modifiant "à la mano" les états logiques des deux lignes SCL et SDA.
Là, pour l'écriture dans le PCF8574A, on suit une procédure de dialogue tout à fait standard :
Et pour la lecture depuis le PCF8574, même principe :

I2C matériel ou I2C logiciel ?

Les routines liées au mode de communication du bus I2C peuvent s'appuyer sur des lignes physiques dédiées du PIC, c'est à dire sur des lignes pour lesquelles une partie du matériel inclus dans le PIC est conçu pour fonctionner de façon optimale avec le protocole I2C. Ces lignes, comme elles sont dédiées, se trouvent à un endroit précis, sur un port précis. Par exemple sur le PIC 18F2420, les deux lignes SCL et SDA se trouvent respectivement sur les lignes RC3/SCL et RC4/SDA. On ne trouve pas de telles lignes dédiées sur les PIC 16F84 ou 16F628, mais cela n'empêche pas de mettre en oeuvre un bus I2C avec ces derniers. Seulement dans ce cas, il faut utiliser des routines I2C qui intègrent les "émulations logicielles" qui remplacent la partie matériel manquante. Comme les fonctions émulées fonctionnent moins rapidement, certaines précautions doivent être prises, mais pour les besoins courants cela suffit généralement. D'un point de vue écriture logicielle, cela fonctionne de la même façon, il suffit juste de choisir les routines adéquates.

Exemple de code pour un PIC 18F2420 possédant un module MSSP / I2C
Le code ci-dessous permet d'initialiser le mode de communication I2C, en utilisant le module MSSP dans le "mode" I2C. Cette ligne est indispensable avant de commencer toute action sur le bus I2C.

procedure Init_I2C_materiel;
begin
I2C1_Init(100000);
end.


A partir de cet instant, la communication peut s'établir sur le bus I2C, avec les données sur la ligne SDA et les signaux d'horloge sur la ligne SCL.

Exemple de code pour un PIC 16F628A ne possédant pas de module MSSP / I2C
Le code ci-dessous permet d'initialiser le mode de communication I2C, qui peut être assuré même en l'absence d'un module de communication série MSSP implémenté de façon matérielle dans le PIC.

// définition des broches du PIC à utiliser
var 

Soft_I2C_Scl: sbit at RB0_bit;
Soft_I2C_Sda: sbit at RB4_bit;
Soft_I2C_Scl_Direction: sbit at TRISB0_bit;
Soft_I2C_Sda_Direction: sbit at TRISB1_bit;

procedure Init_I2C_logiciel;
begin
Soft_I2C_Init(100000);
end.


A partir de cet instant, la communication peut s'établir sur le bus I2C, avec les données sur la broche d'entrée / sortie du PIC qui a été choisie comme ligne SDA et les signaux d'horloge sur la broche d'entrée / sortie du PIC qui a été choisie comme ligne SCL. L'utilisation des routines "soft" est un peu plus contraignante que l'utilisation des routines "hard". Il faut en effet :