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
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