Photo du robot Racoon

L'I2C et arduino

Le par Victor SUEUR

Faire communiquer des composants est la base de l'informatique: l'Ethernet, le wifi, la fibre optique, l'USB, l'HDMI... Toutes ces technologies permettent, grâce à plusieurs moyens (champ électromagnétique, lumière, signal électrique), la communication entre plusieurs appareils. Mais même au sein d'un appareil, ça communique ! En effet, un processeur n'est qu'une unité de calcul, sans mémoire, le disque dur n'est qu'une unité de stockage, sans puissance de calcul, le clavier n'est qu'un périphérique d'entrée, sans mémoire ni puissance de calcul. Mais tous ces composants ont un point commun: ils communiquent !

arrow_back Retour au blog

Après une longue absence, nous voici de retour avec un article sur la liaison I2C! Au programme:

  • Pourquoi faire communiquer des composants?
  • Qu'est-ce que l'I2C et pourquoi?
  • Notions de base
  • Comment s'en sert-on?

Pourquoi faire communiquer des composants?

Faire communiquer des composants est la base de l'informatique: l'Ethernet, le wifi, la fibre optique, l'USB, l'HDMI... Toutes ces technologies permettent, grâce à plusieurs moyens (champ électromagnétique, lumière, signal électrique), la communication entre plusieurs appareils. Mais même au sein d'un appareil, ça communique! En effet, un processeur n'est qu'une unité de calcul, sans mémoire, le disque dur n'est qu'une unité de stockage, sans puissance de calcul, le clavier n'est qu'un périphérique d'entrée, sans mémoire ni puissance de calcul. Mais tous ces composants ont un point commun: ils communiquent! Le processeur va aller chercher un programme dans le disque dur, ainsi que les directives de l'utilisateur grâce au clavier, et lui donner ses résultats grâce à l'écran! et paf, la magie opère, et l'ordinateur prend vie! bien sûr, c'est simplifié à l'extrême, voir caricatural. Mais sans communication, pas d'informatique. On a donc développé des systèmes pour faire communiquer tout ça. Dans un premier temps, analogique, puis numérique. C'est ces dernières qui nous intéressent. Elles se divisent en deux catégories distinctes: les communications parallèles, et les communications séries. Dans une communication parallèle, tous les bits seront transmis en même temps: par exemple, sur une transmission en 16 bits, on trouvera 16 conducteurs, sur une communication 4 bits, on trouvera 4 conducteurs. Chaque bit sera transmis en même temps, en parallèle, donc. Par exemple, transmettons la valeur 13, qui, en binaire, vaut 1101. La tension dans chacun des conducteurs n'évolue pas dans le temps si la valeur n'évolue pas. C'est simple, mais si on veut transmettre de grosses valeurs, il faut beaucoup de conducteurs, ainsi que des connecteurs qui valent plus cher. On a donc développé la communication série, ou les bits sont transmis les uns après les autres. La tension évolue au cours du temps, et l’on réduit le nombre de conducteurs. Par exemple, 13 donnerait donc ceci

Je passe sur les détails de fonctionnement, mais il y a deux types de transmission série: les transmissions synchrones (quand un signal de synchronisation est transmis), et les transmissions asynchrones (quand il n'y a pas de signaux de synchronisation). Par exemple, le RS232 (appelé port serial sur les arduinos) est asynchrone, il n'y a pas de signaux d'horloge. L'I2C et le SPI sont des liaisons synchrones, car un signal de synchronisation est transmis (respectivement SCL et CLK ou CK).

Qu'est-ce que l'I2C?

L'I2C TWI (qui signifie Inter Integrated Circuit Two Wire Interface, ou interface à deux fils entre circuits intégrés, en français), est, comme son nom l'indique... Une interface a deux fils pour la communication entre circuits intégrés. Ce protocole a été conçu par Phillips en 1982 pour être facilement intégré à n'importe quels microcontrôleurs, et s'élève aujourd'hui comme un standard. Comme dit plus tôt, il s'agit d'un protocole série synchrone. Il nécessite 2 signaux: la donnée, appelée SDA (Serial DAta line) et l'horloge, SCL (Serial CLock line). C'est un protocole bidirectionnel, c'est-à-dire que les communications se font dans les deux sens. On peut y mettre jusqu'à 1024 composants, chacun identifié par une adresse. Les composants sur le bus sont soit maitres, soit esclaves, il peut y avoir plusieurs maitres et plusieurs esclaves. Les maitres: Ils peuvent envoyer des données aux esclaves, et leur demander d'en envoyer. Les maitres ne peuvent pas communiquer ensemble. Les esclaves ne peuvent pas prendre l'initiative de la communication. Ile écoutent les maitres et leur répondent.

Notion de base

Pour la programmation, nous aurons besoin de savoir utiliser un masque en et, et le décalage de bits, car nous allons devoir faire quelques manipulations bit a bit, car les envois se font par octets, et qu'un int fait 2 octets on va donc le casser en deux octets distincts

Le décalage à gauche se fait grâce à l'opérateur >>. On choisit le nombre de bits de décalage grâce au nombre suivant. Par exemple, >> 3 décalera de 3 bits à gauche. Par exemple, 1001 >> 2 donne 0010, car les bits laissés vides sont remplis avec des 0, et les bits qui ne sont plus dans une case mémoire disparaissent.

Le masque en et se fait grâce à l'opérateur &, et se présente sous la forme a&b. B est le masque, et lorsque l'on superpose le masque et A, là où le masque a les bits à 1, on conserve ceux de A. là ou le masque a des 0, on conserve les 0. par exemple:
0 1 0 1 1 0 & 1 1 0 0 1 0 donne 0 1 0 0 1 0

Le premier octet sera récupéré en faisant un décalage à gauche de 8 bits: int AB = 43605; // 10101010 01010101 en binaire byte a; byte b; a = ab >> 8; b = ab & 255; //255 vaut 00000000 11111111

pour la reconstitution, on a: byte a = 10101010; byte b = 01010101; int ab = a << 8 | b; // le signe + n'est pas un opérateur bit a bit, on utilise | en bit a bit

On passe au code arduino!

Comment s'en sert-on?

Prérequis: Chaque variable devra être identifiée par une valeur. Toutes les valeurs sont de type integer Limitations: Arduin ne gère que 127 appareils sur le bus i2c.

Le maitre demande une variable:

Dans ce cas, le maitre envoie le numéro de la variable qu'il veut, puis demande 2 octets à l'esclave

Code coté maitre:

1#include <Wire.h> // Librairie I2C;
2const int slave_1 = 1; //Adresse de l'esclave 1;
3 
4//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
5//Variables
6int a ;
7int b ;
8int c = -3;
9//Numero des variables
10const int A = 1;
11const int B = 2;
12const int C = 3;
13 
14 
15void setup() {
16 Wire.begin(); //Joindre le bus I2C en tant que maitre.
17 // put your setup code here, to run once:
18 
19}
20 
21int AskSlave(int variable_attendue, int slave) {
22 Wire.beginTransmission(slave);
23 Wire.write(variable_attendue);
24 Wire.endTransmission();
25 //delay(10);
26 Wire.requestFrom(slave, 2);
27 int n = Wire.read() << 8; //On récupere le premier octet et on le met imédiatement a sa place;
28 n |= Wire.read(); //On récupère le deuxième octet.
29 return n;
30 
31}
32 
33void loop() {
34 a = AskSlave(A, slave_1); //on demande à l'esclave 1 la valeur de a
35 // put your main code here, to run repeatedly:
36 
37}

code coté esclave:

1#include <Wire.h> // Librairie I2C;
2const int slave_1 = 1; //Adresse de l'esclave 1;
3int commandAnswer; // Mémoire de la variable demandée;
4//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
5//Variables
6int a ;
7int b ;
8int c = -3;
9//Numero des variables
10const int A = 1;
11const int B = 2;
12const int C = 3;
13 
14 
15void setup() {
16 Wire.begin(slave_1); //Joindre le bus I2C en tant qu'esclave.
17 
18 Wire.onReceive(WireRecive); //Ces dex lignes servent a déclancher automatiquement les fonctions WireRequest et Wirerecive, vous n'aves pas a les appeler
19 Wire.onRequest(WireRequest);
20 // put your setup code here, to run once:
21 
22}
23 
24 
25void WireRecive() { //on reçois la demande de variable a transmettre
26 int commandRecived = Wire.read();
27 commandAnswer = 0; //par défaut, on envoie 0;
28 
29 if (commandRecived == A) { //On met la bonne variable dans la mémoire qui sera transmit
30 commandAnswer = a;
31 }
32 if (commandRecived == B) {
33 commandAnswer = b;
34 }
35 if (commandRecived == C) {
36 commandAnswer = c;
37 }
38 //En suite, on attend que le maitre envoie la demande de transmission
39 
40}
41void WireRequest() { //Quand on reçois la demande de transmission, on transmet
42 Wire.write(commandAnswer >> 8);
43 Wire.write(commandAnswer & 255);
44}
45 
46void loop() {
47 
48 
49}

Le maitre veut envoyer une valeur ou en recevoir:

Dans ce cas, le maitre envoie le numéro de la variable qu'il s'âprete à transmettre, puis transmet les deux octets:

code coté maitre:

1#include <Wire.h> // Librairie I2C;
2const int slave_1 = 1; //Adresse de l'esclave 1;
3 
4//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
5//Variables
6int a ;
7int b ;
8int c = -3;
9//Numero des variables
10const int A = 1;
11const int B = 2;
12const int C = 3;
13const int SET = 12332;//Commande pour basculer l'esclave en mode reception variable
14 
15 
16void setup() {
17 Wire.begin(); //Joindre le bus I2C en tant que maitre.
18 // put your setup code here, to run once:
19 
20}
21 
22int AskSlave(int variable_attendue, int slave) {
23 Wire.beginTransmission(slave);
24 Wire.write(variable_attendue);
25 Wire.endTransmission();
26 //delay(10);
27 Wire.requestFrom(slave, 2);
28 int n = Wire.read() << 8; //On récupere le premier octet et on le met imédiatement a sa place;
29 n |= Wire.read(); //On récupère le deuxième octet.
30 return n;
31 
32}
33void WriteValue(int NumeroVariable, int valeur, int slave) {
34 Wire.beginTransmission(slave);
35 Wire.write(SET); //On envoie la commande pour passer l'esclave en mode reception de variable
36 Wire.endTransmission();
37 delay(15);
38 Wire.beginTransmission(slave);
39 Wire.write(NumeroVariable); //On transmet le numero de variable a transferer
40 Wire.endTransmission();
41 delay(15);
42 Wire.beginTransmission(slave);
43 Wire.write(valeur >> 8); //On transmet la valeur
44 Wire.write(valeur & 255);
45 Wire.endTransmission();
46 
47}
48 
49void loop() {
50 a = AskSlave(A, slave_1); //on demande à l'esclave 1 la valeur de a
51 // put your main code here, to run repeatedly:
52 
53}

code coté esclave:

1#include <Wire.h> // Librairie I2C;
2const int slave_1 = 1; //Adresse de l'esclave 1;
3int commandAnswer; // Mémoire de la variable demandée;
4int variable_en_reception;
5int variable_buffer;
6int mode = 0;
7const int standby = 0;
8const int variableReception = 1;
9const int octets = 2;
10//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
11//Variables
12int a ;
13int b ;
14int c = -3;
15//Numero des variables
16const int A = 1;
17const int B = 2;
18const int C = 3;
19const int SET = 12332;
20 
21 
22void setup() {
23 Wire.begin(slave_1); //Joindre le bus I2C en tant qu'esclave.
24 
25 Wire.onReceive(WireRecive);
26 Wire.onRequest(WireRequest);
27 // put your setup code here, to run once:
28 
29}
30 
31 
32void WireRecive() { //on reçois la demande de variable a transmettre
33 int commandRecived = Wire.read();
34 commandAnswer = 0; //par défaut, on envoie 0;
35 if (mode == standby) {
36 if (commandRecived == A) { //On met la bonne variable dans la mémoire qui sera transmit
37 commandAnswer = a;
38 }
39 if (commandRecived == B) {
40 commandAnswer = b;
41 }
42 if (commandRecived == C) {
43 commandAnswer = c;
44 }
45 if (commandRecived == SET) {
46 mode = variableReception; //Si on reçoit la commande, on bascule en mode reception de variable
47 }
48 } else {
49 switch (mode) {
50 case variableReception: //On reçois le numéro de la variable
51 variable_en_reception = commandRecived;
52 break;
53 case octets:
54 variable_buffer = commandRecived << 8; //On reçois les deux octets
55 variable_buffer |= Wire.read();
56 if (variable_en_reception == A) { //On les sauvegarde a la bonne place
57 a = variable_buffer;
58 }
59 if (variable_en_reception == B) {
60 b = variable_buffer;
61 }
62 if (variable_en_reception == C) {
63 c = variable_buffer;
64 }
65 mode = standby; //On reviens en mode normal
66 break;
67 default:
68 mode = standby; //Si jamais le mode n'existe pas, on reviens en mode normal
69 break;
70 }
71 if(mode == variableReception){
72 mode == octets; //Quand on a reçu le numéro de variable, on passe en attente des octets
73 }
74 
75 }
76 //En suite, on attend que le maitre envoie la demande de transmission
77 
78}
79void WireRequest() { //Quand on reçois la demande de transmission, on transmet
80 Wire.write(commandAnswer >> 8);
81 Wire.write(commandAnswer & 255);
82}
83 
84void loop() {
85 
86 
87}

Voilà, c'est tout pour aujourd'hui!

Sources:

https://fr.wikipedia.org/wiki/I2C#Adressage_sur_10_bits