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
structCommandEntry
3
{
4
charCB1;/* Kommandobyte 1 */
5
charCB2;/* Kommandobyte 1 */
6
charNumParam;/* Anzahl der benötigten Parameter */
7
char(*function)(constchar*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
structCommandEntryCommandLookup[]={
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
charSystemFunktion_A(constchar*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
Die eigentliche Parserfunktion (eigentlich ist es ja gar kein Parser
29
mehr), welche auch die entsprechende Funktion ausführt
30
*/
31
SL_UINT8Parser_Run(constsl_union16*CBytes)
32
{
33
structCommandEntry*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
returnEntry->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
returnRESULT_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
typedefunion/* 8/16 bit IO conversion union */
4
{
5
SL_UINT16uint16;/* unsigned 16-bit int */
6
SL_SINT16sint16;/* signed 16-bit int */
7
struct
8
{
9
SL_UINT8lo;/* LSB byte */
10
SL_UINT8hi;/* MSB byte */
11
}int8;
12
}sl_union16;
13
14
typedefunion/* 8/16/32 bit IO conversion union */
15
{
16
SL_UINT32uint32;
17
SL_SINT32sint32;
18
struct
19
{
20
SL_UINT16lo;/* LSB word */
21
SL_UINT16hi;/* MSB word */
22
}int16;
23
struct
24
{
25
SL_UINT8b0;/* LSB byte */
26
SL_UINT8b1;
27
SL_UINT8b2;
28
SL_UINT8b3;/* 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