Problem mit Servokommunikation RS232 auf Mega 2560 R3

Hallo zusammen!

Ich versuche, auf der Grundlage von Beispiel #3 in Serial Input Basics mit einem Mega 2560 R3 Statusmeldungen von einem LinMot-Servo abzufragen. Der Servo hängt an Serial3.

const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;

void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  Serial3.setTimeout(10);
}

void loop() {

  // we're sending two commands to the servo via RS232
  // Command: "!PIA\r"  Servo response: "#19532150\r"
  // Command: "!GS1\r"  Servo response: "#R\r"
  
  sub_Servo_Send_And_Receive("!PIA");
  Serial.println("Main loop: PIA done");
  sub_Servo_Send_And_Receive("!GS1");
  Serial.println("Main loop: GS1 done");
  delay(500);
}

void sub_Servo_Send_And_Receive(String cmd) {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '#';
  char endMarker = '\r';
  char rc;

  Serial3.print(cmd + '\r');

  while (Serial3.available() > 0 && newData == false) {
    rc = Serial3.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
  
  if (newData == true) {
    Serial.println("Subroutine prints: " + cmd + ":\t" + receivedChars);
    newData = false;
  }
}

Leider wird scheinbar nur der erste Aufruf der Subroutine ausgeführt (für "!PIA"), denn im Serial Monitor erscheint:

Subroutine prints: !PIA:	19531250
Main loop: PIA done
Main loop: GS1 done
Subroutine prints: !PIA:	19531250
Main loop: PIA done
Main loop: GS1 done

Kommentiert man jeweils einen der beiden Subroutinen-Aufrufe aus, sieht es besser aus:

Subroutine prints: !PIA:	19531250
Main loop: PIA done
Subroutine prints: !PIA:	19531250
Main loop: PIA done

bzw.

Subroutine prints: !GS1:	R
Main loop: GS1 done
Subroutine prints: !GS1:	R
Main loop: GS1 done

Was mache ich falsch?

Sie haben die Grundlagen des Tutorials nicht verstanden.

while (Serial3.available() > 0 && newData == false) {

wird beendet, wenn Sie den Inhalt des Puffers in Serial3 erschöpft haben, selbst wenn newData immer noch falsch ist.

(Ihr Code ist auch ein aktives Warten, Sie stecken in der Funktion sub_Servo_Send_And_Receive () fest, bis die Antwort vollständig empfangen wurde.)

const byte numChars = 32;
char receivedChars[numChars];


void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  Serial3.setTimeout(10);
}

void loop() {

  // we're sending two commands to the servo via RS232
  // Command: "!PIA\r"  Servo response: "#19532150\r"
  // Command: "!GS1\r"  Servo response: "#R\r"

  sub_Servo_Send_And_Receive("!PIA");
  Serial.println("Main loop: PIA done");
  sub_Servo_Send_And_Receive("!GS1");
  Serial.println("Main loop: GS1 done");
  delay(500);
}

void sub_Servo_Send_And_Receive(const char* cmd) {
  boolean recvInProgress = false;
  byte ndx = 0;
  char startMarker = '#';
  char endMarker = '\r';
  char rc;
  boolean newData = false;
  Serial.print("Command \t"); Serial.println(cmd);

  Serial3.print(cmd); // send the command
  Serial3.write('\r'); // add the end marker;
  while (newData == false) { // <==== ACTIVE WAIT FOR THE ANSWER
    if (Serial3.available() > 0) {
      rc = Serial3.read();

      if (recvInProgress) {
        if (rc != endMarker) {
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= numChars) ndx = numChars - 1;
        } else {
          receivedChars[ndx] = '\0'; // terminate the string
          recvInProgress = false;
          ndx = 0;
          newData = true;
        }
      }
      else if (rc == startMarker) recvInProgress = true;
    }
  }
  Serial.print("Answer \t"); Serial.println(receivedChars);
}
1 Like

Im vom TO verlinkten Beispiel 3 ist die Funktion recvWithStartEndMarkers() nicht-blockierend aufgebaut und sammelt ab startMarker solange Zeichen auf, bis endMarker erreicht wird. Daher sind diese Variablen static deklariert.

Die static Deklaration ist derzeit zwar nicht schädlich, wird aber in dieser Form nicht mehr gebraucht, da Ihre Routine jetzt korrekterweise "blockierend" ist. Man könnte sie also entfernen.

Vorab: Das ist ein Fatal, wenn Du auf der Schnittstelle lesen willst.

Auch wenn dein Code jetzt noch anscheinend ansatzweise macht, was Du Dir vorstellst: Es wird Dir mal richtig auf die Füsse fallen.
Grund ist Deiner Verarbeitungskette.
Du trennst das senden nicht vom empfangen.

Du sendest "!PIA"
Und willst dann die Antwort.
Ist auf Serial3 keine Antwort, bist Du raus, weil die Bedingung nicht erfüllt ist:

while (Serial3.available() > 0

Du kommst nie wieder in die Lage, die Antwort auf "!PIA" zu lesen.
Erst wenn Du das nächste Command sendest, in Deinem Fall "!GS1", passiert was eigenartiges: Du bekommst jetzt die Antwort aus dem Puffer der Schnittstelle, die eigentlich für "!PIA" gedacht war.

So nu könntest mir vorhalten, das das mit "!PIA" geht und das nicht Dein Problem ist.
Tausche "!PIA" mit "!GS1". Es ändert sich nichts.

Trenne das senden vom empfangen.
Merke Dir, was Du gesendet hast und ordne das dem empfangenen zu bis:
a) das Timeout zuschlägt
b) die Endekennung da ist
Verarbeitet wird erst wieder, wenn eine Anfangskennug vorhanden ist.
Das machst Du auch nicht.
Den ziehe ich zurück - da ist unten noch nen else, das hatte ich übersehen.
....

Grosse Baustelle.

1 Like

Das hat @J-M-L schon mit einer "blockierenden" Routine gelöst ...

Allerdings bin ich auch ein Freund des nicht blockierenden Lesens in der loop(), da es in der Regel auch noch andere Aufgaben gibt, die man nicht hängen lassen will. Das vom TO verlinkte Beispiel 3 macht es ja nicht-blockierend (daher die static Deklarationen in der Empfangsroutine).

P.S.: Nochmals Glückwunsch zum Erfolg bei der 7-Segment-Nummer im anderen Thread :wink:

es schien mir, dass dies die Absicht von @HurlingFrootmig war (aber Wenn aus irgendeinem Grund das '\r' nicht vorkommt, haben Sie ein Problem ...)

Sorry, das war nicht kritisch gemeint. Ihre Beiträge sind stets hilfreich (und sehr höflich formuliert)! Sie haben genau die Lösung aufgezeigt, die der TO haben wollte. Das Beispiel, aus dem der Sketch abgeleitet worden ist, war nicht-blockierend (deshalb auch dort die static Deklarationen).

Ohne Blockierung konnte das gar nicht funktionieren.

Ich persönlich favorisiere nicht-blockierende Funktionen, weil man sich damit die Freiheit erhält, noch andere Aufgaben in der loop() regelmäßig wahrzunehmen. Das ist aber applikationsabhängig und kein Muss.

Herzliche Grüsse

ec2021

+1

PS: Ich habe das nicht als Kritik aufgefasst (und keine Sorge bei konstruktiver Kritik.)

1 Like

Warum nicht?
Ich werf mal als Idee was rein - reiner Pseudocode und nur um mal nen Gedankengang zu zeigen :wink:

const byte numChars = 32;
char receivedChars[numChars];

void setup()
{
  Serial.begin(9600);
  Serial3.begin(9600);
  Serial3.setTimeout(10);
}

void loop()
{
  // we're sending two commands to the servo via RS232
  // Command: "!PIA\r"  Servo response: "#19532150\r"
  // Command: "!GS1\r"  Servo response: "#R\r"
  bool myBool = subServoReceive();
  if (myBool == false)
  {
    subServoSend("!PIA");
    Serial.println("Main loop: PIA done");
  }
  myBool = subServoReceive();
  if (myBool == false)
  {
    subServoSend("!GS1");
    Serial.println("Main loop: GS1 done");
  }
}

bool subServoReceive()
{
  static unsigned long startMillis = 0;
  static bool inProgress = false;
  static byte ndx = 0;
  const char startMarker = '#';
  const char endMarker = '\r';
  char rc = '\0';
  if (Serial3.available() > 0)
  {
    rc = Serial3.read();
  }
  if (rc == startMarker)  // feststellen das es anfängt
  {
    ndx = 0;
    memset(receivedChars, '\0', sizeof(receivedChars));
    inProgress = true;
    startMillis = millis();
  }
  if (inProgress == true &&           // wenn gesetzt
      rc != startMarker)                  // und nicht der erste Marker
  {
    if (millis() - startMillis > 200) // erste Abbruchbedingung
    {
      inProgress == false;
      Serial.println(F("TimeOut"));
      return inProgress;
    }
    if (rc == endMarker)              // zweite Abbuchfunktion
    {
      receivedChars[ndx] = '\0';
      Serial.println(receivedChars);
      inProgress = false;
    }
    else                              // Zeichen einlesen
    {
      receivedChars[ndx] = rc;
      ndx++;
    }
  }
  return inProgress;
}

void subServoSend(String command)
{
  Serial3.print(command + '\r');
}

Danke - der war wirklich aufregend - zumal die Zuordnung der Pegel nicht stimmte :wink:

Bitte im Zusammenhang lesen: Die vom TO aus dem Beispiel übernommene Funktion konnte so, wie er sie verwendet hat, nicht ohne Blockierung funktionieren. @J-M-L hat das korrigiert :wink:

Ich bin (siehe oben) ein Fan des nichtblockierenden Lesens (wie im übrigen auch das Beispiel 3 aufgebaut ist, wo der Code ursprünglich herkommt). Der Ursprungscode lässt sich durchaus so umschreiben, dass die Absicht des TO erfüllt wird und weitere Routinen in der loop() regelmäßig abgearbeitet werden können.

Da die Aufgabe auch blockierend gelöst werden kann, bestehe ich aber nicht darauf :wink:

Grüße

ec2021

Da beide Versionen blockieren, ist es nur eine Frage wann die Blockade aufgelöst wird :slight_smile:
Sie können auch funktionieren. - Aber In #1 kann es passieren, das die Antwort nicht richtig zugeordnet wird, in #2 wird gewartet bis die Antwort vollständig sein soll...

Ach der TO kommt... - na mal sehen :slight_smile:

Erstmal vielen Dank @J-M-L ! Nach dem Anpassen der Bedingungen läuft es erstmal robust, was ja schon ein großer Fortschritt ist.

Der Servo fängt seine Antworten immer mit '#' an und schließt sie mit '\r' ab. Das betrifft auch alle Fehlermeldungen. Das war auch der Grund, weswegen mir Example #3 zusagte.

Die Codeanregung von @my_xy_projekt finde ich reizvoll, weil sich in der loop() tatsächlich noch einiges tun soll. Danke dafür. Das werde ich mal ausprobieren.

@ec2021: Hättest Du evtl. auch einen Beispiel-Link für eine nichtblockierende Variante?

Beste Grüße
H.

Genau das hab ich mir gedacht.. :wink:

Ja, wer Servos verwendet wird nicht warten wollen...

Ist mein Code.

Ansonsten aus den Beispielen: blink without delay...
Willst Du meinen mal einfach einspielen und sehen, was passiert?
Ich hab drauf geachtet, das er fehlerfrei kompiliert.

Ich würd mal lesen wollen, wie weit mein Gedankengang richtig war....
Hinweis:
Wenn es beim ersten Mal nicht klappt, dann nicht verzweifeln und nach dem senden mal ein 5ms delay() rein. Wenn es dann geht, dann bau ich das fertig.

1 Like

Der Code von @my_xy_projekt ist bereits nicht-blockierend.

Ein aus einem größeren Programm herausgezogener Anteil (ohne "Umfeld" so nicht direkt lauffähig) sieht wie folgt aus:

// This is only an excerpt of a larger program
// It is emulating a traffic light that
// where an ESP32 acts as "traffic light server" and transmits
// the status of Red/Yellow/Green/ via TCP to each client.
// Every single client can control the traffic light 

String Serialcurrentline = "";

void HandleCommands(String Cmd) {
  Serial.println(Cmd);
  // Check to see if the client request was ...
  if (Cmd.endsWith("Ampel On"))  {
    modus = 1;
    interval = 0;
  };
  if (Cmd.endsWith("Ampel Off")) {
    modus = 5;
    interval = 0;
  };
}


void HandleSerialData() {
  while (Serial.available()) {             // if there's bytes to read from the client,
    char c = Serial.read();             // read a byte, then
    if (c != '\r') {  // if you got anything else but a carriage return character,
      Serialcurrentline += c;      // add it to the end of the currentLine
    } else {
      Serial.print("SerialCmd -> ");
      Serial.println(Serialcurrentline);
      // Call the routine  that handles each specific command
     HandleCommands(Serialcurrentline);  
      Serialcurrentline = "";
    }
  }
}


void loop(){
   HandleSerialData();
 // Do something else here if required ...
}

Das Gesamtprogramm auf einem ESP32 emuliert eine Ampel (in Form einer State Machine) und versendet den Status jeder Leuchte (Rot, Grün, Gelb) per TCP an angeschlossene Clients. Jeder Client kann die Ampel ein und ausschalten, dies kann auch per Serial am ESP32 durchgeführt werden; TCP und Serial nutzen die identische HandleCommands()-Routine ...

Beim Zusammenkürzen kann ggf. etwas weggefallen sein, das Prinzip sollte aber erkennbar sein.

1 Like

Danke für das Angebot. Das mach ich auf jeden Fall. Könnte aber erst am WE werden.

Beste Grüße
H

Hallo wieder!

Also Dein Code aus

#9 läuft schön durch und produziert

Main loop: PIA done
Main loop: GS1 done
Main loop: PIA done
Main loop: GS1 done
...

Kein delay() nötig :slight_smile:

Na sag ich doch :slight_smile:
Was mich wundert ist die fehlende Ausgabe vom Serial3.
Kannst Du mal bitte in der subServoReceive() eine neue Zeile 41 einfügen: - sähe dann so aus.

  if (Serial3.available() > 0)
  {
    rc = Serial3.read();
    Serial.println(rc);
  }

Ich will die Antworten sehn....

1 Like

Gerne doch.

Ich habe der Übersichtlichkeit mal eingefügt:

  if (Serial3.available() > 0)
  {
    rc = Serial3.read();
		Serial.print("<");
		Serial.print(rc);
		Serial.println(">");
  }

Da kommt raus:

Main loop: GS1 done
Main loop: PIA done
Main loop: GS1 done
<#>
<1>
<9>
<5>
<3>
<1>
<2>
<5>
<0>
<
>
19531250
Main loop: GS1 done
<#>
<R>
<
>
R
Main loop: PIA done
<#>
<R>
<
>
R
Main loop: GS1 done
<#>
<1>
<9>
<5>
<3>
<1>
<2>
<5>
<0>
<
>
19531250
Main loop: GS1 done
<#>
<R>
<
>
R
Main loop: PIA done
<#>
<R>
<
>
R
Main loop: GS1 done
<#>
<1>
<9>
<5>
<3>
<1>
<2>
<5>
<0>
<
>
19531250
Main loop: GS1 done
<#>
<R>
<
>
R
Main loop: PIA done
<#>
<R>
<
>
R
Main loop: GS1 done
...
1 Like

AAhhh..
Du hast mir die Ausgaben in #16 unterschlagen :slight_smile:
ok.
Und da passiert vermutlich das, was ich für gefährlich halte und was auch meine Itension war, da noch ein wenig weiter zu bohren.

Du bekommst Antworten auf falsche Fragen.
Oder anders:
Du musst Deine Frage stellen und einen TimeOut setzen und auf Antwort warten, bevor Du erneut fragen darfst.
Du bekommst die erste Antwort erst nach drei Fragen. - hier passt es, weil Frage1 == Frage3. Sobald Du da was anderes machst, bist Du raus.

Ich hab na noch ne Idee... :wink:

Unterschlagen ist der falsche Ausdruck.

  1. Es ist tatsächlich möglich, mit dem Code aus #9 sowohl
Main loop: PIA done
Main loop: GS1 done
Main loop: PIA done
Main loop: GS1 done
...

als auch

Main loop: PIA done
R
Main loop: GS1 done
19531250
Main loop: GS1 done
R
Main loop: PIA done
R
Main loop: GS1 done
19531250
Main loop: GS1 done
R
Main loop: PIA done
R
Main loop: GS1 done
19531250
Main loop: GS1 done
R
Main loop: PIA done
R

zu erzeugen. Das scheint irgendwie von der Reihenfolge von Servo-Einschalten, Arduino-Entstromung bzw. Arduino-Reset abzuhängen. Da muss ich noch mal etwas mit experimentieren.

  1. Wenn ich nicht "!PIA" und "!GS1" als Kommandos verwende, sondern stattdessen "!EX1" und "GX1", deren Antworten gleich lang sind, dann tritt kein 2+1 Muster mehr auf, sondern ein 1+1, lediglicg vertauscht:
void loop()
{
  // we're sending two commands to the servo via RS232
  // Command: "!EX1\r"  Servo response in RealTerm: "#129\r"
  // Command: "!GX1\r"  Servo response in RealTerm: "#128\r"
  bool myBool = subServoReceive();
  if (myBool == false)
  {
    subServoSend("!EX1");
    Serial.println("Main loop: EX1 done");
  }
  myBool = subServoReceive();
  if (myBool == false)
  {
    subServoSend("!GX1");
    Serial.println("Main loop: GX1 done");
  }
}

produziert:

Main loop: EX1 done
128
Main loop: GX1 done
129
Main loop: EX1 done
128
Main loop: GX1 done
129
Main loop: EX1 done
128
Main loop: GX1 done
129

Ich habe den Eindruck, dass der blockierende Code von @J-M-L die korrekte Ausgabe von cmd und receivedChars erzwingt, da sie im gleichen Aufruf der Subroutine erfolgt.

Hingegen scheint es beim Trennen von Senden und Empfangen zu einem Versatz zu kommen, dessen Muster nach Länge des Response-Strings unterschiedlich ausfallen kann, nämlich PIA/R, GS1/ 19531250, GS1/R, PIA/R, GS1/19531250, GS1/R vs. EX1/128, GX1/129, EX1/128, GX1/129...

Kann es sein, dass das Lesen bzw. das Ausgeben von 19521250 so lange dauert, dass in der Zeit zweimal GS1 erledigt werden kann?

  1. Nachdem mir aus Versehen zwischenzeitlich mal das serielle Kabel abgegangen war, habe ich gemerkt, dass der Arduino auch mit unendlicher Ausgabe von "TimeOut" hängenbleiben kann, was durch Wiederanstecken nicht zu beheben war.