Photo de 'L'I2C et arduino'

L'I2C et arduino

Par Victor SUEUR le 20/01/2021

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:

#include <Wire.h> // Librairie I2C;
const int  slave_1 = 1; //Adresse de l'esclave 1;

//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
//Variables
int a ;
int b ;
int c = -3;
//Numero des variables
const int A = 1;
const int B = 2;
const int C = 3;

void setup() {
  Wire.begin(); //Joindre le bus I2C en tant que maitre.
  // put your setup code here, to run once:

}

int AskSlave(int variable_attendue, int slave) {
  Wire.beginTransmission(slave);
  Wire.write(variable_attendue);
  Wire.endTransmission();
  //delay(10);
  Wire.requestFrom(slave, 2);
  int  n = Wire.read() << 8; //On récupere le premier octet et on le met imédiatement a sa place;
  n |= Wire.read();          //On récupère le deuxième octet.
  return n;

}

void loop() {
  a = AskSlave(A, slave_1); //on demande à l'esclave 1 la valeur de a
  // put your main code here, to run repeatedly:

}

code coté esclave:

#include <Wire.h> // Librairie I2C;
const int  slave_1 = 1; //Adresse de l'esclave 1;
int commandAnswer; // Mémoire de la variable demandée;
//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
//Variables
int a ;
int b ;
int c = -3;
//Numero des variables
const int A = 1;
const int B = 2;
const int C = 3;

void setup() {
  Wire.begin(slave_1); //Joindre le bus I2C en tant qu'esclave.

  Wire.onReceive(WireRecive); //Ces dex lignes servent a déclancher automatiquement les fonctions WireRequest et Wirerecive, vous n'aves pas a les appeler
  Wire.onRequest(WireRequest);
  // put your setup code here, to run once:

}

void WireRecive() {  //on reçois la demande de variable a transmettre
  int commandRecived = Wire.read();
  commandAnswer = 0; //par défaut, on envoie 0;

    if (commandRecived == A) { //On met la bonne variable dans la mémoire qui sera transmit
      commandAnswer = a;
    }
    if (commandRecived == B) {
      commandAnswer = b;
    }
    if (commandRecived == C) {
      commandAnswer = c;
    }
    //En suite, on attend que le maitre envoie la demande de transmission

}
void WireRequest() { //Quand on reçois la demande de transmission, on transmet
  Wire.write(commandAnswer >> 8);
  Wire.write(commandAnswer & 255);
}

void loop() {

}

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:

#include <Wire.h> // Librairie I2C;
const int  slave_1 = 1; //Adresse de l'esclave 1;

//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
//Variables
int a ;
int b ;
int c = -3;
//Numero des variables
const int A = 1;
const int B = 2;
const int C = 3;
const int SET = 12332;//Commande pour basculer l'esclave en mode reception variable

void setup() {
  Wire.begin(); //Joindre le bus I2C en tant que maitre.
  // put your setup code here, to run once:

}

int AskSlave(int variable_attendue, int slave) {
  Wire.beginTransmission(slave);
  Wire.write(variable_attendue);
  Wire.endTransmission();
  //delay(10);
  Wire.requestFrom(slave, 2);
  int  n = Wire.read() << 8; //On récupere le premier octet et on le met imédiatement a sa place;
  n |= Wire.read();          //On récupère le deuxième octet.
  return n;

}
void WriteValue(int NumeroVariable, int valeur, int slave) {
  Wire.beginTransmission(slave);
  Wire.write(SET); //On envoie la commande pour passer l'esclave en mode reception de variable
  Wire.endTransmission();
  delay(15);
  Wire.beginTransmission(slave);
  Wire.write(NumeroVariable); //On transmet le numero de variable a transferer
  Wire.endTransmission();
  delay(15);
  Wire.beginTransmission(slave);
  Wire.write(valeur >> 8); //On transmet la valeur
  Wire.write(valeur & 255);
  Wire.endTransmission();

}

void loop() {
  a = AskSlave(A, slave_1); //on demande à l'esclave 1 la valeur de a
  // put your main code here, to run repeatedly:

}

code coté esclave:

#include <Wire.h> // Librairie I2C;
const int  slave_1 = 1; //Adresse de l'esclave 1;
int commandAnswer; // Mémoire de la variable demandée;
int variable_en_reception;
int variable_buffer;
int mode = 0;
const int standby = 0;
const int variableReception = 1;
const int octets = 2;
//Table des parametres transmissible, doit etre identique pour le maitre et l'esclave
//Variables
int a ;
int b ;
int c = -3;
//Numero des variables
const int A = 1;
const int B = 2;
const int C = 3;
const int SET = 12332;

void setup() {
  Wire.begin(slave_1); //Joindre le bus I2C en tant qu'esclave.

  Wire.onReceive(WireRecive);
  Wire.onRequest(WireRequest);
  // put your setup code here, to run once:

}

void WireRecive() {  //on reçois la demande de variable a transmettre
  int commandRecived = Wire.read();
  commandAnswer = 0; //par défaut, on envoie 0;
  if (mode == standby) {
    if (commandRecived == A) { //On met la bonne variable dans la mémoire qui sera transmit
      commandAnswer = a;
    }
    if (commandRecived == B) {
      commandAnswer = b;
    }
    if (commandRecived == C) {
      commandAnswer = c;
    }
    if (commandRecived == SET) {
      mode = variableReception; //Si on reçoit la commande, on bascule en mode reception de variable
    }
  } else {
    switch (mode) {
      case variableReception: //On reçois le numéro de la variable
        variable_en_reception = commandRecived;
        break;
      case octets:
        variable_buffer = commandRecived << 8; //On reçois les deux octets
        variable_buffer |= Wire.read();
        if (variable_en_reception == A) { //On les sauvegarde a la bonne place
          a = variable_buffer;
        }
        if (variable_en_reception == B) {
          b = variable_buffer;
        }
        if (variable_en_reception == C) {
          c = variable_buffer;
        }
        mode = standby; //On reviens en mode normal
        break;
      default:
        mode = standby; //Si jamais le mode n'existe pas, on reviens en mode normal
        break;
    }
    if(mode == variableReception){
      mode == octets; //Quand on a reçu le numéro de variable, on passe en attente des octets
    }

  }
  //En suite, on attend que le maitre envoie la demande de transmission

}
void WireRequest() { //Quand on reçois la demande de transmission, on transmet
  Wire.write(commandAnswer >> 8);
  Wire.write(commandAnswer & 255);
}

void loop() {

}

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

Sources:

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


Commentaires

Les commmentaires ne sont pas actifs