Forum: Mikrocontroller und Digitale Elektronik Verwendung von Serial.readStringUntil


von Max M. (sysadmin4444)


Lesenswert?

Ich habe folgenden Code auf meinen Arduino gespielt:

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

void loop()
{
  if (Serial.available() > 0)
  {
    pwm_1 = Serial.readStringUntil(";").toFloat();
    pwm_2= Serial.readString().toFloat();
    Serial.println(pwm_1 );
    Serial.println(pwm_2);
   }
}

Wenn ich beispielsweise 10;20 im Terminal sende bekomme ich als Ausgabe
10.00
0.00

Eigentlich hätte ich erwartet:
10.00
20.00


readStringUntil(";") sollte den String bis zum ";" lesen und der 
restliche String aus dem Buffer sollte in pwm_2 gespeichert werden. Wo 
ist mein Denkfehler?

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

von Volker Z. (vzavza)


Lesenswert?

Der restliche String ist noch nicht im Buffer.
readString() scheint nicht zu warten.

von Gerald M. (gerald_m17)


Lesenswert?

Für mich liest sich:

Returns:
The entire String read from the serial buffer, up to the terminator 
character

Dass er den ganzen Buffer ausliest, aber nur bis zum Trennzeichen. Der 
Rest wird also verworfen und ist auch nicht mehr im Buffer. Deshalb 
erhältst du als zweite Ausgabe "0.00".

Edit: Volkers Aussage macht auch Sinn. Im Notfall einfach in den 
Sourcecode schauen.

: Bearbeitet durch User
von Einer (Gast)


Lesenswert?

Vielleicht probierst du mal 10;20; zu senden….

von EAF (Gast)


Lesenswert?

Gerald M. schrieb:
> Der
> Rest wird also verworfen und ist auch nicht mehr im Buffer.
Doch bleibt er.
Außer der Terminator, der verfällt.

Gerne wird der Timeout vergessen, welcher einem dann Streiche spielt.

von Max M. (sysadmin4444)


Lesenswert?

EAF schrieb:
> Gerald M. schrieb:
>> Der
>> Rest wird also verworfen und ist auch nicht mehr im Buffer.
> Doch bleibt er.
> Außer der Terminator, der verfällt.
>
> Gerne wird der Timeout vergessen, welcher einem dann Streiche spielt.

Daran habe ich auch schon gedacht und setTimeout() von 0 bis 2 Sekunden 
durchprobiert, allerdings erfolglos

von Stefan F. (Gast)


Lesenswert?

Gefährlich ist, dass der String beliebig groß werden kann. Bei einem 
ESP8266 hat readStringUntil() mal mehrere Kilobytes am Stück empfangen, 
das hatte zu einem Speicherüberlauf geführt.

Alternativer Vorschlag:
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
}

Du musst die Funktion wiederholt aufrufen. Da du immer abwechselnd auf 
ein Semikolon versus Zeilenumbruch wartest, würde ich dies in einen 
Zustandsautomaten verpacken:
1
void process_input()
2
{
3
    static enum {INPUT_FIRST, INPUT_SECOND, PROCESS} state = INPUT_FIRST;
4
    static char buffer[10]={0}; // start with an empty string
5
    static double pwm_1;
6
    static double pwm_2;
7
    
8
    switch (state)
9
    {
10
        case INPUT_FIRST:
11
            if (appendUntil(Serial, buffer, sizeof(buffer), ';'))
12
            {
13
                pwm_1=atoi(buffer);
14
                buffer[0]=0; // empty the string
15
                state=INPUT_SECOND;
16
            }
17
            break;
18
            
19
        case INPUT_SECOND:
20
            if (appendUntil(Serial, buffer, sizeof(buffer), '\n'))
21
            {
22
                pwm_2=atoi(buffer);
23
                buffer[0]=0; // empty the string
24
                state=PROCESS;
25
            }
26
            break;
27
28
        case PROCESS:
29
            Serial.println(pwm_1);
30
            Serial.println(pwm_2); 
31
            state=INPUT_FIRST;
32
            break;            
33
    }
34
}
35
36
void loop()
37
{
38
    process_input();
39
    do_something_else(); // optionally
40
}

Einen Timeout kann man dort recht einfach unterbringen:
1
void process_input()
2
{
3
    static enum {START, INPUT_FIRST, INPUT_SECOND, PROCESS, TIMEOUT} state = START;
4
    static char buffer[10]={0};
5
    static double pwm_1;
6
    static double pwm_2;
7
    static unsigned long int waitingSince = 0;
8
    
9
    switch (state)
10
    {
11
        case START:
12
            waitingSince=millis();
13
            state=INPUT_FIRST;
14
            break;       
15
        
16
        case INPUT_FIRST:
17
            if (appendUntil(Serial, buffer, sizeof(buffer), ';'))
18
            {
19
                pwm_1=atoi(buffer);
20
                buffer[0]=0;
21
                state=INPUT_SECOND;
22
            }
23
            else if (millis()-waitingSince > 10000)
24
            {
25
                state=TIMEOUT;
26
            }
27
            break;
28
            
29
        case INPUT_SECOND:
30
            if (appendUntil(Serial, buffer, sizeof(buffer), '\n'))
31
            {
32
                pwm_2=atoi(buffer);
33
                buffer[0]=0;
34
                state=PROCESS;
35
            }
36
            else if (millis()-waitingSince > 10000)
37
            {
38
                state=TIMEOUT;
39
            }
40
            break;
41
42
        case PROCESS:
43
            Serial.println(pwm_1);
44
            Serial.println(pwm_2); 
45
            state=START;
46
            break;     
47
48
        case TIMEOUT:
49
            Serial.println(F("timeout occured"));
50
            state=START;
51
            break;            
52
    }
53
}

von Max M. (sysadmin4444)


Lesenswert?

Um zu verhindern, dass der Einagbe String zu groß wird gibt es die 
setTimeout() Funktion der Serial Klasse. Deine Variante sieht ganz gut 
und logisch aus, ist allerdings ziemlich viel Code für eine doch recht 
überschaubare Aufgabe. Laut anderen Foren sollte meine anfänglich 
dargestellte Variante eigentlich ihren Job erfüllen, tut es aber aus 
irgend einem Grund nicht.

von Stefan F. (Gast)


Lesenswert?

Max M. schrieb:
> ist allerdings ziemlich viel Code für eine doch recht
> überschaubare Aufgabe

Wenn deine Aufgabe wirklich so einfach ist, dann stimme ich dir zu.

Bei meinen Anwendungen laufen allerdings meisten mehreres Threads 
parallel, was die Verwendung von blockierenden Funktionen wie 
readStringUntil() verbietet. Deswegen gehe ich diese Aufgabe wie gezeigt 
an.

> Um zu verhindern, dass der Einagbe String zu groß wird
> gibt es die setTimeout() Funktion der Serial Klasse.

Diese Funktion begrenzt nicht die Länge der Strings. Stelle dir vor du 
empfängst nur ganz viel Müll bis zufällig mal ein Zeilenumbruch dabei 
ist. Meine Methode ist dagegen abgesichert. Wenn das bei dir nicht 
passieren kann, dann hast du es natürlich einfacher.

von Max M. (sysadmin4444)


Lesenswert?

Stefan ⛄ F. schrieb:
> Max M. schrieb:
>> ist allerdings ziemlich viel Code für eine doch recht
>> überschaubare Aufgabe
>
> Wenn deine Aufgabe wirklich so einfach ist, dann stimme ich dir zu.
>
Nicht ganz so einfach aber auch nicht zu kompliziert

> Bei meinen Anwendungen laufen allerdings meisten mehreres Threads
> parallel, was die Verwendung von blockierenden Funktionen wie
> readStringUntil() verbietet. Deswegen gehe ich diese Aufgabe wie gezeigt
> an.
>
>> Um zu verhindern, dass der Einagbe String zu groß wird
>> gibt es die setTimeout() Funktion der Serial Klasse.
>
> Diese Funktion begrenzt nicht die Länge der Strings. Stelle dir vor du
> empfängst nur ganz viel Müll bis zufällig mal ein Zeilenumbruch dabei
> ist. Meine Methode ist dagegen abgesichert. Wenn das bei dir nicht
> passieren kann, dann hast du es natürlich einfacher.

Ich dachte dadurch wird die Zeit begrenzt, in der der String gefüllt 
wird. Also sollte doch z.b. 50 ms als Timeout den String auf eine Länge 
von 50 begrenzen, wenn man von 1ms pro char ausgeht z.B.

von EAF (Gast)


Lesenswert?

Max M. schrieb:
> tut es aber aus
> irgend einem Grund nicht.
Eben.
Parserbau ist doch nicht so einfach......


Hier ganz naiv, aber mit Netz und doppeltem Boden, allerdings ohne 
Timeout.
1
constexpr size_t bufferSize {20};
2
3
String buffer;
4
int counter;
5
6
void serialEvent()
7
{
8
  if(buffer.length() >= bufferSize) buffer=""; // doppelter Boden
9
10
  char zeichen = Serial.read();
11
  switch(zeichen)
12
  {
13
    case '0'...'9': buffer += zeichen; break; 
14
    
15
    case ';':       Serial.print(counter++);
16
                    Serial.print("  ");
17
                    Serial.println(buffer.toFloat());
18
                    buffer = ""; 
19
                    break; 
20
  }
21
}
22
23
24
25
void setup()
26
{ 
27
  buffer.reserve(bufferSize); // Netz
28
  Serial.begin(9600);
29
}
30
31
void loop()
32
{
33
}

von Stefan F. (Gast)


Lesenswert?

Max M. schrieb:
> Ich dachte dadurch wird die Zeit begrenzt, in der der String gefüllt
> wird. Also sollte doch z.b. 50 ms als Timeout den String auf eine Länge
> von 50 begrenzen, wenn man von 1ms pro char ausgeht z.B.

Ach so, ja das klingt plausibel. Ich habe zuletzt immer mit WLAN 
gebastelt, da hat man "etwas" höhere Übertragungsraten.

von W.S. (Gast)


Lesenswert?

Max M. schrieb:
> Um zu verhindern, dass der Einagbe String zu groß wird gibt es die
> setTimeout() Funktion der Serial Klasse.

Und was macht dein Arduino, wenn mal ein etwas langsamerer Mensch an der 
Tastatur am PC sitzt und die Zeit vom ersten Zeichen bis zum letzten 
Zeichen größer ist als dein Timeout?

Nein, auch hier grinst mich wieder mal der Mangel an systematischem 
Denken an. Eingaben über eine asynchrone serielle Schnittstelle sind 
erstens Text (wo man sich auch mal vertippen kann und man hätte es gern, 
da per Backspace-Taste eine Korrektur anzubringen) und zweitens zeitlich 
unbestimmt. Da mit einem Timeout als Endekenner zu arbeiten, ist eine 
denkbar schlechte Idee. Andererseits scheint das ereignisgesteuerte 
Programmieren in Arduino-Kreisen eher unerwünscht zu sein (oder nicht 
verstanden zu sein) und man programmiert dort nach PAP, also durchgehend 
blockierend.

Ich könnte jetzt eine Lösung des Problems posten, aber die würde das 
halbe Arduino-Zeugs für die Mülltonne machen und sie würde ganz 
offensichtlich die Arduino-Leute vor ihre jeweiligen Köpfe stoßen.

Aber eines ist immer klar: Eine Zeitbegrenzung ist nicht das richtige 
Mittel, um eine Platzbegrenzung zu programmieren.

W.S.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Andererseits scheint das ereignisgesteuerte
> Programmieren in Arduino-Kreisen eher unerwünscht zu sein (oder nicht
> verstanden zu sein) und man programmiert dort nach PAP, also durchgehend
> blockierend.

Alles klar...
Keine Fragen mehr....

Tipp:
Darum habe ich eben auch wohl ein "nicht blockierendes" Beispiel 
gezeigt.

Das passt wohl nicht durch deinen Wahrnehmungsfilter. Oder du hast so 
wenig Ahnung von Arduino, dass dir das gar nicht auffallen kann, aber 
dann so mit Vorurteilen und Pauschalisierungen um sich werfen, ist auch 
irgendwie ****, oder?

von W.S. (Gast)


Lesenswert?

EAF schrieb:
> Darum habe ich eben auch wohl ein "nicht blockierendes" Beispiel
> gezeigt.

Du bist nicht der TO, jedenfalls dem Namen nach.
Offenbar ist dir nicht wirklich alles so klar, wie du schriebest.
Abgesehen davon ist dein Beispiel eher ein Ausdrucken des Puffers auf 
der Seriellen und nicht das, was dem TO eigentlich vorgeschwebt hatte.

W.S.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> ... viel bla bla....

Du sprachest "Ereignisorientiert" (oder so)!
Das ist es!

Du sagtest "Ereignis orientiert, nix Arduino".
Och...
Ereignis im Arduino Core verankert.
Also: Du nix Ahnung von Arduino.



W.S. schrieb:
> und nicht das, was dem TO eigentlich vorgeschwebt hatte.
Ach nee..
kleine Anpassungen und es kommt der Schwebung viel näher...

Ich machs dir mal vor... vielleicht lernst du ja auch sogar was davon...
1
constexpr size_t bufferSize {20};
2
constexpr unsigned long timeout {20000}; // ms
3
unsigned long timeoutMerker;
4
5
String buffer;
6
float pwm_1, pwm_2;
7
8
9
void serialEvent()
10
{
11
  if(millis() - timeoutMerker >= timeout)
12
  {
13
    buffer = "";
14
    return;
15
  }
16
  
17
  if(buffer.length() >= bufferSize) buffer = ""; // doppelter Boden
18
  
19
  char zeichen = Serial.read();
20
  timeoutMerker = millis();
21
  switch(zeichen)
22
  {
23
    case '0'...'9': buffer += zeichen; break;
24
    case ';':       Serial.print("pwm_1:  ");
25
      Serial.println(pwm_1 = buffer.toFloat());
26
      buffer = "";
27
      break;
28
    case '\n':      Serial.print("pwm_2:  ");
29
      Serial.println(pwm_2 = buffer.toFloat());
30
      buffer = "";
31
      Serial.println("-------");
32
      break;
33
  }
34
}
35
36
37
38
void setup()
39
{
40
  buffer.reserve(bufferSize  + 1); // Netz
41
  Serial.begin(9600);
42
}
43
44
void loop()
45
{
46
}

von Peter D. (peda)


Lesenswert?

Ich lese in der Loop immer nur ein Zeichen in einen Puffer, sobald es 
empfangen wurde. Ist es \n oder \r, wird eine \0 an den Puffer angehängt 
und dem Parser übergeben, der es dann ausführt.
Timeouts sind generell evil und werden so auch nicht gebraucht, da die 
Loop ja nicht blockiert.

Parsen kann man am flexibelsten mit sscanf() und spart sich damit 
haufenweise Spezialfunktionen, wie .toFloat() usw.
sscanf() gibt außerdem die Anzahl der gelesenen Argumente zurück, d.h. 
man kann ein fehlendes Argument von dem Wert 0 unterscheiden.

von Stefan F. (Gast)


Lesenswert?

Peter D. schrieb:
> Timeouts sind generell evil und werden so auch nicht gebraucht, da die
> Loop ja nicht blockiert.

Bei asynchroner Kommunikation sind Timeouts eine einfache Methode, die 
beiden Kommunikationspartner nach einer Übertragungsstörung wider zu 
synchronisieren. Dateiübertragungen per Moden und TCP/IP machen das so.

von Stefan F. (Gast)


Lesenswert?

EAF schrieb:
> Ich machs dir mal vor... vielleicht lernst du ja auch sogar was davon...

Schön kompakt. Wenn er das auf 3 Werte erweitern will braucht er dennoch 
einen Zustandsautomaten (oder noch ein anderes Trennzeichen).

Ich würde die Eingabe Zeilenweise empfangen und dann am Semikolon 
splitten.

Der TO wird sich darüber sicher noch Gedanken machen, wenn das Programm 
wächst.

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Ich lese in der Loop immer nur ein Zeichen in einen Puffer, sobald es
> empfangen wurde. Ist es \n oder \r, wird eine \0 an den Puffer angehängt
> und dem Parser übergeben, der es dann ausführt.

Also, in anderen Worten: du baust in einem Puffer eine Eingabezeile auf 
(oder man nennt es Kommandozeile). Genau SO ist es richtig, denn dabei 
fällt auch zugleich mit ab, daß man den im Puffer vorhandenen Platz 
überprüfen kann, damit Überläufe vermeidet und obendrein eine simple 
Editiermöglichkeit hat, indem man Backspace auswertet. Genau so mache 
ich das auch. Und wenn dann die Zeile fertig ist, dann kann man auch 
bequem auswerten.

Und das alles ohne irgendwelche Timeouts. Diese sind eher Mode, wenn 2 
Maschinen miteinander kommunizieren. Wenn aber an einem Ende der Mensch 
sitzt, dann nicht.

Nur noch ein kleines Detail: man kann auch nach dem Einstapeln eines 
jeden Zeichens auf den nächsten Platz eine 0 vorsorglich schreiben. 
Kann_ aber nicht _Muss, denn eigentlich ist es egal, wann man den 
String mit einer 0 abschließt. Es erleichtert bloß bei etwaigen 
Editierzeichen die Behandlung ein wenig.

W.S.

von Stefan F. (Gast)


Lesenswert?

Damit haben wir die Erklärung, warum meine append_until() Funktion so 
aussieht wie sie aussieht. Genau für das von W.S. beschriebene Szenario 
war sie vorgesehen und hat sich auch bewährt.

von Peter D. (peda)


Lesenswert?

Stefan ⛄ F. schrieb:
> Bei asynchroner Kommunikation sind Timeouts eine einfache Methode, die
> beiden Kommunikationspartner nach einer Übertragungsstörung wider zu
> synchronisieren. Dateiübertragungen per Moden und TCP/IP machen das so.

Ja, das fällt mir auch ständig unangenehm auf. Netzwerkzugriffe bleiben 
gerne mal 30s hängen (Explorer wird grau). Die IT sagt, sie könne den 
Fehler nicht finden.

Synchronisation ohne Timeout ginge ganz einfach, indem man nach längeren 
Pausen erstmal das Endezeichen überträgt. Der Parser sieht dann entweder 
ein leeres Paket oder Müll und kann sofort das nächste gültige Paket 
empfangen.

von EAF (Gast)


Lesenswert?

Peter D. schrieb:
> Ich lese..
In der C Welt hantiert man mit Zeigern, ist oftmals gezwungen dazu.
Hier ist aber C++ und da ist das nicht wirklich immer nötig.
Pointer, eine gruselige Fehlerquelle, schön wenn es einen Weg drum rum 
gibt.
Die Folgen, wenn sich gestandene Profis, beim pointern verhaspelt haben, 
haben schon viel Leid über die Angegriffenen gebracht.
Es ist ein Gebiet, wo viele Fallen lauern, und einen der Compiler kaum 
warnt, warnen kann.

Natürlich schadet es nicht, wenn auch ein C++ Anfänger irgendwann den 
richtigen Umgang damit lernt.



Peter D. schrieb:
> parsen kann man am flexibelsten mit sscanf() und spart sich damit
> haufenweise Spezialfunktionen, wie .toFloat() usw.
"Spezialfunktionen" und "haufenweise", sind Wertungen, resultierend aus 
deinem Empfinden und Erfahrungen. Wenn man die Funktionen nicht kennt, 
also nicht häufig damit arbeitet, fallen die natürlich auf.


.toFloat ist gar nicht so aufwändig, siehe:
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/WString.cpp#L741

Im Gegenteil, hier ist vom UNO, also von einem AVR, die Rede.
Da gibts dein float fähiges sscanf() erstmal überhaupt nicht.
Und wenn man es einbaut(was natürlich geht), frisst es schon einiges an 
Ressourcen/Flash


----


Zeileneditor...
Die Anforderung steht nicht im Eingangsposting.
Wurde nicht vom TO hier eingeschoben.
Und ist auch zumindest beim Seriellen Monitor der Arduino IDE auch nicht 
nötig, da dieser per Default sowieso nur Zeilenweise sendet, seinen 
eigenen Zeileneditor hat. Aus der konkreten Richtung kommt also kein 
Backspace.
Wenn das ein Maschine zu Maschine Protokoll wird, ist eine Backspace 
Abhandlung auch nicht nötig.

von Stefan F. (Gast)


Lesenswert?

EAF schrieb:
> Die Folgen, wenn sich gestandene Profis, beim pointern verhaspelt haben,
> haben schon viel Leid über die Angegriffenen gebracht.

Ich kann mich an reichlich viele Fälle erinnern, wo Java Entwickler sich 
mit Referenzen ebenso verhaspelt haben. Um den Fehler zu verstehen 
musste ich diesen Leuten erstmal erklären, was ein Pointer auf CPU Ebene 
ist.

Will sagen: Jeder Programmierer sollte wissen, was ein Pointer ist und 
welche Konsequenzen ihre Nutzung hat. Das nützt sogar bei Java, welches 
gar keine Pointer kennt.

von EAF (Gast)


Lesenswert?

Java ist anders.
Bufferoverflows muss es da nicht geben.

Und gegen Memoryleaks gibts den GC Mechanismus.
Das C++ hat da die SmartPointer (leider nicht beim AVR)

Klar, schadet es nicht, Pointer zu kennen.
Und dennoch muss man sie nicht nutzen, wenn es Risiko ärmere 
Möglichkeiten gibt.

Sicherlich kann man auch in Java Bockmist bauen.
Wie wohl in allen Sprachen, oder mit allen Sprachmitteln.

von Stefan F. (Gast)


Lesenswert?

EAF schrieb:
> Sicherlich kann man auch in Java Bockmist bauen.
> Wie wohl in allen Sprachen, oder mit allen Sprachmitteln.

Darauf wollte ich hinaus. Referenzen als Alternative zu Pointern 
bewahren niemanden automatisch davor, Fehler zu machen.

von Peter D. (peda)


Lesenswert?

EAF schrieb:
> In der C Welt hantiert man mit Zeigern, ist oftmals gezwungen dazu.

Ich bevorzuge für Arrayzugriffe die Indexschreibweise. Dem Compiler ist 
das egal, ob Index oder Pointer, er erzeugt (fast) den gleichen Code.

EAF schrieb:
> Die Folgen, wenn sich gestandene Profis, beim pointern verhaspelt haben,
> haben schon viel Leid über die Angegriffenen gebracht.
> Es ist ein Gebiet, wo viele Fallen lauern, und einen der Compiler kaum
> warnt, warnen kann.

Ja, sscanf() muß man die Adresse der Variablen übergeben. Wenn man es 
mal vergißt, sollte das schnell auffallen.

EAF schrieb:
> .toFloat ist gar nicht so aufwändig, siehe:
> 
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/WString.cpp#L741

Hab ich mir schon gedacht, daß es ein verstecktes atof() ist. Der große 
Nachteil ist aber, daß man den Wert 0 nicht von einem Fehler 
unterscheiden kann. Deshalb benutze ich es nicht mehr.

EAF schrieb:
> Und wenn man es einbaut(was natürlich geht), frisst es schon einiges an
> Ressourcen/Flash

Beim AVR sind das einmalig ~7kB. Beim Nano mit 32kB fällt das also nicht 
sehr ins Gewicht.

EAF schrieb:
> Wenn das ein Maschine zu Maschine Protokoll wird, ist eine Backspace
> Abhandlung auch nicht nötig.

Es kostet aber nur wenige Byte Code. Und manchmal hat man nur ein 
einfaches Terminal angeschlossen.

Hier mal meine Empfangsloop:
1
void receive_command(void)                              // receive from UART
2
{
3
  static char cbuf[CMD_MAX_LEN];                        // command buffer
4
  static uint8_t idx = 0;
5
  uint8_t ch;
6
7
  if (ukbhit0() == 0)
8
    return;
9
  ch = ugetchar0();
10
  switch (ch)
11
  {
12
    default:
13
      cbuf[idx] = ch;
14
      if (idx < sizeof(cbuf) - 1)
15
        idx++;
16
      return;
17
    case '\b':
18
      if (idx)
19
        idx--;                          // remove last char (interactive mode)
20
      return;
21
    case '\n':
22
    case '\r':
23
      if (idx == 0)                     // do nothing on empty commands
24
        return;
25
      cbuf[idx] = 0;
26
      idx = 0;
27
      execute(cbuf);
28
      uputchar0('\n');
29
      uputs0(cbuf);                     // send answer
30
  }
31
}

von ... (Gast)


Lesenswert?

EAF schrieb:
> Das passt wohl nicht durch deinen Wahrnehmungsfilter. Oder du hast so
> wenig Ahnung von Arduino, dass dir das gar nicht auffallen kann, aber
> dann so mit Vorurteilen und Pauschalisierungen um sich werfen, ist auch
> irgendwie ****, oder?

W.S. ließt mit Absicht falsch um dann solche netten Dinge zu schreiben.
Das hat bei ihm System.

von EAF (Gast)


Lesenswert?

Peter D. schrieb:
> Beim AVR sind das einmalig ~7kB. Beim Nano mit 32kB fällt das also nicht
> sehr ins Gewicht.
Da kann man geteilter Meinung sein.
Immerhin ist das mehr als ein 1/4 des Flashes.
Das zuletzt von mir gezeigte Gesamtprogramm ist kleiner, 6082 Bytes

Peter D. schrieb:
> case '\b':
>       if (idx)
>         idx--;

Würde mit String, bei mir eingesetzt so aussehen:
case '\b': buffer.remove(buffer.length()-1); break;
Also auch nicht wirklich viel komplizierter.

Peter D. schrieb:
> Ich bevorzuge für Arrayzugriffe die Indexschreibweise. Dem Compiler ist
> das egal, ob Index oder Pointer, er erzeugt (fast) den gleichen Code.
Eben!
Die beiden Verfahren sind äquivalent, so ist es in C und C++ 
festgeschrieben. Es wird der gleiche Code erzeugt(zumindest habe ich nie 
was anderes gesehen).
> cbuf[idx]
Ob cbuf[idx], idx[cbuf] oder *(cbuf+idx), die unterscheiden sich nur in 
der Schreibweise, die Ausdrücke sind gleichwertig. Damit auch keinerlei 
Unterstützung durch den Kompiler, wenn man Mist baut, z.B. bei 
Bereichsüberschreitungen zur Laufzeit.

Peter D. schrieb:
> if (idx < sizeof(cbuf) - 1)
>         idx++;
> /// Schnipp
> if (idx)
>         idx--;
Hier sieht man deutlich, dass du an allen Stellen, wo idx manipuliert 
wird, händisch *Schutzmaßnahmen* gegen Über- und Unterläufe einbauen 
MUSST.
Das erfordert eine starke Disziplin und Aufmerksamkeit.
Sind es doch gerade die Flüchtigkeitsfehler, welche einem dort die 
Streiche spielen können. Da zu den logischen Fehlern gehörend, da vom 
Kompiler nicht angemäckert, sind das recht schwer zu findende Fehler.
Da muss man IMMER sehr aufmerksam sein, wenn man mit nackten C strings 
oder auch anderen Arrays arbeitet.

Sicherlich ist die String Klasse nicht die Lösung aller Probleme.
Aber immerhin sind die Bereichsüberschreitungen vollständig 
weggekapselt.
Der Pointer, welcher (versehndlich) in die Wiese oder in den Wald zeigt, 
ist damit aus dem Rennen.
Das Faktum kann man schätzen lernen, oder ausblenden, bleibt jedem 
selber überlassen.

Ja, ich verwende in dem Code auch eine Bereichsgrenzen Prüfung.
>   if(buffer.length() >= bufferSize) buffer = "";
Sie dient aber nicht dazu Bereichsüberschreitungen, und damit das 
versehentliche überschreiben anderer Variablen zu unterbinden, sondern 
das allokieren von weiterem Speicher zu verhindern.

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Hier mal meine Empfangsloop:

Man sieht, daß dir die Zeilenenden per LF nahestehen. Das ist im Prinzip 
ein Problem zwischen zwei Welten: die einen nehmen CR als Zeilenende, 
die anderen nehmen LF. Nur in der DOS/Windows Welt wird brav beides as 
CRLF benutzt. Das ist früher als Platzverschwendung angesehen worden, 
ist aber heutzutage vom Platzbedarf her unbedeutend geworden.

Eigentlich hätte man das Verwenden von Drucker-Steuerzeichen für das 
Markieren von Zeilenenden vermeiden sollen und sich stattdessen einen 
der Separatoren von ASCII (FS..US) aussuchen sollen. Hat man aber damals 
nicht. Also müssen wir damit irgendwie leben. Und da ich vornehmlich in 
der Windowswelt bin, teste ich als Zeilenende auf CR und werfe LF der 
Einfachheit halber weg. Sonst kriegt man nämlich ne leere Zeile, weil an 
2 Stellen (bei CR und bei LF) ein Zeilenende erkannt wird.

Man könnte jetzt versuchen, dafür eine Logik zu entwickeln, die auf alle 
4 möglichen Situationen richtig reagiert: CR oder LF oder CRLF oder 
LFCR, aber das war mir bislang zu aufwendig für den bescheidenen Nutzen.

W.S.

von S. R. (svenska)


Lesenswert?

EAF schrieb:
>> Beim AVR sind das einmalig ~7kB. Beim Nano mit 32kB fällt
>> das also nicht sehr ins Gewicht.
> Da kann man geteilter Meinung sein.
> Immerhin ist das mehr als ein 1/4 des Flashes.
> Das zuletzt von mir gezeigte Gesamtprogramm ist kleiner, 6082 Bytes

Wenn der Chip 32K hat, dann bekommst du dafür auch kein Geld zurück - 
hängt von der Anwendung ab. Meine Chips sind deutlich dicker, was ich da 
an Overhead ertragen muss geht auf keine Kuhhaut...

W.S. schrieb:
> Man könnte jetzt versuchen, dafür eine Logik zu entwickeln, die auf alle
> 4 möglichen Situationen richtig reagiert: CR oder LF oder CRLF oder
> LFCR, aber das war mir bislang zu aufwendig für den bescheidenen Nutzen.

Alternativ kann man auch einfach definieren, wie das Zeilenende in 
diesem Protokoll auszusehen hat. Wenn da "CR" steht, dann ist das so 
gesetzt und auch für die meisten Windows-Terminalemulatoren akzeptabel 
(oder einstellbar).

von EAF (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Referenzen als Alternative zu Pointern
> bewahren niemanden automatisch davor, Fehler zu machen.
Es unterbindet nicht alle Fehler, welche man machen könnte.
Aber doch schon einige Fehler der gemeinen Sorte.

C++:
Ein Zeiger auf ein Array sagt einem nicht wie groß das Array ist. Er 
zeigt nur auf das erste Element.
Eine Referenz dagegen schon.
Irrtümer sind bei der Index Verarbeitung mit for(;;) nicht 
ausgeschlossen.
Bei dem "Range Based For", dagegen schon, da ist der Index (oder 
vergleichbares) gekapselt.

Ich sage es mal so:
Wenn es Möglichkeiten/Sprachmittel gibt, welche weniger Problempotential 
haben, dann sollte man sie auch einsetzen, wo immer es sinnvoll ist.

von Peter D. (peda)


Lesenswert?

EAF schrieb:
> Aber immerhin sind die Bereichsüberschreitungen vollständig
> weggekapselt.

Was macht denn die Kapselung, wenn eine Über- oder Unterschreitung 
versucht wird?
Die Reaktion muß ja irgendwo festgelegt werden. In meinem Beispiel 
werden die Anweisungen nicht ausgeführt.

Ich hab mit C++ immer das Problem, daß ich schnell die Übersicht 
verliere, weil die Aktionen an völlig anderen Stellen stehen, nur nicht 
an der Stelle des Aufrufs.

von EAF (Gast)


Lesenswert?

Peter D. schrieb:
> Was macht denn die Kapselung, wenn eine Über- oder Unterschreitung
> versucht wird?
> Die Reaktion muß ja irgendwo festgelegt werden. In meinem Beispiel
> werden die Anweisungen nicht ausgeführt.

Dafür ist die Konkrete Klasse/Kapselung zuständig.

Im falle der Arduino Strings würde das so aussehen:
1
  String buffer = "hallo";
2
  char c;
3
  c = buffer.charAt(0) // liefert 'h'
4
  c = buffer.charAt(42)// liefert '/0'
5
  c = buffer[0] // liefert 'h'
6
  c = buffer[4711]// liefert '/0'
Wenn buffer ein C String wäre, würde buffer[42] einen "Zufallswert" 
liefern, eben das was zufällig an der Stelle im Speicher steht.




Peter D. schrieb:
> Ich hab mit C++ immer das Problem, daß ich schnell die Übersicht
> verliere, weil die Aktionen an völlig anderen Stellen stehen, nur nicht
> an der Stelle des Aufrufs.
Habe ich volles Verständnis für!
Prozedurale Programmierer sind gewohnt Programmlaufpläne zu malen, und 
auch so zu denken.
Ich nenne das die Kontrollfluss orientierte Denkweise.
Es wird in jedem Programm nach dem Kontrollfluss gesucht, um es 
verstehen zu können.

Da ist nix falsches dran.

Funktioniert nur nicht mehr in der OOP. Oder nur noch in den Methoden 
selber.
In der OOP setzt man die einzelnen Objekte/Kapseln in Beziehung 
zueinander. Die Objekte schicken sich gegenseitig Nachrichten.
Es werden also keine Programmlaufpläne gemalt, sondern UML Diagramme.
Und auch so gedacht.
Das führt dazu, dass die Beziehungen klar sind, aber der Programmfluss 
weit aufgefächert stattfindet.

Der Prozedurale Programmierer setzt also seinen Fokus auf den 
Programmfluss, anstatt auf die Beziehungen zwischen den Komponenten. Es 
ist eine "andere" Denke. Die Umgewöhnung ist nicht leicht.

Vereinfacht gesagt:
Wer einmal ein richtiger Prozedurale Programmierer geworden ist, ist für 
die OOP verdorben. Stimmt natürlich nicht immer, aber das prozedurale 
Paradigma, steht da wirklich quer im Raum und verstellt gerne den Blick, 
ist eine derbe Hemmung

von S. R. (svenska)


Lesenswert?

EAF schrieb:
> Ein Zeiger auf ein Array sagt einem nicht wie groß das Array ist.
> Er zeigt nur auf das erste Element.
> Eine Referenz dagegen schon.

Hä? Eine Referenz (in C++) ist ziemlich genau das gleiche wie ein 
Zeiger, nur dass es (durch den Compiler garantiert) kein Nullzeiger ist.
(Gibt schöne Bugs, wenn es dann doch mal einer ist.)

Eine Referenz auf einen Vektor enthält dessen Größe, aber eben nur, 
weil der Vektor das selbst weiß. Das hat mit der Referenz nichts zu tun, 
geht mit einem Zeiger auf einen Vektor auch.

> Wenn es Möglichkeiten/Sprachmittel gibt, welche weniger Problempotential
> haben, dann sollte man sie auch einsetzen, wo immer es sinnvoll ist.

Dem stimme ich zu. Es hilft aber, wenn man das umschiffte 
Problempotential auch dann kennt, wenn es durch die verwendeten 
Sprachmittel nicht auftritt. (Gerüchten zufolge ist genau das der 
Unterschied zwischen Autodidakt und "hat den Kram in den letzten 25 
Jahren mal studiert".)

Ansonsten landet man ganz schnell in religiösen Fanatismus in Form 
seltsamer Codestrukturen.

von S. R. (svenska)


Lesenswert?

EAF schrieb:
> Wer einmal ein richtiger Prozedurale Programmierer
> geworden ist, ist für die OOP verdorben.

Einen prozedural strukturierten Code kann man zumindest von oben nach 
unten lesen und hoffentlich nachvollziehen, was da passiert.

Mit massiv OOP-strukturiertem Code geht das nicht. Bei 50 gleichzeitig 
operierenden Objekten, die sich gegenseitig ständig Events und Messages 
schicken, während sie ihre eigenen State Machines aktualisieren, wird 
das ohne zusätzliche Dokumentation nichts. Trocken (d.h. ohne das 
laufende System) erst recht nicht.

Dazu kommt noch, dass OOP andere Probleme mit sich bringt als 
prozeduraler Code. Nicht besser oder schlechter, aber anders.

von EAF (Gast)


Lesenswert?

S. R. schrieb:
> Hä? Eine Referenz (in C++) ist ziemlich genau das gleiche wie ein
> Zeiger, nur dass es (durch den Compiler garantiert) kein Nullzeiger ist.
> (Gibt schöne Bugs, wenn es dann doch mal einer ist.)

Nicht wirklich, oder nicht nur!
Ein Zeiger zeigt auf die erste Zelle des Arrays.
So kann man auch innerhalb einer Funktion die Größe der Zelle ermitteln, 
wenn es nicht gerade ein void Zeiger ist.

Übergibt man dagegen die eine Referenz auf das Array, kann man auch die 
Anzahl Zellen, innerhalb der Funktion ermitteln.
1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Stream &cout = Serial; // cout Emulation für "Arme"
3
4
int test[56];
5
6
template<typename T> void zeigeArrayGroesse(T &array)
7
{
8
  cout << F("zellengr: ") << sizeof(*array) << endl;
9
  cout << F("anzzellen: ") << (sizeof(array)/sizeof(*array)) << endl;
10
}
11
12
void setup() 
13
{
14
  Serial.begin(9600);
15
  cout << F("Start: ") << F(__FILE__) << endl;
16
  zeigeArrayGroesse(test);
17
}
18
19
void loop() 
20
{
21
22
}

Ausgabe:
> zellengr: 2
> anzzellen: 56

Das mach bitte mit einem Zeiger auf das Array.

von EAF (Gast)


Lesenswert?

Oder so, falls man keinen Wert auf das Array selber legt, sondern nur 
die Größe ermitteln möchte:
1
template<typename T, size_t anzzellen> void zeigeArrayGroesse(T (&)[anzzellen])
2
{
3
  cout << F("zellengr: ") << sizeof(T) << endl;//*array
4
  cout << F("anzzellen: ") << anzzellen << endl;
5
}
Hier ist die Referenz dann gar anonym.

von S. R. (svenska)


Lesenswert?

Danke! (Wobei das in deinem Fall nur funktioniert, weil für jede 
Arraygröße eine neue zeigeArrayGroesse()-Funktion gebaut wird. Die 
Größeninformation ist also trotzdem nicht in der Referenz enthalten, 
sondern in der instantiierten Funktion.)

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

S. R. schrieb:
> Die
> Größeninformation ist also trotzdem nicht in der Referenz enthalten,
> sondern in der instantiierten Funktion.)
Eigentlich im Datentype der Referenz, die beiden sind fest verbandelt.

S. R. schrieb:
> weil für jede
> Arraygröße eine neue zeigeArrayGroesse()-Funktion gebaut wird.
Ohne das Template könnte man gar nicht unterschiedliche Arrays per 
Referenz übergeben. Unmöglich!
Das wird da nur so teuer, weil für die Ausgabe Code generiert wird.
Die eigentliche Größenermittlung erfolgt zur Kompilezeit. Die frisst 
kein Brot.

1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Stream &cout = Serial; // cout Emulation für "Arme"
3
4
template<typename ArrayType, size_t count>
5
constexpr size_t arrayCount(const ArrayType (&)[count])
6
{
7
  return count;
8
}
9
10
void setup()
11
{
12
  Serial.begin(9600);
13
  cout << F("Start: ") << F(__FILE__) << endl;
14
  
15
  float test1[56];
16
  cout << F("float test1[56]; ")    << arrayCount(test1) << endl;
17
  
18
  char test2[17];
19
  cout << F("char test2[17]; ")    << arrayCount(test2) << endl;
20
  
21
  float test3[] {3.3, 7.7};
22
  cout << F("test3[] {3.3,7.7}; ") << arrayCount(test3) << endl;
23
}
24
25
void loop()
26
{
27
}

------

Interessant wird das so, mit der Referenz und ihrem Datentype auf diese 
Art:
1
constexpr byte inputPins[] {2,3,4,5,6};
2
3
void setup()
4
{
5
  for(const auto pin:inputPins) pinMode(pin,INPUT_PULLUP);
6
}
7
8
void loop()
9
{
10
}
Auch da wird Größe Zellentype mitgeschleppt.

von W.S. (Gast)


Lesenswert?

EAF schrieb:
> S. R. schrieb:
>> Hä? Eine Referenz (in C++) ist ziemlich genau das gleiche wie ein
>> Zeiger, nur dass es (durch den Compiler garantiert) kein Nullzeiger ist.
>> (Gibt schöne Bugs, wenn es dann doch mal einer ist.)
>
> Nicht wirklich, oder nicht nur!
> Ein Zeiger zeigt auf die erste Zelle des Arrays.
> So kann man auch innerhalb einer Funktion die Größe der Zelle ermitteln,
> wenn es nicht gerade ein void Zeiger ist.

Moment mal!
"Man" kann das wohl überhaupt nicht, sondern der Compiler weiß, was er 
sich bei der Deklaration für eine Typinformation gemerkt hat. Das ist 
der entscheidende Knackpunkt.

Und mal abgesehen davon ist C mit der Prämisse entwickelt worden, dem 
benutzenden Programmierer alle nur erdenklichen Freiheiten zu geben, was 
auch die Freiheit einschließt, jegliche Art von Fehlern zu 
programmieren. Es könnte ja eventuell mal jemanden geben, der das 
gebrauchen kann. Da geraten 2 entgegengesetzte Ideen aneinander:
maximale Freiheit <--> vorsorgliche Einschränkung
Und an welcher Stelle zwischen diesen beiden Polen man sich selber 
befindet, muß wohl ein jeder für sich selbst entscheiden.

Allerdings ist das alles schon weit weg von der Ausgangsfrage des 
Threads, wo jemand seine Uhrzeit "10;56 Uhr" über eine Serielle schicken 
möchte und sich darüber wundert, daß die gewählte Funktion nur die 
Stunden liefert und die Minuten in den Rundordner befördert. Tja, sowas 
kommt vom nicht genügenden Nachdenken vor dem Programmieren besagter 
Funktion, das kann man dem TO nicht als dessen eigenen Fehler anlasten, 
sondern das Konstrukt aus "Beachte den Zeichenstrom bis zum 
Trennzeichen" und "Endekondition ist eine Zeitbegrenzung" ist per se 
falsch angelegt.

W.S.

von Joachim B. (jar)


Lesenswert?

Stefan ⛄ F. schrieb:
> Bei einem
> ESP8266 hat readStringUntil() mal mehrere Kilobytes am Stück empfangen,
> das hatte zu einem Speicherüberlauf geführt.

sehr guter Hinweis

ich mache das zwar so, ist aber auch geschützt vor Überlauf!

im loop();
1
 #if defined(ESP32)
2
  // --------------- serial in ---------------
3
  if (Serial.available() > 0) { 
4
    char incomingByte = (char)Serial.read();
5
    if(incomingByte == '\r') { // Wenn das Enter ankommt
6
      strcpy(serial_in_command, serial_in_buff);
7
      memset(&serial_in_buff[0], 0, sizeof(serial_in_buff));
8
      chr_cnt = 0;
9
      stringComplete=true;
10
      inSTR_Auswertung();
11
    } // if(incomingByte == 13)
12
    else { // Falls kein Enter kommt muss der Text gespeichert werden in dem inText Array
13
      if(isprint(incomingByte)) {
14
        if(chr_cnt<(MAXBUFFER-2))
15
          serial_in_buff[chr_cnt++] = incomingByte;
16
          //serial_in_buff+(chr_cnt++) = incomingByte;
17
        else {
18
          Serial.println(F("serBUF ov-> DEL"));
19
          Serial.print(F("MAXBUFFER:   ")); Serial.println(MAXBUFFER);
20
          Serial.print(F("MAXBUFFER-2: ")); Serial.println(MAXBUFFER-2);
21
          Serial.print(F("chr_cnt:     ")); Serial.println(chr_cnt);
22
          memset(&serial_in_buff[0], 0, sizeof(serial_in_buff));
23
          chr_cnt=0;
24
        } // if(chr_cnt<(MAXBUFFER-1))
25
      } // if(isprint(incomingByte))
26
    } // if !(incomingByte == 13)
27
  } // if (Serial.available() > 0)

von Stefan F. (Gast)


Lesenswert?

Joachim B. schrieb:
> ich mache das zwar so

Ist ja prinzipiell das Gleiche wie meine append_until() Methode. Man 
sammelt die Zeichen ein, bis das "Ende"-Zeichen erkannt wurde und 
beachtet dabei die Puffergröße.

von Joachim B. (jar)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ist ja prinzipiell das Gleiche wie meine append_until() Methode. Man
> sammelt die Zeichen ein, bis das "Ende"-Zeichen erkannt wurde und
> beachtet dabei die Puffergröße.

eben,
es führen wie immer viele Wege nach Rom, Hauptsache man kommt am Ziel an 
und verläuft ich nicht.

von Stefan F. (Gast)


Lesenswert?

Max M. schrieb:
> Deine Variante sieht ganz gut und logisch aus, ist allerdings
> ziemlich viel Code für eine doch recht überschaubare Aufgabe.

Max, konnten wir dich inzwischen überzeugen?

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Moment mal!
> "Man" kann das wohl überhaupt nicht, sondern der Compiler weiß, was er
> sich bei der Deklaration für eine Typinformation gemerkt hat. Das ist
> der entscheidende Knackpunkt.

Natürlich weiß der Compiler die Größe seiner Datentypen.
Aber "ich" muss ihn beauftragen diese Größe als numerischen Wert kund zu 
tun, damit sie ausgegeben, oder sonst wie im Programm verwendet werden 
kann.
Das nenne ich "ermitteln".
Ohne Auftrag rückt er das nicht raus.
Das ist Programmieren.
Man teilt dem Compiler mit, was man haben möchte, und er erfüllt einem 
alle Ansagen, im Rahmen seiner Möglichkeiten.

W.S. schrieb:
> Und mal abgesehen davon ist C mit der Prämisse entwickelt worden, dem
> benutzenden Programmierer alle nur erdenklichen Freiheiten zu geben, was
> auch die Freiheit einschließt, jegliche Art von Fehlern zu
> programmieren.
Während der C++ Entwicklung, und ja, wir reden hier über C++ (genauer: 
gnu++11), hat man die Möglichkeit geschaffen, vielen der in C möglichen 
Fehlern, aus dem Wege zu gehen.
z.B. die Verwendung von Referenzen statt Zeigern, wo immer es 
möglich/sinnvoll ist, ist eine davon.

W.S. schrieb:
> das kann man dem TO nicht als dessen eigenen Fehler anlasten,
Doch schon...
Es ist ein logischer Fehler und liegt damit ganz klar in der 
Verantwortung des Programmierers. Nur er kann ihn beheben. Der Compiler 
tut nur das was man ihm sagt. Ein Compiler- oder Versionswechsel, bringt 
da gar nix.
Das Eingangsprogramm des TO funktioniert genau so, wie es programmiert 
wurde. Dass es nicht so tut, wie er es sich wünscht, liegt eben einzig 
alleine beim TO.
Es braucht halt ein wenig Zeit(einige Jahre) bis man einigermaßen 
Sattelfest ist, und in C++ denken kann.

Zudem: Fehler zu machen ist voll menschlich.
Nur Unmenschen machen keinen Fehler, irren sich nie.

von W.S. (Gast)


Lesenswert?

EAF schrieb:
> W.S. schrieb:
>> das kann man dem TO nicht als dessen eigenen Fehler anlasten,
> Doch schon...

Hmm... das sehe ich anders. Der TO schrieb:
Max M. schrieb:
> if (Serial.available() > 0)
>   {
>     pwm_1 = Serial.readStringUntil(";").toFloat();
>     pwm_2= Serial.readString().toFloat();
>     ...

Und da sehe ich den Verfasser dieser Serial.readStrinUnil(...) als 
Karnickel, denn er hat offenbar alles, was zwischen dem Trennzeichen und 
dem Timeout gekommen ist, in den Rundordner befördert. So etwas ist zu 
kurz gedacht. Und ein Benutzer dieser Funktion rennt dann genau damit 
vor die Wand.

W.S.

von S. R. (svenska)


Lesenswert?

EAF schrieb:
> z.B. die Verwendung von Referenzen statt Zeigern, wo immer es
> möglich/sinnvoll ist, ist eine davon.

Da du in deinem Beispiel eine eigene Funktion pro Arraygröße definiert 
hast, hat das ganze Thema nur mit "Referenz statt Zeiger" nix zu tun.

von EAF (Gast)


Lesenswert?

S. R. schrieb:
> Da du in deinem Beispiel eine eigene Funktion pro Arraygröße definiert
> hast,
Was zumindest in der letzten Version völlig egal ist, da weder RAM noch 
Flash dadurch beansprucht wird. Es sich alles nur im Compiler abspielt.

S. R. schrieb:
> hat das ganze Thema nur mit "Referenz statt Zeiger" nix zu tun.
Eben doch, alleine schon, weil du das mit deinen Zeigen nicht 
hinbekommst.
Das sollte doch schon klar geworden sein.....

Die anderen Vorteile von Referenzen liegen ganz klar dort, dass sie 
nicht wie Zeiger in die Wiese zeigen können.
(Außer man stellt sich ganz besonders blöd an)

Arrays kann man per Referenz an eine Funktion übergeben. Der Type bleibt 
vollständig erhalten. Incl. Größe des Arrays.

Arrays kann man per Pointer an eine Funktion übergeben. Dabei zerfällt 
der Bezeichner des Arrays, zu einem Pointer auf das erste Element. Bei 
der Transformation geht die Arraygröße verloren. Ist also innerhalb der 
Funktion nicht zu ermitteln. Die Größe muss man dann als weiteren 
Parameter übergeben, wenn man sie in der Funktion benötigt.

Eine Übergabe von Arrays, per Value, so wie z.B. bei int oder 
Strukturen, ist nicht möglich/vorgesehen.


Natürlich kann man die Größe eines Arrays auch nur aus seinem Type 
ermitteln. Die std C++ Lib macht es vor. Haben wir nur nicht auf den 
kleinen AVRs.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Und da sehe ich den Verfasser dieser Serial.readStrinUnil(...) als
> Karnickel, denn er hat offenbar alles, was zwischen dem Trennzeichen und
> dem Timeout gekommen ist, in den Rundordner befördert.
Das ist fasch.

Serial.readStringUntil() liefert alles was vor dem Timeout da seriell 
rein getropft ist.

Es hat 2 Abbruchbedingungnen.
1. der Terminator, dieser wird konsumiert.
2. der Timeout.

von Max M. (sysadmin4444)


Lesenswert?

Stefan ⛄ F. schrieb:
> Max M. schrieb:
>> Deine Variante sieht ganz gut und logisch aus, ist allerdings
>> ziemlich viel Code für eine doch recht überschaubare Aufgabe.
>
> Max, konnten wir dich inzwischen überzeugen?

Noch bin ich nicht 100% überzeugt, warum mein ursprünglicher code nicht 
funktioniert hat, klar eure Varianten sind durchdachter und so oder so 
ähnlich habe ich es jetzt auch umgesetzt, aber laut anderen Foren sollte 
das auch Funktionieren:

void loop()
{
    if (Serial.available() > 0)
    {
        // First read the string until the ';' in your example
        // "1;130" this would read the "1" as a String
        String servo_str = Serial.readStringUntil(';');

        // But since we want it as an integer we parse it.
        int servo = servo_str.toInt();

        // We now have "130\n" left in the Serial buffer, so we read 
that.
        // The end of line character '\n' or '\r\n' is sent over the 
serial
        // terminal to signify the end of line, so we can read the
        // remaining buffer until we find that.
        String corner_str = Serial.readStringUntil('\n');

        // And again parse that as an int.
        int corner = corner_str.toInt();

        // Do something awesome!
    }
}

oder kurz:

void loop()
{
    if (Serial.available() > 0)
    {
        int servo = Serial.readStringUntil(';').toInt();
        int corner = Serial.readStringUntil('\n').toInt();

        // Do something awesome!
    }
}

Das war ist ein Beispiel für eine Servo Steuerung für den Input String 
1;130. Eigentlich ziemlich ähnlich zu meinem Vorhaben. Den String bis 
zum delimiter lesen, den rest im buffer dann in einen zweiten String 
schreiben.

: Bearbeitet durch User
von Max M. (sysadmin4444)


Lesenswert?

Man könnte wahrscheinlich auch den input String in 2 variabel 
abspeichern, dann ein readUntil(";") beim einen machen und dann beide 
strings voneinander abziehen, um das zweite zu bekommen. Aber 
mittlerweile läufts bei mir, das ist ja die Hauptsache!

von EAF (Gast)


Lesenswert?

Max M. schrieb:
> aber laut anderen Foren sollte das auch Funktionieren:
Da hasste dich aber wirklich drauf fest gefressen.

Kennst du den Spruch mit den "Tausend Fliegen"?

von Max M. (sysadmin4444)


Lesenswert?

Hier meine finale Variante ohne Timeout, dafür kurz und knapp:

input = Serial.readStringUntil('\n');

pwm1 = input.substring(0, input.indexOf(";")).toFloat();
pwm 2 = input.substring(input.indexOf(";") + 1, 
input.length()).toFloat();

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Das Ergebnis von indexOf() ist -1 wenn der String kein Semikolon 
enthält. Also effektiv:

> pwm2 = input.substring(0, input.length()).toFloat();

Oder anders gesagt: pwm2 enthält dann id erste Zahl vom Input, nicht die 
zweite.

> pwm1 = input.substring(0, -1).toFloat();

Was macht substring() eigentlich, wenn der Range ungültig ist? Steht 
leider nicht in der Doku.
1
String String::substring(unsigned int left, unsigned int right) const
2
{
3
  if (left > right) {
4
    unsigned int temp = right;
5
    right = left;
6
    left = temp;
7
  }
8
  String out;
9
  if (left >= len) return out;
10
  if (right > len) right = len;
11
  char temp = buffer[right];  // save the replaced character
12
  buffer[right] = '\0';  
13
  out = buffer + left;  // pointer arithmetic
14
  buffer[right] = temp;  //restore character
15
  return out;
16
}

Am Quelltext kann ich ablesen, dass er in diesem Fall die beiden 
Parameter tauscht, so als ob da dies gestanden hätte:

> pwm1 = input.substring(-1, 0).toFloat();

Und dann folgt:

> out = buffer + left;
> return out;

Buffer ist ein char[]. Durch die Addition von -1 zeigt Buffer nun ins 
Nirvana. Tolle Wurst. Von einer C++ Bibliothek erwarte ich eine 
vernünftigere Fehlerbehandlung. Wenn man die Vorteile von C++ nicht 
nutzt kann man gleich bei C bleiben. Ich bin immer noch kein Arduino 
Fan.

von EAF (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Am Quelltext kann ich ablesen,
Du fantasierst.

String String::substring(unsigned *int* left, unsigned int right)


Stefan ⛄ F. schrieb:
> Wenn man die Vorteile von C++ nicht
> nutzt kann man gleich bei C bleiben.
Alles klar!
Nicht lesen können aber urteilen.

Stefan ⛄ F. schrieb:
> Ich bin immer noch kein Arduino
> Fan.
Ein Arduino Basher du bist!
Selbst vor Lügen und Täuschungen schreckst du nicht zurück.
Wobei ich eher vermute, dass du dich da selber täuscht und belügst.

Schätze, dass die Meisten da fix hinter schauen.

von Stefan F. (Gast)


Lesenswert?

EAF schrieb:
> Du fantasierst.
> String String::substring(unsigned int left, unsigned int right)

Guter hinweis, habe ich übersehen. Was wird dann aus der -1?

> Selbst vor Lügen und Täuschungen schreckst du nicht zurück.
Nicht nur das, ich bin bösartig, hinterhältig, stinke und ich koche 
meine Kinder an Heiligabend.

von EAF (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Was wird dann aus der -1?

Soll ich die jetzt noch aus dem Handbuch vorlesen, wie die implizite 
Konvertierungen erfolgen?
Das lies mal schön selber nach.

von W.S. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Wenn man die Vorteile von C++ nicht
> nutzt kann man gleich bei C bleiben.

Nana, jetzt trompetest du ein bissel zu laut. Nenne mir mal einen 
wirklich gewichtigen Vorteil von C++. Ich habe den Stroustrup seit etwa 
30 Jahren im Schrank stehen und habe nach dem Lesen dieses Buches 
beschlossen, diese Verschlimmbesserung von C nicht anzufassen.

> Ich bin immer noch kein Arduino Fan.

Kann ich verstehen. Damit kriegen die Kinder zwar eine blinkende LED 
hin, aber gewinnen nichts an wirklichen Elektronik-Kenntnissen. OK, ich 
muß dabei einräumen, daß andere Kompetenz-Bereiche genauso wichtig sind. 
Wer Geige spielen oder Blinddarm-Operationen kann, dem sind andere Dinge 
wichtiger. Verständlichermaßen.

W.S.

von Stefan F. (Gast)


Lesenswert?

EAF schrieb:
> Soll ich die jetzt noch aus dem Handbuch vorlesen

Ja bitte wäre nett von dir.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Nenne mir mal einen
> wirklich gewichtigen Vorteil von C++. Ich habe den Stroustrup seit etwa
> 30 Jahren im Schrank stehen und habe nach dem Lesen dieses Buches
> beschlossen, diese Verschlimmbesserung von C nicht anzufassen.
Nicht nötig.
Der Zug ist abgefahren.
Nee.. gar das Geleis schon abgebaut.


Stefan ⛄ F. schrieb:
> Ja bitte wäre nett von dir.
Auch das lohnt sich nicht.
Die Vorurteile sitzen so fest, da kann keine Ansage mehr was ausrichten.
Nennt sich "selektive Wahrnehmung".
> .... das Gehirn beginnt, Deutungen auf die Umwelt zu projizieren,
> die dann wenig bis nichts mit der objektiven Betrachtung .....

Du hast einfach keinen Bock kauf Arduino.
Das ist eigentlich gar nicht schlimm.
Geht sicherlich einigen so, ist auch ganz sicher keine Krankheit.

Aber so einen Film durchzuziehen, ohne das (je?) zu bemerken, ist schon 
eine harte Nummer.

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.