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/
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.
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.
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
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.
Du musst die Funktion wiederholt aufrufen. Da du immer abwechselnd auf
ein Semikolon versus Zeilenumbruch wartest, würde ich dies in einen
Zustandsautomaten verpacken:
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.
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.
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.
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.
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.
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.
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?
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.
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...
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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).
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.
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.
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
Stringbuffer="hallo";
2
charc;
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
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.
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.
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 ;-)
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.)
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 ;-)
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.
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();
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.
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.
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?
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.
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.
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.
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.
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.
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.
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!
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"?
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();
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.
chartemp=buffer[right];// save the replaced character
12
buffer[right]='\0';
13
out=buffer+left;// pointer arithmetic
14
buffer[right]=temp;//restore character
15
returnout;
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.
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.
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.
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.
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.
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.