[Retour à la page principale]



Haut de la page Résumé sur le protocole I2C :


Signalons, en guise de préambule, que nous n'examinerons pas ici toutes les subtilités du standard I2C, mais que nous nous bornerons à évoquer les échanges d'informations avec les mémoires EEPROMs 24C01 à 24C16. Précisons tout de même que si vous avez bien assimilé les infos présentées dans cette page, l'utilisation d'autres périphériques I2C courants (avec un minimum de documentation) ne devrait pas poser de problème insoluble. Haut de la page


Comme le montre la figure 1, outre la masse (qui sert de référence de tension) le bus I2C n'est constitué que de deux lignes de transmission :
- La ligne SDA (= Serial DAta), qui véhicule toutes les informations (données, adresses, commandes).
- La ligne SCL (= Serial CLock), qui sert à transmettre les impulsions d'horloge qui vont rythmer les échanges.
Ces deux lignes sont donc communes à tous les périphériques qui partagent un même bus I2C. Haut de la page


Etudions de plus prêt l'une de ces lignes (SDA ou SCL, peu importe) ; comme l'indique la figure 2, chaque entrée/sortie a une structure 'drain ouvert' (c'est l'équivalent MOS d'un 'collecteur ouvert'), celle-ci permet de relier ensemble plusieurs sorties sans risquer de situation de conflit ; il faut alors une résistance de tirage Rt pour fixer le potentiel lorsque tous les transistors sont bloqués ; on peut noter que l'ensemble réalise une fonction logique 'ET' câblée (il faut que toutes les sorties reliées à la ligne soient à '1' pour que la ligne soit à '1') ; on en déduit donc que l'état de repos de la ligne est l'état logique '1', chaque périphérique pouvant 'prendre la ligne' en la mettant à '0'.


Lors d'un dialogue entre deux périphériques I2C, les rôles sont bien répartis :
- Le premier est le maître ; il initie le dialogue, envoie les commandes, et génère le signal d'horloge sur SCL.
- Le second est l'esclave : en fonction des ordres et du signal d'horloge, il envoie ou reçoit des données (octets).
Haut de la page
Lorsque le bus est inactif, les lignes sont au niveau logique '1'. Lorsque le bus est actif, l'état de repos de la ligne SCL est l'état '0' ; chaque bit lu ou envoyé (sur SDA) est validé par une impulsion positive sur SCL (soit un front montant, suivi d'un front descendant, générés par le 'maître'), comme l'illustre la figure 3 :


En principe, l'état de la ligne SDA ne doit pas changer pendant que SCL est à '1'. Il y a néanmoins deux exceptions, indiquées ci-dessus figure 4 ; les instructions 'START' et 'STOP' qui servent respectivement à initier et à clore le dialogue, se différencient justement par le fait qu'elles se produisent quand SCL est à '1' :
- par un front descendant sur SDA, suivi d'un front descendant sur SCL, pour 'START'.
Haut de la page - par un front montant sur SCL, puis sur SDA, pour 'STOP'.
Les signaux 'START' et 'STOP' sont bien sûr délivrés par le maître. En principe, rien n'interdit qu'un périphérique soit tantôt maître, tantôt esclave, par exemple dans le cas où deux micro-contrôleurs partagent un même bus I2C. Dans l'application présentée plus loin, celle d'un micro-contrôleur pilotant uniquement une EEPROM I2C, le micro sera toujours le maître, et la mémoire l'esclave. Dure loi que celle de la servitude ! mais ne confondons pas mémoire et conscience...


Attention, pour que le 'maître' puisse recevoir des bits, il faut que sa sortie ait été remise à '1' au préalable, afin que la mémoire puisse mettre la ligne SDA à '0' ou à '1' (structure à 'drain ouvert' exposée précédemment).


Haut de la page Ecriture d'un octet :

Le protocole d'écriture d'un octet est exposé ci-dessous figure 5 ; analysons ensemble en détail les différentes étapes de l'opération :

Haut de la page
  1. Le bus est au repos ; les lignes SDA et SCL sont à '1'.
  2. On (le maître) envoie un START (en vert sur le schéma) : SDA passe à '0', puis SCL passe à '0'.
  3. Ensuite, on envoie un premier octet [B7,B6,B5,B4,B3,B2,B1,B0], bit à bit :
    - On donne à SDA la valeur de B7, puis on valide par une impulsion positive sur SCL.
    - On procède ainsi avec B6, puis B5, B4... jusqu'à B0.
    Que représente ce premier octet :
  4. Après chaque octet transmis, le périphérique récepteur (ici, la mémoire) indique qu'il a bien reçu l'octet en mettant SDA à '0', le temps d'une impulsion d'horloge envoyée par le maître ; on nomme cette validation 'ACKNOWLEDGE' (en abrégé : ACK), que l'on pourrait traduire en bon français par 'Accusé de réception'. Attention, si c'est le maître qui reçoit des données, c'est à lui d'envoyer ce bit à '0' (il repasse à '1' après). Il ne faut pas oublier ce neuvième bit, qui suit l'envoi de chaque octet.
  5. Transmission du deuxième octet, qui représente les huit bits de poids faible de l'adresse : émettre d'abord AD7, puis AD6, etc. jusqu'à AD0 (toujours sur SDA, avec à chaque fois une impulsion positive sur SCL).
  6. Nouvel ACK : impulsion positive sur SCL ; on peut en profiter pour vérifier que SDA est bien mis à '0' par la mémoire (pendant le niveau '1' de SCL).
  7. Transmission de l'octet de donnée (D7 à D0). Là aussi, commencer par le bit de poids fort et à chaque bit, impulsion positive sur SCL). Haut de la page
  8. Dernier ACK : impulsion positive sur SCL, etc.
  9. Pour conclure, un STOP : SCL passe à '1', puis SDA passe à '1'.


Le principe décrit est valable pour une programmation octet par octet ; pour gagner du temps, les mémoires autorisent une écriture en mode 'Page', c'est-à-dire de N octets à la fois ; le nombre N d'octets est donné dans la documentation de la mémoire et doit être respecté, mais surtout pas dépassé ; il vaut par exemple 16 pour les 24C04, 24C08 et 24C16. Chaque octet est suivi d'un ACK, jusqu'au STOP final, comme le montre la figure 5 bis avec N = 4 (la réception d'un octet incrémente automatiquement le compteur d'adresse de la mémoire).


Attention, la durée nécessaire à la programmation de la mémoire est contrôlée automatiquement par celle-ci, mais cela veut dire que la mémoire sera indisponible pendant quelques instants (de l'ordre de quelques millisecondes). Il est sage de faire suivre la routine de programmation par une boucle d'attente (Tempo) ou mieux, de surveiller la mémoire jusqu'à ce qu'elle soit à nouveau disponible. Pour cela, envoyez un START, puis le premier octet d'adressage, puis tester l'ACK, puis un STOP. Si la mémoire a répondu à l'ACK (en mettant SDA à '0'), c'est qu'elle est enfin disponible, sinon, répétez la séquence tant que ce sera nécessaire... Haut de la page


Lecture d'un octet :

Haut de la page

Comme le montre la figure 6 ci-dessus, la lecture d'un (ou plusieurs) octet(s) s'effectue en deux étapes :

  1. La première étape consiste à pointer l'adresse à lire dans le périphérique ; tout se passe comme lors d'une opération d'écriture :
    1. On envoie un START (SDA passe à 0, puis idem pour SCL).
    2. On envoie le premier octet d'adressage (les 4 bits [1,0,1,0], les trois bits de poids fort [AD10,AD9,AD8], et le bit lecture/écriture à '0' (= écriture).
    3. On envoie l'impulsion sur SCL correspondant à l'ACK (la mémoire répond par un '0' sur SDA).
    4. On envoie le deuxième octet d'adressage (les 8 bits de poids faible de l'adresse : AD7 à AD0).
    5. On envoie l'impulsion pour le deuxième ACK.
    6. Pas de STOP nécessaire !
    Là s'arrête cette première étape ; signalons qu'elle n'est pas rigoureusement indispensable si vous venez de réaliser une opération de lecture ou d'écriture et si vous êtes certain que l'adresse actuelle de la mémoire est la bonne...
    Haut de la page
  2. La seconde étape est à proprement parler l'opération de lecture :
    1. On enchaîne directement sur un START.
    2. On émet l'octet d'adressage du périphérique, avec les quatre bits [1,0,1,0], il ne semble pas nécessaire de repréciser les trois bits suivants (on doit pouvoir envoyer trois '0', je le vérifierai), puis vient le bit de lecture/écriture qui est cette fois à '1' (= lecture).
    3. Impulsion d'horloge correspondant à l'ACK (testons si la mémoire est disponible et a bien reçu l'ordre : auquel cas elle a mis SDA à '0').
    4. Nous allons alors lire les huit bits de données en envoyant huit impulsions positives sur SCL ; la lecture de chaque bit s'effectue sur SDA après chaque front montant de SCL ; le premier bit reçu est D7, le dernier est donc D0.
    5. A ce stade, deux cas de figures se présentent :
      • si l'on souhaite continuer à lire des octets, il convient de générer un ACK ; le maître étant l'entité qui a reçu l'octet, c'est à lui de mettre SDA à '0', et ce bien sûr seulement pendant l'impulsion positive qu'il va générer sur SCL. Après, on pourra lire l'octet suivant  ; pour cela retournons à l'étape précédente (2-d.) ; ce processus est présenté ci-dessus, figure 6 bis.
      • si l'on souhaite arrêter là la lecture, il faut générer un 'No ACK', c'est-à-dire une impulsion positive sur SCL, mais en laissant SDA à '1' pour faire comprendre à la mémoire que l'échange va s'interrompre. Terminons alors par l'étape suivante.
    6. Finissons comme d'habitude par un STOP : SCL passe à '1', puis SDA passe à '1'. Le bus I2C est au repos...
    Haut de la page

Voilà, l'opération est terminée ; contrairement à ce qui se passait lors de l'écriture de plusieurs octets, on peut lire autant d'octets qu'on le souhaite ; si l'on arrive à la fin de la mémoire, le compteur repasse à zéro et la lecture reprend simplement à partir du début.


Il y a bien évidemment des durées minimales à respecter (temps d'accès) ; sans entrer dans trop de détails, disons que les différents créneaux doivent avoir une durée d'au moins 5 µs. Consultez la documentation des constructeurs pour de plus amples infos.

Les indications données ci-dessus concernent l'accès (écriture/lecture) aux mémoires I2C allant de la 24C01 à la 24C16 ; les mémoires 24C32 et au-delà nécessitent plus que les 11 bits d'adresse disponibles ; le protocole est presque le même, il utilise non pas deux, mais trois octets d'adressage : Haut de la page

  1. START.
  2. Octet 1 : composé du code [1,0,1,0], suivit de l'état des broches A2, A1, puis A0, et enfin du bit R/W.
  3. Lecture de l'ACK.
  4. Octet 2 : les 8 bits de poids fort de l'adresse (AD15 à AD8).
  5. Lecture de l'ACK.
  6. Octet 3 : les 8 bits de poids faible de l'adresse (AD7 à AD0).
  7. Lecture de l'ACK.
  8. etc...

L'adaptation de l'algorithme n'est pas très compliquée ; n'hésitez surtout pas à consulter la documentation sous forme de fichiers PDF disponibles (en anglais) sur les sites indiqués sur la page principale.


Haut de la page Communication entre un PIC16F84 et une 24C16 :


à gauche : schéma électrique                 à droite : implantation des composants

Haut de la page

Le schéma présenté ci-dessus est assez élémentaire : une pile 9V associée à un régulateur 5V fournissent la tension d'alimentation, une diode protège l'ensemble contre toute erreur de polarité ; les parties 'oscillateur' (quartz 4 MHz + condensateurs céramique 22 pF) et 'Reset' (résistance, condensateur, bouton poussoir) n'appellent pas de commentaire particulier ; le bus I2C est câblé sur deux entrées/sorties du Port A ('drains ouverts'), avec ses deux résistances de tirage ; une led de contrôle est également montée sur une sortie du port A (et reliée au +5V, en raison de la structure 'drain ouvert') ; les entrées A0, A1 et A2 de l'EEPROM sont au niveau logique '0' (les adresses physiques partent donc de $000), ainsi que l'entrée WP (pour autoriser l'écriture dans la mémoire).

Le schéma d'implantation est donné à titre indicatif, et l'ensemble pourra être réalisé sur une plaque pastillée pré-perforée ; on utilisera des supports 'tulipe' pour les circuits intégrés. Haut de la page

Voici le fichier EEPROM05.ASM qui contient les routines d'accès (lecture et écriture) à la 24C16. Mais téléchargez plutôt le fichier EEPROM05.ZIP qui regroupe le fichier source ASM, ainsi que les fichiers LST et HEX fournis par l'assembleur.

Les commandes principales sont :

Ces deux fonctions ne font qu'appliquer les chronogrammes décrits figures 5 et 6 ; chaque étape revient à exécuter l'une des huit routines de base également présentes dans le fichier :

Ces routines sont accompagnées de commentaires détaillés ; prenez le temps d'étudier leur fonctionnement, avec le schéma et les chronogrammes sous les yeux...


Le programme principal n'est pas d'un grand intérêt du point de vue technologique, il n'a pour but que d'illustrer l'utilisation des fonctions Read24C16 et Write24C16 ; libre à vous de le modifier pour lui faire exécuter des tâches plus utiles ; pour l'instant, il se contente d'initialiser tous les octets de la mémoire en mode incrémental ($00, $01, $02, $03, ... $FE, $FF et puis on recommence jusqu'à la fin de la mémoire) ; seul le premier octet est épargné : sa valeur est affichée sur le Port B (contrôlez les sorties au voltmètre) ; à la fin du programme, qui dure environ 6 secondes avec une 24C16, la Led s'allume pour indiquer que tout est terminé...

Pour vérifier cela, programmez une 24C16 (ou inférieure) après avoir rempli le buffer de manière aléatoire, et en ayant relevé la valeur du premier octet ; programmez ensuite le PIC16F84, par exemple avec le programmateur décrit par Patrice F5JTZ sur son site, puis insérez les deux circuits dans leurs supports, appliquez le courant et attendez quelques secondes l'allumage de la Led ; relevez l'état des bits du Port B et comparez-le avec l'octet n°1 ; relisez ensuite le contenu de l'EEPROM avec le module décrit sur la page principale, en vérifiant que ce contenu a été modifié comme convenu...
Attention, le courant doit être appliqué 'proprement' ; si vous ne passez pas par un interrupteur, appuyez sur le bouton de remise à zéro au moment où vous branchez la pile, puis relâchez-le pour lancer le programme. Haut de la page


Voici une petite variante :

Elle intéressera les utilisateurs d'EEPROMs de capacités supérieures à la 24C16 ; ce type de mémoires nécessite un octet supplémentaire d'adressage, ce qui impose une petite modification des routines ; le fichier EEPext01.ZIP, 16862 octets, contient un programme qui réalise la même fonction que précédemment, mais avec une 24C64 (8192 octets) ; petite variante : la led clignote (16 fois) pour faire patienter l'utilisateur. Selon vos applications personnelles, il peut être intéressant d'utiliser le mode d'écriture 'par pages' pour gagner en vitesse, car le programme est actuellement assez lent. Les routines principales s'appellent maintenant 'Read24Cxxx' et 'Write24Cxxx', vous pouvez les utiliser directement, ou vous en inspirer pour implémenter le mode 'par pages'.


Dernière mise à jour : le 22/12/2000.

[Retour à la page principale]