Information aus dem Empfangen String Splitten und auf LCD Modul ausgeben

Hey Leute,

kann mir jemand verraten, wie schwer es ist einen String der über die USB Schnittstelle kommt in 2 Teile zu zerlegen. Als Trennzeichen würde ich vermutlich einfach ein "|" - Zeihen nehmen. Also den Senkrechten Strich bei < >.

Der erste Teil des Strings wird eine Zahl sein, welche zwischen 1 und 4 ist. Das was danach kommt soll einfach auf das LCD - Modul ausgegeben werden.

Also z.B. es kommt folgender String "2|test", damit wird "test" in die zweite Zeile des LCD Moduls geschrieben.

Am Ende soll das Arduinoboard mit einem Raspberry Pi verbunden werden, der via Python mit dem Arduino kommuniziert. Das ist aber ein anderes Thema.

Michael

Um C strings zu zerlegen gibt es strtok(). String Tokenizer:

Das ersetzt dir automatisch die Delimter durch NULL damit die Teil-Strings korrekt terminiert sind.

Man kann auch nach chars mit strchr() suchen:

Damit könntest du nach dem Delimter suchen, diesen per Hand durch NULL ersetzen (für den ersten Teil-String) und dann von dem Index des Delimters + 1 den zweiten String ausgeben. strtrok() ist da aber etwas handlicher bei vielen Teil-Strings. Wenn es nur einmal Trennen ist tut es auch strchr().
Wenn du nur den zweiten Teil-String willst brauchst du auch den Delimiter nicht mit NULL zu überschreiben.

Das bezieht sich auf NULL-terminierte char Arrays. Nicht die Arduino String Objekte. Du kannst aber mit toCharArray (?) daraus auch einen C-String machen.

DIe Standard C string.h Lib ist hier gut erklärt (mit Beispiel Code):
http://www.cplusplus.com/reference/cstring/
Und hier ist alles was du auf dem AVR wirklich hast (was etwas abweicht aber ähnlich ist:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html

Ich denke die Lösung mit "strtok" ist schon ein guter Anfang.

Also nur im Groben zur Verständlichkeit, ist nur Schematik kein Code:

#String definieren
String = "2|TesT"

#Ersten Teil vom String in Variable
zeile = strtok(String, "|")

#Zeile des Displays festlegen
LCD(1, zeile)

#Zweiten Teil vom String in Variable
ausgabe = strtok(String, "|")

#Text auf Display ausgeben
LCD Ausgabe(ausgabe)

Wäre das so im Groben was zum Ziel führen würde? Mir ist klar, dass ich das noch in einem Quellcode packen muss, aber sonst? Und wenn ich mit Abstand von 1 Sekunde Informationen so an den Arduino schicke, schafft er diese alle zu verabeiten oder?

Michael

Am besten du steigst gleich voll auf C-Strings um. Das sind nur char Arrays:

Du musst halt einmal einen Puffer anlegen der groß genug für deinen maximalen String + 1 ist. z.B.:

const int BUFFER_SIZE = 31;
char buffer[30];

Dann kannst du einen Index mitzählen und sowas machen:

buffer[index++] = Serial.read();

Bedenke da dass wenn du alles eingelesen hast (also z.B. ein LF oder CR kommt, je nachdem was du im Serial Monitor eingestellt hast), du den letzten char auf NULL setzten musst. mit buffer[index] = '\0';
Oder du setzt VOR dem Einlesen den gesamten Puffer mit memset() auf NULL:
http://www.cplusplus.com/reference/cstring/memset/

strtok() ist etwas komplizierter als das. Schau dir das Beispiel genau an. Der erste Aufruf gibt dir einen Zeiger auf den ersten Teil-String. Dann musst du die Funktion in zweites mal aufrufen (und dann mit NULL als Parameter) um an den zweiten Teilstring zu kommen. Siehe auch hier:
http://www.cplusplus.com/reference/cstring/strtok/

strtok() verwaltet intern mit einer static Variablen einen Zeiger auf den Delimeter. Dieser Zeiger wird mit dem ersten Aufruf initialisiert, der den String als Parameter hat. Alle weiteren Aufrufe haben NULL als Parameter und arbeiten dann auf dem String der im ersten Aufruf übergeben wurde.

Es gibt da auch mit strtok_r() (für "reentrant) noch eine Version die den Zeiger extern verwaltet, da ist aber auch nicht viel anders.

Ansonsten, ja. So geht es :slight_smile: Und 1 Sekunde ist lange. Serial ist recht langsam (im ms Bereich), aber der Arduino hat einen Takt von 62.5 ns.

EDIT:
Es gibt auch noch eine andere Option. sscanf():
http://www.cplusplus.com/reference/cstdio/sscanf/
http://www.cplusplus.com/reference/cstdio/scanf/

Die hat aber eine ziemlich grauenhafte Syntax wenn man damit anfängt:

char test= "2|aaaaaa";
char buf[10];
int number;
sscanf(test, "%d|%s", &number, buf);

Man legt einen Puffer für den String an und beschreibt dann den String. Hier haben wir eine Dezimal-Zahl %d, gefolgt von einem Strich und der Rest ist ein String %s

snprintf() und sccanf() fressen viel Speicher, aber sind sehr praktisch wenn man sich immer wiederholenden, kurze Strings hat. Vor allem kann man da Teile von Strings gleich in Zahlen wandeln.

Geht auf dem Arduino aber nicht mit Float!

Ich denke, ich werde es die Tage mal testen in Verbindung mit dem PC und dann gucken was passiert.

Melde mich einfach noch mal, wenn es zu Problemen kommt.

Michael

Jurs hatte da übrigens mal eine fertige Routine für Serial Einlesen gepostet:

har* receiveBuffer()
#define ZEILENTRENNZEICHEN 13   // 13 ist Steuerzeichen CR (Carriage Return)
{
  static char lineBuffer[63]; // Maximale Zeilenlänge festlegen
  static byte counter=0;
  char c;
  if (Serial.available()==0) return NULL; // Nullpointer zurück
  if (counter==0) memset(lineBuffer,0,sizeof(lineBuffer));// Puffer vor Benutzung löschen
  c=Serial.read();
  if (c==ZEILENTRENNZEICHEN)
  {
    counter=0;
    return lineBuffer;
  }
  else if (c>=32) // kein Steuerzeichen?
  {
    lineBuffer[counter]=c; // Zeichen im Zeilenpuffer einfügen
    if (counter<sizeof(lineBuffer)-2) counter++;
  }
  return NULL;
}

void loop()
{
      char* text = receiveBuffer()

      if(text != NULL)
      {
               //Auswertung hier
      }
    
}

Da siehst du wie das gemeint war. Terminiert in diesem Fall bei einem CR am Ende

Habe oben auch noch mal eine Alternative zu strtok() geschrieben, die vor allem bei so kurzen Sachen schön ist.

Ansonsten, wenn deine Zahl immer nur eine Stelle hat kannst du auch einfach in den dritten Index greifen :slight_smile:
So ähnlich:
Serial.println(buffer + 2);

Gibt da viele Optionen, je nachdem wie das genau aussieht und was man da genau mit den Strings machen will. Manchmal muss gar nicht die großen Funktionen rausholen.

Wie gesagt, ich will nur die Serielle Information Splitten in Zeilenangabe für das LCD Modul und auszugebene Information.

Es kommt höchstens jede Sekunde eine neue Information und es soll so einfach wie möglich gehalten werden.

Michael

Es geht eher darum wie dein String genau aussieht. Wenn du am Anfang nur eine einstellige Zahl hast (und nie zwei-stellig). Dann geht auch sowas in der Art:

EDIT: sehe jetzt das du geschrieben hast, dass es nur von 1-4 gehen soll :slight_smile:

char buffer[] = "1|aaaaa";
lcd.setCursor(...);
lcd.print(buffer + 2),
buffer[1] = '\0';
lcd.setCursor(...);
lcd.print(buffer);

Und man muss gar keine extra Funktionen nehmen :slight_smile:
buffer zeigt auf Index 0 und dann addiert man einfach 2 dazu. Zeiger Arithmetik.

lcd.print(buffer[0]) könnte auch gehen nur für das erste Zeichen.

Man kann da wie gesagt auch strchr() nehmen um den Index des Delimiters zu suchen. Dann geht es auch wenn die Zahl mal größer ist.

strtok() geht auch, ist aber eher für sowas gedacht:
"aaaaaa#bbbbbbb#cccccccc#jjjjjjjjj|#zzzzzzzz"

Also lange Ketten.

Ansonsten wie gesagt sscanf(). Das kostet etwas Flash, aber ist schön kurz. Beides aber dann doch Overkill hier.

Da wäre nur die Sache, wie bekomme ich bei der Buffer - Lösung die Zeilenzahl raus?

Weil an sich gefällt die mir sogar noch viel besser, da einfacher :stuck_out_tongue:

Und zum String, das werden nur einfache Informationen wie Titel, Interpret, eine Zeitangabe also Min:Sec und in der dritten Zeile kommt eine Temperaturanzeige hin.

Wobei ich aber vermutlich auch nur folgende Informationen an das Board übergebe: Titel, Interpret und Spieldauer eines Titels. Den die Fortlaufenden Sekunden kann ich ja mit dem Arduino auch allein hinbekommen denke ich oder? wobei es dann nur ein Problem gibt, wenn der Player gestoppt wird, also von daher werde ich wohl doch die Spieldauer und Spielzeit übergeben lassen. 8)

Michael

byte line = buffer[0] - 0x30;

Oder -48 oder - '0'

Wird offensichtlich wenn du dir mal die ASCII Tabelle ansiehst und merkst dass die 0 den ASCII Code 0x30 hat.

EDIT: Natürlich 0x30. 0x20 ist das Leerzeichen. Es wird spät :frowning:

Wobei du dir das mit der Zeilen Nummer auch eventuell sparen könntest. Wenn du weist welcher Teil des Strings wo im Display steht, kannst du auch alles auf einmal schicken. Dann bist du aber wieder bei strtok(), da dann alles in einem String steht. Ist dann Geschmackssache.

Der Hacken wäre nur, wenn ich alles auf einmal schicke, dass ich den gesamten Display jede Secunde neu beschreibe oder macht es ihm nichts aus?

Den dann könnte man auch über eine "Fortschrittsleiste" nachdenken. Wie erstellt man eigentlich eigene Symbole für die LCD Methode? Also in Bascom gibt es einen Editor, mit dem man einzelne Pixel aktivieren kann auf der Matrix eines Kästchens. Und wenn ich das noch richtig im Kopf habe, hat jedes "Kästchen" 5 oder 6 Spalten. Also man könnte den Fortschrittsbalken dann Detailierter darstellen lassen. Die Rechenarbeit übernimmt die Datenquelle und würde dann nur einen Prozentwert übermitteln, mit dem dann der Balken gefüllt wird.

Aber wenn das jede Sekunde beschreiben dem Bildschirm nichts aus macht, wäre das auch eine interessante Möglichkeit.

Michael

Ob man das Display komplett jede Sekunde beschreiben kann, kannst du einfach mal ausprobieren. Kann ich jetzt so nicht sagen. Man sollte es nicht dauernd beschreiben, da die Displays träge sind. Aber damit sind alle paar ms gemeint. Jede Sekunde könnte gehen.

Wobei du schon recht hast, das das trotzdem irgendwie nicht toll ist. Auch wenn es geht. :slight_smile:
Dabei immer eine Zeilennummer voranzustellen ist eine normale Lösung, die nicht schlechter ist.

Solche Fortschrittsbalken findest du glaube ich auch fertig im Internet.

Wie man custom chars erstellt ist hier gezeigt:
http://www.dfrobot.com/wiki/index.php?title=I2C_TWI_LCD2004_(SKU:DFR0154)

Und auch generell in den Beispielen die mit bestimmten LCD Libs kommt. Das LCD hat einen bestimmten Bereich im RAM wo die drin stehen. Dann gibt es einen Befehl mit denen man genau den Bereich beschreibt.

Font Editoren für 5x7 gibt es z.B. hier:

Da sieht du schon die Balken drin.
Oder nach "LCD font generator suchen". Das oben ist ein Online Editor. Da gibt es aber auch viele Programme zum runterladen.