Roboterstuerung über serielle Schnitstelle - extrem hohes delay

Guten Tag,

ich versuche von einem Bedienteil mit einem alten Basic microContoller über die Serielle Schnittstelle 7 Steuerbytes an meinen Arduino Mega Robot zu senden.
Dies funktioniert soweit auch, allerdings reagiert der Arduino erst nach ca 5 Sekunden auf die neuen Werte. Da es sich nicht um einen Marsrover handelt, bin ich damit natürlich nicht einverstanden :slight_smile:
Auch die Ausgabe über das Terminal ergibt eben diese Verzögerung von bestimmt 5-6 Sekunden.

Für Ideen wäre ich sehr Dankbar =)

byte data[7];
byte index = 0;
byte b;
byte  w;

int G1;

int IN1_FD=49;
int IN2_FD=3;

void setup()
{
  Serial.begin(1200);
  Serial3.begin(1200);
  
  pinMode (IN1_FD,OUTPUT);
  pinMode (IN2_FD,OUTPUT);

}

void loop(){
//delay(50);
Serial3.print(128);

{
 // delay(100);


    byte b = Serial.read();  
    delay(1);
    if (b == 02) index = 0;  //if byte equals '2' reset index
      if (index <= 6)
    data[index] = b;
    index++ ;


  }

  if (index == 6)  {
    Serial.print("  H = ");
    Serial.print(data[0]);
    G1 = data[1];
    Serial.print("  A = ");
    Serial.print(data[1]);
    Serial.print("  B = ");
    Serial.print(data[2]);
    Serial.print("  C = ");
    Serial.print(data[3]);
    Serial.print("  D = ");
    Serial.print(data[4]);
    Serial.print("  E = ");
    Serial.print(data[5]);
    Serial.print("  F = ");
    Serial.println(data[6]);
    index = 0 ;
}
 
   {
  if (G1 < 125 )                  //PWM Steuerung Rad D
  {
  digitalWrite(IN1_FD,LOW);
  analogWrite(3,(255-G1));
  }
  else if (G1>135 ) 
  {
    digitalWrite(IN1_FD,HIGH);
    analogWrite(3,(255-G1));
  }
  else {
    digitalWrite(IN1_FD,HIGH);
    digitalWrite(IN2_FD,HIGH);
  }
 }
 
 
}

test_serielle_schnittstelle_und_PWM_ino.ino (1.16 KB)

ostiii:
if (b == 02) index = 0; //if byte equals '2' reset index

Was mir auffällt:
Bei dem von Dir gewählten Protokoll dient das Byte mit dem Inhalt 2 zum Zurücksetzen des Leseindex, dabei darf in den nachfolgenden Nutzbytes die 2 nicht mehr vorkommen, sonst würde ja jedesmal bei der 2 der Index zurückgesetzt werden.

Rückfrage: Darf man daher annehmen, dass 2 in den Nutzbytes nicht vorkommen kann?

Im übrigen ist die Einleseroutine aus folgendem Grund fehlerhaft:
a) Bei 1200 Baud reicht ein delay(1) nicht, damit ein neues Zeichen eintrifft
b) Deshalb liest Serial.read(); fast immer den Integerwert -1
c) Dieser Integerwert -1 wird an ein Byte zugewiesen zu einer 0
d) D.h. fast immer werden als Nutzbytes ganz viele unbeabsichtigte Nullen gelesen
e) Im Codeabschnitt
if (index <= 6)
data[index] = b;
index++ ;
wird irgendwann über das Ende des data-Arrays hinausgeschrieben, da index in jedem Falle erhöht wird

Ein sehr befremdlicher Einlesecode, weil Du Zeichen einliest (-1, zugewiesen an Null-Bytes) ohne vorher mit Serial.available() zu prüfen, ob überhaupt ein gültiges Zeichen zum Einlesen vorhanden ist.

Du versucht da sehr viel an Zeichen einzulesen auch wenn gar nichts im Empfangspuffer steht. Serial.read() gibt dann halt -1 zurück. Bei 1200 Baud hast du 1200 Bits pro Sekunde. Also braucht ein Zeichen etwa 8ms wenn ich das richtig sehe. Bei 9600 Baud sind es etwa 1ms. Das ist immer noch sehr lange und die loop ist viel, viel schneller.

Wenn du 7 Zeichen erwartest, dann mach das:

byte buf[7];

if(Serial.available() >= 7)
{
    for(byte i = 0; i < 7; i++)
       buf[i] = Serial.read();
}

Dann wird auch nur gelesen wenn wirklich alles da ist

Das Serial.available() hatte ich dann irgendwann im laufe meiner Verzweiflung heraus kommentiert. Aber es ist natürlich von nöten.

Aber ich denke der Vorschlag von Serenifly könnte mir den Tag retten. Nach ersten versuchen hatte ich mit dieser Methode wesentlich bessere Latenzen.

jurs hat natürlich recht die 2 war als mein Header byte gedacht ( wirklich keine besonders gute Idee, aber die 2 kann tatsächlich nicht in den Nutzbytes vorkommen. )

Aber Serenfly's Lösung könnte klappen, allerdings scheitere ich daran mein Header Byte ( also die 2 - das werde ich noch ändern) zuverlässig als Startbyte zu wählen und somit zuverlässige werte von meinen Sensoren zubekommen.

Schon mal vielen dank für eure schnelle Hilfe =)

if(Serial.available() >= 7)
{
    byte header = Serial.read();
    if (header == 2) {
    for(byte i = 0; i < 7; i++)
       buf[i] = Serial.read();
        
      if( i = 7 ) 
    {  
        
        Serial.print("   HEADER ");
        Serial.print(header);
        Serial.print("   BUF1 ");
        Serial.print(buf[1]);
        FK = buf[1];
        Serial.print("   BUF2 ");
        Serial.print(buf[2]);
        Serial.print("   BUF3 ");
        Serial.print(buf[3]);
        Serial.print("   BUF4 ");
        Serial.print(buf[4]);
        Serial.print("   BUF5 ");
        Serial.print(buf[5]);
        Serial.print("   BUF6 ");
        Serial.println(buf[6]);

Wenn du ein Byte als Header ausliest, darf die for-Schleife aber nur bis 6 gehen (wenn es inklusive Header 7 Bytes sind). Bei 7 Bytes + 1 Byte Header musst du auf available() >= 8 abfragen.

"if( i = 7 )" ist sinnlos. Erstens ist das eine Zuweisung und kein Vergleich. Zweitens wird Code nach der for-Schleife so oder so ausgeführt wenn diese fertig ist. Man muss also nicht extra auf den Index abfragen.

Du müsstest aber die for-Schleife immer machen, auch wenn das Header-Byte ungültig ist. Sonst bleiben die restlichen Bytes im Eingangspuffer. Die werden erst mit Serial.read() entfernt. Leider gibt es anscheinend keinen Befehl um den Puffer komplett zu löschen.

if(Serial.available() >= 7)
{
    byte buf[6];

    byte header = Serial.read();

    for(byte i = 0; i < 6; i++)
          buf[i] = Serial.read();

    if(header == 2) 
    {
           //Ausgabe
    }
}

Also immer alle 7 Bytes (oder wie viele halt erwartet werden) lesen und erst dann auf den Header testen.

Oder so:

if(Serial.available() >= 7)
{
    byte buf[7];

    for(byte i = 0; i < 7; i++)
          buf[i] = Serial.read();

    if(buf[0] == 2) 
    {
           //Ausgabe
    }
}

Wenn es 7 Bytes + Header sind, dann die entsprechenden Indizes um 1 erhöhen. Also die Größe des Arrays, Serial.available() >= 8 und die Zählvariable der for-Schleife.

ostiii:
Aber Serenfly's Lösung könnte klappen

Vorsicht: Serenfly's Lösung ist eine Schönwetter-Lösung, die funktioniert nur dann, wenn das erste empfangene Zeichen das Startzeichen ist und danach alle Zeichen genau so kommen, wie sie erwartet werden.

Falls eine Sendung "ab der Mitte" aufgefangen wird oder asynchron wird, weil mal ein Müllzeichen zusätzlich über die Leitung kommt oder ein Zeichen auf dem Weg zum Empfänger verlorengeht, synchronisiert sich die Lösung nicht wieder auf das Startbyte. Serenfly's Lösung ist immer "synchron auf 7 Zeichen" aber nicht "synchron auf das Startbyte", wenn es mal nicht an der Stelle kommt, an der es kommen sollte.

Alternativvorschlag mit Synchronisierung auf das Startbyte. Und ausgelagert in eine universelle Funktion, so dass man nur ein #define Statement ändern muss, falls sich mal die Puffergröße ändert. Oder das Startbyte.

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

#define STARTBYTE 2
#define BUFSIZE 7
byte buf[BUFSIZE];


boolean getBuf(byte* bytearray, int arraysize)
{
  static int counter=0;
  char c;
  if (!Serial.available()) return false; // nothing to read from Serial
  c=Serial.read();
  if (c==STARTBYTE) 
  {
    bytearray[0]=STARTBYTE;
    counter=1;
  }  
  else if (counter>0 && counter<BUFSIZE)
  {
    bytearray[counter]=c;
    counter++;
    if (counter==BUFSIZE) return true;
  }
  else 
    counter=0;
  return false;  
}


void loop() {
  // put your main code here, to run repeatedly: 
  if (getBuf(buf,BUFSIZE))
  {
       Serial.print("   HEADER ");
       Serial.print(buf[0]);
       Serial.print("   BUF1 ");
       Serial.print(buf[1]);
//       FK = buf[1];
        Serial.print("   BUF2 ");
        Serial.print(buf[2]);
        Serial.print("   BUF3 ");
        Serial.print(buf[3]);
        Serial.print("   BUF4 ");
        Serial.print(buf[4]);
        Serial.print("   BUF5 ");
        Serial.print(buf[5]);
        Serial.print("   BUF6 ");
        Serial.println(buf[6]);   
  }  
}

Ist nicht ausführlich getestet, aber so sollte es laufen.

Das Startbyte selbst darf natürlich nicht in den Nutzbytes vorkommen, wenn das Übertragungsprotokoll vorsieht, auf ein Startbyte zu synchronisieren.

ostiii:
Aber Serenfly's Lösung könnte klappen

Vorsicht: Serenfly's Lösung ist eine Schönwetter-Lösung, die funktioniert nur dann, wenn das erste empfangene Zeichen das Startzeichen ist und danach alle Zeichen genau so kommen, wie sie erwartet werden.

Falls eine Sendung "ab der Mitte" aufgefangen wird oder asynchron wird, weil mal ein Müllzeichen zusätzlich über die Leitung kommt oder ein Zeichen auf dem Weg zum Empfänger verlorengeht, synchronisiert sich die Lösung nicht wieder auf das Startbyte. Serenfly's Lösung ist immer "synchron auf 7 Zeichen" aber nicht "synchron auf das Startbyte", wenn es mal nicht an der Stelle kommt, an der es kommen sollte.

Alternativvorschlag mit Synchronisierung auf das Startbyte. Und ausgelagert in eine universelle Funktion, so dass man nur ein #define Statement ändern muss, falls sich mal die Puffergröße ändert. Oder das Startbyte.

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

#define STARTBYTE 2
#define BUFSIZE 7
byte buf[BUFSIZE];


boolean getBuf(byte* bytearray, int arraysize)
{
  static int counter=0;
  char c;
  if (!Serial.available()) return false; // nothing to read from Serial
  c=Serial.read();
  if (c==STARTBYTE) 
  {
    bytearray[0]=STARTBYTE;
    counter=1;
  }  
  else if (counter>0 && counter<BUFSIZE)
  {
    bytearray[counter]=c;
    counter++;
    if (counter==BUFSIZE) return true;
  }
  else 
    counter=0;
  return false;  
}


void loop() {
  // put your main code here, to run repeatedly: 
  if (getBuf(buf,BUFSIZE))
  {
       Serial.print("   HEADER ");
       Serial.print(buf[0]);
       Serial.print("   BUF1 ");
       Serial.print(buf[1]);
//       FK = buf[1];
        Serial.print("   BUF2 ");
        Serial.print(buf[2]);
        Serial.print("   BUF3 ");
        Serial.print(buf[3]);
        Serial.print("   BUF4 ");
        Serial.print(buf[4]);
        Serial.print("   BUF5 ");
        Serial.print(buf[5]);
        Serial.print("   BUF6 ");
        Serial.println(buf[6]);   
  }  
}

Ist nicht ausführlich getestet, aber so sollte es laufen.

Das Startbyte selbst darf natürlich nicht in den Nutzbytes vorkommen, wenn das Übertragungsprotokoll vorsieht, auf ein Startbyte zu synchronisieren.

Die Alternative wäre, wenn jeder beliebige Bytewert in den Nutzdaten vorkommen soll: Synchronisierung nicht auf ein Startzeichen, sondern auf ein Timeout. Ohne spezielles Startzeichen darf jedes beliebige Zeichen im Code vorkommen. Man müßte dann eben nur beispielsweise als Protokoll eine Sendepause festlegen. Also z.B. "Wenn in der Zeit, in der normalerweise 5 Zeichen eintreffen, kein einziges Zeichen an der Seriellen Schnittstelle ankommt, dann ist das nächste eintreffende Zeichen der Start des nächsten Datenblocks".

Danke euch beiden :slight_smile: also die Übertragung ist nun absolut stabil ... ich benutze also Jurs "wetter feste" - Alternative.

Allerdings und nun kommen wir zum eigentlichen Topic ... habe ich nun wieder gute 5-6 sek delay, bis eine Poti Änderung von meinem Robo wahrgenommen wird.
Die Steuereinheit hat noch einen alten Basic Mikrocontroller drin, der eben wie gesagt mit seinen 1200baud die abgefragten Poti Werte über das Kabel schickt.

Allerdings lässt sich meiner Meinung nach damit kein delay von 5-7 Sekunden erklären. Zwischen durch beim rumprobieren hatte ich auch schon delay's von 1-2 Sekunden. (Immer noch viel :frowning: )

Die Steuereinheit benutze ich noch in Kombination mit meinem alten Basic - Mikro-contoller - Roboter ... da habe ich das Problem nicht. Also vermute ich, dass es doch noch irgendwie am Arduino liegt.

Noch irgendwelche ideen, wo ich ansätzen könnte ??

ostiii:
Allerdings und nun kommen wir zum eigentlichen Topic ... habe ich nun wieder gute 5-6 sek delay, bis eine Poti Änderung von meinem Robo wahrgenommen wird.

Romane drucken kostet Zeit. Besonders Romane drucken bei 1200 Baud.

In den seriellen Eingangspuffer passen 63 Zeichen hinein. Bei Dir passiert das:

  • 7 Bytes (mit Startbyte) empfangen
  • einen Riesen-Roman draus machen und auf dem seriellen Monitor drucken
    Während Du den Roman auf dem seriellen Monitor druckst, kommen neue Zeichen im seriellen Eingangspuffer an.

Und wenn Du den Roman jedes mal ein klein wenig langsamer auf dem seriellen Monitor druckst als die nächsten 6 Bytes ankommen, füllt sich der serielle Eingangspuffer immer mehr, bis er voll ist und Zeichen im Eingang verlorengehen.

Aber irgendwann ist der Roman auf dem seriellen Monitor zuende erzählt und dann schaut der Arduino nach, was er in seinem Eingangspuffer findet. Das ist ein FIFO-Puffer, also First-In-First-Out findet er die ältesten Daten. Bei 7 gesendeten Bytes und vollgelaufenem Puffer bei 7 Bytes Datenblocklänge sind 9 vollständige Datenblöcke im Eingangspuffer. Er zieht also als nächstes einen Datenblock aus dem Puffer (und druckt wieder einen Roman daraus), der schon 9 Sendeperioden alt ist.

Wenn der neuntletzte Datenblock ca. 4,5 Sekunden alt ist, dann bedeutet das: Du sendest ca. 2 Datenblöcke pro Sekunde.

Folgende Strategieen zur Abhilfe fallen mir ein:

  1. Sende die Daten nie schneller, als der Arduino sie verarbeiten kann. Also insbesondere nicht schneller als das Drucken des Romans per Serial.print dauert.

  2. Kürze den Roman, den Du per Serial.print ausdruckst! Wenn nur 7 Bytes ankommen, braucht man eigentlich nicht über 100 Bytes, um diese am Bildschirm darzustellen.

  3. Mut zur Lücke! Hau am Ende, wenn der Roman per Serial.print fertig gedruckt ist, alle Zeichen, die sich im seriellen Eingangspuffer befinden,in die Tonne! Code dazu:

void loop() {
  // put your main code here, to run repeatedly: 
  if (getBuf(buf,BUFSIZE))
  {
       Serial.print("   HEADER ");
       Serial.print(buf[0]);
       Serial.print("   BUF1 ");
       Serial.print(buf[1]);
//       FK = buf[1];
        Serial.print("   BUF2 ");
        Serial.print(buf[2]);
        Serial.print("   BUF3 ");
        Serial.print(buf[3]);
        Serial.print("   BUF4 ");
        Serial.print(buf[4]);
        Serial.print("   BUF5 ");
        Serial.print(buf[5]);
        Serial.print("   BUF6 ");
        Serial.println(buf[6]);   
        // Clear serial input buffer (may be overrun)
       while (Serial.available()) Serial.read();
  }  
}

Der Arduino würde dann danach den NÄCHSTEN vollständigen nachfolgenden Datenblock auswerten. Was bei 2 Sendeblöcken pro Sekunde dann bis zu 0,5 Sekunden dauert, nachdem die Ausgabe auf Serial vollständig in den Serial-Ausgangspuffer (dessen Größe ebenfalls 63 Bytes) hineingeschoben wurde.

63 Zeichen im seriellen Ausgangspuffer zu senden dauert bei 1200 Baud auch ungefähr eine halbe Sekunde. So dass quasi nach Darstellung des letzten Buchstabens nahtlos mit der Ausgabe (praktisch) aktueller Daten fortgesetzt wird.

Tja, merkwürdige Effekte können entstehen, bei nur drei Puffern, in die mal was reingeschoben und mal was rausgezogen wird. In Deinem Fall hast Du es zu tun mit

  • Serieller Sendepuffer des sendenden Systems
  • Serieller Empfangspuffer des Arduino
  • Serieller Sendepuffer des Arduino
    Solange die Puffer praktisch leer sind, ist es äußerst überschaubar, was passiert.
    Sobald auch nur einer dieser Puffer volläuft, können die merkwürdigsten Effekte entstehen.

Danke :slight_smile: Alles funktioniert nun tadellos !

ohne euch == ostiii keine chance =)