Hallo zusammen. Ich glaube, heute steht bei mir was riesiges auf der "Leitung". Eventuell könnt ihr mir ja mit einem Lösungsansatz helfen. Ich habe folgendes Problem: Ich habe eine Sammlung von Funktionen, welche per Interface (z.B. UART) vom Benutzer ausgeführt werden sollen. Die Funktionen haben dabei unterschiedliche Anzahl an Parametern. Mein erster Ansatz war nun die Funktionen via 2 Kommando-Bytes zu "selektieren" und nach diesen die Parameter mitzugeben. Problem ist nun, aufgrund der variablen Parameterinhalte kann ich die Kommandosequenz nicht mit einem fixen Byteinhalt "terminieren" da dieses in jeder der Parameter ungewollt enthalten sein könnte. Also war der näxhste Gedanke, ein Byte zu senden welches die Kommandosequenz einleitet und dann das Kommando+Parameter zu senden. Hier stellt sich nun das Problem, dass die Schnittstelle Wissen über die Anzahl der Parameter für die Kommandos haben müsste um die komplette Kommandosequenz entgegenzunehmen. Ich komme nach längerem Überlegen nicht wirklich auf eine sinnvolle Variante. Ich hoffe Ihr konntet wenigstens meinen Ausführungen soweit folgen und seit jetzt nicht gerade so verpeilt wie ich. Wie würdet ihr diese Aufgabe angehen ?
Komplettes Kommando mitsamt aller Parameter als komplette Zeile übertragen (also mit CR abschließen). Erst nach vollständigem Empfang der Zeile wird Dein Parser darauf angesetzt und zerpflückt sich die Zeile nach Gutdünken. Setzt natürlich voraus, daß ausreichend Speicher vorhanden ist, um eine "Zeile" einzulesen und zu speichern.
Ja, das würde für die UART sicher funktionieren. Da das System aber auch andere Schnittstellen bedienen können soll bräuchte ich leider etwas unabhängigeres.
Du kannst ja der "Schnittstelle" mitteilen, wieviele Parameter (Bytes) demnächst im Telegramm kommen z.B. als Vorspann vor den Funktionsauswahlbytes oder nach diesen und vor den Parameterbytes. Oder die Schnittstelle kann das anhand der Funktionsauswahlbytes und einer entsprechenden Information im Quellcode selbstständig feststellen, wieviele Parameter kommen müssen.
Den Gedanke die Sequenzlänge als zusäzliches Byte mitzugeben hatte ich auch schon. Ist ja ansich auch nichts gegen einzuwenden, nur für den Benutzer der Schnittstelle zusätzlicher Aufwand und potentielle Fehlerquelle. Im Moment bin ich gerade beim Letzten Vorschlag. Die Schnittstelle holt sich die noch benötigte Anzahl an Paramatern, entsprechend den empfagenen Kommandobytes, via Lookup vom Parser. Aber das bedeutet zusätzlichen Code- oder Datenoverhead für die Bereitstellung der Informationen. ..aber im Moment seh ich auch nicht wirklich einen andere Lösung. Also werd ich mal versuchen den Loopup Vorgang so effektiv wie möglich zu gestallten danke für Initiative und Eure Vorschläge :)))
Eine Statusmaschine macht das alles. Im header steht ein code fuer den befehl, und oder die Laenge. Von da an wird runtergzaehlt und dann das Command auseinandergenommen. Z
Ich hab das ähnlich gemacht: Die Kommandos werden mit CR, LF oder CR+LF abgeschlossen. Eine ISR holt die Zeichen einzeln ab und speichert sie in ein Array. In der ISR prüfe ich ab, ob CR oder LF gekommen ist. Wenn ja, also Kommando vollständig übertragen, wird ein Flag gesetzt und der komplette Befehl in ein anderes Array kopiert, das dann ausgewertet werden kann. In der main() gibt es dann eine Abfrage auf das "Kommando Empfangen Flag". Erst dann zerpflückt die main das Kommando und man kann über switch... if usw. die Befehle auswerten. Das funktioniert soweit ganz gut mit wenig Fehlern.
Gast wrote: > Problem ist nun, aufgrund der variablen Parameterinhalte kann ich die > Kommandosequenz nicht mit einem fixen Byteinhalt "terminieren" da dieses > in jeder der Parameter ungewollt enthalten sein könnte. Die Lösung nach ASCII-Standard: den Anfang des Pakets markiert man mit DLE STX, das Ende mit DLE ETX; wenn irgendwo im Datenteil DLE auftaucht wird es durch DLE DLE ersetzt. Der Empfänger muss nichts anderes machen als die Start- und Endemarkierungen zu erkennen und DLE DLE im Datenteil durch DLE zu ersetzen. DLE, STX, ETX sind ASCII-Steuerzeichen; du kannst stattdessen natürlich auch beliebige Bytes verwenden. Siehe z.B. http://www.tecchannel.de/netzwerk/grundlagen/402421/index5.html.
erstmal danke an ALLE für die vielen, tollen Vorschläge. @Andreas: toll, an diese Variante hatte ich noch gar nicht gedacht. Sie hat leider einen grossen Nachteil. Der (end)Benutzer der Schnittstelle(n) müsste Mechanismen zur Duplizierung der Markierungscodes innerhalb der Daten bereitstellen. D.h. würde mein Markierungscode z.B. $AA sein und ein Parameter(byte) zufällig den gleichen Wert haben, müsste der Benutzer diese in $AA $AA umwandeln. Nennt mich nörgelig, aber das verkompliziert die Schnittstelle auf Benutzerseite ganz erheblich :-) Falls es jemanden interessiert, möchte ich Euch auch meine derzeitige Lösung nicht vorenthalten. Da ich ohnehin einen Parser implementieren musste, ist der Schritt zum Abfragen der nötigen Parameterlänge für einen Befehl nicht mehr soo gross. PSEUDOCODE
1 | /* Struktur für einen Tabelleneintag in der Lookup-Tabelle ==> Befehl */
|
2 | struct CommandEntry |
3 | {
|
4 | char CB1; /* Kommandobyte 1 */ |
5 | char CB2; /* Kommandobyte 1 */ |
6 | char NumParam; /* Anzahl der benötigten Parameter */ |
7 | char (*function)( const char* ParamDat); /* Funktionszeiger auf die |
8 | zugehörige Funktion */
|
9 | }
|
10 | |
11 | /*
|
12 | die Tabelle (im FLASH) welche Befehlscodes, Parameterlänge und zugehörige
|
13 | Funktion definiert
|
14 | */
|
15 | struct CommandEntry CommandLookup[] = { |
16 | { 'S', 'A', 0, SystemFunktion_A }, /* Beispiel (S)ystembefehl A */ |
17 | { 'I', 'B', 5, IOFunktion_B }, /* Beispiel (I)O-Befehl B */ |
18 | /*
|
19 | hier weitere Befehlsdefinitionen
|
20 | */
|
21 | { 0x00, 0x00, 0x00, NULL } /* terminator Entry - do not remove */ |
22 | };
|
23 | |
24 | /*
|
25 | Die eigentlichen Funktionen übernehmen lediglich einen Zeiger auf den
|
26 | Begin der Parameterdaten und pop'en ( Öhhmm, jaja ;-) )sich diese vom
|
27 | übergebenen Eingabepuffer
|
28 | */
|
29 | char SystemFunktion_A( const char* ParamDat ) |
30 | {
|
31 | /* Parameter pop'en */
|
32 | /* Funktionscode ausführen ..*/
|
33 | /* ..und Ergebniss zurückliefern */
|
34 | }
|
dazu gibt es noch eine simple Parser-Funktion welche anhand übergebener Kommandobytes einen Eintrag vom Typ "CommandEntry" aus der Tabelle zurückliefert. Sowohl die Eingabeschnittstelle als auch die eigentliche Parserfunktion können diese aufrufen um die benötigten Informationen des jeweiligen Befehl zu erhalten. für Verbesserungen, Anmerkungen und Kritik bin ich jederzeit offen .. PS: super Forum, man bekommt Anregungen und Hilfe - find ich toll
Hi, kannst Du mal den REst vom Code posten? Würde mich interessieren wie Du die Abfrage, um welchen Befehl es sich handelt, den Uart Empfang usw. realisiert hast.
Hallo ..hatte bisher wenig zeit :( Einen ReadyToRun-Code kann ich leider nicht liefern. Es würde Verständniss und Übersicht nicht dienen, selbst wenn ich meinen kompletten Code posten würde. Daher lediglich vom Errorchecking bereinigte und weitestgehend vom Kontext gelösete (Pseudo)Funktionen ..sorry die "Parser"-Funktionen sind eigentlich sehr einfach..
1 | /*
|
2 | die Lookup-Funktion des "Parsers", welche entsprechend den Kommandocodes,
|
3 | den passenden Eintrag aus der Tabelle liefert
|
4 | vermutlich häufig aufgerufenen Befehle stehen in der Tabelle am besten
|
5 | weiter oben
|
6 | */
|
7 | struct CommandEntry* Parser_Lookup( const sl_union16* CBytes ) |
8 | {
|
9 | struct CommandEntry* Entry = (struct CommandEntry*)CommandLookup; |
10 | |
11 | /* durch alle Tabelleneinträge gehen bis das Ende (Dummy) erreicht ist..*/
|
12 | while ( Entry->CB1 != 0x00 ) |
13 | {
|
14 | /* und nach den übergebenen Kommandobytes suchen */
|
15 | /* wenn gefunden, Funktion verlassen und einen Zeiger auf den
|
16 | entsprechenen Eintrag zurückliefern */
|
17 | if ((Entry->CB1 == CBytes->int8.hi) && (Entry->CB2 == CBytes->int8.lo)) |
18 | return Entry; |
19 | |
20 | /* ab zum nächsten Tabelleneintrag */
|
21 | Entry++; |
22 | }
|
23 | |
24 | return NULL; /* nix gefunden, NULL-Pointer zurückgeben */ |
25 | }
|
26 | |
27 | /*
|
28 | Die eigentliche Parserfunktion (eigentlich ist es ja gar kein Parser
|
29 | mehr), welche auch die entsprechende Funktion ausführt
|
30 | */
|
31 | SL_UINT8 Parser_Run( const sl_union16* CBytes ) |
32 | {
|
33 | struct CommandEntry* Entry; |
34 | |
35 | /* finde den Eintrag für die übergebenen Kommando-Bytes */
|
36 | Entry = Parser_Lookup( CBytes ); |
37 | |
38 | /* wenn gefunden.. */
|
39 | if ( NULL != Entry ) |
40 | /* rufe die zugehörige Funktion auf */
|
41 | return Entry->function( Command.named.CA ); |
42 | /* Command.named.CA ist ein Zeiger auf den Begin der empfangenen
|
43 | Parameterdaten */
|
44 | |
45 | /* der Befehl konnte via Lookup nicht gefunden werden, also FEHLER */
|
46 | return RESULT_INVALIDCOMMAND; |
47 | }
|
Die Schnittstellenfunktionen sind so ausgelegt, dass sie einfach ein empfangenes Byte einer beliebigen Schnittstelle entgegen nehmen und im Input-Puffer ablegen (schnelle kurze Funktion, da der typische call aus einer ISR erfolgt). Eine zweite, zyklisch aufgerufene, Schnittstellenfunktion verarbeitet dann die neu angekommenen Daten und reicht kommplett empfangene Befehlssequenzen einfach an eine Ausführungskette weiter. Man könnte auch direkt Parser_Run() aufrufen. Der Empfangspuffer besteht dabei aus einem Ringpuffer auf welchen zwei Zeiger verweisen. Ein "Empfangszeiger" zeigt auf die aktuelle Position des letzen empfangenen Bytes und ein "Verarbeitungszeiger" auf die aktuelle Position der Verarbeitung durch die zyklische Funktion. Also ganz simpel :-) Ist sicher auch nicht für jeden Anwendungsfall das System der Wahl, aber es erfüllt ganz gut meine konkreten Anforderungen mit möglichst wenig Aufwand. LG
Hi Gast, danke! Was mich interessieren würde, wie sieht Deine ISR aus? Ich hab nämlich folgendes Problem: Mein Code ist ähnlich wie der von Dir aufgebaut. Meine ISR holt ein einzelnes Zeichen ab, speichert es im Puffer. Kommt ein "CR" interpretiert die ISR das als Befehlsende, der Puffer wird kopiert und anschließend geleert damit er für den weiteren Empfang wieder bereit ist. Jetzt ist das Problem, dass manchmal Zeichen nicht übertragen werden, also verlorengehen. Kann es sein, dass meine ISR zu lange ist? Somit ein paar Zeichen verschluckt werden? Wie hast Du das gemacht? was ist übrigens SL_UINT8? und sl_union16?
Hallo @Mister: Ohne deinen Code zu kennen, vermute ich jetzt mal das Dein ISR Code zu lang braucht. Das führt bei schnellen Transferraten zu den von Dir geschilderten Problemen. Das ist auch der Grund, warum ich die eigentliche Auswertung der Daten in einer zweiten, zyklisch aufgerufenen, Funktion realisiere. Das setzt allerdings einen gewissen Puffer zum Halten der Daten voraus. Meine Funktion welche die Daten von der Schnittstelle entgegennimmt ist daher sehr kurz. Um genau zu sein, ist es sogar nur ein Makro um den Call-Overhead zu sparen..
1 | /* ..legt das empfangene Byte auf der aktuellen Position im Puffer ab und
|
2 | erhöht den Pufferzeiger. */
|
3 | #define IReciever_PushByte( data )(*IR_RecievePtr++ = data;)
|
was die Definition bzw. union betrifft..
1 | #define SL_UINT8 unsigned char /* unsigned 8 Bit */ |
2 | |
3 | typedef union /* 8/16 bit IO conversion union */ |
4 | {
|
5 | SL_UINT16 uint16; /* unsigned 16-bit int */ |
6 | SL_SINT16 sint16; /* signed 16-bit int */ |
7 | struct
|
8 | {
|
9 | SL_UINT8 lo; /* LSB byte */ |
10 | SL_UINT8 hi; /* MSB byte */ |
11 | }int8; |
12 | } sl_union16; |
13 | |
14 | typedef union /* 8/16/32 bit IO conversion union */ |
15 | {
|
16 | SL_UINT32 uint32; |
17 | SL_SINT32 sint32; |
18 | struct
|
19 | {
|
20 | SL_UINT16 lo; /* LSB word */ |
21 | SL_UINT16 hi; /* MSB word */ |
22 | }int16; |
23 | struct
|
24 | {
|
25 | SL_UINT8 b0; /* LSB byte */ |
26 | SL_UINT8 b1; |
27 | SL_UINT8 b2; |
28 | SL_UINT8 b3; /* MSB byte (exp) */ |
29 | }int8; |
30 | } sl_union32; |
Die Redefinitionen nutzt das System um auf verschiedenen Zielplattformen den gleichen Code, unter gleichen Voraussetzungen, kompilieren zu können. Dazu werden einfach nur die Datentypen auf die des Zielcompilers redefiniert. Die Union's sparen Bitschieberei beim Zugriff auf z.B. Lo- und Hibytes. hoffe geholfen zu haben :-) LG
Gast wrote: [snip] > Die Redefinitionen nutzt das System um auf verschiedenen Zielplattformen > den gleichen Code, unter gleichen Voraussetzungen, kompilieren zu > können. > Dazu werden einfach nur die Datentypen auf die des Zielcompilers > redefiniert. Da fragt man sich doch, warum es immer wieder Compilerbauer und/oder Bibliothekenersteller gibt, die diese dämlichen Headerfiles wie 'stdint.h' und 'inttypes.h' Portieren. Völlig überflüssig, denn man kann's ja auch selber machen. SCNR
Hi Patrick :) Generell hast Du da durchaus Recht. Ich wollte damit auch nur auf Nummer sicher gehen. Mal eben 6 Datentypen zu portieren ist mir lieber als mich auf Richtigkeit und Vorhandensein von 'stdint.h' zu verlassen. Ich kenne Compiler, bei welchen durchaus keine 'stdint.h' oder entsprechende Definitionen zu finden sind. Zugegebenermaßen ist dies hoffentlich eher die Ausmahme. LG
Ich mache das auch so wie Andreas, daß ich eine bestimmte Kombination als Kommandoende benutze. Das hat den Grund, daß ja Daten grundsätzlich gestört sein können (fehlende Bytes, falsche Bytes). Wenn ich dann schon zwischendurch parse, kann ja sonstwas passieren. Außerdem werden die einzelnen Routinen schön übersichtlich: Die Empfangsroutine sammelt nur die Bytes ein, prüft die maximale Länge und das Endekennzeichen. Eventuell prüft jetzt noch ne CRC-Routine, ob das Kommando fehlerfrei ist. Dann erst kann die Parse-Routine loslegen, während die Empfangsroutine schon das nächste Kommando einlesen kann. Wichtig ist für mich, daß bei einer Störung nicht alles austillt, sondern schnell wieder die Synchronität hergestellt wird. D.h. das fehlerhafte Kommando wird verworfen und das nächste aber wieder richtig ausgeführt. Wenn es aber kein eindeutiges Endekennzeichen gibt, weiß man ja nicht, wo nun das nächste Kommando beginnt. Es gibt leider viele nicht durchdachte Protokolle, die, einmal ausgerastet, nur durch den Netzhauptschalter wieder zum Leben erweckt werden können. Peter
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.