Hallo Leute,
ich versuche gerade an einen µC seriell eine ganz einfache
Steuerbefehlskette zu übersenden und scheitere gerade am sehr simplen
Parser!
Ich will an einen µC einen RGBW wert übermitteln um eben drei
Konstantstromquellen entsprechend mit PWM zu dimmen.
Übertragen werden sollen immer vier zahlen von 0-255, stellvertretend
für den PWM Wert, mit einem Komma separiert und mit * startend und #
endend.
z.B. so: *127,255,001,111#
Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an
der richtigen stelle stehen. Das klappt auch!
Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig
per "atoi" zu extrahieren.
Aus dem Beispiel *127,255,001,111# extrahiert mir mein Progrämmchen
127,255127, 1255127, -667894569 statt 127,255,1,111 :-(
Ich hab mir dazu das im Anhang angehängte "Demo" geschrieben um zu
prüfen, ob es das so tut, bevor ich es auf den µC portieren kann.
Es wäre super, wenn sich jemand geübteres als ich sich das mal ansehen
könnte und mir einen Tipp oder einen Wink mit dem Zaunpfahl geben
könnte, denn ich sehe den Fehler einfach nicht :-(
Wie sind Zeichenketten in C aufgebaut?
Wie viel Platz ist in Rbuffer, Gbuffer etc.?
Was parst/wandelt atoi dann u.U. alles bzw. was fehlt?
Hint: Welches Verhalten hat atoi, wenn nach einer Zahl/Ziffer noch
andere Zeichen im String enthalten sind? Sind die Buffer dann noch
nötig?
Arc N. schrieb:> Wie sind Zeichenketten in C aufgebaut?
? als Array aus Chars?
Arc N. schrieb:> Wie viel Platz ist in Rbuffer, Gbuffer etc.?
Die R/G/B/W Buffer sind für drei zeichen gedacht eben von 000 bis
theoretisch 999. Den Wertebereich muss ich später noch abfangen!
Ich dachte erst, ich hätte Probleme mit "001" da zwei vorgestellte
Nullen da sind, aber der Fehler passiert ja schon mit 255.
Arc N. schrieb:> Was parst/wandelt atoi dann u.U. alles bzw. was fehlt?
Auch das weiß ich nicht :-( Ich denke ich muss noch viel lernen!
Folio schrieb:> Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an> der richtigen stelle stehen. Das klappt auch!>> Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig> per "atoi" zu extrahieren.
Dann extrahiere sie doch ganz einfach OHNE "atoi". Sowas sollte ja
wirklich kein Thema sein. Lade dir einfach die Lernbetty herunter und
guck dort in conv.c hinein, falls du so etwas nicht selber aus dem Ärmel
schütteln kannst.
Und wer sowas nicht aus eigener Kraft kann und dennoch die Nase über die
olle Lernbetty rümpft, dem ist nicht zu helfen.
W.S.
Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins
größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').
Einfacher kannst du aber atoi, der eh beim ersten ungültigen Zeichen
aufhört, direkt auf den String loslassen:
1
intparse(char*str)
2
{
3
if(strlen(str)>16
4
&&str[0]=='*'&&str[16]=='#'
5
&&str[4]==','&&str[8]==','&&str[12]==',')
6
{
7
intr=atoi(str+1);
8
intg=atoi(str+5);
9
intb=aoti(str+9);
10
intw=atoi(str+13);
11
// mach was mit r,g,b,w
12
}
13
...
14
}
Alternativ kannst du dir auch mal sscanf anschauen.
foobar schrieb:> Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins> größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').
Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger
:-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur
kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi"
implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war
schon echt am verzweifeln.
Wie gesagt, ich muss noch viel lernen!
Danke!
Folio schrieb:> foobar schrieb:>> Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins>> größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').>> Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger> :-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur> kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi"> implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war> schon echt am verzweifeln.>> Wie gesagt, ich muss noch viel lernen!> Danke!
Am besten lernt man, wenn man selber drauf kommt und das geht am besten,
wenn man von jemand auf das richtige Gleis gesetzt wird, vielleicht
sogar ganz dicht ans Ziel. Dann den letzten Schritt zum Erfolgserlebnis
selber machen.
Wenn man hier also solche Antworten wie oben bekommt, dann geschieht das
in bester Absicht.
Dein Code macht alles mögliche, aber nichts sinnvolles.
Am bequemsten kannt Du mit sscanf() die 4 Zahlen einlesen.
Du kannst aber auch in einer Schleife (0..3) mit strtok() den String
zerlegen und mit atoi() dann einlesen. strtok() ersetzt den Delimiter
"," durch "\0" und gibt für atoi() den Pointer auf den Teilstring
zurück.
sscanf() ist eine mächtige und sehr komfortable Funktion. Es lohnt sich
daher, sich damit zu beschäftigen und die Doku dazu zu lesen (gibt auch
Tutorials dazu in deutsch).
Hinweis: strtok sollte man aus seinem Repertoir streichen! Das ist
eines der schwarzen Schafe im C-Standard. Zum einen hat sie verborgenen
State und zum anderen ändert sie den übergebenen String (das "const"
beim ersten Parameter ist nicht nur einfach vergessen worden). Am
besten einfach ignorieren ;-)
foobar schrieb:> strtok sollte man aus seinem Repertoir streichen!
Es macht allerdings das Parsen von Kommandozeilen sehr bequem. Ich
versuche erstmal Standardfunktionen zu verwenden, ehe ich mir was selber
schreibe.
> Sich darauf zu verlassen, daß immer 4 Bytes Abstand sind
Hat ja keiner gesagt, dass die Aufgabenstellung besonders elegant wäre
;-) Zumindest wird vorher geprüft, dass etwas halbwegs sinnvolles
vorhanden ist und das Programm nicht abstürzt. Besser geht immer -
sscanf, strtoul, handgedengelt, ...
>> strtok sollte man aus seinem Repertoir streichen!>> Es macht allerdings das Parsen von Kommandozeilen sehr bequem.
Ja, ist ärgerlich: die Funktionalität kann man sehr gut gebrauchen.
Leider hängen so viele Fallstricke dran, dass man besser die Finger von
läßt.
Z.B. das Parsen von Kommandozeilen: man hat den Parser fertig und er
scheint zu funktionieren. Bis man die Funktion mit einem Konstantstring
(im .rodata) aufruft - Peng. Dann splittet man z.B. bei Leerstellen und
übergibt das abgetrennte Wort an eine Funktion - wollen wir mal hoffen,
dass die nicht in ihren Tiefen auch irgendwo strtok benutzt denn sonst
fängt meine Schleife an zu spinnen.
Ich bleib dabei: besser nicht benutzen.
Okay, jetzt müsst ihr euch doch nicht meinetwegen über Bibliotheken und
deren Funktionen streiten! Ich dachte nur, das ich darauf so gut es eben
geht verzichten sollte, wenn es für eine µC gerichtetes Programm werden
soll. (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist
voll ODER: Es ist nicht implementiert).
Darum habe ich auch ein eigenes atoi implementiert... aber egal, jetzt
war es halt nur eine Übung für mich ;)
Wie würdet ihr das denn sonst machen?
Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün
hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus
4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen.
Hat vielleicht sonst jemand ein minimales Beispiel für mich, an dem ich
mich orientieren kann?
Aja, falls von belangen: Ich verwende aktuell so ein spottgünstiges
STM32-Arduino Clone ding (für unter 2€!) und ja auch den Arduino
kompatiblen Bootloader von Roger Clark.
Ob ich aber später das Arduinozeugs weiter benutzen will ist noch nicht
sicher! Aktuell benutze ich es nur aus Bequemlichkeit!
Folio schrieb:> (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist> voll ODER: Es ist nicht implementiert).
Nun, seit 1980 hat sich aber einiges getan. Die Compiler habe sich
deutlich weiterentwickelt und auch die MCs haben erheblich mehr Flash
und RAM.
Ein printf, scanf oder float bringt kaum noch einen MC zum Schwitzen.
Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt
werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar
sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das
Fahrrad immer wieder neu zu erfinden.
Folio schrieb:> Wie würdet ihr das denn sonst machen?> Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün> hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus> 4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen.
Also ich kann Herangehensweisen nicht leiden, die von festen Positionen
für Textzeichen ausgehen, so wie du das gemacht hast. So etwas ist sehr
steif und versagt völlig, wenn da irgendwo mal das Format nicht ganz
exakt stimmt. Es wäre ja in deinem Beispiel auch logisch nicht zu
verstehen, wenn man für schwarz unbedingt
*000,000,000,000#
schreiben müßte. Weitaus bequemer ist es, wenn die einzelnen Einträge
formatfrei gehalten wären, also *0 127 24 3# anstelle *000,127,024,003#,
dann hättest du erstens weniger Aufwand beim Erzeugen der Zeilen und
zweitens eine Flexibilität, die ein späteres Ändern viel leichter macht.
Mein Rat mit der Lernbetty war übrigens sehr ernst gemeint. Dort sind
Input-Konvertierungen drin, die mit formatfreien Zeilen zurechtkommen.
Warum? Weil man ja auch damit rechnen muß, daß man manuell eingetippte
Kommandozeilen auszuwerten hat. Wer da auf feste Zahlenpositionen setzt,
macht sich oder anderen Streß.
Beim cmd.c in der Lernbetty sähe das so aus:
char* Cpt;
Cpt = Pzeile;
if (match("*",&Cpt))
{ rot = Long_In(&Cpt);
gruen = Long_In(&Cpt);
blau = Long_In(&Cpt);
W = Long_In(&Cpt);
if (!match("#",&Cpt)) SagFormatfehler();
}
Allerdings erinnere ich mich dunkel, daß abschließende Trennzeichen bei
Zahleneingaben nicht überlesen werden, sondern ggf. getrennt abgefragt
oder übergangen werden müßten - außer Leerzeichen. Naja, und ob du nun
für deine Zwecke ein Long_In(..) brauchst oder mit einer geringeren
Datenbreite auskommst, ist dein Geschmack. Bei 32 Bit Controllern wie
der Lernbetty ist aber long quasi das natürlichste.
Ein ganz anderes Verfahren benutze ich, wenn es wirklich NUR um serielle
Kommunikation zwischen 2 Maschinen geht. Die brauchen keine visuelle
Rückmeldungen und keine Zeilen. Also ist es dort am besten, nur atomare
Daten zu übertragen - dies aber wieder formatfrei.
Auf dein Beispiel bezogen sähe sowas dann so aus:
0R127G24B2W1K
..und so weiter. Ohne Leerzeichen (die würden überlesen) und ohne CRLF,
das würde auch überlesen. Maschinen brauchen es nicht leserlich, sondern
nur sparsam und leicht zu dekodieren. Jede Einstellung wird mit dem
numerischen Wert begonnen und durch einen entsprechenden Buchstaben
abgeschlossen, der den Wert übernimmt und dann den Akku für das
Auflaufen des nächsten Wertes löscht.
und die Interpretation ist:
0R Rotwert=0 setzen
127G Grünwert=127 setzen
24B Blauwert setzen
2W W_wert =2 setzen
1K Konstantstromquelle 1 auf die gesetzten Werte einstellen
Und ob man da nun 0R oder bloß R oder 000000000000R schreibt, ist egal.
Das Angenehme bei diesem Verfahren ist, daß man praktisch keine
Eingabezeile aufbauen muß. Man kann für den Dekoder einfach die Zeichen
nehmen, so wie sie von der Seriellen herkommen. Das ist ne andere Nummer
als das, was du machst:
Folio schrieb:> Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an> der richtigen stelle stehen. Das klappt auch!> Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig> per "atoi" zu extrahieren.
eben. Zuerst mußt du die Zeile aufbauen, um alles an festgelegte
Positionen zu bringen und dann klappt dein atoi trotzdem nicht.
W.S.
Peter D. schrieb:> Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt> werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar> sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das> Fahrrad immer wieder neu zu erfinden.
Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten
"erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß
das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen
können. Abgesehen davon ist auch das Exposé des TO nicht wirklich
zweckmäßig.
Wichtig ist nur eines: daß man als Mikrocontroller-Programmierer mit der
Zeit sich ein Portfolio an hardwareunabhängigen Modul zulegt, die auf
die Bedingungen des µC-Bereiches passen. Und dazu zählt printf und
Konsorten eben nicht, weil das eben immer einen Formatzeilen-Interpreter
bedeutet, der zur Laufzeit Dinge tut, die man eigentlich bereits zur
Compilationszeit erledigt haben könnte. Sowas ist nur der bellenden
Faulheit und gedankenlosem Umgang mit den Ressourcen geschuldet.
Abgesehen davon muß man bei printf auch darauf achten, was für Ausdrücke
die betreffende Variante überhaupt kennt. Die Varianten für µC sind
oftmals gekürzte Implementationen, weil man hier mehr Wert darauf legt,
das Ganze in den Flash zu kriegen.
W.S.
W.S. schrieb:> Also ich kann Herangehensweisen nicht leiden, die von festen Positionen> für Textzeichen ausgehen, so wie du das gemacht hast.
auch wenn ich deine Sichtweise anerkenne, für den Anfang hatte der TO
doch gut überlegt,
wenn schon die Zeile parsen doch dann auf das erste Vorkommen einer
Ziffer (ggffs. erweitert um +-) *1.) als Start Pointer zu Atoi.
Dann die Länge feststellen, also weiterparsen bis keine Ziffer mehr
kommt und wenn dann noch nicht \0 oder Ende vom String erreicht ist
weitersuchen, bis zum nächsten Vorkommen von *1.)
solange bis alle Zahlen gefunden sind, der String beendet ist.
Ist eine schöne Lernübung.
W.S. schrieb:> char* Cpt;> Cpt = Pzeile;> if (match("*",&Cpt))> { rot = Long_In(&Cpt);> gruen = Long_In(&Cpt);> blau = Long_In(&Cpt);> W = Long_In(&Cpt);> if (!match("#",&Cpt)) SagFormatfehler();> }
Es ist weitgehend sinnfrei, Beispiele zu posten, die weder Standardlibs
verwenden, noch den Quelltext der Funktionen beinhalten.
Peter D. schrieb:> noch den Quelltext der Funktionen beinhalten.
Aber das verwendet doch die "Lernbetty"!
Das ist quasi der C-Standardcode überhaupt, auch wenn die ISO den
immer noch nicht abgesegnet hat.
Ob das jetzt gut oder schlecht ist, wie die Nachricht des TO aufgebaut
ist, sei mal dahingestellt. Ohne strtok, atoi & co. koennte das
vielleicht so aussehen (Fehlerbehandlung hab ich mal weggelassen):
$ gcc -std=c99 -Wall -Wextra -o main main.c && ./main
2
Num[0]: 127
3
Num[1]: 255
4
Num[2]: 1
5
Num[3]: 111
Das ist natuerlich nicht das optimum, da Fehlerbehandlung fehlt,
negative Zahlen nicht beruecksichtigt werden, und die Zahl maximal 3
Ziffern haben darf. Funktioniert also nur fuer 000 bis 999. Das koennte
aber fuer den TO vielleicht trotzdem ein Anfang sein.
Kaj schrieb:> void parse(char* str, uint16_t* buff);
Tja, das ist zu wenig.
Dein parse kann keinen String parsen.
Stattdessen ist es nur eine Zusammenfassung von 4x ascii_to_u16(..), die
mit jeweils einem festen Zeiger aufgerufen werden.
Eine parse-Funktion muß anders aussehen.
Etwa so:
my_int_typ parse(char** posinstring);
Das deshalb, weil parse ja den String durchsuchen soll, dabei sowas wie
Leerzeichen übergehen, dann die Zahl konvertieren und anschließend den
Zeiger im String auf die Stelle dahinter setzen. Sowas ist "parsen".
Peter D. schrieb:> Es ist weitgehend sinnfrei,
Nee Peter, dein Beitrag war herzlich sinnlos, da du ja keinen positiven
Beitrag leisten, sondern nur mäkeln wolltest.
Mein Beitrag war sehr sinnvoll, da er einen Eindruck von der
Verwendung echt parsender Eingabe-Konvertierungen gibt. Genau DAS war
das Anliegen. Du hättest es erkennen können, wenn du nur korrekt gelesen
hättest.
Die Betonung liegt auf VERWENDUNG. Wie das modulintern geht, kann ja
jeder nachlesen, die Quelle ist genannt.
Und Rufus liegt mit seiner überflüssigen Bemerkung mal wieder
grundfalsch (wie so oft in letzter Zeit), denn meine Routinen in der
Lernbetty sind im Gegensatz zu dem Standard-Zeugs, was bei den meisten
C-Compilern so dabei ist, eben nicht Standard, sondern an die
Verhältnisse in einem MIKROCONTROLLER angepaßt. In diesem Falle sogar
hardwareunabhängig und dennoch effektiver als sowas wie atoi, denn das
kann ja nicht einmal das Ende der Zahl im String zurückliefern.
Ich erinnere mich an die Bemerkungen eines echten Programmierer-Idioten
vor langer Zeit, der sich darüber aufgeregt hat, daß man bei den kleinen
PIC16 ja nur 4 Plätze im Stack hat - wo er doch bei seinem PC ja
mindestens 16K oder noch mehr an Stack hätte, weshalb also diese Chips
ein völlig ungeeignetes zu nix zu gebrauchendes Zeugs seien.
So ungefähr kommen mir Bemerkungen über Standard-Funktionen in C auf dem
µC vor.
W.S.
W.S. schrieb:> Nochwas:> War das nicht so, daß atoi als Argument ein "const char* string" haben> will?> Was machen denn da die AVR-Anwender?>> W.S.
Ja, atoi hat einen "const char*" Parameter. Wo siehst du da ein Problem?
Bei all deinen "selbstbewussten" Beiträgen können wir doch sicher
erwarten, dass du "const" verstanden hast.
W.S. schrieb:> War das nicht so, daß atoi als Argument ein "const char* string" haben> will?
Erkundige Dich doch erstmal, was "const" in einem Funktionsparameter
bedeutet.
> Was machen denn da die AVR-Anwender?
Dasselbe, was die anderen Anwender auch machen.
W.S. schrieb:> Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten> "erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß> das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen> können.
Warum soll man auf dem AVR kein sscanf verwenden dürfen?
Ich habs mal compiliert, das Programm ist ~2kB groß, paßt also bequem in
einen kleinen ATtiny85.
Man muß heute nicht mehr den Stand von 1980 (P8751: 4kB OTP, 0,1kB RAM)
als Maßstab nehmen.
*127,255,001,111#
Kaj schrieb:> void parse(char* str, uint16_t* buff)> {> buff[0] = ascii_to_u16(&str[1]);> buff[1] = ascii_to_u16(&str[5]);> buff[2] = ascii_to_u16(&str[9]);> buff[3] = ascii_to_u16(&str[13]);> }
das klappt auch nur wenn die zahlen immer 3 byte lang sind
also *001,002,003,004#
ich würde vlt die trenner nullen
*127\0255\0001\0111\0
und dabei die pointer merken
genullt sind sie ja dann alle schon
foobar schrieb:>>> strtok sollte man aus seinem Repertoir streichen!> Ich bleib dabei: besser nicht benutzen.
Nun ja pauschal kann das nicht sagen...
Z.Bsp wenn man solche Funktionen die den String verändern nur eine Kopie
übergibt. Bleibt nur noch der versteckte State..
Aber man solche Funktionen auch selbst sicher bauen...
Wem das smarte strtok() zu "gefährlich" ist, der kann auch mit strchr()
nach '*' bzw. ',' suchen lassen und dann atoi() den Pointer+1 übergeben.
Ich verwende sscanf() auch eher selten. Es ist recht unflexibel, wenn
die Daten verschiedene Formate haben können.
Aber sobald man das in einer state-machine verwenden will, wird es sehr
Komplex (beispiel siehe Anhang).
Ich mache meine Parser stattdessen gerne so, dass ich bei jedem
Durchlauf immer maximal ein Zeichen einlese, und speichere dann einfach
alle nötigen Zustände.
Mal das Ausgangsbeispiel (*127,255,001,111#) in EBNF/Wirth-Notation
übertragen mit der Annahme, dass immer alle vier Werte vorhanden sein
müssen:
cmd_line := start unsigned comma unsigned comma unsigned comma unsigned
end
unsigned := digit { digit }
digit := '0' .. '9'
start := '*'
end := '#'
comma := ','
Zur Notation:
irgendwas = muss (an der Stelle) vorhanden sein
[ irgendwas ] = kann 0 oder 1 mal vorhanden sein
{ irgendwas } = kann 0 oder n mal vorhanden sein
'irgendwas' = Terminal, muss dort so vorhanden sein
Damit lässt sich dann ein simpler Scanner/"Parser" basteln (auch wenn
der hier mMn etwas Overkill ist):
Für solche Aufgaben sind regex doch wie geschaffen. Auf AVR habe ich die
bisher nicht benutzt, sind die in der avr-libc implementiert?
Eine kleine Implementation (2 KB Code) liegt auf Github rum:
https://github.com/kokke/tiny-regex-c