Ringpuffer aufbauen

Hi,

irgendwie klemmts wieder.

Ziel: Ich schicke auf der seriellen eine AT-Kommando an ein Modul.
Als Antwort bekomme ich ca. 300-400 Zeichen auch mit kleinen Pausen. In diesem Antwort-Telegramm ist noch eine Zeichenfolge (~ 5Byte), die ich auswerten will. Und ich will die empfangenen Zeichen auf der Debug-Schnittstelle ausgeben. Es kommt kein eindeutiges Ende-Zeichen.

Ich hab mir jetzt mal eine Sub zusammengebastelt, die von der Funktion her das macht, was ich erwarte.

boolean CmdResponsePrintExt(String AT_Command, char *AT_Response, int wait){
   Debug.println(AT_Command);
   ESP8266.println(AT_Command);
   target = millis() + wait;    // def. Zeit Signale empfangen
   x=0; 
   while (millis() < target) {
      while (ESP8266.available()) {
         buf[x] = (ESP8266.read());
         x++;  
      }  
   }  
   buf[x] = '\0';    //Terminierungszeichen setzen
   Debug.println("Antwort von ESP8266:");     
   Debug.println(buf);    
 
   if (strstr (buf,AT_Response)) {
      return 1; 
      }
   return 0;
}

Nur habe ich dafür buf [400] definiert. Das finde ich Verschwendung. (Da hat mich Serenifly irgendwie angesteckt :wink: )
Kann man da mit einfachen Mitteln einen Ringpuffer von ca. 10 Byte definieren, den man nach jedem read mit strstr() parst?

Mir fällt momentan nur das ein:

buf[0] = buf;[1]
buf[1] = buf[2];
...

Elegant ist was anderes. Evtl. noch ne kleine Schleife...

Eigentlich mache ich ja nichts anderes, als das Serial.find(), aber ich habe es noch nicht zustande gebracht, dann auch noch die empfangenen Daten auf der Debug auszugeben. Der Serial.find() wird wohl intern ein Serial.read() nutzen, das den UART-Puffer leert.

Du kannst dir mal die Arduino Serial Klasse anschauen

Ein primitiver Ringpuffer ist ganz einfach, sofern nicht groß optimiert wird (die angesprochene Serial Klasse hatte da in früheren Versionen deutliche Schwächen).
In der einfachsten Version hast du zwei Zeiger: head und tail. Head ist der Zeiger von dem man liest. Tail ist der Zeiger auf den man schreibt. Dann greift man auf den Index mit einer Modulo-Division mit der Puffer-Größe zu. So fängt er dann wieder von vorne an wenn man am "Ende" ist. Diese Division kostet Performance, aber das ist in diesem Fall völlig egal.

Jetzt muss man nur noch feststellen wann der Puffer voll und leer ist. Der Nachteil obiger Methode ist, dass man es nicht trivial unterscheiden kann. Hier gibt es wiederum eine ganze Reihe Optionen.
Eine der einfachsten Lösungen ist einfach noch eine Variable ob die letzte Operation schreibend oder lesend war. Wenn man zuletzt geschrieben hat und head == tail ist der Puffer voll. Wenn man gelesen hat ist der Puffer leer.

Ist wie gesagt nicht die optimalste Variante, aber bei dir kommt es nicht auf Performance an.

Hi Serenifly,

danke für deine Ausführungen.
Du zeigst mir mal wieder, wie blank ich bin. Ich hab nicht mal die Hälfte davon verstanden :confused:

Aber ich glaube, dass du mir etwas anderes beschrieben hast, als ich suche.

Ich brauche so was wie ein "SchiebeLinksByte in buf[]"
Also in etwa so:

        while (ESP8266.available()) {
           c = (ESP8266.read());
           Debug.write (c);  
           ringbuf[0] = ringbuf[1]; 
           ringbuf[1] = ringbuf[2];           
           ringbuf[2] = ringbuf[3]; 
           ringbuf[3] = ringbuf[4];           
           ringbuf[4] = ringbuf[5];           
           ringbuf[5] = ringbuf[6];          
           ringbuf[6] = ringbuf[7];           
           ringbuf[7] = ringbuf[8];          
           ringbuf[8] = ringbuf[9];            
           ringbuf[9] = c;
           boolean found = strstr (ringbuf,AT_Response);
        }

wobei die letzte Zeile noch einen Compiler-error auswirft. Wohl wegen pointer <-> bool

Du hast doch folgendes beschrieben. Puffer voll, dann leeren und wieder von vorne anfangen. oder?
Dann finde ich aber u.U. meinen Suchstring nicht, wenn ein Teil im alten und ein Teil im Neuen pufferinhalt hängt.

strstr() gibt dir einen Zeiger zurück:
http://www.cplusplus.com/reference/cstring/strstr/

Return Value
A pointer to the first occurrence in str1 of the entire sequence of characters specified in str2, or a null pointer if the sequence is not present in str1.

Ich bastle mal was. Dann kannst du probieren ob das was für dich ist

Serenifly:
strstr() gibt dir einen Zeiger zurück:

Dann halt so, oder?

           if (strstr (ringbuf,AT_Response)) {
              found = true; 
              }

Gerade gemerkt: der String muss natürlich Null-terminiert sein, wenn man mit strstr() sucht. Dadurch geht das schon mal so nicht. Am besten man weicht deshalb auf memmem() aus. Das durchsucht Speicher, aber muss nicht terminiert sein:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#ga1c22a39c9d936f18aa0764e331e3cddc

Hier ist wie man mit memmem() nach einem nicht-terminierten String sucht:

void* ptr = memmem_P(serialBuffer, SERIAL_BUFFER_SIZE, PSTR("blah"), 4);
if(ptr != NULL)
{
    //string gefunden
}

Oder kurz:

if(memmem_P(serialBuffer, SERIAL_BUFFER_SIZE, PSTR("blah"), 4))
{

}

memset() arbeitet wie die anderen memory Funktionen mit nicht-typisierten void Pointern. Deshalb der Rückgabewert void*

Ich habe mal einen echten Ringpuffer implementiert. Das Problem bei der Sache ist, dass wenn man nach "test" sucht, das so aussehen kann "st......te". Dadurch dass er wenn er am Ende angekommen ist einfach vorne überschreibt. So kann man dann nicht einfach suchen. Ist also doch nichts.
Hier ist dann doch deine Lösung besser, links raus zuschieben (was aber kein Ringpuffer ist). Das sollte sich aber in einer Schleife erledigen lassen. :slight_smile:

Serenifly:
Gerade gemerkt: der String muss natürlich Null-terminiert sein, wenn man mit strstr() sucht.

Oh sorry, hatte nicht den ganzen Code gepostet.
Das stand noch zwei Zeilen drüber:

ringbuf[10] = '\0';    //Terminierungszeichen setzen

Dann nennen wir ihn einfach FIFO-Puffer 8)

Noch eine Frage.
Ich habe den Puffer so definiert

char ringbuf[11] = "          "; // 10 x blank

dann ist er doch schon terminiert oder?

Kann man eigentlich den Speicherbedarf von einem char-array zur Laufzeit wieder freigeben?

dann ist er doch schon terminiert oder?

Ja, das ist auch eine Option, wobei du die Zuweisung weglassen kannst (und solltest). Globale und lokale static Variablen werden automatisch mit 0 initialisiert.

Kann man eigentlich den Speicherbedarf von einem char-array zur Laufzeit wieder freigeben?

Dazu musst du das Array dynamisch mit malloc() anlegen:
http://www.cplusplus.com/reference/cstdlib/malloc/
Dann kann man den Speicher mit free() freigeben:
http://www.cplusplus.com/reference/cstdlib/free/?kw=free

Aber mach das nur wenn wirklich unbedingt nötig. Das ist auch eine große Fehlerquelle und nicht unproblematisch.

Eine andere Speicher-sparende Option die ganz ohne Puffer auskommt ist ein Zustands-Automat:

Auch bei der Variante mit dem Puffer könnte man eine Abfrage einbauen, dass Zeichen die nicht in der gewünschten Antwort enthalten sind gar nicht abgespeichert werden.