Probleme mit String Array

Hi,

ich bin Anfänger in Sachen Arduino und konnte mir dank den vielen Websites schon sehr gut selbst helfen. Bei einem Problem hänge ich jetzt aber deutlich, und irgendwie kapier in in dieser Hinsicht die ganzen Anleitungen nicht.

Ich möchte meinem Arduion beibringen, auf Befehle an der seriellen Schnittstelle zu reagieren (z.B. set LED rot = 128). Folgende Vorgehensweise habe ich mir dazu überlegt:

Solange an der seriellen Schnittstelle Daten anliegen, soll der µC diese Char für Char auslesen und nach Sonderzeichen wie Leerzeichen, Doppelpunkt, Komma und 'ist-gleich' absuchen. Normale Zeichen soll er in ein Stringarray ablegen. Bei jedem dieser Sonderzeichen wird ein neues Element in diesem Array begonnen. Kommt ein '/n' werden die Strings ausgewertet und verarbeitet.
Das ganze ist im Moment sehr experimentell implementiert. Hier die relevanten Codeausschnitte:

(...)

char inputChar = 0;
String inputString[] = "";
int inputStringIndex = 0;

(...)

void setup() 
{
  Serial.begin(9600);
   
   (...)

void loop() 
{ 

   (...)

  while (Serial.available())  //solange serielle Daten vorhanden sind
  {
    inputChar = (char)Serial.read(); //nächstes Byte einlesen
    switch (inputChar) //und auf Sonderzeichen prüfen
    {
      case ' ': {inputStringIndex++;} break; 
    
    default: //wenn kein Sonderzeichen
      {
        inputString[inputStringIndex] =+ inputChar;
      } break;
  
    }
  }

(...)

}

Die Fehlermeldungen:

CPCH.cpp: In function 'void loop()':
CPCH:92: error: invalid conversion from 'int' to 'const char*'
CPCH:92: error: initializing argument 1 of 'String& String::operator=(const char*)'

Die hierbei bemängelte Zeile ist:

inputString[inputStringIndex] =+ inputChar;

Wie gesagt: ich habe hierzu schon einige Texte gelesen und meine kapiert zu haben, dass sich ein String nicht so einfach als Array ansprechen lässt. Aber irgendwie hats bei weitem noch nicht "klick" gemacht. Kann mir wer unter die Arme greifen?

Viele Grüße
da_user

Probier

inputString[inputStringIndex++] = inputChar;

Vergiss das abschließende '\0' nicht, wenn Du das char-Array als String verwenden willst!

Dann würde ich ja für jeden einzelnen Buchstaben einen neuen String anlegen, es soll aber für jedes Wort (also wenn ein Leerzeichen da ist) ein neuer String angelegt werden.

Am genannten Beispiel (set LED rot = 128):

String[0] = "set"
String[1] = "LED"
String[2] = "rot"
String[3] = "128"

Wobei dann natürlich noch String[3] in einen Integer/Real gewandelt werden müsste.

Dein Problem ist vermutlich schon das

String inputString[] = "";

Denn damit hast Du noch kein Array (wie das z.B. in PHP der Fall wäre). In C musst Du bei solchen Konstruktionen immer Speicher reservieren oder bei Klassen ein Objekt instantiieren.
Probier mal folgendes:

char inputChar = 0;
int inputStringIndex = 0;

//array für 10 Strings reservieren
String inputString[10];
//Array Initialisieren
for(int i=0;i<10;i++) {
    inputString[i] = String("");
}
...

Die Stringklasse selbst sorgt dann bei den einzelnen Strings für die Speicherverwaltung. Testen kann ich das zur Zeit leider nicht, daher ist der code ohne Gewähr :slight_smile:

Genau das ist der Grund, daß der Code Variablen im Speicher überschreibt und Du nicht weißt wieso das Programm nicht funktioniert.
In C gibt es keine Strings, das sind Arrays von char. Wie bereits geschrieben muß für die Umwandlung der string mit null abgeschlossen sein.
Grüße Uwe

Strings gibt es schon, dafür ist ja die Stringklasse da, aber ein wenig Initialisierung muss schon sein. Folgender Code ist getestet und funktioniert

char inputChar = 0;
int inputStringIndex = 0;

//array für 10 Strings reservieren
String inputString[10];


void setup() {
    Serial.begin(9600);

//Array Initialisieren
for(int i=0;i<10;i++) {
    inputString[i] = String("");
}
Serial.println("starting");
}

void loop() {
  while (Serial.available())  //solange serielle Daten vorhanden sind
  {  
    //wenn maximaler index erreicht, dann alles löschen und von 0 anfangen
    if(inputStringIndex == 10) {
        inputStringIndex =0;
        for(int i=0;i<10;i++) {
          inputString[i] = String("");
        }
    }
    inputChar = (char)Serial.read(); //nächstes Byte einlesen
 
    switch (inputChar) //und auf Sonderzeichen prüfen
    {
      //leerzeichen als trenner
      case ' ': {inputStringIndex++;} break; 
      //eine ";" als zeilenende erkennen.
      case ';':  {inputStringIndex++;showStrings();} break;
    default: //wenn kein Sonderzeichen
      {
        inputString[inputStringIndex].concat(inputChar);
      } break;
  
    } 
  } 
  
}
//Strings ausgeben
void showStrings() {
 for(int i=0;i<10;i++) {
  Serial.print(i);
  Serial.print(": ");
  Serial.println(inputString[i]);
 } 
}

Was nicht funktioniert ist "inputString[inputStringIndex] +=inputChar;". Hier addiert der Compiler wohl den char zum Zeiger auf den String dazu.
Daher der "sicherere" Methodenaufruf: "inputString[inputStringIndex].concat(inputChar);"

Eine Eingabe von "set led1 255" wird nun wie gewünscht in eine "Liste" von String gewandelt.

Die Stringklasse kümmert sich bereits um die Null-Terminierung des Strings und abstrahiert recht gut das Rechnen mit den char* Zeigern und der ganze Speicherverwaltung.
Trotzdem ist es nie verkehrt zu schauen wo der Speicher für irgendwelche Daten her kommt. Gerade wenn Daten mit variabler Länge eingelesen werden, kann der Compiler nicht vorher sehen wieviele Daten kommen.
Grüße,
Mario.

Vielen Dank,

ich werde das nächste Woche wenn ein bisschen Zeit ist mal antesten :wink:

VG
da_user

da_user:
Wobei dann natürlich noch String[3] in einen Integer/Real gewandelt werden müsste.

Das geht mit "atoi()", btw. :wink:

Hi,

danke, durch den sicheren Methodenaufruf funktioniert das ganze nun.

Zwei kleinere Rückfragen noch:

1.) kann es sein, dass über das Terminal kein '/n' als Zeilenabschluss geschickt wird?
2.) Betreffs:

Trotzdem ist es nie verkehrt zu schauen wo der Speicher für irgendwelche Daten her kommt. Gerade wenn Daten mit variabler Länge eingelesen werden, kann der Compiler nicht vorher sehen wieviele Daten kommen.

Kann ich nicht nur einfach den Speicher für 10 Strings resiervieren, sondern auch für 10 Strings á 11 Zeichen?

Das geht mit "atoi()", btw.

Vielen Dank Joghurt :wink:

da_user:
1.) kann es sein, dass über das Terminal kein '/n' als Zeilenabschluss geschickt wird?

Das kann man irgendwo einstellen...

da_user:
Vielen Dank Joghurt :wink:

:wink:

Kann ich nicht nur einfach den Speicher für 10 Strings reservieren, sondern auch für 10 Strings á 11 Zeichen?

Ja kannst Du, ist aber Speicherverschwendung und davon hat der Arduino nicht so üppig.. Abgesehen davon ist es wurscht, was Du bei der Stringklasse am Anfang "reservierst", der Speicher für den Stringpuffer wird ja trotzdem dynamisch verwaltet. Spätestens beim Zuweisen eines kleineren Strings ist der Speicher wieder weg. Negativ könnte sich dann aber eine "Fragmentierung" des Speichers auswirken.
Beispiel:
10x11 Zeichen werden reserviert. Da das auf einen Schlag passiert, ist es sehr wahrscheinlich, das die 110 Bytes direkt hintereinander im Speicher liegen.
Nun wird dem ersten String ein neuer Wert mit 5 Zeichen zugewiesen (+\0 am Ende) werden 5 Bytes freigegeben. Die liegen nun aber als 5 Bytes freier Speicher zwischen dem 1. und dem 2. String. Kommt nun ein malloc(x) mit x>5, können die 5 Bytes nicht genutzt werden und der Speicher ist zwar nicht belegt, kann aber trotzdem nicht gut genutzt werden.
Keine Ahnung wie gut die Speicherverwaltung der eingesetzten Lib ist, aber Zaubern kann die sicher auch nicht.

Vielen Dank Joghurt :wink:

Genau, möge der Saft mit Dir sein, Lonestar :slight_smile:
(sorry da konnte ich nicht wiederstehen)

Mario.

Hallo,
es geht auch so.
char* Mystring = "Empfangen von Bytes ";

if (Serial.available() > 0) {
Mystring[i++] = Serial.read();
i = i & 0x0F; // 16 Bytes max
}

Es gibt auch Beispiele mit Ringbuffern in C und C++ hier laufen zwei Zeiger um die Wette,
der eine schreibt der andere liest aus diesem Buffer.

char* Mystring = "Empfangen von Bytes                                 ";

Ist ja auch wieder vorreservieren von Speicher. Als Puffer zum ständigen Lesen und dann per strcpy() einen neuen String speichern, OK
Dann aber lieber als deutlich erkennbaren Puffer:

#define BUFFERSIZE 17
char puffer[BUFFERSIZE];

Der Code ist, bis auf die Fehlende Belegung des Puffers, identisch, man sieht aber gleich, wieviele Zeichen man zur Verfügung hat.
Wer sicher sein will, kann vorher den Puffer noch "nullen"

for(int i=0;i<BUFFERSIZE;i++) puffer[i]=0;

Durch verwenden der Konstanten "BUFFERSIZE" die man überall im Code verwenden kann, kann man sehr einfach die Größe des Puffers durch Ändern eines einzigen Wertes anpassen.

Es sollen aber mehrere Strings gelesen werden, von denen erst zur Laufzeit beim Lesen bekannt ist, wie lang die sind. Daher müssen dann sowieso von den seriell gelesenen Strings Kopien erzeugt werden, damit der Puffer wieder frei wird für den nächsten zu lesenden String.
Wie gesagt, die Stringklasse behandelt das schon sehr effizient und man muss sich um die Rechnerrei mit dem Speicher keinen Kopf machen.

Mario.