Serial Buffer leeren mit while - Alleine richtig, im Code mit Fehler

Hallo,

Wer sich an mich noch erinnert, wird wohl auch sich an den Solarregler von mir erinnern

Dieser übermittelt die Daten wie folgt an den (Software) Serial Port:

The device transmits blocks of data at 1 second intervals. Each field is sent using the following
format:

Das sieht dann ausgegeben so aus (Solarzelle derzeit nicht angeschlossen, nur 12V Batterie):

PID:0xA056
FW:146
SER#:HQ1903AMETM
V:12590
I:0
VPV:10
PPV:0
CS:0
MPPT:0
OR:0x00000001
ERR:0
LOAD:ON
H19:0
H20:0
H21:14
H22:0
H23:0
HSDS:0
Checksum:⸮

Ich kenn mich nicht so ganz 100%ig mit dem Serial Buffer aus, aber ich gehe davon aus, dasss dieser ankommende Daten in eine FIFO-Warteschlange lädt und sie dann beim lesen rauslädt

Ich würde gerne den gesamten Block auslesen und dann den Rest des Codes ausführen

Habe es mal so probiert (SolarInput ist mein Software Serial)

while (SolarInput.available()>0) 
    {
    String label = SolarInput.readStringUntil('\t');      
    String val = SolarInput.readStringUntil('\n');
    Serial.print(label);
    Serial.print(":");
    Serial.println(val);
    }

Leider kommt er aus unerklärlichen Gründen dann nie aus der while Schleife raus

Deswegen folgendes versucht:

while (SolarInput.available()>0) 
    {
      
    String label = SolarInput.readStringUntil('\t');      
    String val = SolarInput.readStringUntil('\n');
    Serial.print(label);
    Serial.print(":");
    Serial.println(val);
    
    if (label=="Checksum"){
      break;
    }
    }

Dies klappt wunderbar. Füge ich diese Zeile nun in meinen restlichen Code ein (am Anfang von void loop{…}, kommt folgendes raus

PID:0xA056
FW:146
SER#:HQ1903AMETM
V:12590
I:0
VPV:10
PPV
PID:0xA056
FW:146
SER#:HQ1903AMETM
V:12590
I:0
VPV:10
PPV:0
CS:0
MPPT:0
OR:0x00000001
ERR:0
LOAD:ON
H19:0
H20:0
H21:14
H22:0
H23:0
HSDS:0
Checksum:⸮

Sprich er springt unerwartet mittendrin wieder an den Anfang der Daten.

Ich versteh bis jetzt nicht ganz wieso. Weiß einer weiter?

Grüße

Wenn das so stimmt ist das Linefeed am Anfang der Übertragung und nicht am Ende. Das ist etwas seltsam

Serenifly:
Wenn das so stimmt ist das Linefeed am Anfang der Übertragung und nicht am Ende. Das ist etwas seltsam

Gut, dass du das erwähnst, dadrüber habe ich mir nie Gedanken gemacht. Dann ist es wohl logisch, dass die erste while schleife nie beendet wird, immerhin liest er dann ewig bis endlich die nächste Message mit einem Newline kommt

Und ja, das stimmt so tatsächlich (1zu1 Kopie aus Datenblatt)

Wer Lust hat, kann es sich hier anfordern lassen

Das bricht schon ab. readStringUntil() bricht ab wenn eine Sekunde lang kein Zeichen mehr ankommt (sinnvollerweise sollte man die Timeout-Zeit aber niedriger setzen)

Serenifly:
Das bricht schon ab. readStringUntil() bricht ab wenn eine Sekunde lang kein Zeichen mehr ankommt (sinnvollerweise sollte man die Timeout-Zeit aber niedriger setzen)

Danke für die Antwort.

Allerdings kommt (wie oben schon beschrieben) alle 1 sec ein neuer Datenblock. Zusammen mit der Latenz der Ausführung würde damit readStringUntil() neue Zeichen schon vor Abbruch bekommen. Daher bricht das wohl nie ab.

Werde jetzt mal die Timeout Zeit runtersetzen.

Grüße

Die Methode bricht ab wenn das Endzeichen kommt oder eine Sekunde verstrichen ist. Unendlich blockieren kann die nicht.
Du kommst wahrscheinlich wieder eine deine while-Schleife weil schon wieder neue Daten reinkommen während die Methode noch auf das Ende wartet. while ist das eigentlich gar nicht nötig. Sollte genauso auch mit if gehen

Serenifly:
Die Methode bricht ab wenn das Endzeichen kommt oder eine Sekunde verstrichen ist. Unendlich blockieren kann die nicht.

Hallo,

Ja, das habe ich schon verstanden. Das Endzeichen ist allerdings der Anfang vom neuen Block wie du bei deinem ersten Post schon bemerkt hast.

Serenifly:
Du kommst wahrscheinlich wieder eine deine while-Schleife weil schon wieder neue Daten reinkommen während die Methode noch auf das Ende wartet.

Genau das meinte ich mit meinem Post 8)

Serenifly:
Sollte genauso auch mit if gehen

Dann liest er leider nur einmal pro Loop eine Zeile aus, was ich auf jeden Fall vermeiden möchte. Es wäre wichtig, dass er den gesamten Datenblock fertig liest, bevor er den Rest des Codes ausführt

Grüße

DerPeter:
Dann liest er leider nur einmal pro Loop eine Zeile aus, was ich auf jeden Fall vermeiden möchte. Es wäre wichtig, dass er den gesamten Datenblock fertig liest, bevor er den Rest des Codes ausführt

Das ist doch kein Problem. Du merkst Dir, wenn alles da ist und machst dann die Verarbeitung und setzt den Merker wieder zurück.
Du darfst nur keine blockierenden Teile im Loop haben, keine eigenen while/for,… Schleifen und keine langen delays.

Gruß Tommy

Tommy56:
Das ist doch kein Problem. Du merkst Dir, wenn alles da ist und machst dann die Verarbeitung und setzt den Merker wieder zurück.
Du darfst nur keine blockierenden Teile im Loop haben, keine eigenen while/for,... Schleifen und keine langen delays.

Gruß Tommy

Hi Tommy,

Sprich ich nehme als Initiator mein SolarInput.available(), wenn dieser sagt, es ist was da [if(SolarInput.available()] beginn ich mit meiner Verarbeitung

Hättest du auch schon ein Vorschlag im Kopf, wie ich die Verarbeitung komplett durchführen kann?
Er soll folgende Teilschritte machen
a) Den gesamten Datenblock empfangen
b) Den Datenblock in die einzelnen Datennamen+Werte aufschlüsseln und ausgeben
c) Wissen wenn er alles verarbeitet hat und dann den restlichen Code ausführen

Stehe da gerade etwas auf den Schlauch

Grüße

Da ich Deine Verarbeitungslogik nicht kenne, wie soll ich Dir da Vorschläge machen? Aus den Fragmenten bestimmt nicht.
Wie sieht Dein kompletter Datensatz aus?

Ich würde auch nicht mit String arbeiten, sondern mit char-Arrays und dann die Möglichkeiten von strtok und der anderen Verwandtschaft nutzen. Hier habe ich dazu ein paar Infos zusammen getragen.

Gruß Tommy

Tommy56:
Wie sieht Dein kompletter Datensatz aus?

Das oben ist der komplette Datensatz (andernfalls hätte ich es natürlich vermerkt)

Tommy56:
Da ich Deine Verarbeitungslogik nicht kenne, wie soll ich Dir da Vorschläge machen?

Was meinst du mit Verarbeitungslogik?
Ist doch ebenfalls oben angegeben

The device transmits blocks of data at 1 second intervals. Each field is sent using the following
format:

So sendet der Datenlogger das an den Software Serial Input. Dies kann dann ganz normal mit Serial.read gelesen werden.
Je nach Datenname wird jedes Datum anschließend auf einen CAN Bus gelegt.
Der Rest des Codes ist völlig unabhängig von den Daten

Ich versteh jetzt nicht ganz was dir für Informationen fehlen. Teile das mir gerne mit.

(und natürlich danke für den Link!)

Grüße

Na zum Beispiel wie lang die einzelnen Teile sind (konstant oder maximal).
Ach das Zerlegen ist die Verarbeitung. Ok, ich hatte gedacht, da kommt noch mehr.

Das kannst Du doch mit den Beispielen in meinem Tutorial problemlos bauen.

Du nimmst den Zeilenvorschub vom nächsten Satz als Endekennung und prüfst zusätzlich ab, ob die Zeichenkette leer ist (1. Satz)

Gruß Tommy

Schonmal vielen Dank für die Mühe

Tommy56:
Ach das Zerlegen ist die Verarbeitung.

Genau. Wie oben beschrieben wird das zerlegt und dann jeder Wert auf einen CAN Bus gelegt

Sagen wir für jedes Label wird anschließend folgende Funktion aufgerufen (zuvor den Wert zu int konvertiert)

void ToCan(string label, int value){
  switch (label) {
    case "ID":    SendItToCan(canID_1,value)
    case "FW":    SendItToCan(canID_2,value)
    case "SER#":  SendItToCan(canID_3,value)
    case "V":     SendItToCan(canID_4,value)
    ....
  }
}

Der Rest des Codes prüft nur andere Sensorik und hat mit dem Solarregler nichts zu tun

Tommy56:
Na zum Beispiel wie lang die einzelnen Teile sind (konstant oder maximal).

Die Labels sind immer konstant und in der selben Reihenfolge. Die zugehörigen Werte können in Ihrer Länge variieren, ob es eine begrenzung gibt, weiß ich nicht, ich denke eher nicht, da als ASCII verschickt wird und dann sicherlich einfach bei größeren Werten ein neues Zeichen angehangen wird
. Jedes Label hat außerdem eine unterschiedliche Länge

Tommy56:
Du nimmst den Zeilenvorschub vom nächsten Satz als Endekennung und prüfst zusätzlich ab, ob die Zeichenkette leer ist (1. Satz)

Gruß Tommy

Ich denke du spielst mit dadrauf an

// Länge Nutzdaten + 1
const uint8_t BUFLEN = 11;
char puffer[BUFLEN];

// liest bis zum Zeilenende oder bis der Puffer voll ist und gibt dann true zurück
boolean readSerialNL() {
static uint8_t idx = 0;
static boolean fertig = false;
char c;
  // Neubeginn initialisieren
  if (fertig && idx > 0) {
    idx = 0;
    fertig = false;
  }
  if (Serial.available()) {  // hier könnte man evtl. auch while nehmen
    c = Serial.read();
    if (c == '\n' || idx == BUFLEN -1) { // Zeilenvorschub oder voll
      puffer[idx] = '\0';
      fertig = true;
    }
    else if (c >= 0x20 && idx < BUFLEN -1) { // keine Steuerzeichen
      puffer[idx++] = c;
    }
  }
  return fertig;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start");
}

Wie würdest du es machen?
Mein vorgehen wäre jetzt, die Bytes einzeln in ein (natürlich davor groß genug definiertes) char Array zu lesen
quasi à la

if(SolarInput.available()){
for (int i=0;SolarInput.available()>0; i++){
char_array[i]=SolarInput.read();
}
}

Und anschließend “Zerstückelung” mit strtok

Dann hätte ich allerdings wieder eine Schleife drin. Allerdings gäbe es dann nicht mehr die Problematik, dass er auf ein Zeichen wartet

Gibt er denn automatisch ein Zeilenvorschub aus, wenn der Buffer leer ist? Ich denke nicht, oder?
Meine Problematik ist am meisten, dass er bei meinen jetztigen Code solange nach dem Ende des Datenblocks wartet, bis eine Newline durch einen neuen Datenblock reinkommt

Gruß

for (int i=0;SolarInput.available()>0; i++){
char_array*=SolarInput.read();*
[/quote]
Das geht nicht. Ein Zeichen braucht 1 / Baudrate * 10 Sekunden. Strings an einem Stück auszulesen ist daher unmöglich. Genau deshalb arbeitet man ja normal mit Endzeichen
Du hast doch an paar Zeilen darüber eine Einlese-Routine! Wie kommst du jetzt auf sowas?
> Meine Problematik ist am meisten, dass er bei meinen jetztigen Code solange nach dem Ende des Datenblocks wartet,
Im Prinzip geht readBytesUntil() schon. Wenn man die Timeout-Zeit z.B. auf 5 oder 10 ms setzt. Dann kann man entweder auf den Tabulator dazwischen abfragen. Oder man liest solange ein bis nichts mehr kommt.
Man kann auch eine Timeout Zeit in eine eigene Funktion einbauen um das Ende zu erkennnen
Oder sendet das Gerät nach der ersten Zeile ohne Unterbrechung die zweite? Dann ist das wirklich doof implementiert. In dem Fall würde ich das Linefeed als Endzeichen nehmen. Und einen Timeout für die letzte Zeile. Dann wird die erste Zeile ausgewertet wenn die zweite anfängt.

Serenifly:
Das geht nicht. Ein Zeichen braucht 1 / Baudrate * 10 Sekunden. Strings an einem Stück auszulesen ist daher unmöglich. Genau deshalb arbeitet man ja normal mit Endzeichen

Du hast doch an paar Zeilen darüber eine Einlese-Routine! Wie kommst du jetzt auf sowas?

Die Einleseroutine liest doch auch die Zeichen einzeln ein. Warum hast du dort damit kein Problem?

if (Serial.available()) { // hier könnte man evtl. auch while nehmen
c = Serial.read();

puffer[idx++] = c;

Serenifly:
Oder sendet das Gerät nach der ersten Zeile ohne Unterbrechung die zweite?

Ja! Das ist mit Datenblock gemeint

Serenifly:
Im Prinzip geht readBytesUntil() schon. Wenn man die Timeout-Zeit z.B. auf 5 oder 10 ms setzt. Dann kann man entweder auf den Tabulator dazwischen abfragen. Oder man liest solange ein bis nichts mehr kommt.
Man auch eine Timeout Zeit in eine eigene Funktion einbauen um das Ende zu erkennnen

Danke für den Tipp!
Aber dann brauche ich doch wieder eine komplette for-Schleife, oder?

Grüße

Die Einleseroutine liest doch auch die Zeichen einzeln ein. Warum hast du dort damit kein Problem?

Die Funktion wird immer wieder solange aufgerufen bis alles da ist. Wenn nichts da ist bricht sie ab und man kann andere Dinge tun

Aber dann brauche ich doch wieder eine komplette for-Schleife, oder?

Irgendwie ist dir das Zeit-Verhalten noch nicht klar. Bei 9600 Baud kommt nur alle ca. 1ms ein Zeichen an! Diese fertigen Arduino Funktion warten einfach so lange und wenn eine bestimmte Zeit überschritten ist brechen sie ab.

Wenn man das selbst macht dann löst man es besser wenn man einfach die Einlese-Funktion dauernd aufruft statt nur einmal. Siehe auch hier:

Eine Timeout Zeit kann man da implementieren wenn man sich merkt wann das letzte Zeichen eingelesen wurde

DerPeter:
Und anschließend "Zerstückelung" mit strtok
Meine Problematik ist am meisten, dass er bei meinen jetztigen Code solange nach dem Ende des Datenblocks wartet, bis eine Newline durch einen neuen Datenblock reinkommt

Du brauchst keine eigene Schleife, Du hast doch loop.

Ja, das Warten auf das nächste NL ist ein Problem Deines blöden Protokolls, damit musst Du leben. Du kannst sonst nicht zweifelsfrei das Ende bestimmen.

Gruß Tommy

Tommy56:
Du brauchst keine eigene Schleife, Du hast doch loop.

Ja, das Warten auf das nächste NL ist ein Problem Deines blöden Protokolls, damit musst Du leben. Du kannst sonst nicht zweifelsfrei das Ende bestimmen.

Gruß Tommy

Hey,

das Porblem ist, dass es notwendig für die Verarbeitung ist, dass die Daten alle direkt gelesen werden weil es noch weiteren -unabhängigen- Programmcode gibt.

Das Einlesen mit loop klappt seit Anfang an einwandfrei. Das problem wäre dann, dass er nur eine Zeile pro Schleifendurchlauf liest. Da der Rest des Programmes seine Ausführzeit benötigt, würde er mit sehr niedriger Frequenz nur die Daten einlesen. Daher ist es notwendig, dass er a) entweder den gesamten Block einliest oder b) eine eigene Schleife hat.

Hab ich das oben nicht schon im ersten Post erwähnt? Wenn nicht tut es mir leid

Viele Grüße

Das verstehe ich jetzt nicht, weil der Code beim Einlesen ja nicht blockiert. Du kannst dazwischen vieles andere machen.

Gruß Tommy

Tommy56:
Das verstehe ich jetzt nicht, weil der Code beim Einlesen ja nicht blockiert. Du kannst dazwischen vieles andere machen.

Gruß Tommy

Welchen Code meinst du jetzt genau? Den von dir?

So wie ich es richtig verstanden habe, liest dein Code im jeden Loop genau ein Zeichen aus (oder?) und speichert es in den Buffer, gibt selbigen dann zurück sobald ein Loop kommt, bei den der Buffer voll ist oder ein Steuerzeichen gesetzt wurde

Das heißt er würde ein Zeichen auslesen und dann den restlichen Code ausführen und dann erst wieder ein Zeichen. Wenn der restliche Code z.B. aber 0,1ms braucht, würde er für den gesamten Datenblock doch mehrere Sekunden brauchen
Grüße