Forum: Mikrocontroller und Digitale Elektronik "richtiges" implementieren eines einfachen Übertragungsprotokolls


von Julian E. (Gast)


Lesenswert?

Hallo zusammen,

ich steh hier vor einem ganz großen Problem und würde mich hierbei um 
eure Hilfe freuen.
Es geht darum eine Datenkommunikation zwischen PC (serielle 
Schnittstelle) und µC ( ATMega8515 ) aufzubauen. Erst einmal nur zu 
Lernzwecken ein single Master Protokoll: 
<SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme>
Dabei habe ich erst einmal damit anfangen am PC ein kleines Datenpacket 
zu schnüren und dieses dann an den µC zu senden. PC-Seitig klappt das 
auch. Mein Problem ist jetzt allerdings der Empfang am/im µC.

Es würde mich freuen, wenn ihr mir euer Vorgehen einmal schildern 
würdet, denn fertige Implementierungen, die man als Anfänger auch 
verstehen kann habe ich bisher nicht gefunden.
Ich dachte mir das folgendermaßen, fühle mich dabei aber wie wenn ich 
auf dem Holzweg wäre:
Ich erzeuge ein globales Array, in das ich nach und nach die per UART 
reinkommenden Daten im UART-RX-Interrupt schreibe. Wird eine längere 
Zeit nichts empfangen, wird das Array wieder vom ersten Element weg 
beschrieben.
Wenn eine Packet angekommen ist wird ein Flag gesetzt und in der 
main-Routine wird das Array dann "ausgewertet", d.h. checksumme prüfen 
etc.
Leider entdecke ich jetzt schon einige Probleme: als erstes das timeout. 
Wie erkenne ich wie lange keine Daten mehr eingetroffen sind?
Oder was mache ich, damit mir das Array nicht überschrieben wird wenn 
noch nicht alle Daten verarbeitet wurden?

Wäre schön wenn mir grad jemand mit seiner Idee auf die Sprünge helfen 
würde.

Vielen vielen Dank
Julian

von Karl H. (kbuchegg)


Lesenswert?

Julian E. schrieb:

> Lernzwecken ein single Master Protokoll:
> <SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme>

Du hast einen der wichtigsten Punkt vergessen:

Du brauchst entweder vorne oder hinten (oder an beiden Enden) eine 
EINDEUTIGE Kennung, die dir die Synchronisierung auf den Anfang eines 
Datenpaketes ermöglicht.

Das übersehen Neulinge gerne. Neben den Nutzdaten benötigt man für ein 
stabiles Protokoll auch immer ein gewisses Mass an Verwaltungsinfo, 
welche nur dazu dient die Übertragung selbst zu steuern.

> Es würde mich freuen, wenn ihr mir euer Vorgehen einmal schildern
> würdet, denn fertige Implementierungen, die man als Anfänger auch
> verstehen kann habe ich bisher nicht gefunden.
> Ich dachte mir das folgendermaßen, fühle mich dabei aber wie wenn ich
> auf dem Holzweg wäre:
> Ich erzeuge ein globales Array, in das ich nach und nach die per UART
> reinkommenden Daten im UART-RX-Interrupt schreibe. Wird eine längere
> Zeit nichts empfangen, wird das Array wieder vom ersten Element weg
> beschrieben.

Kann man so machen, aber Timeouts zur Synchronisierungssteuerung sind 
meistens keine gute Idee.

> Wenn eine Packet angekommen ist wird ein Flag gesetzt und in der
> main-Routine wird das Array dann "ausgewertet", d.h. checksumme prüfen
> etc.
> Leider entdecke ich jetzt schon einige Probleme: als erstes das timeout.
> Wie erkenne ich wie lange keine Daten mehr eingetroffen sind?

Timer mitlaufen lassen.

Aber mit einer dezidierten Framesteuerung in Form eines eindeutigen 
Frameanfangs ist das normalerweise nicht notwendig. Ungültige Frames 
können da normalerweise sauber identifiziert werden.

> Oder was mache ich, damit mir das Array nicht überschrieben wird wenn
> noch nicht alle Daten verarbeitet wurden?

Das Array muss groß genug sein, damit das längste mögliche Paket da auch 
tatsächlich reinpasst.

von Anja (Gast)


Lesenswert?

Julian E. schrieb:
> Ich erzeuge ein globales Array, in das ich nach und nach die per UART
> reinkommenden Daten im UART-RX-Interrupt schreibe. Wird eine längere
> Zeit nichts empfangen, wird das Array wieder vom ersten Element weg
> beschrieben.

Ich würde das globale Array als "Ringpuffer" organisieren. Ein Löschen 
ist dann "LeseIndex = SchreibIndex;"

Julian E. schrieb:
> Oder was mache ich, damit mir das Array nicht überschrieben wird wenn
> noch nicht alle Daten verarbeitet wurden?

Der Punkt erledigt sich von selbst wenn der Ringpuffer groß genug ist. 
Der Puffer muß dann um soviele empfangene Bytes größer sein wie Zeit 
benötigt wird um die alte Botschaft zu verarbeiten.

Gruß Anja

von Christian T. (Gast)


Lesenswert?

Also ich muss sagen, ich mach es mir da etwas einfacher, und hatte damit 
noch keine Probleme...

Checksumme usw... muss nicht sein, kommt halt darauf an wie fehlersicher 
deine Übertragung sein soll...

Ich nutze immer einen Frame der folgendermaßen aufgebaut ist:

S01!

S = Frame anfang
01 = Daten / Kommando...
! = bezeichnet das ende des Frames


Bei der übertragung von PC -> µC  prüfe ich per Polling das RS232 
Empfangsflag, wird was empfangen gehe ich in die RS232 Empfangsroutine 
die das empfangene Zeichen auswertet ist es ein "S" wird weiterhin immer 
das Empfangsflag geprüft, wird was empfangen kommt die Prüfung nach dem 
Kommando
und danach könnten weitere Daten / Kommandos folgen oder dann das "!" 
was das ende des Strings kennzeichnet.

Aber, viele Wege führen nach Rom...

LG. Christian

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Julian E. schrieb:
> Erst einmal nur zu Lernzwecken ein single Master Protokoll:
> <SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme>
Wozu braucht ein Single Master System eine SeinderID?

> Es geht darum eine Datenkommunikation zwischen PC (serielle
> Schnittstelle) und µC ( ATMega8515 ) aufzubauen.
Wenn du eine "normale" serielle Schnitte hast, dann ist sogar die 
Empfänger-ID ziemlich eindeutig...

> <SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme>
Wie willst du die <Checksumme> verpacken?
Binär oder als ASCII-Zahl?

> <SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme>
Dann also eher so:
<STX><SenderID><EmpfängerID><Länge><Nutzdaten><Checksumme><CR>(oder 
<ETX>)
Und die Daten am besten als ASCII-Zeichenkette. Also nicht 0x22 = 34 
verschicken, sondern '3','4'...

Christian T. schrieb:
> und danach könnten weitere Daten / Kommandos folgen oder dann das "!"
> was das ende des Strings kennzeichnet.
Es gibt tolle Zeichen, die optimal für sowas geeignet sind. Ein 
Carriage-Return ist so eines. Wenn ich mich dann zum Debuggen in eine 
serielle Verbindung reinhänge, bekomme ich am Ende eines Datenpakets 
automatisch und umsonst eine neue Zeile...

von Chris (Gast)


Lesenswert?

Der Empfänger-ID kommt zuerst, da ein uC, welcher den Empfänger mittels 
Interrupt macht ev. dann für einen definierten Timeout nicht hinhören 
will
oder einfach besseres zu tun hat. Sollte es möglich sein, die Adressen 
so vergeben, daß eine Parity damit gegeben ist und so eine 9bit 
Übertragung mit
einem PC kompatibel ist.

von Arne (Gast)


Lesenswert?

Hi,
wenn schon checksumme, wäre zu überlegen, ob du den
Header auch Checksummen-sicherst, ansonsten kannst du nicht sicher
sein, ob das was im Header steht korrekt ist.

Ein Start- und Stop Signal habe ich bisher noch nicht gebraucht.
In solchen Fällen greife ich zu einem finiten Automaten:

Zustand 1:
Idle (warte auf Daten)

Zustand 2:
Header empfangen

Zustand 3:
Daten empfangen

Übergänge:
1-->2 erstes Header-Byte empfangen
2-->2 weitere Header-Bytes empfangen, solange Header nicht komplett
2-->3 wenn Header Checksumme ok
3-->3 Daten-Bytes empfangen, solange noch welche fehlen
3-->1 letztes Datenbyte empfangen

2-->1 Timeout (länger als 2s nichts empfangen)
2-->1 Checksumme Header falsch
3-->1 Timeout (länger als 2s nichts empfangen)
3-->1 Checksumme Datenblock falsch

Wie lang der Header ist, weiß ich ja. Wenn ich also einen Header 
empfangen habe, steht da drin, wieviele Daten noch kommen müssen. Mit 
diesen beiden Informationen betreibe ich den Automaten.

Den Header würde ich in eine Stack-Variable schreiben, wenn ich dann 
weiß,
wieviele Nutzdaten kommen (am Übergang 2->3) würde ich bspw. 
Heap-Speicher allokieren und den Header reinkopieren und die Nutzdaten 
dahinter schreiben. Wenn das letze Datenbyte empfangen ist, kann der 
Heap-Speicher weitergereicht werden und das Protokoll wartet wieder im 
Zustand 1 auf Daten.

...eine von vielen Möglichkeiten

von Peter (Gast)


Lesenswert?

@Karl Heinz Buchegger
>Du hast einen der wichtigsten Punkt vergessen:
>Du brauchst entweder vorne oder hinten (oder anbeiden Enden) eine
>EINDEUTIGE Kennung, die dir die Synchronisierung auf den Anfang eines
>Datenpaketes ermöglicht.
Kopfschüttel!  Da bin ich anderer Meinung. Die Protokolldefinition 
liefert diese Informationen bereits - oder was denkst du, wie sagen wir 
mal UDP funktioniert?

@Lothar Miller
>Wenn du eine "normale" serielle Schnitte hast, dann ist sogar die
>Empfänger-ID ziemlich eindeutig...
Verstehe ich nicht. Angenommen, ich habe in meiner Software ein 
Protokollmodul, welches die serielle Schnittstelle öffnet und oben drauf
sitzen zwei Komponenten (bspw. Datenkanal und Steuerkanal). Nehmen wir
weiter an, dass diese Architektur an beiden Enden der Schittstelle
implementiert ist.
Da halte ich es schon für notwendig, dass es Absende- und Empfänger ID 
gibt,
damit man eine gute Architektur und Komponenten-Kapselung machen kann 
und die verschiedenen Nutzdaten an der richtigen Stelle ankommen.

von Karl H. (kbuchegg)


Lesenswert?

Peter schrieb:
> @Karl Heinz Buchegger
>>Du hast einen der wichtigsten Punkt vergessen:
>>Du brauchst entweder vorne oder hinten (oder anbeiden Enden) eine
>>EINDEUTIGE Kennung, die dir die Synchronisierung auf den Anfang eines
>>Datenpaketes ermöglicht.
> Kopfschüttel!  Da bin ich anderer Meinung.

das sei dir gegönnt.

Jeder der an einer seriellen Schnittstelle Ausfälle wegen abgesteckter 
Kabel hatte, oder Geräte, die während einer laufenden Übertragung ein 
und ausgeschaltet wurden, denkt da anders darüber.

Es gibt auch Leute, die meinen eine Versionsnummer am Anfang eines Files 
sei überflüssiger Luxus.

PS: UDP setzt auf einem Ethernet Frame auf. Und ein Ethernet Frame 
beginnt mit einer Präambel :-)

http://de.wikipedia.org/wiki/Ethernet#Formate_der_Ethernet-Daten.C3.BCbertragungsbl.C3.B6cke_und_das_Typfeld

von Peter (Gast)


Lesenswert?

@Karl Heinz Buchegger
>Jeder der an einer seriellen Schnittstelle Ausfälle wegen abgesteckter
>Kabel hatte, oder Geräte, die während einer laufenden Übertragung ein
>und ausgeschaltet wurden, denkt da anders darüber.
Ein Sync-Token hilft da leider nicht, das bestätigt dir jeder, der
sich ernsthaft mit sicherer Datenübertragung auseinandersetzt.

Ein Protokoll kann nur Datenintegrität zusichern - bspw. mit Checksumme.
Eine Ausfall-Erkennung implementiere ich immer funktional bspw. mit 
zyklischen Handshake-Messages.

von Karl H. (kbuchegg)


Lesenswert?

Peter schrieb:
> @Karl Heinz Buchegger
>>Jeder der an einer seriellen Schnittstelle Ausfälle wegen abgesteckter
>>Kabel hatte, oder Geräte, die während einer laufenden Übertragung ein
>>und ausgeschaltet wurden, denkt da anders darüber.
> Ein Sync-Token hilft da leider nicht,

Solange sicher gestellt ist, dass der Sync Token in den Daten nicht 
vorkommen kann, funktioniert das perfekt. Die Protokolldefinition ohne 
einleitendes Sync-Token hilft dir noch weniger, weil du nicht mit 100% 
Sicherheit davon ausgehen kannst, dass du nicht mitten in einen 
laufenden Frame einsteigst und die empfangenen Bytes vollkommen 
missverstehst.
Wartest du aber auf das Sync Token für den Framestart, dann hast du 
schon mal die Gewähr, dass du auch tatsächlich die Bytes für den 
Frameanfang hältst, die auch tatsächlich den Frameanfang markieren. Und 
ab dort übernimmt dann die Protokolldefinition und erlaubt das Parsen 
des Frames.

> Eine Ausfall-Erkennung

Es geht nicht um Ausfallerkennung.

von Peter (Gast)


Lesenswert?

@Harl Heinz Buchegger
>PS: UDP setzt auf einem Ethernet Frame auf. Und ein Ethernet Frame
>beginnt mit einer Präambel :-)
Du bringst da was durcheinander. Die Präambel signalisiert nicht den 
Beginn eines Ethernet Frames sondern war ganz früher mal gebräuchlich, 
um Sender und Empfänger zu synchronisieren. Sie ist lt. OSI unterhalb 
des MAC Layers definiert und somit nicht Bestandteil des Ethernet 
Frames.
Auch kenne ich keinen Netzwerk-Chip, geschweige denn Treiber, der sich
auf die Ethernet Präambel abstützen würde. Es wird immer nur mit Header 
und Nutzdaten hantiert. Die Ausfallsicherheit wird dann weiter oben 
bspw. mit TCP gemacht.

http://de.wikipedia.org/wiki/Ethernet#Formate_der_Ethernet-Daten.C3.BCbertragungsbl.C3.B6cke_und_das_Typfeld

von Karl H. (kbuchegg)


Lesenswert?

Peter schrieb:
> @Harl Heinz Buchegger
>>PS: UDP setzt auf einem Ethernet Frame auf. Und ein Ethernet Frame
>>beginnt mit einer Präambel :-)
> Du bringst da was durcheinander.

Mag sein.
Trotzdem ist dein Beispiel mit UDP nicht zutreffend, da UDP nicht die 
unterste Schicht ist und sich nicht um Framesynchronisierung kümmert. 
UDP setzt implizit voraus (durch das darunter liegende IP), dass es 
möglich ist ein Datenpaket an die Gegenstelle zu schicken, und das diese 
das Paket auch tatsächlich wieder als Paket erhält (wenn es das Paket 
überhaupt erhält). Aber UDP kümmert sich nicht darum, was zu passieren 
hat, wenn du eine Netzwerkkarte an ein Kabel stöpselst auf der gerade 
ein anderes Paket übertragen wird. Auch IP kümmert sich noch nicht 
darum. Die Ebene der Frames ist die OSI Schicht 1.

> Es wird immer nur mit Header und Nutzdaten hantiert.

Logo. Weil sich die Frame-Synchronisierung die Chips untereinander 
ausmachen. Ausserhalb der Chips interessiert das keinen.

Aber all das ist letzten Endes im gegenständlichen Fall ziemlich 
uninteressant. Der TO hat es mit einer UART zu tun. Und da gibt es nun 
mal keine Frames, die von den UART Bausteinen gehandhabt werden. Also 
muss man sich die, samt zugehöriger Steuerung, in Software selber 
machen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Peter schrieb:
> Du bringst da was durcheinander.
Ich gehe da mit Karl Heinz Buchegger konform, und sehe den Irrtum auf 
deiner Seite ;)
Nicht umsonst nutzen darauf aufbauende Protokolle wie TCP eine 
Sequenznummer. Ein UDP Paket ist vergleichbar mit einem einzelnem Byte 
das du über die RS232 empfängst, auch da gibt es auf der Ebene 
"dadrunter" eine Schicht mit Start und Stopbit welche die Syncronisation 
sicherstellt und dir dann ein fertiges Paket (in diesem Fall ein byte) 
liefert. Ebeneso bekommst du ein UDP-Paket, was aber auch nicht auf 
magische Weise aus dem nichts entsteht, sondern einfach das Ergebnis 
einer sehr viel tieferliegenden Syncronisation der beiden 
Kommunikationspartner ist.

Karl Heinz Buchegger schrieb:
> Und da gibt es nun
> mal keine Frames
Wie gesagt, im kleinerem Rahmen schon ;)

von paule (Gast)


Lesenswert?

Hallo,

wie realisiert man sowas in C?
Stehe vor einer ähnlichen Aufgabe, aber weiss nicht genau wie ich 
Programmiertechnisch mir ein solchen Frame zusammenbaue zum senden und 
wie ich die einzelnen States ändere bzw. ändern muss.

Wäre für ein paar hilfreiche Tipps dankbar.

Gruß
paule

von 123 (Gast)


Lesenswert?

Moin

ich hätte noch 2 kleine weitere dinge, die etwas höher angesiedelt sind.

1. ein kommando um die Protokoll version in erfahrung bringen zu können. 
ggf in der zukunft notwendig damit der PC ggf auf die unterschiedlichen 
Komandos / protokoll verhalten reagieren kann. Software und die 
protokolle entwickeln sich, die im Feld alle auf gleichem level halten 
zu können ist ein ding der unmöglichkeit.

2. ein Protokoll sollte bei unbekannten kommandos die er nicht kennt 
nicht einfach nichts tun. kann dazu führen, das der sender des kommandos 
sich zu tode wartet weil er auf ne antwort wartet (oder ein timeout 
kommt. wobei timeout immer böse sind, bzw wann kommt das timeout, .) 
besser ist es hier wenn das Protokoll ein NAC quasi eine negative 
antwort beitet z.B. Unbekanntes Kommando.

von Arne (Gast)


Lesenswert?

Ich gehe auch mit der meinung von Karl Heinz Buchegger konform. Habe 
hier von einem Lieferanten ein ser.Protokoll, das über UART 
rausgeknüppelt wird und keine EscSequenzen zur Synchronisation 
beinhaltet, sondern dies per Timeouts realisiert. Grauslich! Dann habe 
ich einen RFID-Leser, der per USB (VCOM) mit dem PC kommuniziert. Mit 
EscSequenzen. Läuft rund - auch wenn bei USB ein Kabelabziehen zu 
wesentlich drastischern Konsequenzen unter Windows führt.

@paule:
bastel Dir auf dem Papier eine Statemachine zusammen, die EscSequenzen 
hinzufügt bzw. entfernt. Ist eigentlich sehr einfach, wenn man das 
Prinzip gerafft hat. Die Umsetzung ist dann sehr einfach.

von Falk B. (falk)


Lesenswert?


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.