Hallo, ich bin mal wieder auf ein bisschen Brainstorming angewiesen. Ich möchte ein serielles Protokoll auswerten. Dabei werden normale Zeichen als Daten gesendet und Kommandos durch ein Steuerzeichen eingeleitet. Bei den Kommandos gibt es keine Endeerkennung und die Länge ist variabel. Die Originalsoftware lief auf einem 8051er-Derivat und hatte nichts anderes zu tun. Dabei wurden ständig Zeichen gepollt. Dann wurde nachgeschaut, ob schon die richtige Anzahl an Bytes für dieses Kommando empfangen wurden. In meiner Applikation soll die Kommandoauswertung nur alle 10ms erfolgen, da noch anderer Code auszuführen ist und ich auch einen richtigen Zeitbezug brauche. In diesen 10ms können maximal 19 Bytes eintreffen. Jetzt war mein Gedanke, immer alle Bytes zu nehmen und in einen Puffer zu schaufeln und alle 10ms die Auswertung zu machen. Reicht die Anzahl noch nicht, tu ich nichts. Habe ich die richtige Anzahl oder zuviel, werte ich aus. Wenn die Anzahl stimmt ist alles ok. Dann könnte ich nach der Auswertung den Zeiger auf den Anfang des Puffers setzen und das Spiel geht von vorne los. Habe ich aber zu viele Zeichen, müßte ich beim weiteren Füllen wieder am Ende weitermachen bis zum Ende und dann den Puffer wieder von vorne füllen (Ringpuffer). Das ist erst mal kein Problem. Nur bei der nächsten Dekodierung eines Kommandos muss ich auch den Wrap Arround des Puffers berücksichtigen. Das erscheint mir doch recht umständlich. Habt Ihr dazu noch eine Idee? Gruß Matthias
Wie wäre es, wenn du die Geschichte einfach umdrehst? Ich meine, die Abfrage der Schnittstelle geschiet weiter über polling und die Ausführung deines anderen Codes über einen Timer alle 10ms.
Hallo MC, Dein Vorschlag würde nur klappen, wenn der andere Code im Interrupt laufen würde. Da es aber nicht um ein paar Zeilen Code sondern um eine komplizierte Statemachine geht, die selbst wieder Timer anstößt muß ich das ausschließen. Mein Problem liegt mehr in der Pufferverwaltung und Dekodierung. Ich versuche mal ein einfaches Beispiel: Die Pufferlänge sei 6. Das erste Kommando sei 5C 12 13 und das zweite 5C 15 16 17. Davon ist bis zum Zeitpunkt x folgendes eingetroffen: 5C 12 13 5C 15 16. Bei der Dekodierung fange ich also bei Zeichen Null an. Für das zweite Kommando sehe es zum Zeitpunkt y so aus: 17 18 19 5C 15 16. Jetzt muß ich bei Zeichen 3 anfangen zu dekodieren und den Wrap berücksichtigen. Man kann sich vorstellen, dass das ziemlich aufwendig ist, wenn die Kommandolänge max 16 sein kann und ich bei jedem zu dekodierenden Zeichen prüfen muss, ob ich schon am Ende des Puffers bin und wo das Ende des Kommandos im Puffer ist. Gruß Matthias
1 | MSG_DATA g_Msg; //keep interrupt stack small |
2 | extern IsComplete(MSG_DATA * pMsg); |
3 | |
4 | void AddToMsg(MSG_DATA *pMsg, char c) |
5 | {
|
6 | //füge c zu pMsg hinzu
|
7 | //je nachdem wie die Datenstruktur befüllt werden muss...
|
8 | |
9 | if ( IsComplete(pMsg) ) |
10 | {
|
11 | RingBufAddMsg(pMsg, pMsg->lengthInBytes); |
12 | }
|
13 | }
|
14 | |
15 | void ISR(void) |
16 | {
|
17 | while (charactersInHardwareFifo) |
18 | {
|
19 | char c = ReadChar(); |
20 | AddToMsg(&g_Msg, c); |
21 | }
|
22 | }
|
23 | |
24 | void main(void) |
25 | {
|
26 | MSG_DATA *pMsg; |
27 | UINT len; |
28 | |
29 | while (true) |
30 | {
|
31 | if ( RingBufGetMsg(&pMsg, &len) ) |
32 | {
|
33 | Dispatch(pMsg); |
34 | }
|
35 | }
|
36 | }
|
5 Minuten... Was is hier schwierig?
ansonsten nimm doch die 128oberen Bytes des internen RAM über @r0 und @r1. @r0 nimmst du z.B. um einkommende Daten auf das Ende des Speicherstapels zu legen. @r1 nimmst du, um vom Anfang des Stapels die Daten zu holen. Wenn du ein Kommando von z.B. drei Byte fertig aus dem Speicher geholt und verarbeitet hast, kannst du alle Daten im Speicher nach unten aufrücken lassen, sodass die schon ausgewerteten Daten überschrieben werden. Das könntest du allein über @r1 realisieren, sodass für einkommende Daten @r0 noch zur Verfügung steht. Erst wenn der gesammte Stapel nachgerückt ist, lässt du auch @r0 nachrücken. Hoffe, meine Idee ist verständlich ausgedrückt.
Also wenn ein Kommando maximal 16 Bytes lang sein kann, dann nimmst Du eben einen Ringpuffer mit 32 Bytes. Und dann ist doch alles einfach. Du verarbeitest ein Kommando erst, wenn es komplett im Ringpuffer steht. Wenn es nicht komplett ist, dann versuchst Du es beim nächsten mal. Ich versteh das Problem nicht...sorry...
Nimm keinen Ringpuffer sondern einen Linearpuffer. Der fängt immer beim ersten Byte an, läßt sich also easy parsen. Der serielle Interrupt füllt dann einfach diesen Puffer aufsteigend. Ab und zu guckst Du nach, ob schon ein vollständiger Record empfangen wurde und wertest ihn aus. Und nach der Auswertung kopierst Du alle hinter dem Record empfangenen Zeichen wieder an den Anfang. D.h. der nächste Record kann wieder vom Anfang an geparst werden. Wichtig ist beim Umkopieren, daß die Interrupts gesperrt werden. Sonst denkts Du, Du hast alle Zeichen kopiert, aber der Interrupt pappt Dir noch ein Zeichen hintendran, bevor Du ihm den neuen Pointer gesetzt hast. Peter
@Simon Mein Problem ist nicht einen Ringpuffer zu füllen und zu lesen. Mein Problem ist die variable Länge und die Dekodierung aus einer beliebigen Position des Puffers heraus zu machen, wo ich ja bei jedem Byte prüfen muss, ob das Ende erreicht ist. @Heino Bei Deinem Code mußt Du mir mal auf die Sprünge helfen. Zur Erinnerung: Die Länge des Kommandos ist variabel und ergibt sich aus dem Zeichen nach dem Steuerzeichen. Es können in beliebiger Folge Kommandos oder Daten nacheinander kommen. Gruß Matthias
Hallo Peter, das klingt vernünftig! Das ist ja wirklich eine einfache Lösung. Zwischenzeitlich wäre ich noch auf die Idee gekommen, den Ringpuffer in einen Linearpuffer umzukopieren, die restlichen Bytes nach der Dekodierung an den Anfang eines zweiten zu schreiben und dann einen Pointer umzuschalten. Aber Deine Lösung ist wirklich genial einfach! Besten Dank!!! Gruß Matthias
@Peter Dannegger bei deiner Lösung ist die Interruptabarbeitung nicht deterministisch. Sie wird u.U. sehr lang, wenn ein Record ausgewertet wird. Der Code kann bei Erweiterungen (ggf. durch nachfolgende neue Entwickler) unkalkulierbar reagieren. Konkret: Aufgrund des Designs der ISR können Zeichen verloren gehen, weil sie einfach nicht schnell genug fertig wird. @Matthias Kölling Es gibt ein globales Messageobjekt g_Msg, das so groß ist, dass es die max. vorkommende Kommandolänge enthalten kann. Das Msg-Objekt wird im Interrupt befüllt. Wenn es komplett ist, wird es in den Message-Ringbuffer gestellt. In main wird geprüft, ob eine Message im Ringbuffer ist, wenn ja, wird sie geeignet weiterverarbeitet. Die Ringbuffer-Implementierung kann verschieden lange Objekte aufnehmen, daher der Parameter len bei RingBufGet/RingbufAdd. Du könntest auch einen Ringbuffer machen, der nur Bytes aufnehmen kann, befüllst ihn im Interrupt und verlegst das Befüllen des Msg-Objektes in die Main Schleife - oder sonstwo hin, wo du oft genug vorbeikommst. PS: Meine Empfehlung ist, einen Ringbuffer zu nehmen, auch wenns dir für das aktuelle Problem etwas over-sized vorkommt. Die Implementierung kannst du später immer wieder mal gebrauchen und es ist (auch für dein jetziges Problem) die beste Design-Entscheidung aus meiner Sicht.
@Heino Du hast Peter wahrscheinlich falsch verstanden. Die Auswertung findet nicht im UART Interrupt statt. Wenn ich die Lösung von Peter etwas erweitere und einen Ringpuffer für die UART benutze und alle 10ms die empfangenen Zeichen in einen Linearpuffer kopiere, muß ich keine Interrupts sperren. Außerdem habe ich dann eine saubere Trennung zwischen den Schichten. Gruß Matthias
Hallo, nach vielen Irrungen ud Wirrungen läuft die Sache jetzt prima: Die UART füllt im Interrupt einen Ringpuffer. Alle 10ms werden die zuletzt empfangenen Zeichen in einen Linearpuffer kopiert. Nach jeder abgeschlossenen Aktion (Zeichenkette lesen oder Kommando auswerten) werden die verbleibenden Zeichen an den Anfang des Linearpuffers kopiert. Steht als letztes ein Kommando drin und reicht die Länge nicht aus, wird beim nächsten Mal nach dem letzten Zeichen im Linearpuffer gefüllt und vom Anfang des Puffers geparst. Vielen Dank für Eure Ideen! Gruß Matthias
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.