Le bus I2C (Inter-Integrated Circuit) est un bus de communication série développé par Philips Semiconductor (maintenant NXP Semiconductors) dans les années 1980. Il est largement utilisé pour la communication entre microcontrôleurs et divers périphériques tels que des capteurs, des mémoires, des horloges temps réel, et des DAC/ADC.
Voici quelques aspects clés du bus I2C :
Les lignes SDA et SCK sont bidirectionnelles. De ce fait on peut avoir un conflit sur une ligne si d'un coté on écrit un '1' (Vcc) et de l'autre coté, on écrit un '0' (GND). Du point de vu logique, l'état de la ligne n'est pas défini et du point de vu électrique, c'est un court-circuit. Pour éviter ce problème, Chaque ligne fonctionne en mode collecteur ouvert (ou drain ouvert) de sorte qu'un circuit peut soit forcer la ligne à 0 et imposer un niveau '0', soit libérer la ligne, le niveau '1' est alors obtenu grâce une résistance de tirage externe.
Le niveau '0' est dominant, le niveau '1' est récessif. Une ligne passe à 0 quand un des deux circuits écrit un '0'. Si un circuit écrit un '0' et l'autre écrit un '1', celui qui a écrit le '0' gagne. Pour que la ligne passe à '1', il faut que les deux circuits écrivent un '1' en libérant la ligne et le '1' est alors imposé par la résistance de pull-up.
Le protocole I2C jongle avec cette situation pour organiser l'échange des données entre les deux composants.
Quand le bus est libre, les deux lignes SDA et SCL sont au niveau haut. C'est la condition pour qu'un master prend possession du bus et commence une séquence de communication
Quand un Master veut prendre le contrôle du bus et démarrer une séquence de communication, il génère un signal Start condition sur le bus en ramenant les deux lignes à 0
Quand un Master veut terminer une séquence de communication et libérer le bus, il génère un signal stoP condition en ramenant les deux ligne à 1
Le circuit qui écrit force la ligne SDA à 0 ou à 1 pendant l'impulsion de l'horloge sur SCL (c'est toujours le master qui génère l'horloge)
L'acknowledge est l'accusé de réception. Il est placé par le circuit (qui vient de recevoir un octet) sur la ligne SDA pendant le 9ème coup d'horloge.
Une séquence de communication doit toujours commencer comme suit:
Sur Arduino, on utilise la librairie Wire pour communiquer sur le bus I2C. Dès qu'on initialise la librairie à l'aide de Wire.begin(), les broches A4 et A5 deviennent SDA et SCL
Voici les fonctions usuelle de la librairie wire
Wire.beginTransmission(ADX);
Wire.write(B1);
Wire.write(B2);
…
Wire.write(BN);
int s = Wire.endTransmission();
Wire.beginTransmission(ADX);
Wire.write(TAB,N);
int s = Wire.endTransmission();
Wire.requestFrom(ADX,N);
B1 = Wire.read();
B2 = Wire.read();
…
BN = Wire.read();
Wire.requestFrom(ADX,N);
Wire.readBytes(TAB,N);
#define MEM_I2C_ADR 0x57 //1010 111
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin();
// on rempli la moitié de la 1ère page
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x00); // first page
Wire.write("abcdefghijklmnop"); // 16 caractères
Wire.endTransmission(); // On envoie 18 octets (2+16)
delay(10);
// on rempli la 2ème moitié de la page
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x10);
Wire.write("QRSTUVWXYZABCDEF");
Wire.endTransmission(); // On envoie 18 octets (2+16)
delay(10); // flashing duration
// on rempli la 1ère moitié de la 3ème page
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x40);
for (uint8_t i = 0; i < 16; i++)Wire.write(i);
Wire.endTransmission();
delay(10);
// on rempli la 2ème moitié de la 3ème page
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x50);
for (uint8_t i = 16; i < 32 ; i++)Wire.write(i);
Wire.endTransmission();
delay(10);
// On positionne l'adresse courante
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x00);
Wire.endTransmission();
// On reçoit 32 caractères vers le buffer de réception
Wire.requestFrom(MEM_I2C_ADR, 32);
// on récupère les caractères dans le buffer de réception
for (int i = 0; i < 32; i++) {
char c = Wire.read();
Serial.print(c);
}
Serial.println();
// On positionne l'adresse courante sur 3ème page
Wire.beginTransmission(MEM_I2C_ADR);
Wire.write(0x00);
Wire.write(0x40);
Wire.endTransmission();
// on reçoit 32 octets dans le buffer de réception
Wire.requestFrom(MEM_I2C_ADR, 32);
// on récupère les octets dans le buffer de réception
for (int i = 0; i < 32; i++) {
if(i % 8 == 0)Serial.println();
uint8_t k = Wire.read();
Serial.print(k);
Serial.print(" ");
}
Serial.println();
}
void loop() {
}
Si vous avez un module I2C et vous ne savez pas à quelle adresse il répond, vous pouvez utiliser le petit programme ci dessous qui teste toutes les adresses comprises entre 0 et 127. L'adresse de votre module sera affichée sur le moniteur série. Si votre module répond aussi à l'adresse 0, c'est normal, c'est l'adresse de 'broadcast' GCA (General Call Address). Normalement, chaque module I2C devrait répondre à sa propre adresse et à l'adresse 0, ce n'est pas toujours le cas, les constructeurs ne respectent pas tous cette règle
// Scanneur I2C
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin();
Serial.println("\r\n==== Debut du Scan =======");
for(int adr = 0; adr < 128 ; adr++){
Wire.beginTransmission(adr);
int s = Wire.endTransmission();
if(s == 0){
Serial.println("\r\n---> "+String(adr)+" ---> 0X"+String(adr,HEX)+" ---> "+String(adr,BIN));
}
}
Serial.println("\r\n==== Fin du Scan =======");
}
void loop() {
}