I2C : Fonction onReceive

Bonjour à tous,

J’ai une petite question sur la fonction onReceive utilisé lorsque l’on utilise le bus I2C pour communiquer entre deux Arduino (en Master Write). Voici ci-dessous, un exemple de code (que l’on retrouve sur la plupart des sites web) utilisant le bus I2C

// Librairie Wire - Master Writer (Maitre ecrit)
// par Nicholas Zambetti <http://www.zambetti.com>
// Traduction par MCHobby.be <http://www.mchobby.be>
// Montage & tutoriel détaillé disponibles sur 
//    http://mchobby.be/wiki/index.php?title=Arduino_I2C_Intro-Montage
// 
// Démontre l'usage de la librairie Wire.
// Ecrit des données vers un périphérique esclave I2C/TWI
//
// A utiliser avec le programme esclave de cet exemple.
//
// Crée le 29 mars 2006
// Traduit le 9 mars 2013 (MCHobby)
//
// This example code is in the public domain.
// Ce code d'exemple fait partie du domaine public.
//
#include <Wire.h>

void setup()
{
  Wire.begin(4);                // Joindre le Bus I2C avec adresse #4
  Wire.onReceive(receiveEvent); // enregistrer l'événement (lorsqu'une demande arrive)
  Serial.begin(9600);           // Démarrer une communication série
}

void loop()
{
  delay(100);
}

// Fonction qui est exécutée lorsque des données sont envoyées par le Maître.
// Cette fonction est enregistrée comme une événement ("event" en anglais), voir la fonction setup()
void receiveEvent(int howMany)
{
  while(1 < Wire.available()) // Lire tous les octets sauf le dernier
  {
    char c = Wire.read();     // lecture de l'octet/byte comme caractère
    Serial.print(c);          // afficher le caractère
  }
  int x = Wire.read();        // lecture de l'octet/byte ignoré comme un entier
  Serial.println(x);          // Afficher la valeur numérique 
}

Ma question concerne la fonction receiveEvent(int howMany)… Pouvez-vous me dire pourquoi il y a un integer en paramètre dans cette fonction? Je ne comprends pas pourquoi il est là puisqu’il n’est même pas utilisé dans la fonction (je me trompe?) Que se passe-t-il si l’on met receiveEvent() à la place de receiveEvent(int howMany)?

C'est parce que la fonction qui assure la gestion des receptions (gestionnaire = handler) doit être de la forme

void myHandler(int numBytes)

voir documentation : http://arduino.cc/en/Reference/WireOnReceive

Dans ton setup(), quand tu appelles la méthode onReceive, tu lui passes comme paramètre un pointeur vers une fonction qui doit avoir ce format.

Derrière, il y a un mécanisme que tu ne vois pas, qui appelle cette fonction avec ce format lorsque des données sont reçues.

Dans ton cas tu n'exploites pas l'information numBytes qui t'est passée à l'appel. Si tu l'enlèves de ta déclaration, je ne sais pas si le compilo va te jeter (ce qui me semblerait logique)ou si cela fonctionne malgré tout (mais ce n'est pas propre). As-tu essayé ?

Salut,

bricoleau a été plus rapide, mais je poste quand même pour compléter sa réponse ;)

L'explication est que ta fonction receiveEvent(int howMany) est appelé indirectement par l'interruption TWI (technique du callback). Lorsqu'il y a de l'activité sur le bus I2C/TWI, une interruption est déclenchée. Et lorsqu'on est dans le cas d'une réception de données + signal de STOP (fin de transmission, twi.c, ligne 462), alors on fait appel à la routine twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex); L'adresse du buffer et la quantité de données sont alors passés en paramètres. Cette routine dépend de la lib Wire, une petite mise en forme des données est faite et ta fonction receiveEvent(int howMany) est finalement appelée (Wire.c, ligne 265) avec, passé en paramètre, le nombre d'octet reçu dans le buffer. Dans l'exemple cette information de quantité d'octets reçu n'est pas utilisé, mais on pourrait.

Romain

Merci pour vos réponses, c’est bien plus clair maintenant :slight_smile:

@bricoleau : Je viens de tester à l’instant et comme tu le pensais, le compilo me jette :wink:

Je vais m’attaquer à un autre problème maintenant, j’essaye tant bien que mal d’établir une communication entre deux Arduino. L’objectif est que le premier Arduino envoie la valeur d’un nombre entier au second pour que celui ci fasse tourner un moteur pas à pas de la valeur de ce nombre entier.

Par exemple : l’Arduino 1 envoie la valeur 250 à l’Arduino 2, quand ce dernier la reçoit, il fait tourner un moteur pas à pas de 250 pas.

Voici le code du Master :

#include <Wire.h>

void setup()
{
  Wire.begin();
}

byte x = 100;

void loop()
{
  Wire.beginTransmission(4);
  Wire.write(x);  
  Wire.endTransmission();

  x = x*2;
  delay(500);
}

Et le code du Slave :

#include <Wire.h>
#include <Stepper.h>

// Nombre de pas du stepper moteur par tour
int motorSteps = 200;
 
const int pwmA = 3;
const int pwmB = 11;

const int brakeA = 9;
const int brakeB = 8;
 
const int dirA = 12;
const int dirB = 13;

 
// Initialize the stepper library on pins 8, 9, 12, 13:
Stepper myStepper(motorSteps, dirA, dirB);

void setup()
{
  pinMode(pwmA, OUTPUT);
  pinMode(pwmB, OUTPUT);
  pinMode(brakeA, OUTPUT);
  pinMode(brakeB, OUTPUT);
 
  digitalWrite(pwmA, HIGH);
  digitalWrite(pwmB, HIGH);
  digitalWrite(brakeA, LOW);
  digitalWrite(brakeB, LOW);
  
  // Vitesse du moteur
  myStepper.setSpeed(100);
  
  Wire.begin(4);                
  Wire.onReceive(receiveEvent);
}

void loop()
{
}

// Fonction qui est exécutée lorsque des données sont envoyées par le Maître.
void receiveEvent(int howMany)
{
  int c = Wire.read();
    
  // Faire un tour dans un sens
  myStepper.step(c);
}

Je ne vois pas d’où vient le problème, j’ai testé le moteur à part, il marche niquel, j’ai testé la communication par le bus I2C, ça marchait aussi. Par contre les deux ensemble ça ne marche pas…

Pour ce qui est du hardware, j’utilise deux Arduino Uno, un Motorshield Arduino, un moteur pas à pas (12V - 1,3A) et une pile 9V pour l’alimentation du Motorshield

Maxence

Toi je te soupçonne de pas bien maîtriser le principe des interruptions. Tu devrais approfondir tes connaissances sur le sujet :P

Ta fonction receiveEvent est déclenchée dans le cadre d'une interruption. Elle doit être la plus courte et la plus simple possible.

Essaye plutôt ça (pas garanti) et si ça marche, comprends le

volatile byte c;

void loop()
{
  if (c)
  {
    // Faire un tour dans un sens
    myStepper.step(c);
    c = 0;
  }
}

// Fonction qui est exécutée lorsque des données sont envoyées par le Maître.
void receiveEvent(int howMany)
{
  // Acquisition de la consigne
  c = Wire.read();
}

Effectivement, je ne suis pas un expert, je débute :wink:

Je pense cependant avoir compris le principe des interruptions, le problème c’est que j’ai beau chercher sur les web, c’est toujours les même programmes qui ressortent du coup pas évident de s’améliorer à part en expérimentant par soi même (ce qui est une très bonne chose aussi je pense)… :slight_smile:

Grâce à tes conseils bricoleau, j’arrive à faire tourner le moteur, mais il reste un problème pendant la transmission des données, en effet mon code Master ci dessous envoie les bonnes valeurs de x

#include <Wire.h>

void setup()
{
  Wire.begin(); 
  Serial.begin(9600);
}

int x = 100;

void loop()
{
  Serial.print("X avant = ");
  Serial.println(x);
  
  Wire.beginTransmission(4);
  Wire.write(x);
  Wire.endTransmission();

  x = x*2;

  Serial.print("X apres = ");
  Serial.println(x);
  delay(5000);
}

Comme on peut le voir sur la photo ci-dessous

Alors que sur mon code Slave, je reçois des valeurs de x différentes, savez-vous pourquoi? (désolé si c’est évident pour certains, j’avoue que moi je ne vois pas trop comment ça se fait) :

#include <Wire.h>

// Integer qui prendra les valeurs reçu par le bus I2C
int c;

void setup()
{
  Wire.begin(4);                
  Wire.onReceive(receiveEvent);
  
  Serial.begin(9600);
}

void loop()
{
  if (c)
  {
    Serial.print("Loop = ");
    Serial.println(c);
    c = 0;
  }    
}

// Fonction qui est exécutée lorsque des données sont envoyées par le Maître.
void receiveEvent(int howMany)
{
  // Tant qu'un octet en réception
  while (Wire.available() > 0) 
  {
    // Acquisition de la consigne
    c = Wire.read();
    Serial.print("Recu = ");
    Serial.println(c);
  }
}

Comme je le disais plus haut, ça me retourne des valeurs reçues différentes des valeurs transmises…

D’après ce que je vois je pense avoir un problème au niveau de ma fonction receiveEvent() =(

salut,

c'est tout à fait normal. Tu cherches à transférer un type integer (16 bits) alors que le transfert I2C est sur 8 bits (type byte). Donc tu ne transfères que les 8 premiers bits (bits de poids faibles) Pour transférer un type int, il faut le couper en deux bytes et reconstituer côté slave l'integer. Pour ça, tu peux poser un masque sur les 8 bits de poids fort pour extraire les 8 bits de poids faible et l'envoyer, puis faire un décalage de 8 bits vers la droite pour extraire les 8 bits de poids fort et l'envoyer. Côté slave, c'est l'opération inverse.

Gromain

Idem

La fonction wire.write prend un byte en paramètre. Si tu l'appelles avec un int, seul l'octet de poids faible est pris en compte.

La preuve : 400 = 1x256 + 144 800 = 3x256 + 32 1600 = 6x256 + 64 3200 = 12x256 + 128 6400 = 25x256 + 0 12800 = 50x256 + 0 25600 = 100x256 + 0

Ou alors, peut-être le plus simple :

Tu te contentes de 256 valeurs différentes, ce qui te fait autant de palliers dans la commande de ton moteur (par règle de trois 0..255 => 0..valeurMax)

Et garde bien le volatile dans la déclaration d'une variable globale modifiée par une routine d'interruption.

Effectivement, je pense que le plus simple est peut être d’envoyer un byte et de faire une règle de trois, ou un système de codage à la réception… Voilà ce que ça donne :

Code Master :

#include <Wire.h>

byte envoi = 1;
byte donnees[5] = {1,3,5,7,9};

void setup()
{
  Wire.begin();
}

void loop()
{
  delay(5000);
  envoie(sizeof(donnees), donnees); 
}

void envoie(byte length, byte envoie[])
{ 
  if (envoi == 1)
  {  
    Wire.beginTransmission(4);
  
    for (byte i = 0; i < length ; i++)
    {    
      Wire.write(envoie[i]);
      delay(500);
    }
  
    Wire.endTransmission();
    
    envoi = 0;
  }
}

Code Slave :

#include <Wire.h>
#include <Stepper.h>

// Nombre de pas du stepper moteur par tour
const int motorSteps = 200;
 
const int pwmA = 3;
const int pwmB = 11;

const int brakeA = 9;
const int brakeB = 8;
 
const int dirA = 12;
const int dirB = 13;

// Données à commander
byte donnees[10];
byte commande = 0;
 
// Initialize the stepper library on pins 8, 9, 12, 13:
Stepper myStepper(motorSteps, dirA, dirB);

void setup()
{   
  pinMode(pwmA, OUTPUT);
  pinMode(pwmB, OUTPUT);
  pinMode(brakeA, OUTPUT);
  pinMode(brakeB, OUTPUT);
 
  digitalWrite(pwmA, HIGH);
  digitalWrite(pwmB, HIGH);
  digitalWrite(brakeA, LOW);
  digitalWrite(brakeB, LOW);
  
  // Vitesse du moteur
  myStepper.setSpeed(10);
  
  Wire.begin(4);                
  Wire.onReceive(receiveEvent);
  
  // Vérifier si le motorshield utilise les pins 0 & 1
  //Serial.begin(9600);
}

void loop()
{
  if (commande == 1)
  {
    moteur();
  } 
}

// Fonction qui est exécutée lorsque des données sont envoyées par le Maître.
void receiveEvent(int howMany)
{
  int length = Wire.available();
  
  for(int i = 0; i < length; i++)
  {
    donnees[i] = Wire.read();
  }
  
  commande = 1;
}

void moteur()
{
  for(int i = 0; i < sizeof(donnees); i++)
  {
    if (donnees[i] == 1)
    {
      myStepper.step(200);
      delay(200);
    }
    
    if (donnees[i] == 3)
    {
      myStepper.step(-400);
      delay(200);
    }
    
    if (donnees[i] == 5)
    {
      myStepper.step(100);
      delay(200);
    }
    
    if (donnees[i] == 7)
    {
      myStepper.step(-100);
      delay(200);
    }
    
    if (donnees[i] == 9)
    {
      myStepper.step(1000);
      delay(200);
    }
  }
  
  commande = 0;
}

J’ai apporté quelques motifs au programme, après quelques phases de réflexions, je me suis dis que l’idéal serait peut être d’envoyer une suite de données (codées dans un array de type byte) et de les stocker à réception dans un array de type identique… Le Slave pourrait ensuite s’occuper de traiter les données librement grâce à une fonction de “traduction” (comme tu le suggérais bricoleau).

Je viens de tester le programme et ça marche niquel :slight_smile: Par contre le programme n’est pas très “clean”, si vous avez des idées pour l’optimiser je suis preneur :slight_smile: