Stoppuhr mit seriellem 'START/STOP/RESET'-Signal empfängt STOP nicht immer

Hallo zusammen,

bei einem Projekt habe ich zwei Arduinos im Einsatz:
-auf einem (Arduino Mega, der serielle 'Master') läuft ein Spiel ab, dessen Zeit von Start bis Ende gestoppt werden soll
-auf dem zweiten (Ardunio UNO, der serielle 'Slave') läuft dieses Stoppuhrprogramm, das durch serielle Kommandos gestartet, gestoppt und resettet wird.

der Aufbau:
-Master und Slave sind über je ein RS-485-Modul miteinander verbunden. Wobei der Master bei Bedarf in den Write-Modus versetzt wird und der Slave immer im Read-Modus ist.
-beim Start, Stop & Reset schickt der Master jeweils ein Byte an den Slave (20 => Uhr startet, 22 => Uhr stoppt, 24 => Uhr wird resettet)
-die beiden 485-Module sind über 3m geschirmtes (GND) 2adriges Kabel miteinander verbunden und kommunizieren bei 9600 baud

das Problem:
-der Start der Stoppuhr funktioniert immer, ebenso der Reset. Lediglich der Stoppen der Uhr wird gelegentlich "überhört", sprich: der Slave empfängt das entscheidende Byte nicht.

meine bisherige Lösung:
-die Bytes mehrfach hintereinander schicken, irgendeins wird schon empfangen werden...
das hat schon sehr gut geklappt, was das Starten und Resetten betrifft. Diese Signale wurden bisher auch ganz gerne mal überhört, werden nun aber immer ausgeführt (5x hintereinander senden)
-beim Byte, das für das Stoppen zuständig ist, klappt das leider nicht: ich sende mittlerweile 100x(!) hintereinander, aber die Uhr läuft in ca. 1 von 20 Fällen einfach weiter...

..und hier noch die dafür relevanten Code-Schnipsel:

Master (aufs Wesentliche gekürzt):

// Serial-Definition für RS485
// Serial1 - RX-19 TX-18
#define SerialEnPin 17                  // or 16 , when HIGH => write, when LOW => read
byte command;
boolean serialmodule = true;           // RS485-modul active

void setup() {
  if (serialmodule == true) {
      Serial1.begin(9600);  // zu RS485 RX19-TX18
      pinMode(SerialEnPin, OUTPUT);
      digitalWrite(SerialEnPin, LOW);   //just listen. write when necessary
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int index = 0; index < 3; index++) {
    SerialWriteCommand(index+3);
    delay(5000);
  }  
}


void SerialWriteCommand(int commandpointer) {
  byte repeatmax = 2;
  switch (commandpointer) {
    case 1:
      // GAME-RESET
      command = 10;         
      break;
    case 2:
      // GAME-START
      command = 12;         
      break;
    case 3:
      // CLOCK-START
      repeatmax = 5;            // safe
      command = 20;        
      break;
    case 4:
      // CLOCK-STOP
      repeatmax = 100;          // wie oft denn noch???
      command = 22;
      break;
    case 5:
      // CLOCK-RESET
      repeatmax = 5;            // safe
      command = 24;
      break;
    case 6:
      // GAME-WON-MASTER
      command = 30;
      break;
    case 7:
      // GAME-WON-SLAVE 1
      command = 32;
      break;
    case 8:
      // GAME-WON-SLAVE 2
      command = 34;
      break;
    case 9:
      // GAME-WON-SLAVE 3
      command = 36;
      break;
    case 10:
      // GAME-WON-SLAVE 4
      command = 38;
      break;
    case 11:
      // GAME-SPEED 1
      command = 50;
      break;
    case 12:
      // GAME-SPEED 2
      command = 52;
      break;
    case 13:
      // GAME-SPEED 3
      command = 54;
      break;
    default:
      command = 0;
      break;
  }
    
  digitalWrite(SerialEnPin, HIGH);        // Write-Mode
  delay(1);
  // repeat for no lost signals
  for (byte repeat = 0; repeat < repeatmax; repeat++) {
    Serial1.write(command);          //Serial Write command to RS-485 Bus
    delay(1);
  }
  digitalWrite(SerialEnPin, LOW);         // back to Read-Mode
  delay(1);
}

... Slave folgt...

und der Stoppuhr-Slave (komplettes Programm):

/*
 -WS2811 (5V- 144LED/m)
 -4 WS2811-Outputs (Min:Sec:Fractions und dots)
 -RS485-Module
 -Serial commandos are bytes (START = 20, STOP = 22, RESET = 24)
 */

#include <SoftwareSerial.h>
SoftwareSerial ClockSerial(8, 9); // RX, TX
#define enablePin 10
byte readcommand;

#include <Adafruit_NeoPixel.h>

#define fraPin 6    // fractions
#define secPin 5    // sekunden
#define minPin 4    // minuten
#define dotPin 3    // Punkte

Adafruit_NeoPixel SEGfra = Adafruit_NeoPixel(98, fraPin, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel SEGsec = Adafruit_NeoPixel(98, secPin, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel SEGmin = Adafruit_NeoPixel(98, minPin, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel SEGdot = Adafruit_NeoPixel( 8, dotPin, NEO_GRB + NEO_KHZ800);


byte digits[10][7] = {{0,1,1,1,1,1,1},  // Digit 0
                     {0,1,0,0,0,0,1},   // Digit 1
                     {1,1,1,0,1,1,0},   // Digit 2
                     {1,1,1,0,0,1,1},   // Digit 3
                     {1,1,0,1,0,0,1},   // Digit 4
                     {1,0,1,1,0,1,1},   // Digit 5
                     {1,0,1,1,1,1,1},   // Digit 6
                     {0,1,1,0,0,0,1},   // Digit 7
                     {1,1,1,1,1,1,1},   // Digit 8
                     {1,1,1,1,0,1,1}};  // Digit 9 | 2D Array for numbers on 7 segment

uint32_t colour;

unsigned long number, lastnumber, fractimer, starttime, stoptime;
long minutes, seconds;
float fractions = 0;
float calval = 5;
int timecal = 7875;                              // Calibration-Variable for calval/100 sec
int debouncer, val, index, dots;
boolean blinking, lastblinking, buttonstate, lastbuttonstate, dot;
byte lasthunderttausender, lastzehntausender, lasttausender, lasthunderter, lastzehner, lasteiner;


void setup() {

  ClockSerial.begin(9600);   // initialize serial at baudrate 9600
  pinMode(enablePin, OUTPUT);
  digitalWrite(enablePin, LOW);   //  (Pin EN always LOW to receive value from Master)
  
  SEGfra.begin();
  SEGfra.show(); // Initialize all pixels to 'off'
  SEGsec.begin();
  SEGsec.show(); // Initialize all pixels to 'off'
  SEGmin.begin();
  SEGmin.show(); // Initialize all pixels to 'off'
  SEGdot.begin();
  SEGdot.show();

  
  //Serial.begin(9600);
  Serial.println("ready");

  blinking = false;
  number = 0;
  lastnumber = 888888;
  fractions = 0;
  minutes = 0;
  seconds = 0;
  val=2;
  stoptime = millis();
  delay(500);

  lasthunderttausender = 99;
  lastzehntausender = 99;
  lasttausender = 99;
  lasthunderter = 99;
  lastzehner = 99;
  lasteiner = 99;
  
  
}

void loop() {

  if (blinking == true) { checkTime(); }

  checkSerial();

  if ((number != lastnumber) || (blinking != lastblinking)) { ShowDigits(number, Wheel(val)); }

  lastnumber = number;
  lastblinking = blinking;
  
}



void checkSerial() {
  if (ClockSerial.available()) {
      delay(1);
    while (ClockSerial.available()) {
      readcommand = ClockSerial.read();           // Receive byte from Master
      delay(1);
      if (readcommand == 22) { break; }
    }
    switch (readcommand) {
      case 20:
        // CLOCK START
        fractimer = micros();                                                             // START FRACTIONS-TIMER
        starttime = millis();
        blinking = true;
        val=88;      // grün
        break;
      case 22:
        // CLOCK STOP
        stoptime = millis();
        blinking = false;
        val=2;        //rot
        break;
      case 24:
        // RESET
        blinking = false;
        number = 0; minutes = 0; seconds = 0; fractions = 0;
        lasthunderttausender = 99; lastzehntausender = 99; lasttausender = 99; lasthunderter = 99; lastzehner = 99; lasteiner = 99;
        lastnumber = 888888;
        val=2;
        BlackOut();
        break;
    }
  }
}


void checkTime() {
  while ((micros()-fractimer) > timecal) {
    // calval/100 Sek vergangen:
    fractions++;
    if (fractions > 99) { fractions = fractions - 100; seconds++; }
    if (seconds > 59) { seconds = 0; minutes++; }
    if ((minutes > 98) && (seconds > 58) && (fractions > 98)) { blinking = false; }
    number = (fractions + (seconds * 100) + (minutes*10000));
    //fractimer = micros();
    fractimer = fractimer + timecal;
  }
}

void ShowDigits(long Zahl, uint32_t Farbe) {
  byte einer = Zahl % 10;
  byte zehner = (Zahl / 10) % 10;
  byte hunderter = (Zahl / 100) % 10;
  byte tausender = (Zahl / 1000) % 10;
  byte zehntausender = (Zahl / 10000) % 10;
  byte hunderttausender = (Zahl / 100000) % 10;
  boolean showsegment = false;
    
  for(index = 0; index < 7; index++) {
    //Segment-Minuten
    if ((lasthunderttausender != hunderttausender) || (lastblinking != blinking)) {
      showsegment = true;
      if (digits[hunderttausender][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGmin.setPixelColor((index*7)+(49*0)+dots, colour); }
    }
    if ((lastzehntausender != zehntausender) || (lastblinking != blinking)) {
      showsegment = true;  
      if (digits[zehntausender][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGmin.setPixelColor((index*7)+(49*1)+dots, colour); }
    }
  }
    if (showsegment == true) { 
      SEGmin.show(); 
      showsegment = false; 
      lasthunderttausender = hunderttausender;
      lastzehntausender = zehntausender;
    }

    //Segment-Sekunden
  for(index = 0; index < 7; index++) {
    if ((lasttausender != tausender) || (lastblinking != blinking)) {
      showsegment = true;
      if (digits[tausender][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGsec.setPixelColor((index*7)+(49*0)+dots, colour); }
    }
    if ((lasthunderter != hunderter) || (lastblinking != blinking)) {
      showsegment = true;  
      if (digits[hunderter][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGsec.setPixelColor((index*7)+(49*1)+dots, colour); }
    }
  }
    if (showsegment == true) { 
      SEGsec.show(); 
      showsegment = false; 
      lasttausender = tausender;
      lasthunderter = hunderter;
    }
    
    //Segment-Hundertstel
  for(index = 0; index < 7; index++) {
    if ((lastzehner != zehner) || (lastblinking != blinking)) {
      showsegment = true;
      if (digits[zehner][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGfra.setPixelColor((index*7)+(49*0)+dots, colour); }
    }
    if ((lasteiner != einer) || (lastblinking != blinking)) {
      showsegment = true;  
      if (digits[einer][index] == 0) {colour = 0;}
      else {colour = Farbe;}
      for (int dots = 0; dots < 7; dots++) { SEGfra.setPixelColor((index*7)+(49*1)+dots, colour); }
    }
  }
    if (showsegment == true) { 
      SEGfra.show(); 
      showsegment = false; 
      lastzehner = zehner;
      lasteiner = einer;
    }

  for (dots = 0; dots < 4; dots++) { 
    SEGdot.setPixelColor(dots, Farbe);                    // Punkte Min:Sec
    if ((zehner < 5) || (blinking == false)) { SEGdot.setPixelColor((4+dots), Farbe); } 
    else { SEGdot.setPixelColor((4+dots), 0); }
  }
  SEGdot.show();   
}


void BlackOut() {
  for (int dark = 0; dark < SEGmin.numPixels() ; dark++) {
    SEGfra.setPixelColor(dark, 0);
    SEGsec.setPixelColor(dark, 0);
    SEGmin.setPixelColor(dark, 0);  
    if (dark < 8) { SEGdot.setPixelColor(dark, 0); }
  } 
  
  SEGfra.show();
  SEGsec.show();
  SEGmin.show();
  SEGdot.show();

  lasthunderttausender = 99; lastzehntausender = 99; lasttausender = 99; lasthunderter = 99; lastzehner = 99; lasteiner = 99;
}


// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return SEGmin.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return SEGmin.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return SEGmin.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

Sende oder empfange ich die Daten fachlich falsch?
Sollte ich an der Baudrate etwas verändern?
"Vergewaltige" ich den Serial Buffer mit dem häufigen Senden?
Sollte ich beim Senden/Empfangen mit anderem timing (bestimmte delays) arbeiten?
Da das Problem auch nur auftritt, wenn "die Uhr läuft", sprich: wenn der Arduino sehr mit der recht intensiven Arbeit der Aktualisierung aller Neopixel beschäftigt ist. Könnte da das Problem der "überhörten" Kommandos liegen?
Eine ganz andere Lösung bei gleichem Hardware-Aufbau?

Bin für Rückfragen und ... Inspirationen offen :slight_smile:

noiasca:
Alle delays entfernen.

Leider verschlechtert das das Ergebnis:
-delays beim Senden des Masters raus => Slave empfängt kaum noch etwas
-delays beim Slave in der while-schleife raus => mehr Aussetzer als vorher

Alles auf Mega umbauen und Hardware-Serial nutzen:
-habe ich auch überlegt, ob es mit der Software-Serial zusammenhängt, da diese wohl Einschränkungen gegenüber der Hardware-Serial aufweist, die ich aber (noch) nicht in Zusammenhang bringen kann..

habe mal eine kleine Debug-Routine in den Empfangsteil des Slaves hinzugefügt, das mir die empfangenen Bytes auf dem Seriellen Monitor ausgibt:

START => received: 20,20,20,20,20 (alles gut)
STOP => received: 22, 22, 22, 22 ..... (usw. bis 100x, alles gut)
RESET => received: 24, 24,24,24,24 (alles gut)

Soweit die Ergebnisse wie erwartet, ab und zu mal einzelne Ausbrecher, die ich aber erwartet habe und mit dem mehrfach senden abfangen kann..

Nun aber zu dem Fall, daß die Uhr mal nicht stoppt:

START => received: 20,20,20,20,20 (alles gut)
STOP => received: 145, 100, 100, 100, 100, 100, 100, 100 ..... (was zum....!?)
RESET => received: 24, 24,24,24,24 (alles gut)

Woher können dieses immer gleich falsch abweichenden Werte herrühren...?

Hallo,

ich habe jetzt nicht nachgeschaut, aber sehr wahrscheinlich sperrte die Neopixel die Interrupts beim rausschicken der Daten, um das Timing der WS2812 einzuhalten. Dann kann SoftSerial in dieser Zeit keine seriellen Daten erkennen.
Warum eigentlich die Lösung über seriell? Mit 2 Leitungen und GND könntest Du auch einfach über 2 Bits statisch schalten: 00 nichts, 01 - Start, 10 - Stop, 11 - Reset. Sollte auch über 3m mit den TTL-Pegeln gehen.
Der Master setzt den gewünschten Status und läßt ihn so, bis er was anderes will.
Der Slave liest den Zustand der beiden Bytes, vergleicht, ob sich was gegen borher geändert hat und führt dann den Befehl aus. Ist dann komplett asyncron, egal, wann der UNO die Zeit finden, die Bits einzulesen.
Nur so als Idee.

Gruß aus Berlin
Michael

In der Tat scheint das Problem im Konflikt zwischen Neopixel (noInterrupts) und der Software-Serial zu liegen. Gut erkannt.. Jetzt weiß ich, wonach ich weitersuchen muss :slight_smile:

Ich werde mal zwei weitere Ansätze probieren:
-das Slave-485-Modul in den Write("nicht Read")-Zustand versetzen, solange Neopixel geupdatet werden, dann sollten in dieser Zeit ja keine korrupten Daten im Serial-Buffer landen...? (oder kann ich den Input-Buffer anderweitig nach dem Neopixel-Update leeren?)
-oder die serielle Verbindung für diese Zeit komplett beenden..? (also immer ClockSerial.end(); vor und ClockSerial.begin(9600); nach dem Neopixel-Event?)

Die Idee, zwei Pins als Codierung zu benutzen, finde ich auch gut, werde ich aber nur umsetzen, wenn ich das bisherige Setup nicht software-mässig lösen kann :slight_smile:

Wie gesagt, ich dachte ja, daß ich in der Zeit zwischen zwei Pixel-Updates genug Zeit habe, um dazwischen die Daten "reinzuprügeln" (mehrfach senden), aber irgendwie bedeutet einmal falsch empfangen auch, daß der Rest falsch empfangen wird...
Vielleicht sollte ich auch mal probieren, die Baud-Rate zu erhöhen,.. eventuell finde ich ja damit ein "Zeitfensterchen", durch das mein Kommandobyte reinschlüpfen kann :wink:

Geh mal etwas langsamer in den Readmode zurück. Bei 9600 kann es sein das die UART noch sendet
während du schon den Writemode wegnimmst !

Ulli

Hallo,

Deine Ansätze kannst Du zumindest durchtesten, Baudrate bis max. 38400 könnte SoftSerial auf dem UNO noch sicher hinbekommen, austesten.
Das Problem generell könnte sein, daß wenn die Softserial (Empfang ist ja auch Interrupt gesteuert) auf ein Startbit getriggert und jetzt in der Übertragung die NeoPixel die Interrupts sperrt, der Müll zustande kommt und die Softserial nicht wieder in einen sauberen Zusatand kommt. Die könnte bei solcher Art Störungen vermutlich nicht 100% wasserdicht sein.

Viel Erfolg.

Gruß aus Brlin
Michael

Vielen Dank an Alle für Euer Licht in meinem Dunkel! :slight_smile:

Einfach mal eine Nacht darüber schlafen hilft doch auch gelegentlich... :sleeping:

Ohne Umbau habe ich jetzt (für mich) eine Lösung gefunden:

..all meine bisherigen Ansätze zwecks Umgehung von Turbulenzen im ReadBuffer durch die Neopixel-Library haben nicht gefruchtet.

Daher habe ich mir überlegt, WENN in dieser kritischen Phase nur ein einziges Byte erwartet wird, dann KANN es doch nur das STOP-Byte(22) sein.. ergo habe ich die ersten Zeilen des Serialreads wie folgt abgeändert:

void checkSerial() {
  byte readpointer = 0;
  if (ClockSerial.available()) {
    while (ClockSerial.available()) {
      readcommand = ClockSerial.read();           // Receive byte from Master
      readpointer++;
      if ((readcommand == 20) || (readcommand == 24)) { 
        while (ClockSerial.available()) { ClockSerial.read(); }                          //empty readbuffer 
        break;
      }
      else if ((readpointer > 2) || (readcommand == 22)) {                               //unknown command? must be 22.
        while (ClockSerial.available()) { ClockSerial.read(); }                          //empty readbuffer
        readcommand = 22; 
        break; 
      }
    }

... usw

d.h.:
UHR ist gestoppt => keine Neopixel-Aktivität => START und RESET werden immer erkannt
UHR läuft => Neopixel-Aktivität => Entweder wird STOP empfangen, oder was immer empfangen wird zu STOP gemacht... :smiling_imp:

Es funktioniert.. schlagt mich für die Art der Problemlösung,... aber es funktioniert :smiley:

Jetzt wäre es Zeit für ein bisschen Ehrgeiz :D.
Zumindest für mich an deiner Stelle. Ein Ansatz wäre ein Mega 4 HW-Serial oder sich einen Arduino Clone mit 328PB zulegen der hat auch zwei HW Serial.

Das steht auch noch auf meiner to-do-Liste, da ich diese Uhr gerne noch ein bisschen "aufbohren" und noch mehr Funktionen drin haben möchte. Mit dem UNO kratze ich doch schon sehr am Speicher-Limit (dank der 302(!) Neopixel)

Aber gestatte mir die Rückfrage: Versaut mir nicht die Neopixel-Library generell den seriellen Empfang, egal ob HW- oder SW-Serial?

Hallo,

Deine Lösung ist pragmatisch und könnte auch von mir sein...
HW-Serial hat mindestens Buffer für ein Byte, mache Controller auch 2 Byte. Das passiert komplett in Hardware, wenn niemand rechtzeitig die Daten abholt und das nächste kommt, ist das andere eben weg.
Bei mir wäre der Slave vermutlich sowieso eher ein D1 mini mit ESP8266 (duck und wech...).

Gruß aus Berlin
Michael

Statt des ESPs mit dem ganzen WLAN Geraffel wäre jetzt hier ein Teensy von PJRC wohl die richtige Wahl der hat von allem genug .
Gruß
DerDani

Hallo,

volvodani:
Statt des ESPs mit dem ganzen WLAN Geraffel wäre jetzt hier ein Teensy von PJRC wohl die richtige Wahl der hat von allem genug .

Das ist durchaus richtig. D1 mini gewinnt bei mir auch nur wegen des Preises (unter 5€) und der Möglichkeiten. WLAN muß man nicht nutzen, kostet aber auch nichts extra. Aber selbst bei solchen Sachen ist WLAN bei mir dann meist mit OTA eingerichtet, läßt sich so bequem updaten ohne ihn erst an der Rechner zu stecken.

Gruß aus Berlin
Michael

Ich stehe ja auch total auf die ESP gerade wegen OTA. Habe schon ein paar ESP auf 16MB aufgerüstet.
80% meiner letzten Projekte sind auf dem ESP, weil man auch mit Blynk ne Mega Plattform hat für Fernbedienen ohne Cloud Service (lokale Installation auf nem Pi).

Hier ist der Teensy 4.0 aber mit den 600MhZ und den ganzen Hardware Dingen auch von Preis/Leitung sehr gut. Auch wenn ein 3.2 hier reichen würde aber für 2$ mehr 528 MhZ extra. Da kann man machen was man will. Und die WS2812 Library von Paul Stoffregen für die Teensys ist mega schnell zusammen mit eben diesen.

Gruß
DerDani

Hallo,

volvodani:
Hier ist der Teensy 4.0 aber mit den 600MhZ und den ganzen Hardware Dingen auch von Preis/Leitung sehr gut. Auch wenn ein 3.2 hier reichen würde aber für 2$ mehr 528 MhZ extra. Da kann man machen was man will. Und die WS2812 Library von Paul Stoffregen für die Teensys ist mega schnell zusammen mit eben diesen.

Wenn ich seine Absicht richtig verstanden habe, steurt er eine Stopuhr mit 7-Segment Ziffern aus WS2812 an.
Was soll ein Teensy 4 für den rund 5-fachen Preis eines D1 mini da für Vorteile bringen?
Sich mit 600MHz statt 80MHz schneller langweilen?

Gruß aus Berlin
Michael

Hi

Die Ziffern werden wohl einzeln versendet - somit ist nach spätestens 98 Pixeln der Interrupt wieder frei und könnte was empfangen.
Meine Lösung wäre CAN - man spuckt die Daten in den Bus und jeder Andere empfängt den Kram.
Nachdem die Neopixel durch sind, wird die INT-Leitung des CAN-Modul gepollt - wenn Da 'was ist', wird die Nachricht abgeholt - wenn Die für uns ist (... in diesem Beispiel sind's ja nicht viel mehr) wird der Befehl interpretiert.

Bei CAN ist das Datenpaket auf 8 Byte begrenzt, weiter sendet jeder Knoten (so heißen Die im CAN) eine Absender-Kennung, statt wie sonst üblich eine Empfänger-Kennung.
Auch MUSS diese Kennung EINZIG im ganzen Bus sein - sonst kann sich Dieser aufhängen (wenn zwei identische IDs aushandeln, Wer nun senden darf, gewinnen Beide - beim nachfolgendem Senden stellen Beide einen Fehlerfest, brechen ab und fangen von Vorne an ... never endling story gehört hab)

Auch hat CAN den Vorteil, daß ALLES bereits im CAN-Modul abläuft - der Kampf, Wer nun senden darf, das Senden/Empfangen, die Fehlerkorrektur.
Auch kann man (wenn man kann) filtern, was man empfangen will (von mir noch nicht benutzt, bisher empfängt Jeder Alles).

MfG