COMMUNICATION I2C ENTRE MAITRE ET ESCLAVE

Bonjour,
Pouvez vous me dire comment faire un échange de données entre un maitre et un esclave en I2C.
Le maitre initialise la communication vers l'esclave, lui demande des octets (correspondant a son état).
Si possible ne pas libérer le bus I2C et que l'esclave demande au maitre quelques octets également qui seront retournés a l'esclave.

Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);

le problème avec Wire.onReceive et Wire.onRequest c'est que seulement Wire.onRequest qui répond au maitre mais je n'arrive pas a aller plus loin...

Merci d'avance pour votre aide...

Cordialement

Alors ça c est un STI qui vient d avoir son sujet de projet xD

Non pas du tout, un passionné d’électronique qui débute dans l'arduino... J'utilisais avant des PIC16F ou 18F avec MPLAB, mais tout change... Je bosse sur le code et je vous le met en ligne...

Un esclave ne demande jamais rien à son maître, il ne fait que lui obéir :smiling_imp:

Par contre, après avoir interrogé un esclave, un maître peut décider de lui envoyer quelques consignes supplémentaires.

Partie Maitre:

#include "defines.h"

#include <Wire.h>               // Inclusion de la librairie Wire pour la gestion de l'I2C qui nous permettra de communiquer avec le DS1307
#include <LiquidCrystal_I2C.h>  // Inclusion de la librairie LiquidCrystal I2C qui permet de gérer un écran lcd (ici 2x16)
#include <SerialUI.h>           // Inclusion de la librairie Serial User Interface
#include "TimerOne.h"           // Inclusion de la librairie TimerOne 

 
//LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Création de l'objet lcd, notre lcd sera câbler sur les broches D2~D5 (bus de données) D11 (ENABLE) et D12 (RS)
//Date date; // Création de l'objet date qui contiendra les données sur la date/heure courante 

/* Lcd */  
LiquidCrystal_I2C lcd(Adresse_Lcd_3,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display  
/* Lcd */


// Fonction F Serial Menu //    
SUI_DeclareString(device_greeting,
                  "+++ Bienvenue +++\r\nEnter '?' to list available options.");

SUI::SerialUI mySUI = SUI::SerialUI(device_greeting);
// Fonction F Serial Menu //



  int Etat_Del_Ok = 0;
  
  byte Variable_A_Envoyer_Maitre_1[Nombre_Octets_A_Recevoir];
  byte Old_Variable_A_Envoyer_Maitre_1[Nombre_Octets_A_Recevoir];
  byte Octet_Maitre_A_Envoyer_1 = 0x0A; 
  byte Octet_Maitre_A_Envoyer_2 = 0x0B; 
  byte Octet_Maitre_A_Envoyer_3 = 0x0C;

  byte Variable_Requette_Esclave_1[Nombre_Octets_A_Recevoir];
  byte Old_Variable_Requette_Esclave_1[Nombre_Octets_A_Recevoir];
  byte Octet_Esclave_Recu_1;  
  byte Octet_Esclave_Recu_2;  
  byte Octet_Esclave_Recu_3;
  
  
// Setup()
void setup() 
{

// ------- Broches en sortie -------  
    pinMode(Del_Ok, OUTPUT);    
    
  Serial.begin(Serial_Baud_Rate);
  
  mySUI.println(F("***********************"));  
  mySUI.println(F("Module Maitre"));
  mySUI.println(F("***********************"));
   
  Wire.begin();        // Activation de l'I2C en Maitre
  
  
  
// Initialisation Lcd 
  lcd.init();                     
  delay(10);

  lcd.begin(16,2);
  lcd.backlight();
  lcd.clear();              // clear display, set cursor position to zero

  delay(10);
 
/* Lcd */  
//  lcd.backlight();
  lcd.clear(); 
  lcd.print("   Tests I2C    ");    
/* Lcd */   
 
  
// Initialisation Timer 1
//  Timer1.initialize(1000000);                   // initialize timer1, and set a 1 Sec 
  Timer1.initialize(200000);                   // initialize timer1, and set a 200 mSec 

  Timer1.attachInterrupt(Interruption_Timer1);  // attaches Interruption_Timer1() as a timer overflow interrupt  

} // Fin du void setup()
 
 
// loop()
void loop() 
{
 
  Variables_A_Envoyer();
 
  Requette_Variables_A_Recevoir();
 
 

  
  delay(1000);  
   
//  digitalWrite(Del_Ok, Etat_Del_Ok);
//  analogWrite(Del_Rouge, Etat_Del_Rouge);

} // fin de la fonction loop() - le programme recommence au début de la fonction loop sans fin
// ********************************************************************************

// --- Fin programme ---


void Variables_A_Envoyer()
{
  Wire.beginTransmission(Adresse_I2C_Esclave_1); // transmit to device

// Variables à envoyer
  Variable_A_Envoyer_Maitre_1[0] = Octet_Maitre_A_Envoyer_1,HEX; 
  Variable_A_Envoyer_Maitre_1[1] = Octet_Maitre_A_Envoyer_2,HEX; 
  Variable_A_Envoyer_Maitre_1[2] = Octet_Maitre_A_Envoyer_3,HEX;

  Wire.write(Variable_A_Envoyer_Maitre_1,Nombre_Octets_A_Recevoir); 
  Wire.endTransmission();    // stop transmitting
  
  
 if ((Variable_A_Envoyer_Maitre_1[0] != Old_Variable_A_Envoyer_Maitre_1[0]) || (Variable_A_Envoyer_Maitre_1[1] != Old_Variable_A_Envoyer_Maitre_1[1]) || (Variable_A_Envoyer_Maitre_1[2] != Variable_A_Envoyer_Maitre_1[2]))
  {
  mySUI.print(F("Envoyer Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Maitre_1[0],HEX);
  mySUI.print(F("Envoyer Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Maitre_1[1],HEX);
  mySUI.print(F("Envoyer Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Maitre_1[2],HEX); 
  
     } // Fin du if ((Variable_A_Envoyer_Maitre_1[0] != Old_Variable_A_Envoyer_Maitre_1[0]) || (Variable_A_Envoyer_Maitre_1[1] != Old_Variable_A_Envoyer_Maitre_1[1]) || (Variable_A_Envoyer_Maitre_1[2] != Variable_A_Envoyer_Maitre_1[2]))

   Old_Variable_A_Envoyer_Maitre_1[0] = Variable_A_Envoyer_Maitre_1[0];
   Old_Variable_A_Envoyer_Maitre_1[1] = Variable_A_Envoyer_Maitre_1[1];
   Old_Variable_A_Envoyer_Maitre_1[2] = Variable_A_Envoyer_Maitre_1[2]; 
 
} // Fin du void Variables_A_Envoyer()




void Requette_Variables_A_Recevoir()
{
  Wire.requestFrom(Adresse_I2C_Esclave_1, Nombre_Octets_A_Recevoir);   
 
    if(Wire.available() == Nombre_Octets_A_Recevoir) 
    {
      for (int i = 0; i < Nombre_Octets_A_Recevoir; i++) Variable_Requette_Esclave_1[i] = Wire.read();  // get nodes data

    Octet_Esclave_Recu_1 = Variable_Requette_Esclave_1[0],HEX;
    Octet_Esclave_Recu_2 = Variable_Requette_Esclave_1[1],HEX;
    Octet_Esclave_Recu_3 = Variable_Requette_Esclave_1[2],HEX;
   
    } // Fin du if(Wire.available() == Nombre_Octets_A_Recevoir)
    
    
  if ((Variable_Requette_Esclave_1[0] != Old_Variable_Requette_Esclave_1[0]) || (Variable_Requette_Esclave_1[1] != Old_Variable_Requette_Esclave_1[1]) || (Variable_Requette_Esclave_1[2] != Old_Variable_Requette_Esclave_1[2]))
  {

  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[0],HEX);
  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[1],HEX);
  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[2],HEX);

   Old_Variable_Requette_Esclave_1[0] = Variable_Requette_Esclave_1[0];
   Old_Variable_Requette_Esclave_1[1] = Variable_Requette_Esclave_1[1];
   Old_Variable_Requette_Esclave_1[2] = Variable_Requette_Esclave_1[2]; 
 
  } // Fin du if ((Variable_Requette_Esclave_1[0] != Old_Variable_Requette_Esclave_1[0]) || (Variable_Requette_Esclave_1[1] != Old_Variable_Requette_Esclave_1[1]) || (Variable_Requette_Esclave_1[2] != Old_Variable_Requette_Esclave_1[2]))  
    
} // Fin du void Requette_Variables_A_Recevoir() 





void Interruption_Timer1()
{  
  Etat_Del_Ok =! Etat_Del_Ok; // Inversement de l'etat

  digitalWrite(Del_Ok, Etat_Del_Ok);

} // Fin du void Interruption_Timer1()

Partie Esclave:

#include "defines.h"

#include <Wire.h>               // Inclusion de la librairie Wire pour la gestion de l'I2C qui nous permettra de communiquer avec le DS1307
#include <LiquidCrystal_I2C.h>  // Inclusion de la librairie LiquidCrystal I2C qui permet de gérer un écran lcd (ici 2x16)
#include <SerialUI.h>           // Inclusion de la librairie Serial User Interface
#include "TimerOne.h"           // Inclusion de la librairie TimerOne 

 
//LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Création de l'objet lcd, notre lcd sera câbler sur les broches D2~D5 (bus de données) D11 (ENABLE) et D12 (RS)
//Date date; // Création de l'objet date qui contiendra les données sur la date/heure courante 

/* Lcd */  
LiquidCrystal_I2C lcd(Adresse_Lcd_1,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display  
/* Lcd */


// Fonction F Serial Menu //    
SUI_DeclareString(device_greeting,
                  "+++ Bienvenue +++\r\nEnter '?' to list available options.");

SUI::SerialUI mySUI = SUI::SerialUI(device_greeting);
// Fonction F Serial Menu //


// DEL //
  int Etat_Del_Ok = 0;
  int Etat_Del_Armee = 0;
// DEL //  



  byte Variable_A_Envoyer_Esclave_1[Nombre_Octets_A_Recevoir]; 
  byte Old_Variable_A_Envoyer_Esclave_1[Nombre_Octets_A_Recevoir];  
  byte Octet_Esclave_Recu_1;
  byte Octet_Esclave_Recu_2;  
  byte Octet_Esclave_Recu_3;  


  byte Variable_Requette_Esclave_1[Nombre_Octets_A_Recevoir];
  byte Old_Variable_Requette_Esclave_1[Nombre_Octets_A_Recevoir];
  byte Octet_Esclave_A_Envoyer_1 = 0x0D; 
  byte Octet_Esclave_A_Envoyer_2 = 0x0E; 
  byte Octet_Esclave_A_Envoyer_3 = 0x0F;
  

// Setup()
void setup() 
{

// ------- Broches en sortie -------  
    pinMode(Del_Ok, OUTPUT);    

    
  Serial.begin(Serial_Baud_Rate);
  
  mySUI.println(F("***********************"));  
  mySUI.println(F("Module Esclave"));
  mySUI.println(F("***********************"));
   
  Wire.begin(0x11);        // Activation de l'I2C en Esclave
  Wire.onReceive(receiveEvent); 
   // enregistrement du handler "receive"
  Wire.onRequest(requestEvent); 
   // enregistrement du handler "request"
   
   
// Initialisation Lcd 
  lcd.init();                     
  delay(10);

  lcd.begin(16,2);
  lcd.backlight();
  lcd.clear();              // clear display, set cursor position to zero

  delay(10);
 
/* Lcd */  
//  lcd.backlight();
  lcd.clear(); 
  lcd.print("   Tests I2C    ");    
/* Lcd */   
 
  
// Initialisation Timer 1
//  Timer1.initialize(1000000);                   // initialize timer1, and set a 1 Sec 
  Timer1.initialize(200000);                   // initialize timer1, and set a 200 mSec 

  Timer1.attachInterrupt(Interruption_Timer1);  // attaches Interruption_Timer1() as a timer overflow interrupt  

} // Fin du void setup()
 
 
 
// loop()
void loop() 
{
 
   
  
  
  
  
  
  
  
//  digitalWrite(Del_Ok, Etat_Del_Ok); 
//  analogWrite(Del_Rouge, Etat_Del_Rouge);

} // fin de la fonction loop() - le programme recommence au début de la fonction loop sans fin
// ********************************************************************************

// --- Fin programme ---





void Interruption_Timer1()
{  
  Etat_Del_Ok =! Etat_Del_Ok; // Inversement de l'etat
  digitalWrite(Del_Ok, Etat_Del_Ok);
  
} // Fin du void Interruption_Timer1()


//------------------------------------------------------
// réception d'un événement I2C
// mode Esclave
// cette fonction "handler" a été enregistrée dans le setup()
//------------------------------------------------------
     
void receiveEvent(int howMany)
{
  while (1 < Wire.available()) // loop through all but the last
  {
  
    for (int i = 0; i < Nombre_Octets_A_Recevoir; i++) Variable_A_Envoyer_Esclave_1[i] = Wire.read();  

      Octet_Esclave_Recu_1 = Variable_A_Envoyer_Esclave_1[0],HEX;  
      Octet_Esclave_Recu_2 = Variable_A_Envoyer_Esclave_1[1],HEX;
      Octet_Esclave_Recu_3 = Variable_A_Envoyer_Esclave_1[2],HEX;
    
  Wire.write(Variable_A_Envoyer_Esclave_1,Nombre_Octets_A_Recevoir);  
  
  } // Fin du while (1 < Wire.available())  
 
 
  if ((Variable_A_Envoyer_Esclave_1[0] != Old_Variable_A_Envoyer_Esclave_1[0]) || (Variable_A_Envoyer_Esclave_1[1] != Old_Variable_A_Envoyer_Esclave_1[1]) || (Variable_A_Envoyer_Esclave_1[2] != Variable_A_Envoyer_Esclave_1[2]))
  {
  mySUI.print(F("Recevoir Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Esclave_1[0],HEX);
  mySUI.print(F("Recevoir Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Esclave_1[1],HEX);
  mySUI.print(F("Recevoir Event Etat du Maitre: 0x0"));
  Serial.println(Variable_A_Envoyer_Esclave_1[2],HEX); 
  
     } // Fin du if ((Octet_Esclave_Recu_1 != Old_Octet_Esclave_Recu_1) || (Octet_Esclave_Recu_2 != Old_Octet_Esclave_Recu_2) || (Octet_Esclave_Recu_3 != Old_Octet_Esclave_Recu_3))
  
   Old_Variable_A_Envoyer_Esclave_1[0] = Variable_A_Envoyer_Esclave_1[0];
   Old_Variable_A_Envoyer_Esclave_1[1] = Variable_A_Envoyer_Esclave_1[1];
   Old_Variable_A_Envoyer_Esclave_1[2] = Variable_A_Envoyer_Esclave_1[2]; 
 
 
 
 
 
  
} // Fin du void receiveEvent(int howMany)
     
     
     
//------------------------------------------------------
// réponse à une requête I2C
// mode Esclave
// cette fonction "handler" a été enregistrée dans le setup()
//------------------------------------------------------
     
void requestEvent()
{
// Variables à envoyer lors d'une requete I2C recue par le Maitre
  Variable_Requette_Esclave_1[0] = Octet_Esclave_A_Envoyer_1,HEX; 
  Variable_Requette_Esclave_1[1] = Octet_Esclave_A_Envoyer_2,HEX; 
  Variable_Requette_Esclave_1[2] = Octet_Esclave_A_Envoyer_3,HEX;
  
  Wire.write(Variable_Requette_Esclave_1,Nombre_Octets_A_Recevoir);   


  if ((Variable_Requette_Esclave_1[0] != Old_Variable_Requette_Esclave_1[0]) || (Variable_Requette_Esclave_1[1] != Old_Variable_Requette_Esclave_1[1]) || (Variable_Requette_Esclave_1[2] != Old_Variable_Requette_Esclave_1[2]))
  {

 
  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[0],HEX);
  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[1],HEX);
  mySUI.print(F("Requette Event Etat de l'esclave 1: 0x0"));
  Serial.println(Variable_Requette_Esclave_1[2],HEX);

   Old_Variable_Requette_Esclave_1[0] = Variable_Requette_Esclave_1[0];
   Old_Variable_Requette_Esclave_1[1] = Variable_Requette_Esclave_1[1];
   Old_Variable_Requette_Esclave_1[2] = Variable_Requette_Esclave_1[2]; 
 
  } // Fin du if ((Variable_Requette_Esclave_1[0] != Old_Variable_Requette_Esclave_1[0]) || (Variable_Requette_Esclave_1[1] != Old_Variable_Requette_Esclave_1[1]) || (Variable_Requette_Esclave_1[2] != Old_Variable_Requette_Esclave_1[2]))



} // Fin du void requestEvent()

avec mon code, voila ce que le maitre voit:

***********************
Module Maitre
***********************
Envoyer Event Etat du Maitre: 0x0A
Envoyer Event Etat du Maitre: 0x0B
Envoyer Event Etat du Maitre: 0x0C
Requette Event Etat de l'esclave 1: 0x023
Requette Event Etat de l'esclave 1: 0x00
Requette Event Etat de l'esclave 1: 0x0FF

la reponse de sa requette est mauvaise

avec un délais entre l'envoi et la requête, j'obtiens de bon résultats

***********************
Module Maitre
***********************
Envoyer Event Etat du Maitre: 0x0A
Envoyer Event Etat du Maitre: 0x0B
Envoyer Event Etat du Maitre: 0x0C
Requette Event Etat de l'esclave 1: 0x0D
Requette Event Etat de l'esclave 1: 0x0E
Requette Event Etat de l'esclave 1: 0x0F

Partie Maître : Cela devrait fonctionner, malgré quelques "curiosités"

...= Octet_Maitre_A_Envoyer_1,HEX;

Le ,HEX est certainement en trop. Je ne sais pas ce que cela fait, et suis même surpris que cela compile.

  • utilisation redondantes de variables seules et de tableaux qui contiennent les mêmes valeurs
... || (Variable_A_Envoyer_Maitre_1[2] != Variable_A_Envoyer_Maitre_1[2]))

manque un _Old

Partie Esclave :

Là y a les mêmes curiosités + d'autres points qui ne vont pas du tout

onReceive() n'exploite pas howmany, contient un while dont je ne vois pas l'utilité, et surtout contient un Wire.write pas logique avec le protocole I2C et des print à proscrire dans une routine d'interruption.

onRequest() contient également des print à supprimer impérativement

Globalement, j'ai l'impression que tu n'appréhendes pas correctement le codage des routines d'interruption côté esclave

Bon, voila la trame avec la requête de lecture et voila la réponse quelque 10mS plus tard grâce au délais. mais comment pouvoir garder le bus occupé jusqu’à la réponse de l’esclave

RECEPTION.JPG

ENVOI I2C.JPG

bien vu pour le Old oublié, pour le how many, obligé de le mettre sinon ça compile pas... pour les HEX, je les ai toujours mis pour etre sur qu'il voit bien de l'hexa... Ok pour les print, je les enleveraient quand ce sera ok... Après, je suis débutant sur Arduino donc mea-culpa

HEX n'est qu'un format d'affichage quand tu fais un print.

La valeur d'une variable est juste un nombre.
Tu peux choisir de l'afficher en binaire, en hexadécimal ou en décimal, mais cela ne change rien à la manière dont il est stocké en mémoire.

Pour les print côté esclave il faut les enlever maintenant, car c'est une cause possible de dysfonctionnement.

Une routine d'interruption, c'est un code qui se déclenche à un moment que tu ne maîtrises pas.

Tu as un programme principal, avec ses traitements ou appels de fonctions décrits dans la fonction loop().
Et à n'importe quel endroit de ton programme principal, le code peut s'interrompre pour aller exécuter ta routine d'interruption, avant de reprendre là où il en était.

La routine d'interruption doit être la plus simple et la plus rapide possible.
Idéalement, elle ne doit manipuler que quelques variables globales, prévues à cet effet et déclarées en volatile.

Le traitement principal (avec des print et tout ce que tu veux) doit rester dans le loop

A mon sens, le traitement “béton” de la réception côté esclave devrait toujours ressembler à ceci :

//Variables de réception des messages I2C entrants
volatile byte buffer_reception[BUFFER_LENGTH];//BUFFER_LENGTH défini dans Wire.h
volatile byte nb_octets_a_traiter = 0;
volatile unsigned long nb_octets_perdus = 0;

void loop()
{
  ...
  if (nb_octets_a_traiter > 0) traiterReception();
}

void receiveEvent(int howMany)
{
  if (nb_octets_a_traiter == 0) //buffer de réception dispo, sinon destruction du message sans traitement
  {
    while (nb_octets_a_traiter < sizeof(buffer_reception) && nb_octets_a_traiter < howMany)
    {
      buffer_reception[nb_octets_a_traiter++] = Wire.read();
    }
  }
  while (Wire.available())
  {
    nb_octets_perdus++;
    Wire.read();
  }
}

void traiterReception()
{
  byte buffer_traitement[sizeof(buffer_reception)];
  byte nb;
  
  //Transfert des octets à traiter dans des variables locales
  nb = nb_octets_a_traiter;
  for (byte i = 0; i < nb; i++)
  {
    buffer_traitement[i] = buffer_reception[i];
  }
  nb_octets_a_traiter = 0; //A partir de là, le buffer de réception est dispo pour enregistrer tout nouveau message entrant
  
  //Traitement effectif du message à partir des variables locales
  ...
}

Par exemple

Dans ton cas, où le maître envoie des données et en demande d'autres juste après : il faut imaginer que côté esclave, tu peux être en train de traiter les print associés à la réception, et être interrompu pour exécuter l'émission.

D'ailleurs de manière générale l'émission est souvent piégeuse.
Supposons que l'esclave doive remonter au maître la valeur de deux variables a et b.

Fatalement, quelque part dans ton programme esclave, il va y avoir des instructions qui changent les valeurs de a et b.
Mais même dans le cas où a=xxx; est immédiatement suivi par b=yyy; l'interruption onRequest peut être déclenchée entre les deux affectations.
Et du coup, ton message sortant peut contenir la nouvelle valeur de a et l'ancienne valeur de b, ce qui n'est pas forcément cohérent.

Et encore, je ne parle que du cas simple où a et b sont des variables codées sur un seul octet.
Pour une variable occupant plusieurs octets, la modification de valeur occupe forcément plusieurs cycles horloge, au milieu desquels l'interruption peut survenir, avec pour conséquence la remontée d'une valeur qui peut être inconsistante.

Merci pour ton retour, j'essaye d'incorporer ton code au miens et je reviens vers toi après.
J'ai également des problèmes de caractères bizarres qui s'affichent par moment sur mes lcd I2C, tu crois que cela a avoir avec mon problème actuel ???

Comment dois je faire avec ce nouveau code.
je transmet depuis l'esclave avec des wire.write

  Wire.beginTransmission(Adresse_I2C_Esclave_1); // transmit to device

// Variables à envoyer
  Variable_A_Envoyer_Maitre_1[0] = Octet_Maitre_A_Envoyer_1; 
  Variable_A_Envoyer_Maitre_1[1] = Octet_Maitre_A_Envoyer_2; 
  Variable_A_Envoyer_Maitre_1[2] = Octet_Maitre_A_Envoyer_3;

  Wire.write(Variable_A_Envoyer_Maitre_1,Nombre_Octets_A_Recevoir); 
  Wire.endTransmission();    // stop transmitting

mais comment les récupérer coté esclave et comment retourner des infos au maitre ???

Un petit up