Forum: Mikrocontroller und Digitale Elektronik Frage zu Serial.parseInt (Arduino)


von Ansgar (Gast)


Lesenswert?

Guten Abend,

habe ein Problem mit dem Verständniss zu Serial.parseInt!
Ich sende eine Zahl in Ascii (z.B. -1000 mit einem \n am Schluss) zu 
meinem uC und wandel diese in einen long. Dazu nutze ich den Seriellen 
Port. Chip ist ein ATMega8.

Abfangen tu ich es mit:

if (Serial.available() > 0){

long myInt = Serial.parseInt(SKIP_ALL, '\n');

...

}

Mein Problem:
Sobald ich Serial.parseInt verwende, scheint es als würde der uC sich 
eine Gedenksekunde gönnen und wird blockiert. Benötigt Serial.parseInt 
tatsächlich so viel Leistung oder fährt es in einen TimeOut?

Die Wandlung funktioniert nämlich wie ich es möchte. Nur diese ewige 
Pause möchte ich vermeiden, da eine andere Aufgabe dann stehen bleibt.

Wenn ich hingegen:
  long myInt = -1000;
schreibe, mehkt man keine Verzögerung (und kann auch keine mit dem Sozi 
messen)!

von chris_ (Gast)


Lesenswert?

Könnte es daran liegen, dass das Endzeichen nicht erkannt wird und statt 
dessen ein Timeout ausgelöst wird? Oder dass das Endzeichen erkannt 
wird, aber noch ein zusätzliches Zeichen in der Pipe steckt, welches 
dann den Timeout auslöst?

https://www.arduino.cc/reference/en/language/functions/communication/serial/parseint/

=> Serial.setTimeout()

von Norbert (Gast)


Lesenswert?

Ansgar schrieb:
> Ich sende eine Zahl in Ascii (z.B. -1000 mit einem \n am Schluss) zu
> meinem uC und wandel diese in einen long. Dazu nutze ich den Seriellen
> Port.

> if (Serial.available() > 0){

Sobald das erste Zeichen (›-‹) empfangen wurde, wird die Abfrage wahr 
und

> long myInt = Serial.parseInt(SKIP_ALL, '\n');

wird aufgerufen.

> Sobald ich Serial.parseInt verwende, scheint es als würde der uC sich
> eine Gedenksekunde gönnen und wird blockiert. Benötigt Serial.parseInt

Das wartet nun solange bis alle notwendigen Zeichen empfangen wurden. 
Hängt von deiner Schnittstelle ab, bei 9600 ungefähr 1ms pro Zeichen.

von chris_ (Gast)


Lesenswert?

>Das wartet nun solange bis alle notwendigen Zeichen empfangen wurden.
>Hängt von deiner Schnittstelle ab, bei 9600 ungefähr 1ms pro Zeichen.

Das Problem ist der Ausdruck "alle notwendigen Zeichen".
Ich habe es gerade mal im Wokwi-Simulator ausprobiert:

https://wokwi.com/projects/334148643034497620

Beim ersten Zeichen wird parseInt() aufgerufen. Wenn man hingereinander 
1,2,3 eintippt (ohne CR,LF) geht es etwa eine Sekunde (TimeOut) und die 
Zahl wird angezeigt.

von Norbert (Gast)


Lesenswert?

chris_ schrieb:
> Beim ersten Zeichen wird parseInt() aufgerufen. Wenn man hingereinander
> 1,2,3 eintippt (ohne CR,LF) geht es etwa eine Sekunde (TimeOut) und die
> Zahl wird angezeigt.

Wenn's in einen Timeout läuft, dann wurden eben nicht alle notwendigen 
Zeichen empfangen.

Vernünftigerweise sammelt man ohne Verzögerung erst einmal eine 
Eingabezeile und extrahiert dann die Informationen.

von Stefan F. (Gast)


Lesenswert?

Anstelle von Serial.available() kannst mit Serial.peek() antesten, ob 
das letzte empfangene Zeichen ein Zeilenumbruch war. Erst dann rufst du 
Serial.parseInt() auf.

Dieses Vorgehen hat aber zwei Knackpunkte:

a) Wenn der Empfangspuffer nicht groß genug, die ganze Zahl aufzunehmen, 
kommt Schrott heraus. Ich denke der Puffer von Arduino ist normalerweise 
groß genug.

b) Wenn du nach dem Zeilenumbruch noch etwas anders empfangen hast, dann 
ist das letzte Zeichen eben kein Zeilenumbruch. Dadurch "verpasst" du 
unter Umständen eine komplette Zeile.

Um es besser zu machen habe ich folgende Prozedur geschrieben:
1
/**
2
 * Append characters from a stream to the buffer until the terminator has been found.
3
 * In case of buffer overflow, read until the terminator but skip all characters that 
4
 * do not fit into the buffer.
5
 * @param source The source stream.
6
 * @param buffer The target buffer, must be terminated with '\0'.
7
 * @param bufSize Size of the buffer.
8
 * @param terminator The last character that shall be read from the stream, e.g. '\n'.
9
 * @return True if the terminator has been reached, false if the stream did not contain the terminator.
10
 */
11
bool appendUntil(Stream& source, char* buffer, int bufSize, char terminator)
12
{
13
    int data=source.read();
14
    if (data>=0)
15
    {
16
        int len=strlen(buffer);
17
        do
18
        {
19
            if (len<bufSize-1)
20
            {
21
                buffer[len++]=data;
22
            }
23
            if (data==terminator)
24
            {
25
                buffer[len]=0;
26
                return true;
27
            }
28
            data=source.read();
29
        }
30
        while (data>=0);
31
        buffer[len]=0;  
32
    }
33
    return false;
34
}

Diese kannst du innerhalb der Hauptschleife so benutzen:
1
char buffer[200];
2
3
void loop()
4
{
5
    if (appendUntil(Serial, buffer, sizeof(buffer), '\n'))
6
    {
7
        // If the line starts with "GET", do something
8
        if (strncmp(buffer,"GET ",4)==0)
9
        {
10
            ...
11
        }
12
        else
13
        {
14
            // Prepare to read the next line
15
            buffer[0]=0;
16
        }
17
    }
18
}

Letztendlich läuft das auf eine doppelte Pufferung hinaus.

von chris_ (Gast)


Lesenswert?

>>https://wokwi.com/projects/334148643034497620
>Wenn's in einen Timeout läuft, dann wurden eben nicht alle notwendigen
>Zeichen empfangen.

Das ist erst mal eine "Schlafschulweisheit". Die Probleme liegen hier 
tiefer.
Wenn du den obigen Simulatorlink ausprobierst, kannst du sehen, dass 
beim Drücken der Return-Taste eine unnötige '0' als geparster Integer im 
Ausgabefenster auftaucht.

von Norbert (Gast)


Lesenswert?

chris_ schrieb:
>>>https://wokwi.com/projects/334148643034497620
>>Wenn's in einen Timeout läuft, dann wurden eben nicht alle notwendigen
>>Zeichen empfangen.
>
> Das ist erst mal eine "Schlafschulweisheit". Die Probleme liegen hier
> tiefer.
> Wenn du den obigen Simulatorlink ausprobierst, kannst du sehen, dass
> beim Drücken der Return-Taste eine unnötige '0' als geparster Integer im
> Ausgabefenster auftaucht.

Es wird gemunkelt das lesen hilft:
1
parseInt() returns the first valid (long) integer number from the serial buffer. Characters that are not integers (or the minus sign) are skipped.
2
3
In particular:
4
5
    Initial characters that are not digits or a minus sign, are skipped;
6
    Parsing stops when no characters have been read for a configurable time-out value, or a non-digit is read;
7
    If no valid digits were read when the time-out (see Stream.setTimeout()) occurs, 0 is returned;

von newline (Gast)


Lesenswert?

cr ist \r und nicht \n.
Du gibst cr lf über das Terminal ein.

von chris_ (Gast)


Lesenswert?

>cr ist \r und nicht \n.
>Du gibst cr lf über das Terminal ein.

Das wiederum ist der Grund vieler Fehler bei der Datenübertragung 
zwischen Unix- und Windowssystemen.

Die Frage ist, ob Ansgar hier ein Unix- oder Windows System benutzt. Im 
Eingangspost schreibt er nur \n ohne \r. Beim Simulator im Browser 
vermute ich, dass er unabhängig vom Betriebssystem CR/LF beim Drücken 
der Returntaste erzeugt.


>They are used to mark a line break in a text file. As you indicated, >Windows 
uses two characters the CR LF sequence; Unix only uses LF and the >old MacOS ( 
pre-OSX MacIntosh) used CR.

aus
https://stackoverflow.com/questions/1552749/difference-between-cr-lf-lf-and-cr-line-break-types

von chris_ (Gast)


Lesenswert?

Stefan ⛄ F. (stefanus)
>Anstelle von Serial.available() kannst mit Serial.peek() antesten, ob
>das letzte empfangene Zeichen ein Zeilenumbruch war. Erst dann rufst du
>Serial.parseInt() auf.

Vermutlich ist deine Lösung die sauberste. Schade, dass man es so selber 
implementieren muss. ParseInt scheint für viele Anwendungen ungeeignet 
und bedürfte dahingehend einer besseren Implemtierung mit der Erkennung 
eines "Terminator"-Zeichens.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.