Le bus I2C est très utilisé pour récupérer des mesures ou piloter des équipements sur les projets Arduino, ESP32, ESP8266 et Raspberry Pi à l’aide de la librairie standard Wire.h. Le bus I2C est un bus de communication série mis au point par Philips à partir de 1982 qui permet à deux appareils (ou plus) de communiquer. Plus précisément, c’est un bus de données série synchrone bidirectionnel half-duplex. Les appareils connectés au bus peuvent agir en tant qu’équipement principal – c’est à dire l’appareil qui «contrôle» le bus – ou secondaire.
Dans cet article, nous allons aborder le principe de fonctionnement de la librairie Wire.h qui permet de faire communiquer simplement des Arduino entre eux. Par contre nous aborderons pas le fonctionnement technique du bus I2C qui est déjà très bien expliqué sur cette page Wikipedia.
Présentation rapide du bus I2C
Le bus I2C est donc une amélioration de la communication série destinée à faire communiquer des accessoires domotiques développé à partir des années 1980 par Philipps. La plupart du temps le bus I2C est constitué d’un équipement principal et un ou plusieurs équipements secondaires, mais la norme permet des organisations plus complexes.
Le bus I2C ne nécessite que deux lignes de communication (fils) pour connecter les appareils entre eux :
- (Serial Data Line), ligne de données bidirectionnelle,
- (Serial Clock Line), ligne d’horloge de synchronisation bidirectionnelle. Le signal est généré uniquement par l’équipement principal
D’un point de vue électrique, tous les équipements doivent être connectés à la même masse électrique GND.
Les deux lignes doivent être connectées à une tension de référence (Vdd) via des résistances pull-up.
Voici un exemple de réseau I2C. Un Arduino Uno constitue l’équipement principal du réseau. Deux équipements secondaires sont connectés au bus I2C. A l’adresse 0x76 est connecté un capteur BME280. A l’adresse 0x5A se trouve un capteur d’intensité lumineuse BH1750FVI.
Le nombre maximum d’équipements est limité par le nombre d’adresses disponibles. Comme l’adressage des périphériques est constitué par un mot de 7 bits, on pourra adresser jusqu’à 128 périphériques (27). Le nombre de périphériques I2C dépend également de la qualité du circuit (sa capacité) ainsi que de la vitesse de transmission désirée.
Cinq vitesses de transmission sont possibles :
- Standard mode (Sm) ≤ 100 kbit/s
- Fast mode (Fm) ≤ 400 kbit/s
- Fast plus mode (Fm+) ≤ 1 Mbit/s
- High-speed mode (Hs-mode) ≤ 3,4 Mbit/s
- Ultra-fast mode (UFm) ≤ 5 Mbit/s, unidirectionnel uniquement.
La vitesse maximale est imposée par le fabricant. La plupart du temps, la vitesse est inférieure à 1 Mbit/s, en général elle se situe entre 100 et 400 kbit/s pour les capteurs.
Les adresses I2C
Chaque équipement secondaire dispose donc d’une adresse unique qui prend la forme d’une chaîne hexadécimale, par exemple 0x69.
Cette adresse est attribuée par le fabricant du composant. Il n’existe pas de norme mais les fabricants s’organisent pour ne pas utiliser une adresse déjà utilisée par un composant existant. En général, une adresse I2C secondaire est disponible. Elle est attribuable par programmation (rarement) ou en modifiant le circuit (à l’aide d’un jumper à souder ou en appliquant une tension pré-définie).
Voici deux exemples
En cas de doute sur l’adresse I2C du breakout, vous pouvez consulter cette liste des modules les plus courant pour les projets Arduino, ESP32, ESP8266 et Raspberry Pi
Broches i2c par défaut des cartes de développement les plus courantes
Il est possible d’attribuer manuellement par programmation (nous verrons comment au prochain paragraphe) les broches du bus I2C. La plupart des fabricants de cartes de développements (Arduino, ESP32, ESP8266, STM32, Raspberry Pi…) exposent les broches I2C standards.
Voici la liste des broches par défaut des cartes de développement les plus courantes
SDA | SCL | SDA1 | SCL1 | |
Bus principal | Bus secondaire | |||
Uno* | A4 | A5 | ||
Ethernet | A4 | A5 | ||
Mega2560 | 20 | 21 | ||
Leonardo | 2 | 3 | ||
Due | 20 | 21 | SDA1 | SCL1 |
ESP8266 | D1 | D2 | ||
ESP32 DevkitC v4 | 21 | 22 | Utilisateur | Utilisateur |
ESP32 (anciennes générations) | IO21 | IO22 | Utilisateur | Utilisateur |
STM32 | PB7 ou PB9 | PB6 ou PB8 | PB11 | PB10 |
Raspberry Pi (toute génération) | 3 | 5 |
(*) Ne pas utiliser les broches repérées SDA et SCL sur l’Arduino Uno.
Présentation de la librairie Wire.h pour Arduino, ESP32 et ESP8266
Le bus i2c est pris en charge par la librairie Wire.h qui a également été portée sur ESP32 et ESP8266.
C’est une librairie standard qui ne nécessite aucune installation complémentaire. La version dédiée aux ESP32 et ESP8266 est installée en même temps que le SDK.
Pour utiliser le bus I2C dans un projet Arduino, il suffit déclarer la librairie wire en début de code.
#include <Wire.h>
On initialise le bus i2c à l’aide de la méthode Wire.begin(adresse).
Sur Arduino, les broches ne sont pas spécifiées, la librairie utilise les broches par défaut pour la carte de développement ciblée. L’adresse correspond à l’adresse que l’on désire attribuer à l’équipement secondaire. On utilisera par exemple ce paramètre lorsqu’on développe un réseau I2C entre plusieurs Arduino. Nous verrons deux exemples juste après.
Wire.begin();
Les modules I2C du commerce disposent d’une librairie qui s’occupe de récupérer les données ce qui facilite grandement de travail de développement puisqu’on n’a pas à s’occuper du décodage des paquets envoyés.
Il est toutefois possible d’utiliser la librairie Wire.h pour développer notre propre protocole de communication entre deux (ou plus) Arduino (ou tout autre micro-contrôleurs d’ailleurs)
Pour faire simple, la librairie Wire.h dispose de 4 méthodes principales. Toutes les méthodes sont détaillées ici.
permet de démarrer un envoi de données vers l’équipement dont l’adresse est passée en paramètre.
permet d’envoyer une donnée vers l’équipement spécifié auparavant. Plusieurs formats de données sont supportés par la librairie
Wire.write(value) une valeur à envoyer sur un octet (byte)
Wire.write(string) une chaîne à envoyer sous forme de série d’octets (byte). La conversion d’une chaîne de caractère en byte est gérée par la librairie
Wire.write(data, length)
- data un tableau d’octets (bytes)
- length le nombre d’octets à transmettre
permet de fermer la communication avec l’équipement
Nous verrons d’autres méthodes utiles dans les exemples suivants
Limitations de la librairie Wire.h pour les cartes Arduino
Pour les cartes Arduino, la méthode Wire.begin() permet de spécifier manuellement l’adresse d’un équipement. Par contre, on ne pourra pas spécifier manuellement les broches SDA et SCL.
Wire.begin(0x09);
Limitations de la librairie Wire.h pour ESP32 et ESP8266
L’adaptation de la librairie Wire.h pour les cartes de développement ESP32 et ESP8266 ne permet pas d’attribuer une adresse à un équipement secondaire. On ne pourra donc pas créer un réseau I2C pour faire communiquer plusieurs ESP32 ou ESP8266 ensembles.
Par contre, on peut spécifier les broches SDA et SCL. Ici par exemple on attribue manuellement les broches 21 (SDA) et 22 (SCL) au bus I2C en appelant la méthode comme ceci Wire.begin(broche_sda,broche_scl).
Wire.begin(21,22);
En résumé
Arduino | ESP32 / ESP8266 | |
Attribuer une adresse à un équipement secondaire sur le réseau I2C | Oui | Non |
Créer un réseau I2C | Oui | Non |
Attribuer les broches SDA et SCL | Non | Oui |
Piloter et récupérer des données d’équipements I2C | Oui | Oui |
Scanner I2C compatible Arduino, ESP32 et ESP8266
La plupart du temps, on utilisera le bus I2C pour communiquer avec des capteurs prêt à l’emploi (breakout).
L’adresse I2C des modules les plus courant sont répertoriées dans cet article
Si vous ne trouvez pas l’adresse I2C de votre composant ou que vous rencontrez des problèmes de communication, voici le code source d’un scanner I2C compatible Arduino, ESP32 et ESP8266.
#include <Arduino.h>
#include <Wire.h>
#define DISPLAY_ERROR false
#define LOOP_DELAY 10000
#define USER_PIN false
// Personnaliser les broches du bus I2C pour ESP8266 ou ESP32
// Customize I2C bus pins for ESP8266 or ESP32
const int PIN_SCL = D1;
const int PIN_SDA = D2;
String I2Ctest() {
byte error, address;
int nDevices;
String s;
s="Scanning...n";
nDevices = 0;
for(address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
s+="I2C device found at 0x";
if (address<16)
s+="0";
s+=String(address,HEX);
s+="n";
nDevices++;
} else if ( error > 0 ) {
if ( DISPLAY_ERROR ) {
s+="Unknow error at 0x";
if (address<16)
s+="0";
s+=String(address,HEX);
s+="n";
}
}
}
if (nDevices == 0)
s+="No I2C devices foundn";
else
s+="donen";
return s;
}
void setup() {
Serial.begin(115200);
Serial.println("I2C scanner");
#if USER_PIN
Wire.begin(PIN_SDA, PIN_SCL);
#else
Wire.begin();
#endif
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(I2Ctest());
delay(LOOP_DELAY);
}
Exemple 1, comment faire communiquer deux Arduino via le bus I2C
Pour comprendre les bases de la librairie wire.h, nous allons faire communiquer deux Arduino via un bus I2C.
Voici le schéma de câblage.
Code à téléverser sur l’Arduino Principal
L’Arduino principal envoie à l’adresse 0x9 un entier toutes les 500ms.
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9 / transmission sur l arduino secondaire a l adresse 0x09 (=9 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
x++; // Increment x / incremente x
delay(500); // delay of 500 milli seconde between two send / delay de 500 milli seconde entre 2 envoi sur le bus I2C
}
Créer un premier croquis sur l’IDE Arduino et coller le code suivant.
Ensuite connecter l’Arduino principal à l’ordinateur et téléverser le projet.
/*Arduino 1 code
Example of code for use I2C communication between two Arduino :
Arduino 1 Secondary Arduino
A4 A4
A5 A4
GND GND
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int x = 0; // creation of the variable x for stockage the state of the led on the secondary arduino / creation d une variable x qui stocke l etat de la led sur l arduino secondaire
void setup() {
// Start the I2C Bus / innitialisation du bus I2C
Wire.begin();
}
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9 / transmission sur l arduino secondaire a l adresse 0x09 (=9 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
x++; // Increment x / incremente x
delay(500); // delay of 500 milli seconde between two send / delay de 500 milli seconde entre 2 envoi sur le bus I2C
}
Code à installer sur l’Arduino secondaire
Créer un second croquis et coller le code suivant. Connecter le second Arduino, sélectionner le port sur lequel il est connecté et téléverser le projet.
/*
Secondary arduino code
Example of code for use I2C communication between two Arduino :
Main Arduino (Uno) Secondary Arduino (Uno)
A4 A4
A5 A4
GND GND
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int LED = 13;
int x = 0;
void setup() {
pinMode (LED, OUTPUT); // Define the LED pin as Output / definir le pin de la led comme sortie
Wire.begin(9); // Start the I2C Bus with address 9 in decimal (= 0x09 in hexadecimal) / innitialisation du bus I2C avec comme adresse 9 en decimal soit 0x09 en hexadecimal
Wire.onReceive(receiveEvent); // Attach a function to trigger when something is received / Attacher une fonction a declencher lorsque quelque chose est recu
}
void receiveEvent() {
x = Wire.read(); // read one character from the I2C / lire le caractere recu sur le bus I2C
}
void loop() {
//If value received is a multiple of 2 turn on the led / si la valeur recu sur l I2C est un multiple de 2 alors on allume la led
if (x % 2 == 0) {
digitalWrite(LED, HIGH);
}
//Else turn off the led / sinon eteindre la led
else {
digitalWrite(LED, LOW);
}
}
Comment fonctionne ce code ?
On spécifie l’adresse de l’équipement, ici 0x9 ou 9, les deux écritures fonctionnent.
Wire.begin(9);
Dès qu’un nouveau message est reçu par l’équipement, on exécute la méthode receiveEvent().
Wire.onReceive(receiveEvent);
Ici la méthode receiveEvent actualise une variable temporaire en lisant le message reçu
void receiveEvent() {
x = Wire.read(); // read message from I2C / lire le message recu sur le bus I2C
}
A chaque passage dans la boucle loop(), la LED est allumée si la valeur de la variable x est paire et inversement.
Démonstration
Dès que le second Arduino démarre, la LED présente sur la carte se met à clignoter attestant que les deux Arduino communiquent entre eux via le bus I2C.
Exemple 2, faire communiquer trois Arduino via le bus I2C
On ajoute un deuxième Arduino (Mega) secondaire au réseau I2C.
Le réseau est constitué
- Arduino Uno principal
- Arduino Uno secondaire à l’adresse 0x9 (9)
- Arduino Mega secondaire à l’adresse 0x01 (10)
Code à téléverser sur l’Arduino Principal (1)
Le code est similaire au code précédent. On envoie la valeur d’une variable entière sur l’Arduino secondaire situé à l’adresse 0x0A sur le réseau I2C.
/* Arduino 1 code (main)
Example of code for use I2C communication between two Arduino
Arduino 1(Uno) Arduino 2(Uno) Arduino 3 (Mega)
A4 A4 20
A5 A5 21
GND GND GND
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int x = 0; // creation of the variable x for stockage the state of the led on the secondary arduino / creation d une variable x qui stocke l etat de la led sur l'arduino secondaire
void setup() {
// Start the I2C Bus / initialisation du bus I2C
Wire.begin();
}
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9 / transmission sur arduino 2 a l adresse 0x09 (=9 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
Wire.beginTransmission(0x0A); // transmit to device #10 / transmission sur arduino 3 a l adresse 0x01 (=10 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
x++; // Increment x / incremente x
delay(500); // delay of 500 milli secondes between two send / delay de 500 milli secondes entre 2 envoi sur le bus I2C
}
Code à téléverser sur l’Arduino secondaire n°2
Le code est inchangé
Code à téléverser sur l’Arduino secondaire n°3
L’Arduino Mega prend l’adresse 10 (0x0A). La LED est allumée si x est paire et inversement.
/*Arduino 3 code
Example of code for use I2C communication between tree Arduino
Arduino 1(Uno) Arduino 2(Uno) Arduino 3 (Mega)
A4 A4 20
A5 A5 21
GND GND GND
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int LED = 13;
int x = 0;
void setup() {
pinMode (LED, OUTPUT); // Define the LED pin as Output / definir le pin de la led comme sortie
Wire.begin(10); // Start the I2C Bus with address 10 in decimal (= 0x0A in hexadecimal) / initialisation du bus I2C avec comme adresse 9 en decimal soit 0x09 en hexadecimal
Wire.onReceive(receiveEvent); // Attach a function to trigger when something is received / Attacher une fonction a declencher lorsque quelque chose est recu
}
void receiveEvent() {
x = Wire.read(); // read one character from the I2C / lire le caractere recu sur le bus I2C
}
void loop() {
//If value received is a multiple of 2 turn on the led / si la valeur recu sur l I2C est un multiple de 2 alors on allume la led
if (x % 2 == 0) {
digitalWrite(LED, LOW);
}
//Else turn off the led / sinon eteindre la led
else {
digitalWrite(LED, HIGH);
}
}
Démonstration
Dès que l’Arduino Mega redémarre, la led commence à clignoter.
Mises à jour
6/10/2020 Ajout du scanner I2C compatible Arduino, ESP32 et ESP8266
24/08/2020 Première publication de l’article
- 4 solutions pour ajouter des E/S à un projet Arduino, ESP8266, ESP32, ESP8266, Raspberry Pi. ADS1115, MCP23017, PCF8574, PCA9685
- Résumé de la semaine du 31 Août au 4 septembre 2020.
- Débuter avec le bus I2C sur Arduino ESP8266 ESP32. Librairie Wire.h
- Résumé de la semaine du 24 au 28 Août 2020
- Adresses I2C des capteurs et actionneurs les plus courants