Zweiwege I2C Kommunikation Arduino zu Arduino

Hallo Leute,

ich stecke bei meinem aktuellen Projekt fest. Ich habe schon einiges ausprobiert komme aber nicht weiter. Die Kommunikation einzeln ist nicht das Problem Master (Teensy3.2) / Slave (Arduino Micro) habe ich alles schon am laufen. Nur hängt es jetzt an entscheidender Stelle.

Der Master und Slave sind exakt den Beispielen MasterWriter und MasterReader nachempfunden.

Ich baue einen 2 Achsen Kamera Slider. Dazu gibt es einen Arduino "Master" welcher Befehle ab den Arduino "Slave" schickt. Am Slave ist der Treiber für einen Stepper angeschlossen. Nun möchte ich das der Master per Befehlt an den Slave dessen Stepper auf Home Position fährt. Das funktioniert.

Aufruf am Master:

void SendCommandSlave2(char Command){
    Wire.beginTransmission(I2C_Slave2Address);  // transmit to device #9
    Wire.write(Command);        // sends one byte
    Wire.endTransmission();     // stop transmitting
}

Code im Slave:

void ReceiveCommandEvent(int howMany) {
  while (Wire.available()){ // Enter this loop only when Data are available
    char Command = Wire.read(); // receive byte as a character
    if(Command == 'Z'){HomeStepperPAN();}
}

Ist der Slave nun in der Funktion HomeStepperPAN() ist dieser komplett damit beschäftigt diese Aufgabe zu erfüllen ()er rattert die Schritte an den Stepper Driver durch.

Nachdem der Master den Befehl an den Slave geschickt hat das dieser den Stepper in Home Position bringen soll, will ich natürlich wissen wann das der Fall ist.

So pollt der Master den Slave mit:

char GetHomeStatusPANStepper(){  
  Wire.requestFrom(I2C_Slave2Address, 1);    // request 1 bytes from slave device #9
  char c = 'X';
  while (Wire.available()) { // slave may send less than requested
    c = Wire.read(); // receive a byte as character
    if(c == 'X'){Serial.println("Homing in progress...");}
    else if (c == 'Z'){Serial.println("Homing finished!");}
    }
    return c;
}

Der Slave Antwortet mit dieser Funktion:

void requestEvent() {
  Wire.write(PANHomingStatus); // respond with message of 1 bytes as expected by master 'X' = Homing in progress... / 'Z' = Homing done
}

Der Code funktioniert solange der Slave nicht in der while Schleife für das Homing vom Stepper ist. Also wenn der Master erst nachfragt wenn der Slave soweit ist. Das ist aber nicht sinn der Sache da der Master ja so eine fixe Zeit warten muss. Frägt der Master aber den Request beim Slave an wenn dieser noch in der while Schleife für das Homing ist, erhält er vom Slave keine Daten (Wire.available).

Ich denke das Problem ist das der Master beim Slave per Request Daten anfordert wenn dieser auf den Request nicht reagieren kann. Kann er dann reagieren sendet er aber nichts mehr zurück. Warum genau weiß ich noch nicht. Da hoffe ich auf eure Hilfe. Ich kann euch leider nicht den ganzen Code schicken da das Projekt sehr sehr groß ist.

Danke und Grüsse
Chris

Entweder Du musst die Schleife nach Home nichtblockierend bauen oder Du nutzt einen weiteren Pin am Slave, mit dem er dem Master mitteilt dass er fertig ist.

Ohne Code ist das alles nur stochern im Nebel.

Gruß Tommy

Ohne den kompletten Sketch gibs keine konkreten Antworten. Nicht weil wir nicht wollen, sondern nicht können.

Mögen täten wir schon wollen, aber dürfen haben wir uns nicht getraut.
Quelle: Mögen täten wir schon wollen, aber dürfen haben wir uns nicht getraut.

Grüße Uwe

Da stimme ich Tommy zu. Der Fehler liegt m.E. in der Ausführung des "HomeStepperPAN" innerhalb von ReceiveCommandEvent.

Vielleicht ungefähr so:

int homeCommandRequest = 0;
char PANHomingStatus = 'Y';  // unknown
...
void loop()
{
   ...
  // aller-einfachste Variante - ohne wirkliche Berücksichtigung von Seiteneffekten, die bei Interrupts
  // und Nebenläufikeiten _immer_ auftreten (z.B. ein erneutes "Home"-Kommando vom Master)
  if (homeCommandRequest == 1) 
  {
    PANHomingStatus = 'X';
    HomeStepperPAN();  // das dauert ja etwas...
    PANHomingStatus = 'Z';
    homeCommandRequest = 0;
  }

  ...
  // wenn hier am Stepper gedreht wird: Bitte Status wieder auf 'Y' setzen (ist ja dann nicht mehr HOME).
  ...
}

void ReceiveCommandEvent(int howMany) {
  while (Wire.available()){ // Enter this loop only when Data are available
    char Command = Wire.read(); // receive byte as a character
    if(Command == 'Z'){homeCommandRequest++;}
}

Gruß Walter

Verzeih, wenn ich die Klammern anders setze; das mache ich schon 30 Jahre so :slight_smile:
@Uwe: Ich bin ja ziemlich neu hier - sind die Sitten bzgl. komplettem Sketch wirklich so streng? :wink:

wno158:
@Uwe: Ich bin ja ziemlich neu hier - sind die Sitten bzgl. komplettem Sketch wirklich so streng? :wink:

Nein, Es ist nicht ein Zwang, aber viele, vor allem Anfänger schießen sich auf eine oft falsch Fehlerquelle/Ort ein und geben dann nur den Teil, wo sie glauben, daß der Fehler liegt. Selbst der Compiler kann bestimmte Fehler nicht lokalisieren zB wenn einen geschwungene Klammer fehlt oder zuviel ist.
Je kompletter die Infos sind ( Sketch, Bibliotheksquellen, Schaltplan Links zu den verwendeten Hardwarekomponenten) desto einfacher ist es den Fehler nachzuvollziehen bzw zu finden.

Beispiel: Der Sketch macht komisches, unvorhergesehenes:

for( int i=0; i<10; i++)
array[i] = i;

Dieser Kodeschnipsel ist fehlerlos.

Der Fehler kommt jetzt:
vor dem setup() steht:

 int array[8];

Eine weitere böse Fehlerquelle sind Zeiger und eine Verwechslung von “=” und “==”. Ein “;” nach einem IF() gibt auch Probleme.

Grüße Uwe

Danke erst mal für eure Antworten. Nichts für ungut , aber der Code umfasst mehr als 10 Tabs mit mehr als insgesamt 70kB. Sich dort einzufinden / zurechtzufinden dauert.

Jeder der länger programmiert kennt das ja, das der Fehler immer genau von dort kommt wo man ihn am wenigsten vermutet hätte. Dehalb mache ich mir ja immer vorher einen kleinen Tischaufbau der die eigenliche Funktion “nachstellen soll” die integriert werden muss. Hier habe ich gleiches verhalten. Wird der der Request vom Master angefordert wenn der Slave nicht darauf reagieren kann, hängt es sich auf. Dann reagiert er auch nicht mehr auf anfragen wenn er reagieren könnte…

Ich habe es nun gemacht wie Tommy56 es schon empfohlen hat. Ich habe nun einen StepPin vom Master und einen StepPin vom Slave parallel am Steppertreiber angeschlossen. Nun muss ich nur darauf achten das nicht beide gleichzeitig treiber da die TTL Pegel unterschiedlich sind. ABER so kann ich diesen Pin am Master als pinMode(StepperPin, INPUT); gleich als Eingang zum zurück lesen verwenden. Nicht schön aber nach 2 Wochen mit I2C Fehlschlägen nun eine Lösung die wenn auch unschön gut funktioniert.

Nun weiß ich auch warum viele zu SPI raten…

MacMen:
Ich habe es nun gemacht wie Tommy56 es schon empfohlen hat. Ich habe nun einen StepPin vom Master und einen StepPin vom Slave parallel am Steppertreiber angeschlossen. Nun muss ich nur darauf achten das nicht beide gleichzeitig treiber da die TTL Pegel unterschiedlich sind. ABER so kann ich diesen Pin am Master als pinMode(StepperPin, INPUT); gleich als Eingang zum zurück lesen verwenden. Nicht schön aber nach 2 Wochen mit I2C Fehlschlägen nun eine Lösung die wenn auch unschön gut funktioniert.

Da hast Du meinen Vorschlag nicht richtig verstanden.

Ich meinte einen Ausgang vom Slave, der auf einen Eingang vom Master geht. Diesen Ausgang zieht der Slave für eine bestimmte Zeit z.B. auf High, wenn er fertig ist. Der Master wertet den Eingang entweder per Polling oder mit einem PinChangeInterrupt aus und weiß, dass der Slave fertig ist.

Gruß Tommy

Es gibt 2 Wege das sauber abzuhandeln.

Erstmal zur ursprünglichen Ursache der Blockade:

Wire nimmt die I2C Anforderung "GoToHome" an.
Die GoToHome Funktion läuft im ISR Kontext von Wire.
Nachfolgende Anfragen können nicht beantwortet werden, solange GoToHome den Rücksturz der ISR verhindert. Der Slave wird den Bus erst los lassen, wenn das getan wurde.
Die totale Bus Blockade.

Eine Teillösung wurde oben schon genannt: In der ISR nur ein Flag setzen.
Damit ist der Weg frei, den Slave per Polling zu befragen, ob er mittlerweile bei "BinHome" angekommen ist.

Da kommt auch deine Lösung ins Spiel.
Über einen Open Drain Ausgang dem Master mitteilen, ob fertig/bereit.
Sowas wie ein externes Busy/Interrupt Flag/Signal.
Ein übliches Verfahren, bei z.B. intelligenten Sensoren mit Alarmausgang.

Die auch mögliche Alternative:
Sobald das GoHomeKommando vom Hauptprogramm aktiviert wurde, schaltet es den Slave Mode ab.
Ein Wire.begin(); reicht dazu.
Wenn GoHome abgeschlossen, dann wieder Wire.begin(slaveadr);
So kann der Master per Acknowledge Polling darauf warten, dass der Slave(welcher ja jetzt, ab und zu, keiner mehr ist) irgend wann mal fertig wird.
Ein übliches Verfahren, welches z.B. dumme EEPROMs anwenden.

Warum so kompliziert? Wenn jeder Arduino, der gerade nichts zu sagen hat, im Slave-Modus bleibt, kann sich der andere jederzeit zum Master machen und etwas übertragen.

DrDiettrich:
Warum so kompliziert? Wenn jeder Arduino, der gerade nichts zu sagen hat, im Slave-Modus bleibt, kann sich der andere jederzeit zum Master machen und etwas übertragen.

So finde ich es auch sauberer. Ich habe es am Wochenende mal ausprobiert. Hat aber nicht funktioniert. Der Slave empfängt das Command, hängt sich dann aber irgendwie auf. Ich denke das Problem ist hier evtl. das der Master sich abmeldet und als Slave joint. Ich denke da gibt es eine Überschneidung. Die Wartezeit hat auch nichts gebracht. Natürlich könnte ich auch die ganze Zeit am Slave Pollen wie im “Master reader / Slave writer” Beispiel, pollen will ich aber nicht. Da kann ich auch bei meinem digitalen Eingang bleiben…

Anbei der Code…

Master:

#include <Wire.h>
#define MasterAddressWhenSlave 6
#define SlaveAddress 8


void setup() {
  Serial.begin(9600);
  Wire.begin(); // join i2c bus (address optional for master)
  Wire.onReceive(receiveEvent);          // register event
  Serial.println("Ready");
}

byte x = 0;

void loop() {
  if(Serial.available()>0){
    char c = Serial.read();
    if(c=='X'){SendCommandToSlaveAndWaitForAnswer(c);}
    else{SendCommandToSlave(c);}
  }
  delay(1);
}

void SendCommandToSlave(char Command){
  Serial.print("Send Command to Slave. Command = ");
  Serial.println(Command);
  Wire.begin();
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(Command);              // sends one byte
  Wire.endTransmission();    // stop transmitting
}

void SendCommandToSlaveAndWaitForAnswer(char Command){
  Serial.print("Send Command to Slave and wait for answer. Command = ");
  Serial.println(Command);
  Wire.begin();                          // join i2c bus without address = master?
  Wire.beginTransmission(SlaveAddress);  // transmit to device #8
  Wire.write(Command);                   // sends one byte
  Wire.endTransmission();                // stop transmitting
  delay(100);                            // wait for Slave reseive before switch to master
  Wire.begin(MasterAddressWhenSlave);    // switch master to slave and join bus with address 6
  //Wire.onReceive(receiveEvent);          // register event
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

Slave:

#include <Wire.h>
#define MasterAddressWhenSlave 6
#define SlaveAddress 8

void setup() {
  Serial.begin(9600);           // start serial for output
  Wire.begin(SlaveAddress);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.println("Ready");
}

void loop() {
  delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  while (Wire.available()> 0) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print("Reseive Command = ");
    Serial.println(c);
    if(c=='X'){AnswerMaster('X');}  // if reseived Command X answer the master with X
  }
  
}

void AnswerMaster(char Command){
  delay(100);                                      // let the master time to switch to slave
  Wire.begin();                                    // join i2c bus without address = master?
  Wire.beginTransmission(MasterAddressWhenSlave);  // transmit to device #8
  Wire.write(Command);                             // sends one byte
  Wire.endTransmission();                          // stop transmitting
  delay(100);                                      // wait for Slave reseive before switch to master
  Wire.begin(SlaveAddress);                        // switch master to slave and join bus with address 6
  
}

Was mache ich falsch, ich sehe es nicht…

Ich rate dir:

  1. Werte die Rückgaben der Methoden aus. In den Rückgaben steckt Information.
  2. Verzichte auf die ganzen Wire.begin(). Einmal in setup() reicht meist. Durch Masse wirds nicht besser.
  3. Ein Multimaster Betrieb mit mehr als 2 AVR kann zu Deadlocks führen.

I2C Adressen unterhalb 16 sollten vermieden, davon haben einige Sonderfunktionen!

In einer ISR könnten delay und Wire.begin() Quell von Ungemach sein.

Einfache Kommunikation: Teensy schickt als Master Daten an den Micro als Slave. Dieser wartet die Bewegung 100ms ab und schickt die Daten als Master an den Teensy als Slave. Anstelle der 500ms Wartezeit beim Teensy stünde die Ablaufsteuerung.

// Teensy 3.2 (getestet mit UNO)
#include <Wire.h>

void setup() {
  Serial.begin(9600);           // start serial for output
  Serial.println("Teensy Start");
  Wire.begin(20);               // join i2c bus as device #20
  Wire.onReceive(receiveEvent); // register event
}

byte x = 0;

void loop() {
  Wire.beginTransmission(21); // transmit to Micro, device #21
  Wire.write("x is ");        // sends five bytes
  Wire.write(x);              // sends one byte
  Wire.endTransmission();     // stop transmitting

  x++;
  delay(500);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  howMany++;
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int y = Wire.read();    // receive byte as an integer
  Serial.println(y);         // print the integer
}
// Micro mit Stepper (getestet mit Mega2560)
#include <Wire.h>
volatile bool flag = false;
volatile byte x = 0;

void setup() {
  Serial.begin(9600);           // start serial for output
  Serial.println("Micro Start");
  Wire.begin(21);               // join i2c bus as device #21
  Wire.onReceive(receiveEvent); // register event
}

void loop() {
  if (flag) {
    flag = false;
    delay(100);
    Wire.beginTransmission(20); // transmit to Teensy, device #20
    Wire.write("X is ");        // sends five bytes
    Wire.write(x);              // sends one byte
    Wire.endTransmission();     // stop transmitting
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  howMany++;
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
  flag = true;
}

Quelle: MasterWriter

Was ich mir aus den Beiträgen in diesem Forum gemerkt habe:

  • Ein Master kann, ein Slave muß eine Adresse haben.
  • Ein µC ist nicht Master oder Slave, sondern handelt als Master oder Slave.

Mein Eindruck ist, der Multi-Master-Mode mit der Wire-Bibliothek ist nicht ohne Tücken. Wenn Master und Slave die Rollen tauschen, dürfte das unproblematisch sein. Man sollte aber mehrere Master gleichzeitig an einem Bus durch eine Logik des Händeschüttelns vermeiden.