I2C Array verschicken - Werte springen in die falsche Zeile

Hallo Leute,
ich habe an meinem I2C Slave (Arduino Uno) 8 Drehencoder angeschlossen. Dieser sendet die Werte der Encoder an den Master - bzw. der Master fragt diese ab.

Die 8 Drehencoder steuern aber nicht jeweils nur einen Wert, sondern bekommen durch Menüabrufe andere Variablen zugewiesen die dann den Wert der Encoder annehmen.
Die eingestellten Werte Speicher ich.
Wird der Menüpunkt neu abgerufen und der Drehencoder befindet sich - sagen wir mal auf Position 40 - der Wert der Variable des Menüpunkte ist aber 12, so soll der Master dem Slave die aktuellen Werte schicken, damit bei 12 weitergeregelt wird und nicht bei 40.

Pro Menüpunkt bekommen alle Drehencoder eine neue Zuweisung.

Soweit klappt das eigentlich ganz gut. Nur nach teilweise 2, 3 Menüwechseln verspringen die Werte im Array dann plätzlich eine Position nach vorne.

Woran kann das liegen?

Slave:

#define ENCODER_DO_NOT_USE_INTERRUPTS

#include <Encoder.h>
#include <Wire.h>

Encoder myEnc0( 8, 9 );
Encoder myEnc1( 7, 10 );
Encoder myEnc2( 11, 6 );
Encoder myEnc3( 12, 5 );
Encoder myEnc4( 4, 13 );
Encoder myEnc5( 3, A0 );
Encoder myEnc6( 2, A1 );
Encoder myEnc7( A3, A2 );
long pos[] = {0, 0, 0, 0, 0, 0, 0, 0};
long newPos[] = {0, 0, 0, 0, 0, 0, 0, 0};
int encAnz = 8;
int bus_adr = 2; // i2C bus address
int var[] = {0,0,0,0,0,0,0,0};

void setup() {
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
  Wire.begin(bus_adr);                // join i2c bus
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent); // register event
}

void loop() {
  
  for (int i=0; i < encAnz; i++){
    if (newPos[i] != pos[i]) {
     // Serial.print( i );
      for (int j=0; j < encAnz; j++){
        if( newPos[i] <= 0 ){
          switch (i){
            case 0: myEnc0.write(0); break;
            case 1: myEnc1.write(0); break;
            case 2: myEnc2.write(0); break;
            case 3: myEnc3.write(0); break;
            case 4: myEnc4.write(0); break;
            case 5: myEnc5.write(0); break;
            case 6: myEnc6.write(0); break;
            case 7: myEnc7.write(0); break;
          }
           newPos[i] = 0;
           pos[i] = 0;
        }
        Serial.print ("\t");
        Serial.print ( newPos[j] );
      }
      Serial.println("");
      newPos[0] = constrain(newPos[0],0,14);
      newPos[1] = constrain(newPos[1],0,100);
      newPos[2] = constrain(newPos[2],0,100);
      newPos[3] = constrain(newPos[3],0,100);
      newPos[4] = constrain(newPos[4],0,100);
      newPos[5] = constrain(newPos[5],0,100);
      newPos[6] = constrain(newPos[6],0,100);
      newPos[7] = constrain(newPos[7],0,100);
      myEnc0.write(newPos[0]);
      myEnc1.write(newPos[1]);
      myEnc2.write(newPos[2]);
      myEnc3.write(newPos[3]);
      myEnc4.write(newPos[4]);
      myEnc5.write(newPos[5]);
      myEnc6.write(newPos[6]);
      myEnc7.write(newPos[7]);
      pos[i] = newPos[i];
    }
  }

  newPos[0] = myEnc0.read();
  newPos[1] = myEnc1.read();
  newPos[2] = myEnc2.read();
  newPos[3] = myEnc3.read();
  newPos[4] = myEnc4.read();
  newPos[5] = myEnc5.read();
  newPos[6] = myEnc6.read();
  newPos[7] = myEnc7.read();
}

void receiveEvent(int howMany) {
  
  for (int i=0; i<howMany; i++) {
    var[i] = Wire.read();
    newPos[i] = var[i];
    Serial.print ("\t");
    Serial.print ( newPos[i] );
  }
  myEnc0.write(var[1]);
  myEnc1.write(var[2]);
  myEnc2.write(var[3]);
  myEnc3.write(var[4]);
  myEnc4.write(var[5]);
  myEnc5.write(var[6]);
  myEnc6.write(var[7]);
  myEnc7.write(var[8]);
  
}

void requestEvent(){
  union {
    int encoder [8];
    byte buf [16];
    } encValues;
  encValues.encoder [0] = pos[0];
  encValues.encoder [1] = pos[1];
  encValues.encoder [2] = pos[2];
  encValues.encoder [3] = pos[3];
  encValues.encoder [4] = pos[4];
  encValues.encoder [5] = pos[5];
  encValues.encoder [6] = pos[6];
  encValues.encoder [7] = pos[7];
  Wire.write((byte *) &encValues, sizeof encValues);
}

Da der Mastercode viel zu riesig ist hier die Cod schnipsel um die es geht:

Globale Variablen

int var[8] = {0,0,0,0,0,0,0,0};
int var2[8] = {0,0,0,0,0,0,0,0};
int bus_adr = 2;
int dis2Men1Cam5, dis2Men1Cam6, dis2Men1Cam7, dis2Men1Cam8, dis2Men2Cam5, dis2Men2Cam6, dis2Men2Cam7, dis2Men2Cam8;
int dis2Men3Cam5, dis2Men3Cam6, dis2Men3Cam7, dis2Men3Cam8, dis2Men4Cam5, dis2Men4Cam6, dis2Men4Cam7, dis2Men4Cam8;
int dis2Men5Cam5, dis2Men5Cam6, dis2Men5Cam7, dis2Men5Cam8, dis2Men6Cam5, dis2Men6Cam6, dis2Men6Cam7, dis2Men6Cam8;
int dis2Men7Cam5, dis2Men7Cam6, dis2Men7Cam7, dis2Men7Cam8, dis2Men8Cam5, dis2Men8Cam6, dis2Men8Cam7, dis2Men8Cam8;
int dis2Men9Cam5, dis2Men9Cam6, dis2Men9Cam7, dis2Men9Cam8;
// Display 3
int dis3Men1Cam5, dis3Men1Cam6, dis3Men1Cam7, dis3Men1Cam8, dis3Men2Cam5, dis3Men2Cam6, dis3Men2Cam7, dis3Men2Cam8;
int dis3Men3Cam5, dis3Men3Cam6, dis3Men3Cam7, dis3Men3Cam8, dis3Men4Cam5, dis3Men4Cam6, dis3Men4Cam7, dis3Men4Cam8;
int dis3Men5Cam5, dis3Men5Cam6, dis3Men5Cam7, dis3Men5Cam8, dis3Men6Cam5, dis3Men6Cam6, dis3Men6Cam7, dis3Men6Cam8;
int dis3Men7Cam5, dis3Men7Cam6, dis3Men7Cam7, dis3Men7Cam8, dis3Men8Cam5, dis3Men8Cam6, dis3Men8Cam7, dis3Men8Cam8;
int dis3Men9Cam5, dis3Men9Cam6, dis3Men9Cam7, dis3Men9Cam8;

geht bis Display12 aber ist ja unrelevant

Hier hole ich die Werte vom Slave - Funktioniert einwandfrei

void getEncValues() {
  union {
    int encoder [8];
    byte buf [16];
    } encValues;
 
  if (Wire.requestFrom( bus_adr, sizeof encValues) != sizeof encValues)
    return;  // oops!
  
  for (byte i = 0; i < sizeof encValues; i++)
    encValues.buf [i] = Wire.read ();
  
  var[0] = encValues.encoder [0];
  var[1] = encValues.encoder [1];
  var[2] = encValues.encoder [2];
  var[3] = encValues.encoder [3];
  var[4] = encValues.encoder [4];
  var[5] = encValues.encoder [5];
  var[6] = encValues.encoder [6];
  var[7] = encValues.encoder [7];
}

Und hier möchte ich die gespeicherten Werte übergeben

void displayMenu() {
    static int lastScaledMenu;  
     if (menu == 1 && lastScaledMenu != 1)
    {
      display2HeaderString = "Video Format";
      display3HeaderString = "Gain";
      display4HeaderString = "Detail";
      display5HeaderString = "Auto Exposure";
      display6HeaderString = "White Balance";
      display7HeaderString = "Shutter Speed";
      var2[0] = 0;
      var2[1] = dis2Men1Cam5;
      var2[2] = dis3Men1Cam5;
      var2[3] = dis4Men1Cam5;
      var2[4] = dis5Men1Cam5;
      var2[5] = dis6Men1Cam5;
      var2[6] = dis7Men1Cam5;
      var2[7] = dis8Men1Cam5;
      Wire.beginTransmission(2);
      for (int i=0; i<8; i++) {
        Wire.write(var2[i]);
      }
      Wire.endTransmission();
      display2();
      display3();
      display4();
      display5();
      display6();
      display7();
      display8Logo();
      lastScaledMenu = 1;
    }
    else if (menu == 2 && lastScaledMenu != 2)
    {
      display2HeaderString = "Aut. Gain Contr";
      display3HeaderString = "Audio Input";
      display4HeaderString = "Mic Level";
      display5HeaderString = "Input Level";
      display6HeaderString = "Ch1 Input";
      display7HeaderString = "Ch2 use Ch1 Input";
      display8HeaderString = "Ch2 Input";
      var2[0] = 0;
      var2[1] = dis2Men2Cam5;
      var2[2] = dis3Men2Cam5;
      var2[3] = dis4Men2Cam5;
      var2[4] = dis5Men2Cam5;
      var2[5] = dis6Men2Cam5;
      var2[6] = dis7Men2Cam5;
      var2[7] = dis8Men2Cam5;
      Wire.beginTransmission(2);
      for (int i=0; i<8; i++) {
        Wire.write(var2[i]);
      }
      Wire.endTransmission();
      display2();
      display3();
      display4();
      display5();
      display6();
      display7();
      display8();
      lastScaledMenu = 2;
    }
    else if (menu == 3 && lastScaledMenu != 3)
    {
      display2HeaderString = "HDMI Overlays";
      display3HeaderString = "HDMI Meters";
      display4HeaderString = "Focus Peaking";
      display5HeaderString = "Zebra";
      display6HeaderString = "Zebra Level";
      display7HeaderString = "TallyLight Brightnis";
      display8HeaderString = "Displ Bat Perc";
      display2();
      display3();
      display4();
      display5();
      display6();
      display7();
      display8();
      lastScaledMenu = 3;
    }
}

Beispiel aus dem Seriellen Monitor.
Ich war im Menü 2 Cam 5 und bin ins Menü 2 Cam 5 gegangen. gespeichert war nur auf der zweiten Position der Wert vier. Dieser verschpringt aber auf die erste Position.

0 4 0 0 0 0 0 0 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0
 0 4 0 0 0 0 0 0 4 0 0 0 0 0 0 1024
 4 0 0 0 0 0 0 100
 4 0 0 0 0 0 0 100

Auch die Zahlen die nach Rechts viel zuweit rausrücken kann ich mir nicht erklären.

Ich benutze nur zwei originale Arduinos, am Bus hängen zwei 4,7kOhm Widerstände.

Ich bin dankbar für hilfreiche Tips
Marcel

Auf einem 8 Bit Prozessor hat ein int 2 Byte. write() kann aber nur 1 Byte senden, bzw. ein Byte Array. Daher muss man das aufteilen.

Wozu die Union ist scheinst du nicht verstanden zu haben. In einer Union belegen alle Variablen den gleichen Speicher. Wenn man also in die Integer schreibt hat man die Werte direkt im Byte Array. Und umgekehrt. Wenn du das auf beiden Seiten konsequent anwendest musst du nicht ständig Werte per Hand in Zwischenvariablen umkopieren.

Und man hat eben automatisch die Daten als Byte Array. So könnte man auch problemlos z.B. ein struct gleichzeitig als Byte Array betrachten.
Eine andere Option ist aber einfach ein Cast der Adresse auf byte*

Also du hast 8 Integer mit 16 Bytes welche gleichzeitig ein Byte Array der Größe 16 sind.

Dann gibt einiges an dem Code was nicht schön ist:

Arrays kann man auf einmal mit write() senden:

Wenn du das mit der Union richtig machst schreibt du die Encoder Werte in das Integer-Array und versendest dann die 16 Bytes in einem Rutsch

Das kann gar nicht kompilieren:

 if (Wire.requestFrom( bus_adr, sizeof encValues) != sizeof encValues)
    return;  // oops!
  
  for (byte i = 0; i < sizeof encValues; i++)
    encValues.buf [i] = Wire.read ();

Vielleicht ein Kopierfehler aber da müssen Klammern beim sizeof() Operator sein. Auch solltest du sizeof() durchgehend verwenden. Ich nehme mal an dass stammt aus fremdem Code und wo du was selbst geschrieben hast, schreibst du lieber 8 fest rein

Generell solltest du mal den Code strukturieren. Unterschiedliche Variablen die zusammengehören in Strukturen fassen. Dann Arrays daraus für mehrere Obejekte. Und über Arrays mit for-Schleifen iterieren. Statt überall Variablen durchzunummerieren. Das ist so sehr schlecht lesbar

Vielen Dank für deine Antwort.

das mit sizeof hab ich geändert, sodass jetzt in den klammern die Anzahl direkt steht.
die Variablen möchte ich später noch zusammfassen. Das ist eine gute Idee.

beim Empfangen am Master habe ich weiterhin keine Proble das gesammte Array fehlerfrei zu erhalten. jedoch funktion es bei dem senden nicht. der Fehler ist dann ja bei den Zeiln wo ich an den Slave Sende bzw. der Slave empängt.

Irgendwie macht mein Kopf grad stopp und ich verstehe nicht konkret was ich bei den anderen Sachen jetzt machen soll. Hättest du vielleicht einen Beispiel Code oder kannst du das in meinem ergänzen wie du es für richtig hälst?

Muss natürlich auch nicht heute sein.

Bis hier hin schon mal vielen dank.

die Variablen möchte ich später noch zusammfassen. Das ist eine gute Idee.

Einfacher ist es man macht das von Anfang an. Sich vernünftige Datenstrukturen zu überlegen ist normal der erste Schritt bevor man überhaupt was programmiert.

Aber ok, du hast erst mal ein praktischeres Problem

das mit sizeof hab ich geändert, sodass jetzt in den klammern die Anzahl direkt steht.

In den Klammern steht die Variable von der die Größe ermitteln willst

int data[8];

Dann ist sizeof(data) 16! Auf einem 8 Bit Prozessor. Das ist die Größe in Bytes. Der Sinn darin besteht das alles vom Compiler ausrechnen zu lassen. Dann kann man einfach an einer Stelle die Datentypen ändern und alles andere passt sich an

Wenn du ganz sicher gehen wills dass es immer 16 Bytes sind kannst du auch short statt int nehmen.

beim Empfangen am Master habe ich weiterhin keine Proble das gesammte Array fehlerfrei zu erhalten. jedoch funktion es bei dem senden nicht. der Fehler ist dann ja bei den Zeiln wo ich an den Slave Sende bzw. der Slave empängt.

Schau dir mal das mit den Unions genau an

union Data
{
   int encoderValues[8];
   byte asBytes[sizeof(encoderValues)];
} data;

Wenn du jetzt deine 8 Werte in EncoderValues schreibst belegt asBytes den gleichen Speicher. Dann kannst du einfach das machen:

Wire.write(data.asBytes, sizeof(data));

Und es werden 16 Bytes versendet

Anders herum genauso. Du liest mit Wire.read() sizeof(data) Bytes ein und hast dann deine 8 int Werte

Wie gesagt, ich nehme mal an du hast da irgendwo her Code, aber das dann nicht verstanden. Weil eine Union definiert ist, aber nie richtig verwendet wird.

Und überlege dir mal genau was du als Datentyp überhaupt brauchst. Ich hatte erst angenommen dass int richtig ist, weil man bei Encodern durchaus mal negative Werte hat wegen der Drehrichtung. Aber du scheinst generell int etwas freigiebig zu verwenden. Praktisch als Standard-Datentyp.

Brauchst du negative Werte? Wenn nein, dann "unsigned". Reicht vielleicht ein Byte aus? Wenn ja, braucht man keinen Datentyp mit 2 oder 4 Bytes. Ist es eine Konstante, die sich nie ändert? Dann mach das "const" (z.B. die Adresse)

Zur Verdeutlichung und um zu zeigen dass es hier nicht nur Arrays sein müssen:

union Data
{
  struct
  {
    unsigned int var1;
    byte var2;
  };
  byte asBytes[sizeof(var1) + sizeof(var2)];
} data;

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

  data.var1 = 0xAADD;
  data.var2 = 0xFF;

  for (byte b : data.asBytes)   //Stichwort: range based for loop
    Serial.println(b, HEX);
}

void loop()
{
}

Ausgabe:

DD
AA
FF

Also ich habe es versucht umzusetzen, aber es funktioniert leider nicht. Ich verstehe wohl irgendwas falsch. Ich werd es wohl morgen noch einmal versuchen.

Am besten du lässt erst mal den ganzen Kram mit Encodern und Displays weg. Mach dir zwei Test-Sketche die nur das aller nötigste enthalten

Master:

#include <Wire.h>

const byte SLAVE_ADR = 3;

union Data
{
   //short ist garaniert 2 Bytes, aber überlege dir hier gut was du eigentlich brauchst!
   short values[3];
   byte asBytes[sizeof(values)];
} data;

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

void loop()
{ 
    //hier im richtigen Programm die Encoder-Werte eintragen. Aber bitte mit for-Schleifen statt 8 Variablen durchzunummieren
    data.values[0] = 0xAABB;
    data.values[1] = 0x1222;
    data.values[2] = 0x00FF;

    Wire.beginTransmission(SLAVE_ADR);
    Wire.write(data.asBytes, sizeof(data));
    Wire.endTransmission();

    delay(1000);
}

Slave:

#include <Wire.h>

const byte SLAVE_ADR = 3;

union Data
{
  short values[3];
  byte asBytes[sizeof(values)];
} data;

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

void loop()
{
}

void receiveEvent(int howMany)
{
  if (Wire.available() == sizeof(data))
  {
    for (byte& b : data.asBytes)   //muss wegen Lesezugriff eine Referenz sein
      b = Wire.read();
  }

  //for each macht das einfacher, da man keine Schleifen Abbruchbedingung braucht
  for (byte b : data.asBytes)
    Serial.println(b, HEX);

  //auto bestimmt automatisch den passenden Datentyp
  for (auto b : data.values)
    Serial.println(b);
  Serial.println();
}

Ohne das Außen herum und deine verwirrende, inkonsistente Programm-Struktur sieht man Fehler wahrscheinlich eher. Neben der grundlegenden I2C Kommunikation ist nämlich auch die Größe der Datentypen und die verwendeten Datenstrukturen eine Fehlerquelle. Wenn du mehr oder weniger empfängst als gedacht deutet das darauf hin dass beide Seiten nicht mit den gleichen Datenstrukturen arbeiten.

Du hast z.B. einmal Union drin, aber an einer anderen Stelle machst du einen Cast auf byte*. Das geht beides, aber du solltest dich für eine Variante entscheiden.
Genaugenommen ist der Cast sauberer, da laut C Standard Schreiben und sofortiges Lesen auf eine Union undefiniertes Verhalten ist. Der GCC Compiler kann das allerdings.

Und Union wird für dich erst mal besser lesbarer sein als Adressen und Zeiger. Ich hoffe du siehst jetzt wie man das verwendet. Es ist Unsinn erst mal die Encoder Werte in einem Array zu speichern und dann nur zum Senden die Werte eine lokale Union zu kopieren. Selbst wenn, dann kann man Datenstrukturen (egal ob Array, struct oder union) viel einfacher mit memcpy() kopieren

Ich glaube man muss auf dem Slave auch gar nicht abfragen wie viele Bytes da sind, sondern kann gleich so viel Auslesen wie man erwartet. Da bin ich bin ich mir aber nicht sicher. Ist etwas her, dass ich das ausprobiert habe

Achtung: nicht getestet!! Es geht nur um das Prinzip