LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mardi 19 mars 2024

Visiteurs connectés : 36

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

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 »

Bibliothèque Accessories (1)

Bibliothèque Accessories (2)

Un décodeur d’accessoires universel (1)

Un décodeur d’accessoires universel (2)

Un décodeur d’accessoires universel (3)

Bibliothèque LcdUi (1)

Bibliothèque LcdUi (2)

La bibliothèque Servo

Bibliothèque SoftWare Serial

Bibliothèque Serial

Bibliothèque EEPROM

Bibliothèque Wire : I2C

Bibliothèque LCD

La bibliothèque ScheduleTable

Bibliothèque MemoryUsage

Bibliothèque EEPROMextent

La bibliothèque SlowMotionServo

Bibliothèque Commanders

Bibliothèque DCCpp

Bibliothèque DcDccNanoController

La bibliothèque ACAN (1)

La bibliothèque ACAN (2)

Bibliothèque LightEffect

Les derniers articles

Bibliothèque LightEffect


Christian

La bibliothèque ACAN (2)


Jean-Luc

La bibliothèque ACAN (1)


Jean-Luc

La bibliothèque SlowMotionServo


Jean-Luc

Bibliothèque DCCpp


Thierry

Bibliothèque DcDccNanoController


Thierry

Bibliothèque LcdUi (2)


Thierry

Bibliothèque LcdUi (1)


Thierry

Bibliothèque Accessories (2)


Thierry

Bibliothèque Accessories (1)


Thierry

Les articles les plus lus

Bibliothèque Wire : I2C

Bibliothèque SoftWare Serial

Bibliothèque DCCpp

La bibliothèque Servo

La bibliothèque ACAN (1)

Bibliothèque DcDccNanoController

Bibliothèque Commanders

Bibliothèque EEPROM

Un décodeur d’accessoires universel (2)

Un décodeur d’accessoires universel (1)