master reader et slave sender en I2C

Bonsoir, j’ai besoin de faire dialoguer deux machines entre elles…
Le maître demande à l’esclave quelque octets et c’est là qu’est le problème: je ne sais pas au moment où je lui demande, combien d’octets l’esclave va me retourner.
Voici le sketch master_reader:

// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this

// Created 29 March 2006

// This example code is in the public domain.

//////////////////////////////////////////////////////////////////
//                                                              //
//          Test en I²C de réception du maître                  //
//                                                              //
//////////////////////////////////////////////////////////////////

#include <Wire.h>

#define bouton  4  // bouton test sur pin 4

char message[100];    // buffer de caractères pour réception I2C


void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  
  pinMode(bouton, INPUT);
  
  Serial.begin(9600);  // start serial for output
}

void loop() {
    if (!digitalRead(bouton)){
      master_read (2,6);    // lecture en I2C sur l'esclave 2 de 6 octets
            
    }
    
}

/////  Fonction de lecture d'information par le maître sur le bus I²C  /////
void  master_read (int d, int n)  {
    Wire.requestFrom(d, n);    // request n bytes from slave device #d
    char c;
    while (1 < Wire.available()) { // loop through all but the last
     c = Wire.read(); // receive a byte as character
     Serial.print(c);         // print the character
    }
   c = Wire.read(); // receive a byte as character
   Serial.println(c);

   delay(250);
 }

et celui du slave_sender:

// Wire Slave Sender
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Sends data as an I2C/TWI slave device
// Refer to the "Wire Master Reader" example for use with this

// Created 29 March 2006

// This example code is in the public domain.

//////////////////////////////////////////////////////////////////
//                                                              //
//          Test en I²C d'émission de l'esclave                 //
//                                                              //
//////////////////////////////////////////////////////////////////


#include <Wire.h>


//char * entete = "Arduino";    // string à transmettre
char * mess = "Arduino";    // string à transmettre



void setup() {
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

void loop() {
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  //Wire.write("hello "); // respond with message of 6 bytes
  
  Wire.write(mess); // respond with message of x bytes
  
  // as expected by master
}

En gros, je voudrais pouvoir recevoir n’importe quel mot (string) à partir du moment où je lui demande et non avoir à me soucier de la longueur de celui-ci; il n’y aurait pas la fonction “readStringUntil()” dans la class stream https://www.arduino.cc/en/Reference/Stream qui pourrait m’aider ? Mais je ne l’ai pas vu implémentée.

Donc comment feriez-vous pour transmettre un string de longueur indéfinie sachant que le maître lui demandant impose une longueur finie avec la fonction Wire.requestFrom (addresse esclave I2C, nombre d’octets) ?

Hello

Déjà “infinie” = 30 caractères max sinon bidouiller ou réécrire Wire.h car celle-ci est basée sur un buffer de cette taille.

En réalité le maître ne transmet pas à l’esclave la quantité d’octets attendus.
Il donne la parole à l’esclave, et la coupe lorsqu’il a reçu le nombre d’octets attendus, ou bien sur time out.

Ok, bah concrètement, comment tu fais avec la fonction Wire.requestFrom(d, n) sans savoir combien tu vas en recevoir (donc n = x), y'a pas le '\0' ...

exempeul is possibol ?

Détaillons Wire.requestFrom(d, n)

Cette fonction retourne le nombre d'octets effectivement reçus de l'esclave

Maintenant soulevons le capot et entrons à l'intérieur de cette fonction

(voir également le protocole I2C bien expliqué ici)

L'arduino écrit l'adresse de l'esclave sur le bus i2c et lui donne la parole (en lui envoyant un bit R/W à 0).
L'esclave commence alors à causer.
A chaque octet écrit par l'esclave sur le bus, le maitre répond par un acquittement. Et quand le maître en a marre il envoie un STOP sur le bus et l'eclave arrête de causer.

Tous les octets retournés par l'esclave sont mémorisés dans un buffer interne à la bibliothèque.

Lorsque n octets ont été reçus et chargés dans le buffer, l'arduino envoie à l'esclave une condition stop.
Et la fonction Wire.requestFrom() rend la main en retournant n.

Si l'esclave renvoie moins de n octets, Wire.requestFrom() va tomber en time out et retourner le nombre d'octets effectivement reçus.

Charge ensuite à l'arduino d'effectuer des Wire.read() pour prendre connaissance des octets reçus, mais cette fonction ne s'accompagne d'aucune communication sur le bus I2C. Elle ne fait que lire les octets qui ont été préalablement chargés dans le buffer en RAM.

A noter que l'arduino esclave n'a pas connaissance du nombre n d'octets attendus par le maître. Cette quantité n'est pas transmise dans le protocole de communication.

Si nécessaire, on peut envoyer ce nombre n à l'esclave par une séquence de wire.write() avant le requestFrom().

Pour ton histoire de string :

Si par "longueur indéfinie" tu veux dire n'importe quelle longueur inférieure ou égale à 30 caractères, alors pas de souci.

int nb_reçus = Wire.requestFrom(d, 30);

et après tu reconstitues le string avec des Wire.read().

Si c'est plus long il te faut soit bidouiller la lib pour augmenter la taille du buffer interne (côté maître ET côté esclave), soit gérer plusieurs requestFrom() en suivant.

Merci beaucoup pour ces explications mais y’a un truc que je pige pas trop par rapport à ta suggestion de solution avec:

int nb_reçus = Wire.requestFrom(d, 30);

Là, le maître demande toujours 30 octets que l’esclave lui enverra quelque soit la longueur du string à envoyer ou il lira 30 octets dans le buffer mais le résultat est le même (voir l’image)

Comment détecter la fin su tring ?

/////  Fonction de lecture d'information par le maître sur le bus I²C  /////
void  master_read (int d)  {
    
    int i = 0;
  
    int n = Wire.requestFrom(d, 30);    // request n bytes from slave device #d
    char c;
    while (1 < Wire.available()) { // loop through all but the last
     c = Wire.read(); // receive a byte as character
     
     message[i] = c;    // caractère reçu dans le tampon
     i++;               // incrémentation du tamon
     
     //Serial.print(c);         // print the character
    }
   c = Wire.read(); // receive a byte as character
   
   message[i] = c;
   
   message[n] = '\0';
   
   Serial.println(message);
   Serial.print(" et n = ");
   Serial.println(n);

   delay(200);
 }

Le maitre ne demande rien.
Il attend d'avoir reçu 30 octets pour mettre fin à la communication
Si l'esclave en envoie moins, le maitre finira par couper la communication apres un petit delai d'attente.
Et requestFrom retournera le nombre d'octets effectivement recus.
Ton prog devrait bien fonctionner.
Affiche la valeur de n pour verifier

Bah nan, j'affiche déjà n dans mon programme et c'est toujours 30, voir l'image jointe ci-dessus. il lit des ÿ ...

Bonjour

On dirait que je t'ai donné une mauvaise info. Pourtant j'avais bien vu la gestion des timeout en décortiquant ce qu'il y a sous Wire.h

En cherchant sur le net je vois effectivement un bug signalé par le Grand Maître Arduino Nick Gammon sur le requestFrom, qui retourne le nombre d'octets attendus au lieu du nombre d'octets effectivement reçus, avec un fix annoncé au 1er juillet 2015.

Peut-être qu'une actualisation de ton IDE résoudrait ce point.

Sinon, pour que ton code soit indépendant de la version des "bibliothèques de base", je te propose deux solutions :

  1. envoyer systématiquement un caractère supplémentaire de fin de chaîne (tout sauf 0x00, par exemple 0x01 ou 0xff). Comme ça côté maître tu sauras repérer la fin de chaine.

  2. transmettre la longueur de la chaîne en tant que donnée, soit en binaire dans le premier caractère transmis, soit proprement dans un paquet séparé, par exemple :

Wire.beginTransmission(d);
Wire.write(0); //place l'esclave en mode "envoi de longueur"
Wire.endTransmission();
Wire.requestFrom(d, 1);
byte longueur = Wire.read();
Wire.beginTransmission(d);
Wire.write(1); //place l'esclave en mode "envoi de texte"
Wire.endTransmission();
Wire.requestFrom(d, longueur);
...

En ajoutant bien sur les tests de bonne transmission sur les requestFrom() et les endTransmission()

a priori ÿ correspond à 0xff donc en version crade, tu as déjà ton marqueur de fin de chaîne...

Ceci a des chances de marcher

/////  Fonction de lecture d'information par le maître sur le bus I²C  /////
void  master_read (int d)  {
    
    int i = 0;
  
    Wire.requestFrom(d, 30);

    char c;
    while (Wire.available()) {
     c = Wire.read();
     if (c != 0xff) message[i++] = c;
    }
   message[i] = (char) 0;

   Serial.println(message);
   Serial.print(" et i = ");
   Serial.println(i);

   delay(200);
 }

Bonjour et merci encore pour ton aide mais le "problème perdure:

Il ne reconnaît pas le 0xFF ou le ÿ donc impossible de fermer le string, il fait toujours 30 caractères.

C’est un bug assez fréquent d’après google

/////  Fonction de lecture d'information par le maître sur le bus I²C  /////
void  master_read (int d)  {
    
    int i = 0;
    char c;
  
    Wire.requestFrom(d, 30);    // request n bytes from slave device #d
    while (1 < Wire.available()) { // loop through all but the last
     c = Wire.read(); // receive a byte as character
     
     if (c != 0xFF) message[i++] = c;
     //if (c != 'ÿ') message[i++] = c;
    
     message[i] = (char) 0;
     
     //message[i] = c;    // caractère reçu dans le tampon
     //i++;               // incrémentation du tamon
     
    }
    
   Serial.print(" ici i = ");
   Serial.println(i);
   c = Wire.read(); // receive a byte as character
   
   message[i] = c;
    
   Serial.println(message);


   delay(200);
 }

Essaye avec

   c = Wire.read(); // receive a byte as character
   Serial.print(c);
   Serial.print("(0x");
   Serial.print((int) c, HEX);
   Serial.print(')');

Ce qui te permettra de connaître la valeur exacte du caractère de fin de chaîne

NB : ton test while(1 < Serial.available()) ne lit pas le dernier caractère reçu

Et si avec ça cela tu n’y arrives pas, voir la solution propre en #8 ci-dessus (faut tout lire hein :slight_smile: )

C’est bon mais c’est vrai que je préférais faire en sorte que le maître ne demande qu’une seule lecture même si la solution de demande de longueur est beaucoup plus propre => j’la garde sous la main.

Cependant il détecte maintenant la fin de caractère 0xFF mais en 32 bits: 0xFFFFFFFF alors que le char est bien du 8 bits, n’est-il ?

char est bien sur 8 bits = 1 octet.
Là je pense que c'est juste une blague de la méthode print() qui passe le nombre sur 32 bits.
Tu y es presque !

char est signé. Si on le cast en int, il y a extension du signe.

oui ça à la limite je veux bien, mais le 32 bits?

Il faudrait voir la taille de l'int sur la plate-forme utilisée

NEWS !!!

Je me suis un Trumpé en fait; j’ai fait un système de telle sorte que l’ordre de mesure du capteur est envoyé par le maître à l’esclave ayant le module RF émetteur et la réponse de celui-ci serait reçu par un autre esclave ayant lui, le module RF récepteur, voir domo.jpg

L’information de mesure de capteur à me renvoyer est envoyée via RF au module “marcel” (soudé le 16 janvier) qui se charge de faire ce qu’il a à faire et de me renvoyer aussi par RF le résultat, voir domo_marcel.jpg

Cependant, en I2C, il me faut autre chose que ce que je voulais jusqu’à présent; je ne veux pas demander à un esclave qu’il me donne la valeur, mais envoyer l’ordre de le faire à un esclave dont le résultat arrivera au final par m’être retourné par un autre esclve, on comprends tout de suite avec les schémas.

Je voudrais donc envoyer l’ordre de faire la mesure via ce système et ensuite le récupérer via un autre système.

Pour cela, il faudrait que l’esclave qui doit me retourner l’information du capteur puisse m’interpelé en interruption pour me dire de le lire, là à ce moment, j’ai besoin de tout ce pourquoi ce sujet à été créé.

Donc il y a .requestFrom qui me servira mais comment implémenter la routine d’interruption du maître que l’esclave lui enverra pour lui dire qu’il est à lui envoyer la mesure…

Y’a trop d’mots mais avec la simplicité des schémas…

Ou une solution serait:

qu'en même temps que je demande la température en I2C à l'esclave(1) du capteur de la carte(M) via RF,

je lance en même temps un timer d'interruption d'un certain temps

(le temps que l'ordre soit transmis en RF par l'esclave(1), que la lecture de température soit faite par la carte(M), que celle-ci renvoie l'information de température en RF à un autre esclave(2) du maître qui lui, est à l'initiative de la demande de température)

Et, au bout de ce certain temps, le maître est interrompu par l'esclave(2) ayant reçu la température et,

à ce moment là, le maître demande à cet l'esclave la température en I2C...?