LOCODUINO

Bibliothèque Wire : I2C

.
Par : Dominique, Guillaume

DIFFICULTÉ :

Selon Wikipedia, I2C est un bus de données qui a émergé de la « guerre des standards » lancée par les acteurs du monde électronique. Conçu par Philips pour les applications de domotique et d’électronique domestique, il permet de relier facilement un microprocesseur et différents circuits, notamment ceux d’une télévision moderne : récepteur de la télécommande, réglages des amplificateurs basses fréquences, tuner, horloge, gestion de la prise péritel, etc.

Il existe d’innombrables périphériques exploitant ce bus et tous les Arduino l’intègrent et en bénéficient pleinement. Le poids de l’industrie de l’électronique grand public a permis des prix très bas grâce à de nombreux composants.

I2C permet également la communication entre plusieurs Arduino qui se partageraient chacun certaines tâches.

Ce bus porte parfois le nom de TWI (Two Wire Interface) chez certains constructeurs.

Article mis à jour le 15 septembre 2022 pour modifier un lien rompu.

Une bibliothèque déjà intégrée

Bibliothèque officielle faisant partie du référentiel Arduino, livrée avec l’IDE, elle facilite l’écriture du code pour communiquer en évitant la programmation bas-niveau et la gestion des tâches de fond sous interruption.

Nous allons donc voir les différentes commandes de cette bibliothèque. Nous n’allons pas ici mettre en pratique ces commandes pour, par exemple, dialoguer entre deux Arduino. Ce sera le sujet d’autres articles.

Installation

Cette bibliothèque fait donc partie intégrante de l’IDE Arduino, elle est installée de base. Il suffit juste de l’importer dans le programme que l’on écrit.

Mise en oeuvre matérielle

Trois ou plutôt quatre fils seulement sont nécessaires pour une communication :

  • SDA (Serial Data Line)
  • SCL (Serial Clock Line)
  • GND pour un commun entre les 2 appareils

Généralement le +5V (ou 3,3V) les accompagne pour 2 raisons :

  • pour alimenter le périphérique
  • pour ne pas oublier les 2 résistances de liaison au 5V connectées à un seul endroit du bus sans lesquelles le bus ne fonctionne pas (entre 2 et 10 kOhms). Mais certains Arduino comportent ces résistances en interne (le DUE en particulier pour les lignes SDA et SCL seulement, mais pas son second canal I2C avec SDA1 et SCL1, ce qui garantit la limitation à 3,3V).
Câblage I2C

Attention, les transferts I2C sont sensibles aux parasites et peuvent être perturbés par les moteurs des locomotives, les bobines des aiguillages et des dételeurs, et les mauvais contacts, il faut éviter les câbles trop longs et passant tout prés de ces sources de parasites.

Ces broches sont différentes selon les cartes. Voici quelques exemples :

Cartes Arduino Broches I2C / TWI
Uno, Nano A4 (SDA), A5 (SCL)
Mega2560 20 (SDA), 21 (SCL)
Leonardo 2 (SDA), 3 (SCL)
Due 20 (SDA), 21 (SCL), et SDA1, SCL1

Il faut aussi noter que cette communication fonctionne dans un mode particulier.
Les échanges sont démarrés par un périphérique « maître ». Il peut s’agir d’un micro-contrôleur ou même d’un PC type raspberry Pi. Dans notre cas, nous considérerons qu’il s’agit d’un Arduino. Celui-ci envoie un signal de départ, puis l’adresse du récepteur, puis le registre, enfin les données et termine avec une séquence de stop. Tous ces signaux se suivent en série sur la ligne de données. Le récepteur signale qu’il a bien reçu le message en envoyant un signal d’acquittement (ACK).

Sur un bus I2C, on peut trouver un ou plusieurs Arduino, d’autres types de microcontrôleur (l’un d’eux joue le rôle de maître), des afficheurs LCD, des cartes pilotes de moteurs, des convertisseurs analogique-numérique, des mémoires EEPROM, des horloges, des périphériques audio…
D’autres Arduino ou microcontrôleurs peuvent aussi être « périphériques » donc esclaves. La plupart des périphériques possède une bibliothèque qui gère les communications de manière transparente.

L’Arduino maître n’a pas d’adresse (il est unique) et chaque périphérique possède une adresse. Plusieurs périphériques peuvent donc être reliés sur le bus en même temps.

On trouve dans le commerce des quantités de périphériques tout faits dans lesquels l’adresse est pré-programmée. On prendra donc soin de connaître l’adresse I2C de chaque périphérique.

Si ce périphérique est accompagné d’une fiche de caractéristiques, on trouvera cette information dedans. Parfois ce n’est pas facile, en particulier quand on fait ses courses en chine. Dans ce cas on tape sur le net la recherche suivante « I2C address mon_peripherique » où mon_peripherique est l’objet du questionnement.

Par exemple, pour un afficheur LCD on trouvera :

  • This board/chip uses I2C 7-bit address 0x20 (chez Adafruit).
  • set the LCD address to 0x27 for a 20 chars 4 line display (YwRobot Arduino LCM1602 IIC V1).
  • I2C Address : 0x3F (chez SaintSmart).

On se reportera aussi à la table des adresses I2C d’Adafruit pour la plupart des périphériques.

Et puis quand rien ne marche, il ne reste plus qu’à essayer les adresses au hasard en modifiant le programme. On finit toujours par la trouver, il n’y en a que 128 au maximum !!!

Mais vu la tâche fastidieuse, nous allons vous donner un programme qui permet de la retrouver automatiquement. Ce programme est à la fin de cet article.

Dans la suite du texte :

  • le mot maître se réfère à l’Arduino maître
  • le mot esclave se réfère au périphérique esclave (qui peut être un Arduino)

Importer la bibliothèque

#include <Wire.h>
Cela se fait en début de programme.

Les différents codes et fonctions

Tout comme la bibliothèque Serial ou Servo, il est nécessaire avant chaque fonction de mettre en argument Wire. comme nous le verrons par la suite.
Certaines fonctions sont exclusives au périphérique ou à l’esclave, soit les deux. Cela sera spécifié dans le titre.

begin() (maître/esclave)

Cette fonction accepte comme argument facultatif l’adresse. Si l’Arduino rejoint le bus de communication sans adresse, il le rejoint comme maître. A noter qu’un seul maître est nécessaire et suffisant (il est unique), sinon les communications vont être parasitées (si 2 maîtres envoient chacun 1 ordre...). Mettre une adresse comme paramètre indique que le périphérique rejoint le bus comme un périphérique esclave.

Wire.begin()     // en mode maitre
Wire.begin(adresse)  // en mode esclave

Cette déclaration est à faire dans le setup().

requestFrom() (maître)
Fonction utilisée par le périphérique maître, elle sert à demander une information à un esclave.
L’argument de cette fonction est l’adresse de l’esclave à interroger.

Wire.requestFrom(address, quantity, stop)

Les deux premiers paramètres sont indispensables :

Paramètre address
Comme son nom l’indique, elle est l’adresse de l’esclave codée sur 7 bits.

Paramètre quantity
Le nombre d’octets (bytes) que le maître demande de l’esclave dans sa réponse.

Paramètre stop
Valeur booléenne, elle est par défaut à True.
True : après la requête du maître, requestFrom() envoie un message stop sur le bus, le libérant.
False : à contrario, le bus n’est ici pas libéré.

Cette fonction renvoie aussi le nombre d’octets retournés par l’esclave

beginTransmission() (maître)

Cette fonction commence la transmission vers un esclave sur le bus de communication. L’adresse de ce périphérique doit être passée en argument.

Wire.beginTransmission(adresse)

Cette fonction sera suivie dans le code de la fonction write() ainsi que de endTransmission() pour réaliser entièrement la séquence de communication.

endTransmission() (maître)
Comme son nom l’indique, elle ferme la communication.

Wire.endTransmission(stop)

Elle accepte comme paramètre une valeur booléenne. C’est le même paramètre que dans la fonction requestFrom(). Par défaut, elle est à True et la fonction envoie, après son exécution, un message stop pour relâcher le bus de communication ; comportement contraire pour la valeur False.

Cette fonction retourne une valeur de type byte qui peut prendre 5 arguments :

  • 0 : succès quand la transmission est acquittée (ACK) par le récepteur

Sinon, quand une erreur est détectée la réponse est :

  • 1 : données trop longues pour être contenues dans la mémoire tampon de l’envoi
  • 2 : signifie NACK (no acknowledge / non acquittement) pour erreur sur l’adresse de transmission
  • 3 : signifie NACK pour erreur sur la transmission des données
  • 4 : autre erreur

write() (maître/esclave)
Cette fonction écrit le paramètre qu’on lui donne (le premier) sur le bus de communication. Elle est commune aux deux types de périphériques :

  • le maître écrit sur le bus, fonction utilisée entre beginTransmission() et endTransmission()
  • l’esclave écrit sur le bus mais après requête du maître, il ne peut pas écrire de son propre chef.

Le premier paramètre contient les données à envoyer. Cela peut être une simple valeur de type byte, une chaîne de caractères ou des données de type tableau ou record. Dans ce dernier cas, il est nécessaire d’ajouter un second paramètre indiquant le nombre de bytes à transmettre.

Cette fonction retourne le nombre de bytes envoyés (la lecture de ce retour est optionnelle).

Exemple de code d’un Arduino maître :

#include <Wire.h>

byte val = 0;

void setup()
{
  Wire.begin(); 
   // se déclare maitre sur le bus i2c
}

void loop()
{
  Wire.beginTransmission(44); 
   // début de la transmission au périphérique d'adresse #44 (0x2c)
  Wire.write(val);             
   // envoi d'un octet val  
  Wire.endTransmission();     
   // envoi de la somme de contrôle et fin de transmission

  val++;        // incrémente la valeur
  if(val == 64) 
   // position maximum
  {
    val = 0;    
    // on recommence en partant de 0
  }
  delay(500);
}

available() (maître/esclave)

Cette fonction retourne le nombre d’octets disponibles à la lecture. Cette fonction est à appeler après requestFrom() sur un maître ou dans dans la fonction onReceive() sur l’esclave. Elle sert à savoir s’il y a quelque chose à lire ou pas et surtout combien d’octets seront à recevoir/stocker et analyser par le programme.

read() (maître/esclave)
Cette fonction lit le premier octet disponible sur le bus de communication. Elle renvoie donc cette valeur.

Exemple de code :

#include <Wire.h>

void setup()
{
  Wire.begin();        
   // initialisation de la liaison I2C
  Serial.begin(9600);  
   // initialisation de la liaison série vers la console
}

void loop()
{
  Wire.requestFrom(2, 6);   
   // demande à recevoir 6 octets du périphérique d'adresse 2

  while(Wire.available())    
   // attente des octets
  {
    char c = Wire.read();    
    // réception des octets, un par un
    Serial.print(c);         
    // envoi à la console
  }

  delay(500);
}

Les handlers

La bibliothèque I2C se charge des transmissions de façon complètement automatique. Pour ce faire elle installe un mécanisme qui s’appuie sur une interruption. Elle gère donc de façon interne l’émission et la réception des bits et des octets. Comme toute bibliothèque de ce type, il est possible d’accrocher un traitement de notre programme qui sera invoqué automatiquement par la bibliothèque I2C quand une condition ou un événement est réalisé. C’est ce qu’on appelle un « handler ».

La bibliothèque I2C en propose 2 :

onReceive() (esclave)

Wire.onReceive(maFonction)

installe un handler qui appelle un traitement (une fonction) lorsque l’esclave reçoit une commande du maître.
Cette fonction doit avoir comme seul paramètre une variable de type int (nombre d’octets lus en provenance du maître).
Par exemple :
void maFonction(int numBytes)

Elle ne doit pas renvoyer de valeur.

onRequest() (esclave)
Wire..onRequest(Fonction)
installe un handler qui appelle une fonction quand le maître requiert des données de l’esclave.

Cette fonction appelée ne doit accepter aucun paramètre et ne pas retourner de valeur :
void Fonction()

Ces 2 handlers sont mis en place dans la fonction setup() ;

Par exemple, pour un Arduino esclave d’adresse 4, dans le setup() :

// Initialisation de l' I2C
  Wire.begin(4);                
   // rejoindre le bus avec l'adresse #4
  Wire.onReceive(receiveEvent); 
   // enregistrement du handler "receive"
  Wire.onRequest(requestEvent); 
   // enregistrement du handler "request"

Puis dans les fonctions ;

//------------------------------------------------------
// réception d'un événement I2C
// mode Esclave
// cette fonction "handler" a été enregistrée dans le setup()
//------------------------------------------------------

void receiveEvent(int howMany)
{
  byte c;
  int id;
  
  while(Wire.available()) {
  
  c = Wire.read();          
   // reçoit un octet
  I2Cdata[Windex++] = c;    
   // stockage dans un tampon circulaire
  if (Windex == 64) Windex = 0;
  }
  EventCount++;
}
  
//------------------------------------------------------
// réponse à une requête I2C
// mode Esclave
// cette fonction "handler" a été enregistrée dans le setup()
//------------------------------------------------------

void requestEvent()
{
  Wire.write(binaig); 
   // répond par un message d'un octet (état des aiguilles)
}

Rechercher l’adresse I2C d’un périphérique

Afin de connaître l’adresse d’un périphérique I2C non documenté, il suffit de brancher ce périphérique sur le bus I2C.

J’ai utilisé dans cet exemple un Arduino Uno et, comme pour le Nano, il faut préciser que les pins SDA et SCL sont A4 et A5 et NON PAS 4 et 5 !

et de lancer le programme I2C_Scanner que l’on peut télécharger ici :

Programme I2C_Scanner.ino

Au préalable n’oubliez pas les 2 résistances de 4,7 KOhms entre SDA et +5V et entre SCL et +5V, sinon ça ne marche pas et on se demande pourquoi.

Le programme teste toutes les adresses possibles et affiche l’adresse trouvée sur le terminal de l’IDE.

Scanning.......................................
I2C device found at address 0x27  !
done

Voici comment il fonctionne :

#include <Wire.h>

On commence par déclarer la bibliothèque Wire.

void setup()
{
  Wire.begin(); 
  Serial.begin(9600);
  Serial.println("\nI2C Scanner");
}

Le terminal de l’IDE doit être configuré à 9600 bits/seconde. Vous pouvez indiquer une autre valeur de vitesse si vous le voulez, à condition que cette vitesse corresponde à celle qui est configurée pour le terminal de l’IDE.

void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.print("Scanning");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    // La valeur de retour de Wire.endTransmission() 
    // est false (0) si le peripherique existe a cette adresse
    Serial.print(".");
 
    if (error == 0)
    {
      Serial.println("");        // retour à la ligne
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !"); 
      nDevices++;
      break;     // commenter cette ligne si vous recherchez plusieurs péripheriques I2C
                     // sur le bus, sinon le test s'arrête dès le premier trouvé
    }
    else if (error==4)
    {
      Serial.println(""); 
      Serial.print("Unknow error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }  
    delay(50);
  }
  if (nDevices == 0)
  {
    Serial.println(""); 
    Serial.println("No I2C devices found\n");
  }
  else
    Serial.println("done\n");
 
  delay(3000);           // on attend 3 secondes et on recommence
}

Toutes les explications sont dans le code ci-dessus.

Faites vos branchements, lancez l’Arduino et admirez le résultat.

Scanning.......................................
I2C device found at address 0x27  !
done

Scanning.......................................
I2C device found at address 0x27  !
done

Méfiez-vous tout de même : certains périphériques I2C, comme certains écrans LCD de 4 lignes de 20 caractères achetés en chine avec son module I2C, peuvent parfois dérailler au bout d’un certain temps. Pour un écran LCD, cela peut être dû à une mauvaise initialisation du périphérique.

Scanning.......................................
I2C device found at address 0x27  !
done

Scanning......................................
I2C device found at address 0x26  !
done

Scanning......................................
I2C device found at address 0x26  !
done

Scanning..............................................................................................................................
No I2C devices found

Scanning.......................................
I2C device found at address 0x27  !
done

Dans ce cas on débranche le câble USB et on recommence après quelques secondes de repos.

Attention, quand on débranche et rebranche un périphérique I2C sous tension, il est hautement conseillé de couper la connexion de la masse (GND) en dernier. Symétriquement, quand on branche un périphérique I2C sur un montage sous tension, il faut relier la masse (GND) en premier. L’ordre des 4 fils de l’I2C étant GND-VCC-SDA-SCL, il suffit de pencher le connecteur de façon à présenter le coté GND en premier.

Lorsque l’adresse est déterminée, il reste à l’indiquer dans un programme qui utilise ce périphérique et vérifier qu’il répond bien à cette adresse.

Pour vous familiariser avec la bibliothèque Wire, nous vous recommandons de regarder et tester les exemples qui sont livrés avec l’IDE, sous le menu "Exemples" et la ligne "Wire".

Si vous souhaitez plus de détails sur le protocole I2C, visitez Wikipedia qui traite ce sujet de façon très approfondie.

15 Messages

  • Bibliothèque Wire : I2C 27 janvier 2016 21:37, par Dominique

    Cet article a été complété avec la recherche de l’adresse I2C d’un périphérique non documenté !

    Répondre

  • Bibliothèque Wire : I2C 28 janvier 2017 09:06, par David Rey

    Bonjour,
    je voudrais envoyer par I2C l’humidité et la température d’une DHT22

    qui sont 2 valeurs "float" et le recevoir sur un autre Arduino

    mais je ne sais pas séparer les valeurs

    pourriez-vous m’aider (un petit exemple)

    Merci

    david

    Répondre

  • Bibliothèque Wire : I2C 28 janvier 2017 10:00, par Dominique

    Bonjour,

    Votre question n’est pas très claire et le rapport avec le modélisme non plus.
    Je vous renvoie vers cette question déjà traitée ici.

    Transfert de float

    Répondre

  • Bibliothèque Wire : I2C échange entre arduinos 20 août 2018 15:38, par Amenda

    Bonjour
    Etant débutante en « Arduino « je ne maîtrise pas bien le code.
    Mon but : Création d’une lyre à led RGBW ‘ spot orientable ‘ DMX
    Je rencontre un problème de transmission entre deux atmel ‘ arduino’ en liaison I2C.
    J’aimerai transmettre 8 x données PWM par liaison I2c entre un Arduino Maître et un Arduino esclave.
    Donc :
    Pour l’émetteur : Afin de transmettre les huit Variables Rouge, Vert, Bleu, … Ouverture dialogue i2c Vers l’esclave 0Xc , envoi des huit valeurs PWM , Rouge, Vert, Bleu etc ; et fermeture communication.
    Pour l’esclave : Ouverture de la liaison esclave I2C 0xc , vérification si des données sont présentes, si non fermer la communication , … Si oui réceptionner les huit valeurs PWM , fermer la communication , et mettre à jour les Variables Rouge, Vert, Bleu etc.
    Merci par avance pour votre aide
    Amenda

    Répondre

    • Bibliothèque Wire : I2C échange entre arduinos 22 août 2018 12:33, par Jean-Luc

      Bonjour,

      Franchement les deux exemples de la bibliothèques Wire, master_writer d’un côté et slave_receiver de l’autre vous donneront un code de départ qui vous suffira de modifier après consultation de la doc de Wire.

      Cordialement.

      Répondre

  • Bibliothèque Wire : I2C 20 août 2018 15:50, par Dominique

    Tout intéressant qu’il soit sans doute, votre projet ne concerne pas le modélisme ferroviaire.

    Répondre

  • Bibliothèque Wire : I2C 31 août 2018 10:14, par Nws

    Bonjour Dominique,

    Je suis en train de réaliser une maquette d’une ville intelligente avec un chemin ferroviaire inséré. Cette maquette est controlée par un Arduino et dispose de plusieurs Grove NFC connecté en I2C via un mux I2C (adresse fixée en dur..).

    Je parviens à détecter mes deux grove NFC (il y en aura plusieurs par la suite), à communiquer avec eux (présence d’un tag ou non) mais je rencontre un petit souci que je vous expose :

    Lorsque je place un tag sur un des deux capteurs, la détection se fait bien, mais lorsque je le retire puis le replace, je dois attendre un temps de latence de 5 secondes avant qu’il le redétecte, alors que si je place le tag sur le second grove NFC la détection se fait instantanément. Autrement dit la détection est instantanée lorsque je passe d’un capteur NFC à l’autre, tandis que j’ai un temps de latence de 5 secondes entre deux détections d’un même capteur NFC. Je suspecte le fait que le capteur mette du temps à transmettre et relacher le bus I2C mais dans ce cas je ne comprends pas pourquoi la détection se fait rapidement si je passe sur l’autre capteur NFC..

    Si jamais vous avez des idées pour m’éclaircir ce serait sympa..

    Merci à vous et bon courage

    Répondre

  • Bibliothèque Wire : I2C 31 août 2018 11:21, par Nws

    Bon et bien comme quoi il fallait creuser un peu... Pour ceux qui utiliseraient le même capteur grove NFC de chez Seeduino, il y a un timeout à préciser ou non dans la librairie NFCAdapter, fonction NFC.tagPresent(timeout)

    Voila voila

    Répondre

  • Bibliothèque Wire : I2C 19 octobre 2019 21:16, par thomas

    Bonjour, je suis actuellement entrain de connecter une JetsonNano et un ESP32 avec l’IDE Arduino, mais lors de la compilation il y a le probleme suivant qui apparait :

    sketch/transfert_i2c.ino.cpp.o :(.literal._Z5setupv+0x1c) : undefined reference to TwoWire::onReceive(void (*)(int))' sketch/transfert_i2c.ino.cpp.o: In functionsetup()’ :
    /home/thomas/Arduino/dossier_esp32/transfert_i2c/transfert_i2c.ino:27 : undefined reference to `TwoWire::onReceive(void (*)(int))’
    collect2 : error : ld returned 1 exit status
    Plusieurs bibliothèque trouvées pour "Wire.h"
    Utilisé : /home/thomas/Arduino/hardware/espressif/esp32/libraries/Wire
    exit status 1
    Erreur de compilation pour la carte ESP32 Dev Module

    Avez vous une idée pour résoudre mon probleme ?

    Répondre

    • Bibliothèque Wire : I2C 20 octobre 2019 00:24, par Dominique

      Bonsoir,
      j’ai eu un problème similaire entre un ESP8266 et un nano. Sur quel microcontroleur le compilateur dit tout ça (c’est l’esp32) et quel est le maître et l’esclave ? Je pense que vous avez plusieurs fichiers autour du sketch qui appellent la bibliothèque Wire plusieurs fois. C’est parfois compliqué de jongler avec les #INCLUDE.

      Répondre

  • Bibliothèque Wire : I2C 22 juillet 2021 17:44, par DUGNOILLE STEPHANE

    Bonjour, je travaille sur un projet de détection de choc sur un capteur qui est relié à un xbee. J réceptionne les infos sur un arduino auquel j’ai connecté un afficheur 16 *2 sur le port I2C. L’idée est d’afficher sur celui-ci les résultats . Chaque système (capteurs xbee et arduino avec afficheur I2C) fonctionne individuellement ; Le problème est de tout faire fonctionner ensemble , je pense que des conflits existent ou apparaissent . Je dirais même plus , je crois que les rapidités relatives de chacun des sytèmes interfèrent . Avez-vous un exemple déjà discuté sur ce forum ? Merci .

    Voir en ligne : dugnoille stéphane

    Répondre

  • Bibliothèque Wire : I2C 22 juillet 2021 22:15, par Dominique

    Bonjour, nous ne répondons que sur les sujets ayant trait au modélisme ferroviaire ET quand la question est bien documentée.

    Répondre

  • Bibliothèque Wire : I2C 22 juillet 2021 22:27, par Dominique

    Toutefois il faut savoir que le protocole I2C est fait pour que l’émetteur et le récepteur se synchronisent quelles que soient leurs différences de performance. Les explications sont à lire dans l’article et les exemples donnés dans la bibliothèque I2C permettent de maîtriser cette technique.

    Répondre

Réagissez à « Bibliothèque Wire : I2C »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Bibliothèques »

Les derniers articles

Les articles les plus lus