Forum: Mikrocontroller und Digitale Elektronik Serielles Protokoll


von Matthias Kölling (Gast)


Lesenswert?

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

von MC (Gast)


Lesenswert?

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.

von Simon (Gast)


Lesenswert?

Was ist an einem Ringpuffer umständlich und schwierig?

von Matthias Kölling (Gast)


Lesenswert?

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

von Heino (Gast)


Lesenswert?

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?

von MC (Gast)


Lesenswert?

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.

von Simon (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Matthias Kölling (Gast)


Lesenswert?

@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

von Matthias Kölling (Gast)


Lesenswert?

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

von Heino (Gast)


Lesenswert?

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

von Matthias Kölling (Gast)


Lesenswert?

@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

von Matthias Kölling (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.