Hallo,
ich beschäftige mich gerade mit der Thematik, Befehle an einen µC zu
senden. Im kleinen Rahmen konnte ich mir bisher immer damit behelfen,
einzelnen Zeichen bestimmte Funktionen zuzuordnen. Das aktuelle Projekt
ist ein bisschen größer (geworden als ich dachte), und da wäre eine
Befehlsverarbeitung mit Parametern geschickter.
Je mehr ich nun versuche, in die Thematik zu kommen, desto verwirrender
wird's momentan: suche ich nun einen Interpreter, einen Parser, eine
Commandshell oder einen Mix aus allem? Soweit ich es verstanden habe,
sind diese Begriffe nicht unbedingt Synonyme für ein und dasselbe.
Von der Art her hätte ich's gern ála Kommandokonsole. Was mir
vorschwebt, wäre z.B. Unterstützung von Umgebungsvariablen, die man auch
als Parameter an Kommandos weitergeben kann, und die Möglichkeit,
Befehle bzw. mathematische Ausdrücke/Formeln, die einen Rückgabewert
liefern, selbst als Parameter übergeben zu können.
Was ich also zumindest schon mal ausklammern kann: ich will (vermutlich)
keinen (vollwertigen) Compiler o.ä. basteln.
Von den verschiedenen Parsertypen, die ich bis jetzt gesehen habe, wäre
es nach meinem Verständnis wohl am ehesten ein mathematischer Parser.
Also "Kommandoformel" eintippen, Enter, fertig (oder Fehlermeldung
grins ). Ist jetzt natürlich sehr vereinfacht ausgedrückt, das ist mir
klar.
Laufen soll das Ganze auf einem Cortex-M0 bzw. M3 (bis 64kB RAM),
programmiert wird in C.
Ich habe mir einige hier im Forum verfügbare Implementationen
verschiedener Parser heruntergeladen und bin gerade dabei, die jeweilige
Funktionsweise durchzugehen.
Das ist zwar hilfreich, um die grobe Funktionsweise an sich zu
verstehen, aber ich bin jetzt an einem Punkt angekommen, an dem ich mich
frage, ob ich den Lexer/Parser selber schreibe oder zu Tools wie
Flex/Bison bzw. GOLD greife.
Selber schreiben würde natürlich die Details auftun und dem Verständnis
sehr helfen. Die Verwendung von Tools ist gefühlt nicht unbedingt
schneller, wenn ich davon ausgehe, dass es ein paar Anläufe braucht bis
es korrekt beschrieben ist und funktioniert. Zumal es bei den Tools wohl
so ist, dass generierte Ausgabe evtl. immer angepasst werden muss
(Tabellen ins Flash, etc.).
Zu was würdet ihr tendieren, selber schreiben oder Parsergeneratoren
verwenden? Und im Fall von selber schreiben, was nehmt ihr als Basis? Es
gibt zig Webseiten, welche die Funktionsweise von Lexer/Parser recht
gut, aber für mich abstrakt, erklären. Ich konnte mit meinen
Suchbegriffen nichts finden, was ich als Grundlage verwenden konnte, um
damit von Grund auf einen Parser zu schreiben.
Was brauche ich denn alles dazu?
* die Befehlszeile muss in Teilstrings zerlegt werden, Trenner sind ','
sowie Leerzeichen und Tabs (es sei denn, es kommt ein Paar
Anführungszeichen vor)
* die Teilstrings werden typisiert, also in entsprechende
Datentypen/Operatoren/Funktionsaufrufe konvertiert und/oder direkt als
String/Bezeichner zusammen mit einem entsprechenden Tokenwert weiter
gereicht
* Aus den Token wird der ParseTree aufgebaut und entsprechend
abgearbeitet
Das Zerlegen, Konvertieren und die Werte mit Token zusammenführen traue
ich mir noch zu (das dynamische Speicherhandling muss ich mir noch
anschauen), aber das Erstellen und Abarbeiten vom ParseTree ist mir noch
nicht klar.
Habt ihr Tipps für mich?
Ralf
Hängt davon ab wie komplex die Sprache ist. Wenn es eine reguläre
Sprache ist, reichen reguläre Ausdrücke und du kannst dir einen
richtigen Parser sparen. Das sollte für die meisten einfachen
Batch/Shell-Sprachen ausreichen.
Wenn du so etwas wie mathematische Formeln mit beliebiger
Verschachtelung lesen können willst, brauchst du doch einen Parser.
Schau dir auch mal Boost.Spirit an, damit brauchst du keinen Code zu
generieren sondern kannst die Grammatik direkt im C++ Code definieren,
aus welcher dann durch den C++ Compiler ein optimierter
Parsing-Algorithmus generiert wird. Du musst dir halt überlegen, ob der
Parserbaum (und der ganze Overhead für die Speicherverwaltung) in 64kB
RAM passt...
Was willst Du denn genau machen?
Ich bin gerade dabei eine Wetterstation zu bauen und da habe ich ein
ähnlich gelagertes Problem.
Ich habe einen "Hauptrechner" der die Daten von mehreren Clients
konsolidiert und weitere Berechnungen durchführt.
Die Clients sind untereinander und mit dem Hauptrechner per RS232, also
seriell, verbunden. Alle Komponenten bilden also eine Kette
(Hauptrechner <-> client1 <-> Client2 usw.)
Für Kommunikation habe ich mir folgenden Stringaufbau ausgedacht:
<Gerätegruppe>ID|Art|Param</Gerätegruppe>. Die gruppiert mehrere
Clienten zu einer Gruppe. ID ist eine eindeutige ID für den jeweiligen
Clienten innerhalb der Gruppe. Art gibt an ob Daten (D) oder Kommandos
(C) übertragen werden. Param ist eine Parameterliste wobei die Parameter
mit Semikola getrennt werden. Bei der Art Kommando (C) ist der erste
Parameter das Kommando/der Befehl selbst. Ich habe mir eine kleine
Routine geschrieben die diesen String auseinander fummelt. Der Client
erkennt an Hand der Gruppe und der ID ob der Befehl für ihn selbst
bestimmt ist, wenn nein wird der komplette String über den 2. UART an
den nächsten Clienten durchgereicht. Letztendlich läuft das Ganze auf
einen Parser hinaus. Die Kommandos auf die der Client reagiert sind in
einer Liste hinterlegt.
Wenn ein Client eine Anforderung an den Hauptrechner sendet, dann
bekommt dieser ebenfalls obigen String. Er erkennt welcher Client eine
Anforderung gesendet hat und baut mit Daten (Gruppe und ID) einen
Antwortstring.
Die Kommandos, die die Clienten verstehen sind können für jeden
unterschiedlich sein.
Bei mir funktioniert das für den vorgesehenen Zweck ganz gut.
Vielleicht hilft es Dir ja etwas weiter. Das Ganze erhebt keinen
Anspruch auf Perfektion . Kann man sicher auch anders lösen.
Moin,
für Befehle mit einfachen Argumenten hat sich bei mir folgendes bewährt,
eine Art RPN-Konstrukt: Sendet man dem "Parser" einen String, sucht er
in einer Liste nach bekannten Befehlen (zB set_samplingrate).
Wird kein bekannter Befehl gefunden, wird versucht, den string als Zahl
zu interpretieren und die Zahl wird auf einen stack gepackt.
Ist der Befehl bekannt, wird dieser ausgeführt und kann auch Werte vom
stack nehmen und auch Werte wieder auf diesen schieben.
Eine Konfig würde dann zB so aussehen:
1000
set_samplingrate
24
set_res
0
20
23
set_clock
Nicht sehr elegant, kein richtiger Parser, aber fix programmiert und
erfüllt seinen Zweck (bei mir^^).
Ist natürlich dann auch nicht in der art samplingrate=1000...
Viele Grüße
Wilko
Ralf schrieb:> Selber schreiben würde natürlich die Details auftun> und dem Verständnis sehr helfen. Die Verwendung von> Tools ist gefühlt nicht unbedingt schneller, wenn ich> davon ausgehe, dass es ein paar Anläufe braucht bis> es korrekt beschrieben ist und funktioniert. Zumal es> bei den Tools wohl so ist, dass generierte Ausgabe evtl.> immer angepasst werden muss (Tabellen ins Flash, etc.).>> Zu was würdet ihr tendieren, selber schreiben oder> Parsergeneratoren verwenden? Und im Fall von selber> schreiben, was nehmt ihr als Basis?
Man kann mit endlichen Automaten ("finite state machines")
recht weit kommen. Voraussetzung ist natürlich, dass man
die Grundbegriffe zuverlässig beherrscht (Festlegen der
Zustände; Überführungsfunktion; Ausgabefunktion) und eine
gewisse Systematik hat, wie man das aufschreibt. Kenntnisse
der Steuerungstechnik sind hilfreich.
Je nach Problem kann auch die umgekehrte polnische Notation
ein Geheimtipp sein; der zugehörige Kellerautomat ist noch
ziemlich einfach, aber schon ziemlich leistungsfähig.
Wilko N. schrieb:> ine Art RPN-Konstrukt: Sendet man dem "Parser" einen String, sucht er> in einer Liste nach bekannten Befehlen (zB set_samplingrate).> Wird kein bekannter Befehl gefunden, wird versucht, den string als Zahl> zu interpretieren und die Zahl wird auf einen stack gepackt.> Ist der Befehl bekannt, wird dieser ausgeführt und kann auch Werte vom> stack nehmen und auch Werte wieder auf diesen schieben.
Client stürzt ab und es liegen noch Werte auf dem Stack, was passiert
mit den Werten auf dem Stack?
Wenn ich Dein Problem richtig verstanden habe, ist TCL fuer dieses
Problem gemacht. Du kannst es in C einbetten, und eigene Kommandos
definieren. Nur mal so.als.Alternativvorschlag.
Ralf schrieb:> Je mehr ich nun versuche, in die Thematik zu kommen, desto verwirrender> wird's momentan: suche ich nun einen Interpreter, einen Parser, eine> Commandshell oder einen Mix aus allem?
Die meisten einfachen Commandshells sind als Interpreter realisiert. Es
gibt allerdings kein Gesetz, dass das vorschreibt.
Ein Parser bereitet Eingangsdaten so auf, dass man deren Bedeutung
analysieren kann. Sowohl Compiler als auch Interpreter verwenden daher
Parser. Bei einfachen Sprachen geht der Parser im Code schon mal unter,
weil er ad-hock im Code eines Interpreters (seltener eines Compilers)
zusammengeschustert wurde.
Mixe von Compiler und Interpreter gibt es schon lange. Traditionell wird
dabei in einen Zwischencode (statt Maschinencode) compiliert, der zur
Laufzeit interpretiert wird. In neuerer Zeit compiliert man den
Zwischencode teilweise zur Laufzeit nach (Hotspot Compiler).
Die älteste für Embedded genutzte Sprache, die ein Mix ist, dürfte FORTH
sein. Forth kommt mit einem eingebauten Kommandozeilen-Interpreter,
einem Compiler, und einem weiteren Interpreter[1] für den kompilierten
Zwischencode. Das ganze passt trotzdem noch in ein paar Kilobyte.
Eine andere Sprache, die typischerweise als Mix aus Kommandozeile und
Programmierumgebung kommt und traditionell als Interpreter, ist BASIC.
Auch hier passen klassische Implementierungen in ein paar Kilobyte[2].
Beispiel https://en.wikipedia.org/wiki/Tiny_BASIC
Nicht zu vergessen die original v7 Bourne-Shell ist vom Code auch
ziemlich klein. Allerdings verwendet sie viele Betriebssystemfunktionen,
die dir fehlen dürften.
> Von der Art her hätte ich's gern ála Kommandokonsole.
Nur die Kommandozeile, oder auch (wie bei FORTH, BASIC, sh) die
Möglichkeit kleine Programme zu erstellen?
> vorschwebt, wäre z.B. Unterstützung von Umgebungsvariablen, die man auch> als Parameter an Kommandos weitergeben kann, und die Möglichkeit,> Befehle bzw. mathematische Ausdrücke/Formeln, die einen Rückgabewert> liefern, selbst als Parameter übergeben zu können.
Wenn du die Formeln in bekannter Notation schreiben möchtest, dann eher
einen BASICaritigen Interpreter. Wenn RPN ok ist, dann FORTH.
> Von den verschiedenen Parsertypen, die ich bis jetzt gesehen habe, wäre> es nach meinem Verständnis wohl am ehesten ein mathematischer Parser.
Der dir alleine nichts nützt. Du brauchst einen
Kommandozeilen-Interpreter, der natürlich intern einen Parser verwendet.
Bei einem Mathe-Parser fehlt der Interpreter und was willst du mit
etwas, dass einzig und alleine Formeln zur weiteren Bedeutungsanalyse
auswertet?
> Ich habe mir einige hier im Forum verfügbare Implementationen> verschiedener Parser heruntergeladen
Ein Parser alleine hilft nicht. Ich hoffe mal, dass ein paar davon
Interpreter waren, die nur jemand Parser genannt hat.
> aber ich bin jetzt an einem Punkt angekommen, an dem ich mich> frage, ob ich den Lexer/Parser selber schreibe oder zu Tools wie> Flex/Bison bzw. GOLD greife.
Wenn du eine fertige Sprache oder einen fertigen Interpreter nimmst,
dann hat dir jemand sowohl diese Entscheidung, als auch die
Implementierung des Parsers abgenommen.
> Zu was würdet ihr tendieren, selber schreiben oder Parsergeneratoren> verwenden?
Bei kleinen Aufgaben Interpreter mit Parser von Hand schreiben
Bei großen Aufgaben fertige Sprache nehmen.
Parsergenerator nur wenn es um eine große, spezielle Aufgaben geht, für
die es nichts gibt. Für die Auswertung von Kommandozeilen gibt es mehr
als genug.
> Ich konnte mit meinen> Suchbegriffen nichts finden, was ich als Grundlage verwenden konnte, um> damit von Grund auf einen Parser zu schreiben.
Dragon Book oder Drachenbuch googlen.
> aber das Erstellen und Abarbeiten vom ParseTree ist mir noch> nicht klar.
Das ist der Kern des Interpreters oder Compilers.
> Habt ihr Tipps für mich?
Dragon Book / Drachenbuch oder eine fertige Sprache nehmen.
-----
[1] Beide Interpreter teilen sich den selben Code, der zweite kostet
praktisch nichts extra.
[2] Schon frühe BASIC-Interpreter hatten als Optimierung gelegentlich
eine kleine Compiler-Komponente. Die Zeilennummer von Sprungzielen wurde
in eine Speicheradresse übersetzt. Damit sparte man sich das elendig
langsame lineare Suchen nach der Sprungziel-Zeilennummer bei jedem
Sprung.
Ralf schrieb:> Befehle an einen µC zu> senden. Im kleinen Rahmen konnte ich mir bisher immer damit behelfen,> einzelnen Zeichen bestimmte Funktionen zuzuordnen. Das aktuelle Projekt> ist ein bisschen größe
Die Tipps für parsen und interpretieren hier sind schön und gut aber
denk mal nach ob es wirklich die richtige Lösung ist.
Eine µC seitige API ist imho die beste Lösung und auch für sehr große
Anwendungen geeignet.
In C kannst du für jeden Befehl eine struct mit den Parametern bauen.
Über deine Verbindung (RS232, RS485, USB, TCP/IP, ...) schickst du dann
[START, FUNKTIONSID, LÄNGE, PAYLOADSTRUCT, ... PRÜFSUMME, STOP]. Im µC
reicht dann ein einfaches switch(FUNKTIONSID) {...} et voila.
Das hat mMn gleich mehrere Vorteile:
* auf PC Seite kannst du eine graphische Oberfläche oder einen einfachen
Kommandointerpreter bauen. (z.B. mit QT oder TCL/TK sogar portabel für
gängige OS)
* bei der Übertragungsschicht bist du flexibel (s.o)
* auf µC Seite sparst du dir das rumgefrickel mit Strings, parsen etc.
Wenn du keine "Programme" zum interpretieren an den µC senden musst,
ist diese Lösung besonders für große Projekte aus meiner Sicht
wesentlich sauberer und auch flexibler.
Gruß Mike
Hallo zusammen,
vielen Dank für die zahlreichen Antworten.
@Dr.Sommer:
> Du musst dir halt überlegen, ob der Parserbaum (und der ganze Overhead für > die
Speicherverwaltung) in 64kB RAM passt...
Kann das wirklich wirklich 64kB sprengen? Mir ist bewusst, dass es mit
ein paar Bytes RAM nicht getan ist, aber das so etwas tatsächlich an die
64 kB oder darüber kommen kann, hätte ich nicht gedacht.
@Zeno:
> Was willst Du denn genau machen?
Danke für die Erläuterungen zu deiner Wetterstation. Später möchte ich
auch mal etwas mit mehreren Teilnehmern an einem Bus machen, da eignet
sich der (relativ) fixe Aufbau wie es dein Protokoll macht
wahrscheinlich eher.
In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur
Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine
oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben.
@Andreas Rückert:
Danke, schau ich mir an.
@ Detlev T.:
Schaue ich mir ebenfalls an.
@Wilko:
Mjaaaa, das geht ungefähr in meine Wunschrichtung, wobei ich schon gern
wie du auch geschrieben hast, die Zuweisung mit '=' und den
Funktionsaufruf wahlweise mit Klammernpaaren und Parameter getrennt
durch Komma sowie ohne Klammernpaar und Komma hätte - in dem Fall evtl.
benannte Parameter, damit die Reihenfolge keine Rolle spielt.
Wobei das jetzt schon wieder arg oversized klingt. Das ging mir halt
anfangs so durch den Kopf.
@Possetitjel:
Das steht auch auf der Liste. Intern in UPN umzubauen sollte auch gehen.
@Dumdi Dum:
Okay, dann werde ich mir TCL auch ansehen =)
@Hannes Jaeger:
Vielen Dank für die ausführlichen Erläuterungen.
>> Von der Art her hätte ich's gern ála Kommandokonsole.> Nur die Kommandozeile, oder auch (wie bei FORTH, BASIC, sh) die> Möglichkeit kleine Programme zu erstellen?
Naja, wenn FORTH insgesamt nicht wirklich groß (relativ zu anderen
Interpretern/Parsern) ist, könnte es auch die "Vollversion" sein. Ich
kann mich ja trotzdem erstmal auf die Eingabe einzelner Zeilen
beschränken.
> Der dir alleine nichts nützt. Du brauchst einen Kommandozeilen-> Interpreter, der natürlich intern einen Parser verwendet.> Bei einem Mathe-Parser fehlt der Interpreter und was willst du mit> etwas, dass einzig und alleine Formeln zur weiteren Bedeutungsanalyse> auswertet?> Ein Parser alleine hilft nicht. Ich hoffe mal, dass ein paar davon> Interpreter waren, die nur jemand Parser genannt hat.
Genau deswegen habe ich eingangs geschrieben, dass ich nicht alle
Begrifflichkeiten für ein und dasselbe halte ;)
>> Ich konnte mit meinen Suchbegriffen nichts finden, was ich als Grundlage>> verwenden konnte, um damit von Grund auf einen Parser zu schreiben.> Dragon Book oder Drachenbuch googlen.
Danke, werd ich machen.
@MikeH:
Danke für die Idee. Ich sehe die Vorteile, aber der Nachteil ist m.E.
dass man auf der Gegenseite die Befehle "generieren" muss. Das wollte
ich eigentlich vermeiden, an zwei Ecken zu programmieren. Ich muss mir
das nochmal durch den Kopf gehen lassen, ob das nicht doch evtl. die
bessere Wahl ist.
Ralf
Ralf schrieb:> Kann das wirklich wirklich 64kB sprengen?
Das hängt von der Größe der Eingabe ab! Ein Quellcode mit tausenden
Zeilen wird da wohl nicht reinpassen, eine einzelne Befehlszeile mit
Max. 100 Syntax Elementen schon eher. Es wäre dabei schlau beim parsen
auf "out of memory " zu prüfen und dann sauber abzubrechen anstatt
abzustürzen ;-)
Schau vielleicht auch mal nach eLua das ist für sowas gemacht.
Ich finde es auf jeden Fall spannend so etwas selber zu implementieren
auch wenn das länger als nötig dauert, du musst selbst wissen wie viel
du selber machen willst. Man kann ja auch seinen eigenen Parser bauen,
dem man Grammatik + Eingabetext gibt und der den Syntax Baum
zurückgibt, ggf direkt ohne einen Parser-Code (in C) zu generieren. Die
Grundversion ist relativ simpel, aber das effizient und flexibel
hinzubekommen ist nicht so einfach ;-)
wie wäre es mit JSON? Es gibt einen sehr kompakten parser, JSMN :
http://zserge.com/jsmn.html
Ist zwar kein richtiger Kommonadozeileninterpreter, aber das lässt sich
dann gut durch ein Programm bedienen.
Ralf schrieb:> In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur> Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine> oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben.
Hier reicht es doch aus, dass ein festes Protokoll verwendet wird.
Beispielsweise ein Befehlswort aus einem oder mehreren Zeichen, gefolgt
von irgendwie getrennten Daten. Die Daten könnten auch binär übertragen
werden. Das macht es zwar einem Menschen schwerer, per Terminal
zuzugreifen, ist aber einfacher zu parsen (zB. Zahleneingaben).
Beispie:
1
D:100 Setze Verzögerung auf 100.
2
D Liefert die aktuell eingestellte Verzögerung zurück.
3
X:2,3,4 Befehl mit mehreren Parametern.
Hierzu ist kein flexibler Parser notwendig. Es müssen lediglich die
Parameter aufgeschlüsselt werden. Ist jeweils nur maximal ein Parameter
erlaubt, macht es die Sache noch einfacher.
Bison, JSON, Boost, TCL...und das auf einem uC? Im Ernst, Leute? Ich
schmeiss gleich noch .NET in die Runde.
Mein Credo, keep it simple. Eine shell, die keine mathematischen
Ausdrücke auflösen muss, braucht keinen dicken Parser, es reicht eine
einfache State-Machine mit weniger Zeilen, als ihr euch hier schon einen
Wolf geschrieben habt.
Als Anschauungsbeispiel (wenn zu primitiv, darf es gerne aufgebohrt
werden): http://section5.ch/dsp/blackfin/shell-1.1eval.tgz, siehe
shell.c
Wenn die Shell zum KOnfigurieren/Abfragen von Werten im Klartext dienen
soll: Die guten alten AT-Modem-Kommandokonzepte sind zwar grottig, aber
nicht totzukriegen. Einfacher ist nur noch eine simple
register-orientierte API.
Ralf schrieb:> In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur> Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine> oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben.
Dann reicht ein relativ simpler Parser, den du z.B. mit bison generieren
lassen kannst. In das action-Feld jeder Parser-Regel packst du einfach
Aufrufe der Funktionen, die die gewünschte Operation ausführen. Du
ersetzt einfach deine bisherige switch () case Orgie durch den Parser.
Ob du für den Lexer auf z.B. flex zurückgreifen mußt oder den von Hand
schreiben willst, kommt auf die Komplexität deiner "Sprache" an.
Die "Klassiker" der einfachen Kommandointerpreter nehmen einfach
die ersten N (z.B. 4) Zeichen und interpretieren sie als Zahl die dann
einfach in einem Switch landet. Siehe z.B. die ganzen ftpd.
Abkuerzungen waeren dann nur ein weiterer case in diesem Switch.
Fuer Trennzeichen, Zahlen und Stringargumente braeuchte Mann dann
noch weitere Funktionen die Pattern Matching und Evaluierung betreiben.
Ob sich fuer sowas ein *lex" lohnt?
Sowas wie:
> 1000> set_samplingrate> 24> set_res> 0> 20> 23> set_clock
koennte auch ein Forthinterpreter relativ direkt verwursten.
Ralf schrieb:> In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur> Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine> oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben.
Das würde mit meinem Konstrukt schon gehen. Bei meiner Lösung würde ich
im Parameterblock z.B. Folgendes schreiben:
setPort;4;5;OUTPUT;1 >> übersetzt: setze Port 4.5 als Ausgang und auf
HIGH. Du müßtest also dem Befehl "setPort" nur eine passende Funktion
zuordnen und dieser die restlichen Parameter übergeben. Den Rest von
meinem Ausgangskonstrukt kann man ja erst mal weglassen, wenn man keinen
Bus aufbauen will.
> Habt ihr Tipps für mich?
Nimm Flex. Ich hab es schon genau fuer das was du da machen willst
verwendet. Im Prinzip brauchst du hoechstens 1-2h um dir das Inputscript
fuer flex zu schreiben das dir den C-Source erzeugt welche dann deine
Funktionen mit den richtigen Parametern aufrufen. Beim erstenmal
brauchst du vermutlich 3h laenger um die Manpage zu lesen oder etwas zu
lernen.
Der Vorteil von flex kommt danach. Wann immer du deinen Interpreter
spaeter um ein neues Kommando oder ein vorhandenes Kommando um einen
Parameter erweitern willst dann ist das in 1min zu loesen.
Ich hab ausserdem noch ein kleines Programm im Sourcetree welches eines
Testumgebung fuer mein Betriebssystem erzeugt. Damit kann ich
Aenderungen am Flexinterpreter auf dem PC testen bevor es in den
Microcontroller geht.
Wie schon gesagt, die Nutzung ist eine wahre Freude! Und weil es so
schoen schnell und einfach geht neigt man dazu jede Menge kleine Befehle
in seinen Interpreter einzubauen um die Funktionalitaet des eigenen
Programmes und seiner Hardware sehr gut testen zu koennen. Dadurch
steigt die Codestabilitaet immens an. Bei mir laeuft das fertige
Programm dann auf einem ST32F103.
Olaf
Vor acht Jahren hatte ich die Gelegenheit eine Zeitlang in der Arbeit
mit einem FORTH System zu spielen welches auf einer Embedded Artist Dev
Board lief mit LPC2467 Prozessor drauf und TFT LCD und in C geschrieben
war. Es war sehr leicht neue Keywords ins Dictionary hinzuzufügen und
man konnte alles von der command line aus machen. Auch als RPN
Calculator konnte man es verwenden. Hat mächtig Spaß gemacht damit zu
arbeiten. Neue Commands konnten praktisch in Minuten geschrieben und
compiliert werden. Compiler war von IAR. Es war ein sehr beeindruckendes
System. Ein Bekannter von mir schrieb das Ganze von Start an. Sogar
320x240 TFT Graphikfunktionion hatten wir laufen. Für das Testen von
Hardware war FORTH ideal. Leider habe ich nicht mehr die Sourcen dafür.
Aber es war eine gute Lern Experience.
Eigentlich sollte man auch heutzutage noch FORTH nicht übersehen. Es
könnte für bestimmte Sachen immer noch ganz nützlich sein. Früher hatte
man damit angeblich sogar große Teleskope gesteuert.
Ich hab mir in solchen Situationen immer meinen Parser mit strtok(),
atoi(), strtol() und so weiter selbst zusammengeschustert. Erstes Wort
ist der Befehl, dann kommen die Argumente, reicht aus.
Die Hauptschleife testet die wichtigsten Befehle ("quit", "help") direkt
ab, wandert dann durch eine Tabelle { "befehl", "beschreibung",
funktionspointer }[NUM_COMMANDS] und ruft die entsprechende Funktion mit
dem Reststring auf. Diese Funktionen parsen ihre Argumente selbst und
geben einen Fehlercode zurück.
Reicht völlig hin, ist mit 20 Zeilen Code erledigt, und bis auf fehlende
History und mangelhaften Zeileneditor (nur Backspace) auch relativ
bequem.
Wichtig ist, dass eine überlange Zeile keinen Überlauf produziert.
Falls moeglich sollte man das Protokoll zustandsfrei schreiben. Denn was
geschieht, wenn ein Befehl unterwegs verloren geht ? Er wird wiederholt,
wie selten auch immer das geschieht. Es geschieht ...
zB
SetAusgang=high : wiederholung ist ok
ToggleAusgang : wiederholung ist wahrscheinlich nicht ok
IncrementPower : wiederholung ist nicht ok
Power=200 : wiederholung ist ok
Datenblock(200) : wiederholung ist ok
NextDatenblock : wiederholung ist nicht ok
Alles klar ?
Forth ist mal gar nicht so exotisch wie's klingt, wer kennt nicht Chuck
Moores Forth Prozessoren (HW). Die J1 und andere Derivate sind mal
richtig cool. Irgendwer hat halt mal Stackmaschinen als uncoole Hardware
definiert, so existieren sie vor allem in Software. Für einen Parser
dürfte bei einer SM allerdings der kompakteste Code bei rausschauen.
Ralf schrieb:> Tools wie Flex/Bison
Wenn, dann würde ich eher zu byacc als bison tendieren, schon der
embedded-freundlicheren Lizenz wegen.
Ich habe vor Jahren mal (aus Spaß) einen BASIC-Interpreter für einen
ATmega128 mit flex/byacc geschrieben. War nicht ganz leichtgewichtig,
funktionierte aber durchaus brauchbar. Sollte also auf einem M0 kein
Problem sein.
Du schleppst zwar bisschen mehr Overhead als bei einer handgefeilten
Lösung mit, aber dafür kannst du das Ding einfach als Werkzeug benutzen
und dich auf dein eigentliches Problem konzentrieren.
Möglich. Bison ist aber ohnehin m. W. aufgeblasener als byacc, schon
deshalb wäre es für einen schmalbrüstigen Controller nicht meine Wahl
gewesen.
(Natürlich musste ich, damit das auf einem AVR sinnvoll funktioniert,
sowohl byacc als auch flex patchen, damit sie Flash-Konstanten für die
vielen Tabellen erzeugen. Für den hier gewünschten Cortex-M sollte es
aber aus der Dose raus mit beiden funktionieren.)
Olaf schrieb:> Der Vorteil von flex kommt danach. Wann immer du deinen Interpreter> spaeter um ein neues Kommando oder ein vorhandenes Kommando um einen> Parameter erweitern willst dann ist das in 1min zu loesen.
In Boost.Spirit geht das sogar noch schneller - man passt die
Grammatikdefinition direkt im C++ Code an, und ruft den C++ Compiler neu
auf. Man braucht keine externen Tools und man kann problemlos beliebig
viele verschiedene Sprachen parsen ohne am Buildsystem rumbasteln zu
müssen. Einfache Sprachen (z.B. reguläre) kann man in 1 Zeile parsen
(inkl. Definition!).
Das ist das Schöne an einer Lösung, die auf Metaprogrammierung basiert.
Ein sehr einfaches Beispiel findet sich hier:
Beitrag "Re: Datenreihe neu Sortieren"
Alles ist kompakt in einer kleinen Sourcedatei, es musst nur eben Boost
installiert sein.
Was man prüfen müsste ist ob man dessen dynamische Speicherallokation
genug reduzieren kann damit es auf Mikrocontroller passt.
Dr. Sommer schrieb:> Alles ist kompakt in einer kleinen Sourcedatei, es musst nur eben Boost> installiert sein.>> Was man prüfen müsste ist ob man dessen dynamische Speicherallokation> genug reduzieren kann damit es auf Mikrocontroller passt.
Was höchstwahrscheinlich bedeutet, dass man ein simples Problem
erfolgreich durch ein höchst komplexes ersetzt hat ;)
> In Boost.Spirit geht das sogar noch schneller - man passt die> Grammatikdefinition direkt im C++ Code an, und ruft den C++ Compiler neu> auf.
Wieso sollte das jemand interessieren? Auf Mikrocontrollern wird zu 99%
C verwendet und flex liefert reinen C-Code der ohne Aenderung direkt
fuer einen Microcontroller compiliert und verwendbar ist. Auch flex ist
schon etwas Resourcenverschwendend was Flashverbrauch angeht, aber da
man ja heute praktisch immer 64-128k oder mehr hat ist das nicht mehr so
das Problem.
Olaf
Olaf schrieb:> Auf Mikrocontrollern wird zu 99% C verwendet
Interessante Aussage, wo ist deine Quelle? Das hat mich schon immer
interessiert. Viele behaupten, es sind 50% Matlab.
Jedenfalls kann man erwiesenermaßen sehr gut C++ auf Mikrocontrollern
nutzen, und es bei Bedarf auch mischen. C++ Code kompiliert auch auf
Mikrocontrollern ohne Änderung, man muss nur an den entscheidenden
Stellen die richtigen Container-Typen & Allokatoren auswählen. Daher
finde ich es durchaus interessant. Habe aber gerade keine Zeit es selbst
vernünftig auszuprobieren.
> Interessante Aussage, wo ist deine Quelle?
Mein Bauch.
> Viele behaupten, es sind 50% Matlab.
Nur bei den Ruebennasen frisch von der Uni. In der Praxis lernen die
dann entweder was richtiges oder machen auf Projektleiterassistent. :-)
> Jedenfalls kann man erwiesenermaßen sehr gut C++ auf Mikrocontrollern> nutzen, und es bei Bedarf auch mischen.
Theoretisch, aber die Schnittmenge aus guten Embeddedprogrammierern und
guten C++ Programmierern ist zu klein. Daher noch keine praxisrelevanz.
Ausserdem hat der OP gleich im ersten Posting zu erkennen gegeben das er
in C programmiert.
> Habe aber gerade keine Zeit es selbst vernünftig auszuprobieren.
Du bist faul und willst anderen deine Traeume aufzwingen?
Olaf
Olaf schrieb:> Mein Bauch.
Ah, die gekonnte Argumentationsart sich Zahlen auszudenken wie sie einem
passen.
Olaf schrieb:> Theoretisch, aber die Schnittmenge aus guten Embeddedprogrammierern und> guten C++ Programmierern ist zu klein.
Ich würde fast behaupten, dass gute Embedded Programmierer keine
größeren Probleme mit C++ haben sollten.
Olaf schrieb:> Daher noch keine praxisrelevanz.
C++ gibts ja erst seit 30 Jahren, dauert noch ein paar bis es von der
Industrie angenommen wird? Klingt eher nach Unwillen (was der Bauer
nicht kennt, ...).
Olaf schrieb:> Du bist faul und willst anderen deine Traeume aufzwingen?
Ich wollte lediglich auf eine Möglichkeit hinweisen. Der OP darf ja auch
selbst etwas tun. Du hast mit rumstänkern angefangen.
Hallo,
sorry für die späte Rückmeldung, vielen Dank für eure weiteren Beiträge.
Da ist ja ganz schön was zusammen gekommen =)
@Dr.Sommer:
Japp, sind ja jeweils nur einzelne Zeilen. Und out-of-memory versuche
ich natürlich im Auge zu behalten. Bin auch grad am schauen, ob man
Infos zum Heap oder "unbereinigte" malloc()-Aufrufe erkennen/abfragen
kann.
eLua geht nicht, das sprengt Flash & RAM, wenn ich die Infos auf der
eLua-Seite so anschaue.
@Johannes:
JSON schau ich mir an, danke.
@Brummbär:
Ja, für das aktuelle Projekt würde das wahrscheinlich auch reichen.
Parsen, Kommandozeile, etc. ist halt immer interessanter geworden, je
mehr ich mich damit beschäftigt habe =)
@Martin S.:
grins .NET geht leider auch nicht auf dem System (aber jucken würd's
mich schon, das mal auf einem µC zu probieren). Aber ich glaub da war
auch noch was mit Lizenzen, etc.
Das Beispiel schau ich mir an, danke.
@Axel:
Für lex & yacc hab ich mir ein Buch vom Kollegen geliehen, werd ich auch
anlesen.
@(º°):
Stimmt, auch eine KISS-Variante.
@Zeno:
Das geht schon in die (meine) richtige Richtung.
@Olaf:
Hört sich gut an. Werd mich dazu durch das o.e. Buch lesen.
@Gerhard:
Hab ein bisschen bzgl. FORTH nachgeschaut, scheint wirklich für den
Einsatz auf µCs gemacht zu sein (zumindest laut Wiki).
@S.R.:
Das war auch was, was mir durch den Kopf ging. Aber ist es nicht
umständlich, wenn die Funktionen selbst parsen müssen? Das bläht doch
immens auf, oder?
@Sapperlot W:
Ähm... nein, nicht ganz klar =) Was heisst zustandsfrei? Wenn ein Befehl
nicht korrekt empfangen wird, dann muss erkannt werden, dass ein neuer
Befehl beginnt anstatt diesen als Teil des "kaputten" Befehls zu
erkennen?
@Strubi:
Du meinst, weil in FORTH die Eingabe gleichzeitig der Befehl selbst ist?
@Jörg:
Wie bereits erwähnt, sobald ich verstanden habe, wie
Bison/YACC/BIYACC/FLEX/LEX etc. genau funktionieren, werde ich das mal
anprobieren.
@lazycoder:
Bitlash muss doch in Verbindung mit der Arduino-Umgebung laufen, oder?
Bei den restlichen Beiträgen ging es nicht konkret um mein Thema, daher
antworte ich darauf nicht. Trotzdem interessant zu lesen ;)
Nochmals danke an alle für die zahlreichen Antworten.
Ralf
Ralf schrieb:> Hab ein bisschen bzgl. FORTH nachgeschaut, scheint wirklich für den> Einsatz auf µCs gemacht zu sein
Sagen wir mal so: als es erfunden wurden ist, gab es noch gar keine
Mikrocontroller oder man hat sie zumindest noch nicht so genannt. :)
Aber für Mikrocomputer ist es in der Tat ausgelegt worden.
> Sagen wir mal so: als es erfunden wurden ist, gab es noch gar keine> Mikrocontroller oder man hat sie zumindest noch nicht so genannt. :)
Forth war interessant als es noch nicht heutige Entwicklungssysteme gab.
Als man also ein EPROM und etwas RAM am Microcontroller hatte und die
Alternative aus EPROM loeschen/brennen bestand. Ich hab mich damals ja
auch dafuer interessiert und st6forth geschrieben. Allerdings schon kein
klassisches Forth sondern ein Compiler.
Interessant waren die echten Forthinterpreter damals weil man in
Echtzeit entwickeln konnte. Allerdings heute mit den Moeglichkeiten
eines Debuggers und der Programmierschnittstelle auf der Zielhardware
sehe ich keine grosse Relevanz mehr. Die Vorteile sind gering geworden,
wenn zum Teil auch noch vorhanden. Dafuer sind aber die Nachteile extrem
gewachsen. Ein Forth-programm ist nicht gerade ein Muster an
selbsterklaerendem. Wenn man bedenkt was heutige Microcontroller an
Platz bieten und das es Programme gibt die den auch nutzen dann kommt
man sehr schnell an Grenzen wo man nicht mehr versteht was man selber
geschrieben hat. Und diese Grenze ueberschreitet man deutlich schneller
als bei C.
Die Sprache ist aber natuerlich ein schoenes intellektuelles Spielzeug.
So gesehen kann sich die Beschaeftigung doch wieder lohnen. :-)
Olaf
Dr. Sommer schrieb:> C++ gibts ja erst seit 30 Jahren, dauert noch ein paar bis es von der> Industrie angenommen wird?
Das ist doch kein Argument. Lisp gibt es seit 60 Jahren und es ist noch
nicht auf muc angekommen. Das Problem ist, das C++ fuer Systeme mit
Beschraenkungen bei der dynamischen Speicherverwaltung nur
eingeschraenkt verwendbar ist und seine Staerken nicht ausspielen kann.
Mit dem innovativen Entwicklungssystem "Arduino" ist C++ aber schon seit
10 Jahren millionenfach auf dem Mikrocontroller angekommen.
Nur bei manchem MC-Netz Nutzer geht's halt ein wenig länger.
Dumdi D. schrieb:> Das Problem ist, das C++ fuer Systeme mit Beschraenkungen bei der> dynamischen Speicherverwaltung nur eingeschraenkt verwendbar ist und> seine Staerken nicht ausspielen kann.
C++ hat noch viel mehr Stärken außer ein paar Containern in der Standard
Library, welche dynamische Speicherverwaltung brauchen. Das geht von
sowas einfachem wir Funktions Overloads bis zu Metaprogrammierung. Und
das kann man sehr wohl sinnvoll auf Mikrocontrollern nutzen. Der
Hauptgrund warum C da noch so dominant ist, ist wahrscheinlich dass
Embedded Entwicklung oft von E-Technikern gemacht wird und die nur C in
Studium lernen. Im Informatik Studium hatten wir jedenfalls "Embedded
C++".
Man kann sich natürlich auch einfach einen Tokenizer schreiben (oder
strtok nehmen) und dann mit einem endlichen Automaten arbeiten. So habe
ich schon AT-Parser geschrieben, als man µCs noch mit Dampf betrieben
hat.
Dr. Sommer schrieb:> Dumdi D. schrieb:>> Das Problem ist, das C++ fuer Systeme mit Beschraenkungen bei der>> dynamischen Speicherverwaltung nur eingeschraenkt verwendbar ist und>> seine Staerken nicht ausspielen kann.
...
> Der Hauptgrund warum C da noch so dominant ist, ist wahrscheinlich dass> Embedded Entwicklung oft von E-Technikern gemacht wird und die nur C in> Studium lernen. Im Informatik Studium hatten wir jedenfalls "Embedded> C++".
Wobei man auch berücksichtigen muß, daß vieles außer den "bösen
Containern" erst mit C++11 dazu kam und viele im Embeded-Bereich
kommerziell genutzte Toolchains schon damit Probleme haben (laut
Wikipedia-Übersichten der Compiler). Das Nichtvorhandensein der Tools
ist aber kein Problem der Sprache, sondern der Hersteller, aber auch der
Kunden, denn Hersteller bauen ungern, was die Kunden nicht nachfragen.
OSS ist da besser dran, da reicht es, wenn die Entwickler es wollen.
Ich muß aber zugeben, daß ich leicht reden hab und mir eben bei Bedarf
den aktuellsten GCC selber baue und notfalls auch nichtmal
GCC-offizielle Erweiterungen wie static_print auch mal selber
rein-patchen kann und vor allem darf.
Ralf schrieb:> Das war auch was, was mir durch den Kopf ging. Aber ist es nicht> umständlich, wenn die Funktionen selbst parsen müssen? Das bläht doch> immens auf, oder?
Schreibe mal einen kleinen Parser, der "read 0x1234" und "write 0x1234
300" mit den Standardfunktionen versteht. Das ist mit strtok() und
atoi() in wenigen Zeilen Code erledigt. Funktionsaufrufe sind billig.
Jede Funktion muss selbst wissen, welche Parameter sie hat. Dann kann
sie sich auch selbst darum kümmern, die rauszupopeln. In den meisten
Fällen ist das nicht schwer - siehe die Beispiele von oben.
In jedem Fall ist das schneller hingeschrieben und getestet als eine
Parsergrammatik für yacc oder so.
Nop schrieb:> Man kann sich natürlich auch einfach einen Tokenizer schreiben (oder> strtok nehmen) und dann mit einem endlichen Automaten arbeiten.
Genau das tun lex/yacc, antlr und andere Parsergeneratoren. Was die
rauswerfen, ist eine Statemachine bzw. wenn man Lexer und Parser trennt,
dann zwei. Der Vorteil ist hier, daß man die Grammatik in einer lesbaren
Form schreiben kann. Das vereinfacht Fehlersuche und Erweiterung schon
sehr.
Daß man ein paar Bytes mehr Flash braucht als mit der handgeschriebenen
Variante, ist mittlerweile egal. Die µC haben genug Resourcen. Und
Geschwindigkeit ist bei einem Parser für eine Kommandozeile nun wirklich
gar kein Thema.
Axel S. schrieb:> Der Vorteil ist hier, daß man die Grammatik in einer lesbaren Form> schreiben kann.
Der zweite Vorteil ist, dass man zwar ein bisschen initialen Aufwand
spendiert (sowohl bezüglich Arbeitszeit als auch memory footprint),
danach aber jegliche Erweiterungen saueinfach sind, sofern man nicht
gerade eine völlig verkorkste Grammatik verbockt hat. Die Codegröße
steigt dann auch mit den Erweiterungen nur noch sehr langsam, da die
generierten Tabellen ziemlich effizient sind. Dass den C-Code darin
kein Schwein lesen kann interessiert nicht, denn man muss ja nur den
Sourcecode auf lex/yacc-Ebene pflegen.