[CLOS car foireux] communication i2c entre deux arduino, je suis pas loin

Bonjour
Je me suis intéressé au i2c hier, j'ai fait un petit tuto pour les LCD i2c.
Aujourd'hui, je me suis calé en tête de faire communiquer deux arduino.

Le Maitre UNO:
Il affiche sur LCD une valeur Luminosite qu'il demande à l'esclave

L'esclave NANO:
Il lit les valeurs analogiques de deux capteurs, en fait une moyenne et, lorsqu'une requête lui arrive, il renvoie cette moyenne. Tout ça en i2c.

Je ne dois pas être loin. Dans un premier temps c'est 1 qui permet de déclencher cet échange, plus tard j'essaierai avec la lettre L...

Actuellement, ça ne fonctionne pas: Luminosite=-1, et c'est pas ce que j'attends.

Et le code:

Maitre

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// indiquer (adresse i2c, nombre de colonnes, nombre de lignes)
LiquidCrystal_I2C lcd(0x3F, 16, 2);

void setup()
{
  // initialise l'afficheur LCD
  lcd.begin();

  // efface l'ecran et place le curseur en 0,0
  lcd.clear();

  // ecrire à la position par defaut
  lcd.print("Arduino i2c comm");
  lcd.setCursor(2, -1);
  lcd.print("kammo making");
  // petite pause
  delay(1000);


  // faire clignoter le block
  lcd.blink_on();
  // petite pause
  delay(2000);
  lcd.blink_off();
  lcd.clear();
  lcd.print("Luminosite");

}

void loop()
{

  lcd.setCursor(12, -1);
  lcd.print(millis());
  delay(100);
  lcd.setCursor(12, -1);
  lcd.print("    ");



}

Esclave:

#include <Wire.h> // blibliothèque de comm i2c

int SomLum; // la somme des lectures
int MoyLum; // la moyenne des lectures
int A1Lum;  // lecture sur A1
int A2Lum;  // lecture sur A2


void setup() {
  Wire.begin(4); // adresse de l'esclave
  Wire.onReceive(RecepEvent); // fonction de réception
}

void loop() {

  for (int i = 0; i < 10; i++) { // on effectue 10 lectures
    A1Lum = analogRead(A1);      // on lit A1
    A2Lum = analogRead(A2);      // on lit A2
    SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
  }

  MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

  SomLum = 0; // remise à zero des variables
  MoyLum = 0;
}

void RecepEvent(int Q) {// fonction qui se lance si un evenement a lieu
  byte Requete = Wire.read(); // recevoir la requete
  if (Requete == 1) { // si la requete est la lettre L (comme Luminosite)
    Wire.write(MoyLum);
  } // envoyer MoyLum en reponse
}

Merci d'avance

Selon la spécification I²C, les adresses 0-->7 et 78-->7F sont réservées.
Ensuite, dans le programme du maître on ne voit aucune requête à destination de l'esclave.

Dans le programme de l'esclave, juste après avoir calculé la moyenne, tu la remets à zéro.

C'est valide de mettre un argument négatif pour la colonne dans setCursor ?
Voici le code de la bibli :

void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
	if (row > _rows) {
		row = _rows-1;    // we count rows starting w/0
	}
	command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

si row est négatif, on va chercher un élément de tableau d'indice négatif, ça craint.

Par rapport à la réponse de fdufnews, en effet, il vaudrait mieux mettre les acquisitions et le calcul de la moyenne dans la fonction RecepEvent :

void loop() {



}

void RecepEvent(int Q) {// fonction qui se lance si un evenement a lieu
  byte Requete = Wire.read(); // recevoir la requete
  if (Requete == 1) { // si la requete est la lettre L (comme Luminosite)
  SomLum = 0; // remise à zero des variables
  MoyLum = 0;
  for (int i = 0; i < 10; i++) { // on effectue 10 lectures
    A1Lum = analogRead(A1);      // on lit A1
    A2Lum = analogRead(A2);      // on lit A2
    SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
  }

  MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

    Wire.write(MoyLum);
  } // envoyer MoyLum en reponse
}

Merci

En adresse j'ai mis 14... un peu au pif... est-ce que je peux faire plus intelligent?

En ce qui concerne le -1 du LCD, aucun souci, si je remplace Luminosite par millis() j'ai bien un compteur qui défile. En plus je ne comprends pas ton code ^^

Pour écarter une erreur de calcul de la part de l'esclave, j'ai remplacé la moyenne par 123.

J'ai toujours -1 qui s'affiche sur l'écran LCD

Est-ce que ça vient de l'adresse? vu que j'ai mis ça au pif
Est-ce que ça vient de la commande de requete, de l'écriture (slve->mastr)?
Est-ce un problème de lecture (Mstr sur Slve) comme on peut en avoir avec les fonctions?

Je pensais être tout près du but ^^

Je remets les codes:
Master:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

int Luminosite; // variable qui recupere la luminosite sur esclave

// indiquer (adresse i2c, nombre de colonnes, nombre de lignes)
LiquidCrystal_I2C lcd(0x3F, 16, 2);

void setup()
{
  Wire.begin(); // se connecter au bus i2c, le maitre n'a pas besoin d'adresse
  lcd.begin();  // initialise l'afficheur LCD
  lcd.clear();  // efface lcd

  // petite intro:
  lcd.print("Arduino i2c comm");
  lcd.setCursor(2, -1);
  lcd.print("kammo making");
  delay(1000); // petite pause
  // faire clignoter le block
  lcd.blink_on();
  // petite pause
  delay(2000);
  lcd.blink_off();
  lcd.clear();
  lcd.print("Luminosite");

}

void loop()
{
  GetData();   // va chercher les donnees
  LCDPrint();  // afficher ces données
  delay(300);  // temporisation
}


void GetData() { // fonction pour recuperer les donnees
  Wire.beginTransmission(14); // requete adresse 14
  Wire.write(1);           // envoyer requete
  Luminosite = Wire.read();  // récupérer la valeur dans Luminosite
}

void LCDPrint() { // fonction pour afficher sur lcd
  // combine du pauvre pour effacer la ligne
  lcd.setCursor(12, -1);
  lcd.print("    ");
  // ecrire la valeur
  lcd.setCursor(12, -1); // place le curseur
  lcd.print(Luminosite); // ecrit la valeur de Luminosite
}

Je m'étais emmêlé les pinceaux, j'avais ouvert deux fois le code et corrigé un seul...

Slave:

#include <Wire.h> // blibliothèque de comm i2c

int SomLum; // la somme des lectures
int MoyLum; // la moyenne des lectures
int A1Lum;  // lecture sur A1
int A2Lum;  // lecture sur A2


void setup() {
  Wire.begin(14); // adresse de l'esclave
  Wire.onReceive(RecepEvent); // fonction de réception
}

void loop() {

  for (int i = 0; i < 10; i++) { // on effectue 10 lectures
    A1Lum = analogRead(A1);      // on lit A1
    A2Lum = analogRead(A2);      // on lit A2
    SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
  }

  MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

  SomLum = 0; // remise à zero des variables
}

void RecepEvent() {// fonction qui se lance si un evenement a lieu
  byte Requete = Wire.read(); // recevoir la requete
  if (Requete == 1) { // si la requete est la lettre L (comme Luminosite)
    SomLum = 0; // remise à zero des variables
    MoyLum = 0;
    for (int i = 0; i < 10; i++) { // on effectue 10 lectures
      A1Lum = analogRead(A1);      // on lit A1
      A2Lum = analogRead(A2);      // on lit A2
      SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
    }

    MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

    Wire.write(123); // 123 en attendant que ça communique
  } // envoyer MoyLum en reponse
}

J'ai ajouté un flash led lors de l'envoi de '123' de slave vers master, et plusieurs plus rapides lorsque la requete est repérée.
Ca ne marche pas, ca ne s'allume pas (la led s'allume une seconde au démarrage pour etre sur qu'elle fonctionne)

#include <Wire.h> // blibliothèque de comm i2c

int SomLum; // la somme des lectures
int MoyLum; // la moyenne des lectures
int A1Lum;  // lecture sur A1
int A2Lum;  // lecture sur A2


void setup() {
  Wire.begin(14); // adresse de l'esclave
  Wire.onReceive(RecepEvent); // fonction de réception
  pinMode(2,OUTPUT);
  digitalWrite(2,HIGH);
  delay(1000);
  digitalWrite(2,LOW);
}

void loop() {

  for (int i = 0; i < 10; i++) { // on effectue 10 lectures
    A1Lum = analogRead(A1);      // on lit A1
    A2Lum = analogRead(A2);      // on lit A2
    SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
  }

  MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

  SomLum = 0; // remise à zero des variables
}

void RecepEvent() {// fonction qui se lance si un evenement a lieu
  byte Requete = Wire.read(); // recevoir la requete
  if (Requete == 1) { // si la requete est la lettre L (comme Luminosite)

  for (int i=0; i<4; i++) {
    digitalWrite(2,HIGH);
    delay(50);
    digitalWrite(2,LOW);
    delay(50);}
   
    SomLum = 0; // remise à zero des variables
    MoyLum = 0;
    for (int i = 0; i < 10; i++) { // on effectue 10 lectures
      A1Lum = analogRead(A1);      // on lit A1
      A2Lum = analogRead(A2);      // on lit A2
      SomLum = SomLum + A1Lum + A2Lum; // on fait la somme
    }

    MoyLum = SomLum / 20;  // 2 capteurs, 10 lectures, ça fait 20

    Wire.write(123); // 123 en attendant que ça communique
    digitalWrite(2,HIGH);
    delay(200);
    digitalWrite(2,LOW);
  } // envoyer MoyLum en reponse
}

Il n'y a pas requete...

Je me suis inspiré de cet article, j'aurais du le tester avant :s
https://letmeknow.fr/blog/2013/08/09/tuto-utiliser-2-arduinos-en-serie/
Son adresse est 4, c'est déjà étrange...

Tu t'enmêles effectivement les pinceaux…
En I2C, le maître envoie des octets OU demande des octets, toi tu mélanges les deux…
Et l'escalve du coup reçoit des octets OU renvoie des octets.
Ce sont deux choses bien distinctes.

Dans un cas très simple, si l'esclave doit toujour renvoyer la mesure de luminosité, le maître n'a pas besoin d'envoyer des infos, il doit juste réclamer à l'esclave:

Maître:

setup ()
{
    Wire.begin ();
    Serial.begin (115200);
}

loop ()
{
    short    mesure;


    Wire.requestFrom (50, sizeof (short)); // addresse au pif, mais la même que plus dans le code de l'esclave - on demande 2 octets à l'esclave
    mesure = 0;
    while (Wire.available ())
        mesure = mesure * 256 + Wire.read ();
    Serial.print ("mesure: ");
    Serial.print (mesure);

    delay (1000);
}

Esclave:

setup ()
{
    Wire.begin (50); // adresse au pif, mais la même que plus dans le code du maître
    Wire.onRequest (requestHandler);
}

loop ()
{
}

void requestHandler (void)
{
    // bien que le maître ait demandé explicitement 2 octets, l'esclave n'en sait rien… la lib Wire est un peu bizarre…
    short    mesure;


    mesure = analogRead (A1);
    Wire.write (&mesure, sizeof (mesure)); // on envoie 2 octets
}

Si tout se passe bien, chaque seconde le maître réclame des données à l'esclave, qui les lui envoie.

EDIT:

regarde les deux tutos:

cbrandt:
Tu t'enmêles effectivement les pinceaux…

Salut ^^

Ouais, en effet, mon code ne ressemble pas vraiment à ça ^^

Short mesure c'est quoi?

Ensuite je ne vois pas trop où tu indiques les octets

mesure = mesure * 256 + Wire.read ();
Ca j'imagine que c'est propre à ton projet?

Bon à voir ce que tu m'as envoyé je suis à des plombes de ce que je pensais faire >< hinhin

je clos ce topic et je recommence tout depuis le début, ya plus simple!

Merci

rah mais faut pas abandonner comme ça ! :slight_smile:

short: c'est en gros comme un int

mesure = mesure * 256 + Wire.read ();
quelques explications:
Wire.read () renvoie UN octet
la valeur que tu veux lire va de 0 à 1023 (puisque issue de analogRead), et ça ne tient pas sur 1 octet. Donc il en faut 2.
Ces deux octets on va devoir les lire un par un, et comme chaque octet peut aller de 0 à 255, on reconstruit la valeur finale à partir des deux octets lus.

Dans ton projet tu veux j'imagine envoyer des commandes différentes, c'est ça ? Et que l'esclave renvoie une réponse en fonction de la commande reçue ? Ça complique un peu, car l'esclave va SEPAREMENT recevoir la commande (dans un fonction précisée avec Wire.onReceive) et la demande de la réponse (dans un fonction précisée avec Wire.onRequest). L'esclave devra donc mémoriser la commande reçue pour pouvoir répondre après…

Laisse moi quelques minutes, je vais essayer de te faire un truc…

C'est sympa^^

Oui je voudrais que le maitre puisse faire plusieurs requetes, de préférence avec des lettres, c'est plus simple de s'y retrouver ensuite (côté humain)

Le but c'est de faire un module i2c qui contienne différents types de capteurs (infrarouge, ultrasons)
Le maitre envoie la requete des capteurs dont il souhaite récupérer la valeur: 'i' ou 'u'.

Le but c'est de gagner des pins sur le maitre et de s'amuser avec une sorte de multiplexage...
Ca rentre dans mon projet de robot sumo+line tracker+contournement d'obstacles :slight_smile:

Bon, comme d'hab, c'était plus long que prévu…

La lib Wire a quand même des bizarreries, j'ai un peu galéré. Je dirai même plus, elle est bugguée…

Donc voici le code du maître:

//
//  maitre.cpp
//
//  Created by Christian Brandt on 19/06/18.
//
//

#include <Wire.h>

#define ADRESSE_ESCLAVE 14

void envoiCommande (char *commande);
void lectureReponse (char *reponse);

//--------------------------------------------------------------------------------------------------------------------------------------------

void setup (void)
{
 Wire.begin ();
 
 Serial.begin (9600);
 Serial.println ();
 Serial.println ();
 Serial.println ();
 Serial.println ("----------------------------");
 Serial.println (" MAITRE ");
 Serial.println ("----------------------------");
}

//--------------------------------------------------------------------------------------------------------------------------------------------

void loop (void)
{
 char reponse[32]; // max 32 octets pour la réponse


 Serial.println ("----------------------------");

 envoiCommande ("luminosite");
 lectureReponse (reponse); 
 Serial.print ("luminosité: ");
 if (strlen (reponse) == 0)
 Serial.println (" - pas de réponse -");
 else
 Serial.println (reponse);

 envoiCommande ("millis");
 lectureReponse (reponse); 
 Serial.print ("millis: ");
 if (strlen (reponse) == 0)
 Serial.println (" - pas de réponse -");
 else
 Serial.println (reponse);
 
 delay (1000);
}

//--------------------------------------------------------------------------------------------------------------------------------------------

void envoiCommande (char *commande)
{
 // on envoie des octets à l'esclave, ils seront reçus par l'esclave dans la fonction précisée avec Wire.onReceive 
 Wire.beginTransmission (ADRESSE_ESCLAVE);
 Wire.print (commande);
 Wire.endTransmission ();
 
 Serial.print ("[MAITRE] commande envoyée: ");
 Serial.println (commande);
}

//--------------------------------------------------------------------------------------------------------------------------------------------

void lectureReponse (char *reponse)
{
 int taille;
 int c;
 
 
 // on demande des octets à l'esclave, la demande sera reçue dans la fonction précisée avec Wire.onRequest
 Wire.requestFrom (ADRESSE_ESCLAVE, 32); // on demande 32 octets, mais l'esclave pourra en renvoyer moins

 // la réponse fera toujour le nombre d'octets demandés, même si l'esclave en renvoie moins
 taille = 0;
 do
 {
 c = Wire.read ();
 reponse[taille++] = c;
 }
 while (c != '\n' && c != -1 && taille < 32);
 reponse[taille--] = 0;

 Serial.print ("[MAITRE] réponse reçue (");
 Serial.print (taille);
 Serial.print (" octets): ");
 Serial.println (reponse);
}

//--------------------------------------------------------------------------------------------------------------------------------------------

et voici l'esclave:

//
//  esclave.cpp
//
//  Created by Christian Brandt on 19/06/18.
//
//

#include <Wire.h>

#define ADRESSE_ESCLAVE 14

char commande[32];

void receptionCommande (int longueur);
void envoiReponse (void);
int lectureLuminosite (void);

//--------------------------------------------------------------------------------------------------------------------------------------------

void setup (void)
{
 Wire.begin (ADRESSE_ESCLAVE);
 Wire.onReceive (receptionCommande);
 Wire.onRequest (envoiReponse);

 Serial.begin (9600);
 Serial.println ();
 Serial.println ();
 Serial.println ();
 Serial.println ("----------------------------");
 Serial.println (" ESCLAVE ");
 Serial.println ("----------------------------");
}


//--------------------------------------------------------------------------------------------------------------------------------------------

void loop (void)
{
}

//--------------------------------------------------------------------------------------------------------------------------------------------

void receptionCommande (int longueur)
{
 int taille;
 
 
 // on lit tous les octets envoyés par le maître (on ignore 'longueur')
 taille = 0;
 while (Wire.available ())
 commande[taille++] = Wire.read ();
 commande[taille] = 0;
 
// Serial.print ("[ESCLAVE] commande reçue: ");
// Serial.println (commande);
}

//--------------------------------------------------------------------------------------------------------------------------------------------

void envoiReponse (void)
{
 unsigned long ms;
 int lum;
 char reponse[32];
 
 
 // on traite la commande précédemment reçue
 if (strcmp (commande, "luminosite") == 0)
 {
 lum = lectureLuminosite ();
 snprintf (reponse, 32, "%d\n", lum);
 Wire.write (reponse);

 Serial.print ("[ESCLAVE] commande 'luminosite', réponse envoyée: ");
 Serial.print (reponse);
 }
 else if (strcmp (commande, "millis") == 0)
 {
 ms = millis ();
 snprintf (reponse, 32, "%d\n", ms);
 Wire.write (reponse);

 Serial.print ("[ESCLAVE] commande 'millis', réponse envoyée: ");
 Serial.print (reponse);
 }
}

//--------------------------------------------------------------------------------------------------------------------------------------------

int lectureLuminosite (void)
{
 // ici, faire les analogRead et le calcul de la moyenne
 
 return 123;
}

//--------------------------------------------------------------------------------------------------------------------------------------------

Quelques notes:

  • le maître demande toutes les secondes la luminosité et (à titre d'exemple) le temps passé par l'esclave depuis la mise en route (fonction 'millis ()')

  • l'esclave ne fait rien dans le loop () et répond aux requêtes

  • les commandes et réponses sont au format texte lisible par un humain, ça prend quelques microsecondes de plus à envoyer/recevoir, mais ça rend les programmes plus lisibles.

  • dans l'esclave, pour cause de bug de la lib Wire, il faut construire la réponse à part, et l'envoyer avec UN SEUL Wire.write ou Wire.print - sinon seul les données du dernier Wire.write ou Wire.print seront reçues par le maître. Ne pas utiliser Wire.println, car ça fait au final deux Wire.write (voir le code de la lib Wire elle-même).
    Donc bien utiliser snprintf pour construire la réponse, sans oublier le \n dans la réponse - c'est ce charactère que le maître attend pour savoir que la réponse est complète. Si on avait que des réponses de la même taille on pourrait simplifier, mais là c'est plus flexible.

Punaise, c'est propre ^^
J'ai eu du mal à émerger ce matin, encore une cafetière et j'essaie ça.
Un grand merci, c'est super sympa! Hop un karma ^^

Il y a des choses que je ne connaissais pas^^ pas étonnant:

void envoiCommande (char *commande);
void lectureReponse (char *reponse);

Quel intérêt de déclarer en tête de programme, à part pour la lisibilité?

  • indique un pointeur? ça faut vraiment que je potasse... J'évite toujours les tableaux ^^

Il y a aussi deux fonctions que je n'avais jamais vues, mais ça je vais demander à Gougoule :wink:

Déclarer les fonctions avant l'endroit où elles sont utilisées est normalement obligatoire. Cependant l'IDE arduino ajoute automatiquement les déclarations.

  • indique bien un pointeur... ça devient vite indispensable de les maîtriser ! Mais ce n'est pas aussi compliqué que ça en a l'air :slight_smile: