Hallo,
bekanntlich lösen alle uC IRQs aus, wenn über SPI,I2C usw etc Daten
eintrudeln. Bisher habe ich das so gelöst, dass für jedes Byte die ISR
extra aufgerufen wird, in ihr läuft eine State Machine, die das
Protokoll decodiert und Header, Cheksummen etc von Nutzdaten trennt. Ist
teilweise etwas kompliziert, vor allem wenn man damit rechnen muss dass
ein Datensatz abgebrochen wird, vor allem bei Infrarotsignalen. Dan
hängt die Statemachine fest, weil der IRQ nicht mehr kommt und kann nur
zB durch einen Timer wieder rückgesetzt werden.
Nun gäbe es noch die Möglichkeit beim ersten Byte reinzuspringen, dann
in der ISR bleiben und pollen bis alle Bytes gekommen sind. Man könnte
einen Timer als Timeout verwenden und dann rausspringen wenn er
überläuft. Bei SPI weiss ich ja, da ich beide uC programmiere, dass das
nie passieren kann aber bei externen Signalen muss man damit rechnen.
Was ist die "anerkannte" Methode oder wie löst ihr das?
Gruss,
Christian
das mit der state machine passt schon
bloss sind inputs in die state machine nicht nur Ereignisse wie "Zeichen
empfangen", sondern auch "Timeout" oder was anderes (zB "Schnittstelle
wurde (de)aktiviert")
die state machine der Schnittstelle reagiert daher nicht nur auf
"Zeichen empfangen"
der Zustand bleibt in einer globalen Datenstruktur, damit können andere
Ereignisse auch auf den Zustand zugreifen & verändern, nicht nur die ISR
damit kann Dir der Hänger nicht mehr passieren - starte einfach am
Beginn der ISR einen Timer
wenn der abläuft, ist es eben ein weiteres input-event in die state
machine - zB Rücksetzen in den Ausgangszustand
-Michael
@Christian J. (elektroniker1968)
>eintrudeln. Bisher habe ich das so gelöst, dass für jedes Byte die ISR>extra aufgerufen wird, in ihr läuft eine State Machine, die das>Protokoll decodiert und Header, Cheksummen etc von Nutzdaten trennt.
Falscher Ansatz. ISRs sollen möglichst kurz sein. Die Hauptaufgabe
verlagert man in die Hauptschleife, siehe Interrupt.
>Nun gäbe es noch die Möglichkeit beim ersten Byte reinzuspringen, dann>in der ISR bleiben und pollen bis alle Bytes gekommen sind.
AUA!!!
Pollen und ISR schliesst sich gegenseitig aus! Siehe Artikel oben!
> Man könnte>einen Timer als Timeout verwenden und dann rausspringen wenn er>überläuft.
Unsinnig.
MFG
Falk
Hallo,
es ist leider nicht möglich komplett auf eine Decodierung in der ISR zu
verzichten, da der Header Dinge wie Startbyte, Anzahl Bytes und
Checksumme enthält, die es erst ermöglichen Daten in der richtigen Folge
zu empfangen.
SPI Zeitprobleme gibt es nicht, man darf sowieso nicht Full speed
senden, also ohne Pause, weil dann der Slave keine Zeit hat
abszuspeichern etc.
Sieht derzeit so:
state 1: prüfe Startbyte
goto raus
state 2: Anzahl zeichen holen
goto raus
state 3: datenbytes einzeln ablegen u. Checksumme bilden
goto raus
state 4. Checksumme prüfen
Statemachine zurücksetzen
goto raus
Danke aber für die Bestätigung, ich werde es so machen und auf pollen
verzichten.
Christian J. wrote:
> Hallo,>> es ist leider nicht möglich komplett auf eine Decodierung in der ISR zu> verzichten,
Warum?
Die ISR kann sich damit begnügen das eine Byte, für dessen Empfang sie
aufgerufen wurde, von der Schnittstelle zu holen und in den Input Buffer
zu schieben.
In der Hauptschleife hast du dann alle Zeit der Welt den Input Buffer
sukzessive auszuwerten und laut deinem Protokoll zu analysieren. Dort
löst sich dann auch dein Timeoutproblem in der Statemachine sehr viel
einfacher als in der ISR.
Niemand sagt, dass aus der ISR schon fertig dekodierte Datensätze
herausfallen müssen. Die ISR kann sich vollauf damit begnügen, die
Schnittstelle zu bedienen und 'Rohdaten' zu holen.
Christian J. wrote:
> es ist leider nicht möglich komplett auf eine Decodierung in der ISR zu> verzichten, da der Header Dinge wie Startbyte, Anzahl Bytes und> Checksumme enthält, die es erst ermöglichen Daten in der richtigen Folge> zu empfangen.
Natürlich ist das möglich. Was soll "in der richtigen Folge zu
empfangen" denn überhaupt bedeuten? Die Daten werden in der Reihenfolge
empfangen, wie sie nun mal reinkommen. Das nächste Byte ist das nächste
Byte.
Der normale Weg ist es, die Daten in der ISR einfach zu empfangen und in
einem kleinen Ringpuffer abzulegen. Interpretiert werden sie dann in der
Hauptschleife.
Was spricht dagegen, einfach den ankommenden Datenstrom in einem Array
(zeischen)zuspeichern und später (in main) auszuwerten?
Also etwa so:
1
ISR(irgendwasempfangen)
2
{
3
Puffer[idx++]=UDR;
4
}
Das idx wird durch Timeout oder irgendeine Ende-erkennung auf null
gesetzt. gleichzeitig damit wird ein "ich hab was empfangen" Flag
gesetzt. Dann startet die main-Auswertung, etwa so:
Ich decodiere auch ein Protokol in der ISR mit einer Statemaschine. Wenn
man die Instruktionen zaehlt, die aufgerufen werden, so sind es nicht
sehr viele. Ein riesiges Switchkonstrukt kann pro Umlauf auch nur eine
Handvoll Instruktionen enthalten. Ja, man sollte einen Timeout laufen
lassen, ein Timer wird bei jedem Zeichen zurueckgesetzt. Das ist eine
optomale Loesung.
Die ISR schreibt die Daten nur in eine FIFO.
Das main liest dann aus der FIFO in einen Paketpuffer ein und wertet das
Paket aus, wenn es vollständig ist.
Das kann z.B. so aussehen:
Hallo,
also mal langsam zum verstehen....
Wenn es einen Ringpuffer gibt, dann brauche ich einen Trigger, der mir
anzeigt, dass der Datensatz vollständig ist. Was nützt mir eine
Auswertung in Main wenn nur die Hälfte da ist, verschwendete Rechenzeit.
Den Trigger gibt es also nicht, weil noch nichts ausgewertet wurde, der
Prozessor läuft ja nur stur mit und schiebt rein. Wird nichts abgeholt
läuft er über und fängt von vorne an. Während der Auswertung muss der
Puffer eingefroren werden, also können mir Daten verloren gehen, wenn
dann neue kommen. Auch das Kopieren in einen anderen Puffer kostet Zeit,
wäre aber denkbar.
Ich werde das aber nochmal durchdenken, derzeit ist es mir nicht so
klar, da dann zwei Prozesse (ISR und Main) gegeneinander arbeiten
können, weil sie auf die gleiche Datenbasis zugreifen, es als zu
Konflikten kommen kann. Als Trigger könnnte bei der SPI der Slave Select
dienen, weil der High geht wenn Daten fertig gesendet wurden. Bei einer
IR übertragung geht das aber nicht mehr, da muss Bit für Bit ausgewertet
werden und das geht wohl doch besser in der ISR, wenn die Bytes da
zusammengebaut werden.
Gruss,
Christian
Ein Ringpuffer ist in diesem Kontext u.U. nicht optimal, da die Analyse
der empfangenen Message durch das Hauptprogramm etwas darunter leidet.
Wenn die Puffergrösse nicht sehr hoch ist, kann es angenehmer sein, den
Puffer bei einer vollständig verarbeiteten Message aufzuräumen und den
Rest aufzuschieben. Dann kann man die älteste Message sehr einfach als
buffer[0..n] ansprechen und ohne zusätzliche Statemachine so lange im
Hauptprogramm die Runde drehen bis genug da ist.
Man muss nur darauf achten, die Verschiebeaktion interrupt-fest zu
gestalten. Wenn man das 2stufig erledigt, dann ist die Sperrzeit des
Interrupts dennoch sehr kurz: Erst den grad bekannten Teil ohne
Interrupt-Sperre schieben, dann den ggf. zwischendrin eingetrudelten
Rest mit Sperre.
Falls man sich entschliesst mit Ringbuffern zu arbeiten, benutzt man ein
kleines flag (boolean), wie datacame, das die Statemaschine im main
anwirft. Heisst, das main pollt dieses flag und arbeitet die
statusmaschine ab. Gleichzeitiger zugriff auf die selben daten ist kein
problem, solange einer nur schreibt, und der andere nur liest.
Hi,
ok, ziehe ich mal Resumee, wenn ich heute abend wieder an den Source
gehe:
SPI:
SPI Bytes per ISR in einen Fifo schieben ist ein Dreizeiler
Mainroutine
1. Main schaut nach, ob Slave Select = HIGH ist, denn dann kommen
garantiert keine neuen Daten an. Ein Datensatz läuft immer zwischen
SS=1,0,1 durch.
2. Main blockiert Puffer
3. Main schaut sich puffer[0] an, ist es ein Startbyte?
Nein => index = 0, Puffer vérwerfen.
Funkdaten über RFM12:
Hier wird auch ein "halber" Puffer durchlaufen, stellt er sich als
unvollständig heraus wird er in Ruhe gelassen, bis ein Timeout
abgelaufen ist, dann wird er zurückgesetzt. Die Entscheidung über
unvollständig kann ich über Checksummen prüfen.
Schaun mer mal :-)
Christian J. wrote:
> Wenn es einen Ringpuffer gibt, dann brauche ich einen Trigger, der mir> anzeigt, dass der Datensatz vollständig ist.
Nö, der FIFO sammelt nur stur die Bytes und nichts weiter.
Im Main hast Du dann einen Framepuffer und eine Routine, die nur die
Frameerkennung macht und diesen Puffer füllt.
Das ist mein oben angegebenes Beispiel.
Und erst, wenn diese erkennt, daß ein Frame vollständig ist, wird dieser
ausgewertet, das ist im obigen Beispiel mit "exec();" gemeint.
Diese kann z.B. Sendedaten bereitstellen und diese in die Sende-FIFO
packen.
Der Empfangs-FIFO-Interrupt kann derweil unbekümmert weitere Bytes
sammeln.
Du hast also insgesamt 3 Tasks:
Interrupt:
- Empfangs-FIFO
Main:
- Frameerkennung
- Auswertung
Du verschwendest damit keine Rechenzeit und brauchst auch keinen
Timeout.
Peter
Christian J. wrote:
> Wenn es einen Ringpuffer gibt, dann brauche ich einen Trigger, der mir> anzeigt, dass der Datensatz vollständig ist.
Du hast immer noch nicht verstanden, worauf wir hinauswollen.
Der Ringbuffer dient nur dazu, die ISR von der eigentlichen Verarbeitung
des Empfangenen zu entkoppeln.
Ob deine Statemachine jetzt mit jedem Zeichen aus der ISR heraus
getrieben wird ...
1
ISR(...)
2
{
3
charc=UDR;
4
Statmachine_bearbeite_nächstes_Zeichen(c);
5
}
.... oder ob das Zeichen in der Mainloop in die Statemachine
hineingestopft wird, ist für die Statemachine 'gehupft wie gesprungen'.
Der entscheidende Unterschied ist aber: Mit einem Ringbuffer ist die
Abarbeitungszeit der ISR von der Zeit die für die Statemachine draufgeht
entkoppelt.
Das Ganze hat nichts mit Frames oder Datensätzen oder so zu tun. Du
brauchst deine Statemachine wahrscheinlich noch nicht mal umschreiben.
Alles was du tun musst:
Die ISR schreibt das empfangene Zeichen in den Ringbuffer
und in die Hauptschleife kommt dazu
1
ISR(...)
2
{
3
SchreibeZeicheninRingbuffer(UDR);
4
}
5
6
....
7
intmain()
8
{
9
...
10
11
while(1){
12
13
....
14
15
if(ZeicheninRingbuffer){
16
cli();
17
c=HoleZeichenausRingbuffer;
18
sei();
19
20
Statmachine_bearbeite_nächstes_Zeichen(c);
21
}
22
23
....
24
}
25
}
und wenn dein restlicher Softwareaufbau vernünfitg war, dann läuft im
wesentlichen alles gleich wie bisher, nur dass die die UART-Receiver-ISR
nicht die Maschine blockiert, weil sie das emfpangene Zeichen unbedingt
verarbeiten muss. Und wenn die Statemachine mal mit den empfangenen
Zeichen nicht hinterherkommt, dann hast du zusätzlich noch einen
Ringbuffer dazwischen, der eine gewisse Verzögerung in der Statemachine
auffangen kann, in der der Prozessor trotzdem aufnahmebereit ist, obwohl
die Statemachine noch an den vorhergehenden Zeichen kiefelt.
PS:
Ich stöbere grad nach Code für einen Ringbuffer und habe auch einen
gefunden. Prinzipiell kreist da der write Zeiger von 0 auf max und fängt
bei 0 wieder an, wenn Überlauf. Wird der Read Zeiger nicht nachgezogen,
weil zB der Slave gerade "pennt" kommt es zum Überlauf, der write Zeiger
überholt den Read Zeiger.
Das würde bedeuten, dass ein Datenframe da irgendwie mittendrin liegen
kann.
ich habe als Startbyté bisher nur ein Byte genommen, da dieses natürlich
auch in den Daten vorkommen kann wäre die Starterkennung nicht mehr
sicher. Was ist denn da ein gängiges Verfahren? Würden zB 32 Bit
ausreichen um es ausreichend unwahrscheinlich zu machen, dass im
Datensatz die Startsignatur auftaucht. Oder ist es einfach egal, wenn
man bei jedem gefundenen Startbyte stur durchprüft, ob nach x Bytes die
Checksumme auch am richtigen Platz mit dem richtigen Wert steht?
Peter, wie hast Du´das gemacht?
Ein Satz bei mir bisher:
<start> <anzahl bytes> <datenbytes> <checksumme 2 Bytes>
Checksumme ist einfach die Ver-Xorung aller Bytes incl des Headers.
>Du hast also insgesamt 3 Tasks:>Interrupt:>- Empfangs-FIFO>Main:>- Frameerkennung>- Auswertung>>Du verschwendest damit keine Rechenzeit und brauchst auch keinen>Timeout.
Ein Timeout braucht man trotzdem um zu erkennen, ob eine Uebertragung
abgebrochen wurde. Dann wird verworfen.
Christian J. wrote:
> Das würde bedeuten, dass ein Datenframe da irgendwie mittendrin liegen> kann.
Ja und?
Interessiert den Verarbeiter nicht weiter. Für den ist der Ringbuffer
einfach nur ein Zwischenspeicher aus dem die Bytes in derselben
Reihenfolge herausfallen in der sie hineingesteckt wurden. Wo die
konkret innerhalb des Buffers zu liegen kommen, ist dem Verwender völlig
schnuppe (genauso wie ihn der Lese und der Schreibzeiger nicht zu
interessieren haben)
> Wird der Read Zeiger nicht nachgezogen,> weil zB der Slave gerade "pennt" kommt es zum Überlauf, der write Zeiger> überholt den Read Zeiger.
Dafür sorgen hoffentlich die Ringbufferroutinen, dass dieser Fehlerfall
sauber gehandhabt wird.
Aber im Grunde ist dieses Problem ja nichts neues. Wenn in einer UART
Zeichen schneller ankommen, als sie die verarbeitende Funktion abholt,
dann verschwinden ein paar Bytes im Nirvana.
> Was ist denn da ein gängiges Verfahren?
Pragmatische Lösung:
Den unvollständigen Datensatz verwerfen und solange weiterlesen, bis
wieder ein Startbyte einwandfrei identifiziert werden konnte. ALles
dazwischenliegende wird verworfen.
Das Problem besteht einfach darin, für das Startbyte etwas zu finden,
dass eindeutig in einem Datenstrom erkannt werden kann. Und zwar egal an
welcher Stelle man im Datenstrom anfängt zu lesen.
> Oder ist es einfach egal, wenn> man bei jedem gefundenen Startbyte stur durchprüft, ob nach x Bytes die> Checksumme auch am richtigen Platz mit dem richtigen Wert steht?
Woher weißt du den, ob die nächsten 4 Bytes eine Checksumme sind oder
einfach nur ein paar Datenbytes? Das Problem besteht ja doch darin,
einen eingehenden Datenstrom in Datenframes aufzuteilen. Und erst wenn
das gelungen ist, weißt du welche Bytes die Checksumme sind.
Bei deinem gedachten Protokoll, schreib einfach mal einen Bytestrom auf,
so wie ihn der Sender senden würde. Ein paar Datensätze lang.
Und dann lass die ersten x Bytes einfach mal weg und geh deine
Statemachine durch, ob sie von alleine wieder auf die Füsse fällt
(sprich: sich wieder mit einem Datensatzanfang synchronisiert). Wenn
nicht, dann ist dein Protokoll nichts wert (zumindest nicht wenn es um
das Thema geht: Neusynchronisierung im Fehlerfall) und du kannst das
Startbyte genausogut auch weglassen.
Christian J. wrote:
> ich habe als Startbyté bisher nur ein Byte genommen, da dieses natürlich> auch in den Daten vorkommen kann wäre die Starterkennung nicht mehr> sicher. Was ist denn da ein gängiges Verfahren? Würden zB 32 Bit> ausreichen um es ausreichend unwahrscheinlich zu machen, dass im> Datensatz die Startsignatur auftaucht. Oder ist es einfach egal, wenn> man bei jedem gefundenen Startbyte stur durchprüft, ob nach x Bytes die> Checksumme auch am richtigen Platz mit dem richtigen Wert steht?
Meine obige Funktion macht es genau so.
Erst wenn das Startbyte empfangen wurde, zählt idx hoch.
Erreicht idx die Länge (im 2.Byte), wird die CRC-Prüfung aufgerufen und
dann der Frame ausgewertet.
Die Daten dürfen also auch das Startbyte enthalten, da erstmal alles bis
zur Länge im Framepuffer landet.
Geht die Synchronisation verloren, merkt das der Master daran, daß keine
Antwort erfolgt (ich beantworte jeden Frame).
Dann kann er Dummy-Bytes (kein Startbyte) senden bis zur maximalen
Framelänge.
Das nächste Startbyte ist dann wieder synchron.
Ein Timeout ist daher nicht nötig.
Der einzige Unterschied ist nur, daß mein FIFO die UART einliest, daher
die Funktionsnamen:
kbhit - mindestens ein Byte im FIFO
ugetchar - lese ein Byte aus FIFO.
Peter
Christian J. wrote:
> ich habe als Startbyté bisher nur ein Byte genommen, da dieses natürlich> auch in den Daten vorkommen kann wäre die Starterkennung nicht mehr> sicher. Was ist denn da ein gängiges Verfahren?
Protokolle, die ein Startbyte verwenden, das an anderer Stelle ebenfalls
auftreten kann, sind wertlos. Allerdings kommt sowas auch im
industriellen Umfeld regelmäßig vor, also bei Protokollen, die
eigentlich von Profis entworfen worden sein sollten. Insofern handelt es
sich um ein zwar unbrauchbares, aber durchaus "gängiges" Verfahren. ;)
Dennoch würde ich empfehlen, entweder:
- das Startbyte wegzulassen, denn in dieser Form ist es nutzlos
- oder dafür zu sorgen, dass es ausschließlich am Anfang eines Paketes
vorkommt.
Gruss
Andreas
Ich mache das immer so, dass ich eine Pause zwischen zwei Paketen von
>2Bytes festlege.
Somit landet alles im Empfangspuffer bis zum Timeout. Danach setzt die
Prüfung auf "Protokolltauglichkeit" ein.
Als innerer Aufbau des Pakets nehme ich gewöhnlich:
ByteNr | Bedeutung
-------------+---------------
0 | Header ['M']
1 | Header ['L']
2 | Anzahl Nutzdaten [0..X]
3..3+(X-1) | Nutzdaten, evtl mit weiterem inneren Aufbau
3+X | Prüfsumme, Rest zu NULL
Was spricht dagegen?
Nichts spricht dagegen, den Anfang eines Paketes mit einer Pause zu
markieren. Aber die ersten beiden Bytes Deines Protokolls sind natürlich
Verschwendung von Bandbreite, Speicherplatz und Prozessorzeit, da
überflüssig. ;)
Gruss
Andreas
Interessantes Thema !
Wie wäre denn ein StartByte und eine (Frame)Zählernummer.
Die Wahrscheinlichkeit eine Sichere Übertragung zu haben würde sich mit
jedem Frame erhöhen,
denn nach jedem Frame wird doch die Kombination von StartByte und
FrameZählerNr immer einmaliger und somit eindeutiger !?!
Nach einer bestimmten Anzahl von DummyFrames die einfach nur zur
"Feststellung der Synchronisation" dienen,
sollte das ganze doch recht gut sein...
Zusätzlich noch sein geXORe und gut ist ?
Wie machen das denn die "richtig" guten Protokolle ???
Wie nennt sich das in der Lieteratur (NVT?) ?
Andreas Vogt wrote:
> Protokolle, die ein Startbyte verwenden, das an anderer Stelle ebenfalls> auftreten kann, sind wertlos.
Das ist ausgemachter Blödsinn.
Die Bedeutung ergibt sich nicht durch das Byte selber, sondern durch
seine Stellung im Frame.
Solange es das Protokoll ermöglicht, sich wieder zu synchronisieren,
unabhängig vom augenblicklichen Zustand, ist das vollkommen ausreichend.
Oder willst Du behaupten, daß Du keine Wörter verstehst, wenn deren
Anfangsbuchstabe im Wort nochmal vorkommt?
Peter
Peter Dannegger wrote:
> Das ist ausgemachter Blödsinn.> Die Bedeutung ergibt sich nicht durch das Byte selber, sondern durch> seine Stellung im Frame.> Solange es das Protokoll ermöglicht, sich wieder zu synchronisieren,> unabhängig vom augenblicklichen Zustand, ist das vollkommen ausreichend.
Bist Du sicher, dass Du weißt, wovon Du redest?
Es gibt viele Möglichkeiten zur Synchronisation, die Verwendung eines
Startbytes ist nur eine davon. Und grundsätzlich ist für ein Protokoll
auch genau eine Synchronisationsmethode ausreichend - sofern sie
funktioniert. Wenn man also ein Startbyte hat, zur Synchronisierung aber
weitere Berechnungen oder Untersuchungen anstellen muß, weil das
Startbyte nicht eindeutig ist - was ist das Startbyte dann?
Genau, überflüssiger Datenmüll. ;)
Gruss
Andreas
>>Wie machen das denn die "richtig" guten Protokolle ???
Die machen das mit klar defnierten Zeitfenstern. Ein 100% sicheres
Protokoll ist zb Das LIN Protokoll (aufgebohrte RS232 mit norm
definierter PHY) aus dem Automotive Bereich, damit habe ich viel zu tun
gehabt früher. Die Timeouts sind in der Spec festgeschrieben, der Slave
synchronisiert sich neu wenn abgelaufen. Für Hobby aber zu aufwendig und
überladen.
Ich halte den Timeout, also Pause zwischen Frames, für die beste
Synchronisation, mehr als ~70% Nutzdaten pro Frame sind sowieso
Wunschdenken auf Kosten der Sicherheit. IR Protokolle haben noch
weniger.
Das RFM12 Modul verwendet die Codefolge D44D als Sync Marke um den Fifo
aufzumachen, taucht beim reinen Rauschen alle paar Stunden einmal auf,
wie ich feststellte.
Bei einer Slave SPI kann man den Chipselect nehmen.
Andreas Vogt wrote:
> Bist Du sicher, dass Du weißt, wovon Du redest?
Ja natürlich, sonst hätte ich ja das Codebeispiel nicht gepostet.
Es hat sich in der Praxis bestens bewährt und man kann zwischendurch
ruhig mal den Stecker ziehen, ohne daß was schief läuft.
Der Master kriegt keine Antwort, also schickt er ein Sync-Paket und dann
das verlorene Paket nochmal.
Warum unnötig Aufwand treiben mit Zeitfenstern, wenns auch einfacher
geht.
> Und grundsätzlich ist für ein Protokoll> auch genau eine Synchronisationsmethode ausreichend - sofern sie> funktioniert.
Genau, ist doch meine Rede.
Man könnte noch für die ganz besonders Überängstlichen, wenn das
Startbyte im Datenstrom vorkommt, immer noch ein Dummybyte 0 einfügen,
da ja eine Paketlänge 0 nicht vorkommt.
Dieser Test ist auch nicht aufwendig, die Notwendigkeit ergab sich aber
nicht.
Peter
P.S.:
Die Überlegung mit Zeitfenstern hatte ich zuerst auch, aber da sind mir
die PC-Programmierer auf die Barrikaden gestiegen. Unter Windows ein
bestimmtes Zeitregime 100%-ig einzuhalten ist ne verdammt hart Nuß. Da
macht jeder Programmierer lieber nen weiten Bogen rum.
Peter Dannegger wrote:
> Der Master kriegt keine Antwort, also schickt er ein Sync-Paket und dann> das verlorene Paket nochmal.
Sehr schön, Deine Synchronisationsmethode ist also ein Handshake.
Zusätzlich verwendest Du in Deinem Code jedoch ein Startbyte mit dem
Wert COMSTART. Schauen wir mal, wozu das führt:
1. Akt: Der Master sendet ein Sync-Paket, das mit COMSTART beginnt. Du
empfängst das Byte, und die Zeile
liefert true, woraufhin Du idx um eins erhöhst. Dann wird die Funktion
verlassen, ich nehme an, sie wird von einer Hauptschleife in Kürze
erneut aufgerufen.
2. Akt: Dummerweise herrscht gerade ein Gewitter, und alle weiteren
Bytes des Sync-Paketes fallen einer EMI zum Opfer. Davon weiß Dein
Programm natürlich nichts.
3. Akt: Du bist inzwischen ein paar Mal in der command-Funktion gewesen
ohne etwas zu empfangen, was Dich aber nicht weiter stört.
4. Akt: Der Master beginnt jetzt mit der der Übertragung des nächsten
Paketes, das wieder mit COMSTART beginnt. Auch das stört Dich zunächst
nicht, Du interpetierst das Byte als Telegrammlänge.
5 Akt - der Held stirbt: Je nach dem, welchen Wert COMSTART hat, wird
Dein Programm früher oder später hier:
1
if(com.com.length>sizeof(unioncombuf))
oder hier
1
if(idx>=com.com.length)
aussteigen und dieses völlig fehlerfrei und korrekt übertragene zweite
Paket irrtümlich komplett wegwerfen, weil es nicht bemerken kann, dass
der Fehler im vorhergehenden Paket lag. Und der arme Master muss den
ganzen Quark nochmal senden...
Hier haben wir also den verschärften Fall, dass ein nicht eindeutiges
Startbyte nicht nur überflüssig, sondern sogar schädlich ist.
Wenn Du das Startbyte weglässt und statt dessen nur das Längenbyte und
den CRC prüfst, kannst Du das zweite Telegramm korrekt empfangen. ;)
Gruss
Andreas
Andreas Vogt wrote:
> 1. Akt: Der Master sendet ein Sync-Paket, das mit COMSTART beginnt.
Nein das Sync-Paket sind einfach nur 9 Dummybytes (bei max 8 Byte
Daten), danach wartet der Empfänger wieder auf das Startbyte, ist also
synchron.
Und das sendet der Master auch nur, wenn er mal keine Quittung bekommt.
> aussteigen und dieses völlig fehlerfrei und korrekt übertragene zweite> Paket irrtümlich komplett wegwerfen
Ein Paket, welches asynchron ist, muß ich wegwerfen, es ist fehlerhaft.
Die Übertragung soll ja sicher sein.
> der Fehler im vorhergehenden Paket lag. Und der arme Master muss den> ganzen Quark nochmal senden...
Ja, das ist die einzig mögliche Vorgehensweise, bei Übertragungsfehlern
muß die Nachricht wiederholt werden.
Z.B. der CAN-Bus macht das Retry sogar schon in der Hardware.
> Hier haben wir also den verschärften Fall, dass ein nicht eindeutiges> Startbyte nicht nur überflüssig, sondern sogar schädlich ist.
Warum sollte es denn schädlich sein, nur weil mal ein Paket
sicherheitshalber wiederholt werden mußte?
Ganz im Gegenteil, es ist ein sehr einfacher und wirksamer
Synchronisationsmechanismus.
> Wenn Du das Startbyte weglässt und statt dessen nur das Längenbyte und> den CRC prüfst, kannst Du das zweite Telegramm korrekt empfangen. ;)
Und wie willst Du ohne Startbyte überhaupt erstmal das Längenbyte und
die CRC erkennen?
Peter
>Protokolle, die ein Startbyte verwenden, das an anderer Stelle ebenfalls
auftreten kann, sind wertlos. Allerdings kommt sowas auch im
industriellen Umfeld regelmäßig vor, also bei Protokollen, die
eigentlich von Profis entworfen worden sein sollten.
Ist natuerlich Geplapper. Kommt auch auf den Kontext an. Bei einem
Master Slave System sendet eigentlich nur der Master und die Slaves
antworten. Ein Packt ist meist mit einem CRC gesichert und wird
verworfen wenn der nicht passt. So ein System resynchronisiert sehr gut.
Ich glaube Ihr redet gleichzeitig über mehrere Themen. Die sind (mit
unterschiedlicher Intensität ;-):
(1) framing - sicheres Erkennen von Paket-Anfang und Ende
(2) Erkennung von Paketverlust und Duplikaten
(3) Paketaufbau
(4) Durchsatz
Mein Senf dazu. Executive summary am Ende.
idF:
EOF = End-of-Frame Smybol
SOF = Start-of-Frame Symbol
idF: Frame = netto-Paket plus SOF davor plus EOF dahinter (ev plus
layer-1 Schnickschnack wie Prüfsumme oder Bits zum Synchronisieren, wie
die Ethernet-Preambel).
grundsätzlich hat sich beim Protokolldesign durchgesetzt, dass nicht nur
Anfang, sondern auch Ende von Frames unabhängig vom Paketinhalt erkannt
werden kann. Vorteil: der layer2 handler braucht vom Paketinhalt nichts
zu verstehen; solange er Start und Ende erkennen kann - wunderbar. Bei
Multi-Protokoll-Netzen ist das gar nicht anders machbar - du kannst zB
nicht jedem Ethernet-Switch upgraden, bloss weil Firma X wieder einen
neuen Ethernet-Frame-Typ erfunden hat. Das Ding vermittelt
Ethernet-Frames - und aus. Das ISO-Zwiebelschalenmodell hat schon seinen
Sinn - die ISR braucht von der Anwendungsebene nix zu verstehen.
(1) - es gibt zum Thema Framing iW zwei Wege - beide laufen sich darauf
hinaus, dass ein SOF bzw EOF Symbol sicher unabhängig vom Paketinhalt
erkannt werden kann.
Bei bit-orientierten Protokollen wie HDLC oder Ethernet wird für sowas
ein spezielles Bit-Muster verwendet, das in Nettodaten nicht vorkommen
kann (siehe http://en.wikipedia.org/wiki/Bit_stuffing). Hardware
erkennt/behandelt iA diese Muster schon selbsttätig und signalisiert dem
Treiber daher SOF bzw EOF und damit Paketlänge.
Bei Zeichen-orientierten Protokollen gibts diese Möglichkeit nicht, weil
die unterste Ebene eben nur Einheiten von iA 8 bits beherrscht. Also
alles, was asynchron über UARTs läuft, aber auch sowas wie BSC selig
(http://einstein.informatik.uni-oldenburg.de/rechnernetze/bsc.htm).
Nachdem die Zeichen, die SOF und EOF definieren, im Nettopaket
vorkommen können, gibts das Erkennungsproblem, das durch "escaping" der
besonderen Symbole umgangen wird. Also zB bei "transparentem" BSC ist
SOF als DLE-STX definiert und EOF als DLE-ETX. Wenn DLE-ETX im
Datenpaket vorkommt, wird das DLE einfach verdoppelt ("escaped") und am
anderen Ende wieder auf ein einzelnes DLE eingedampft. Damit bleibt ein
DLE-ETX sicher als EOF-Symbol.
(2) vereinfacht gesagt: jedes Link-Protokoll, das Bestätigungen sendet,
ohne zu beschreiben, was es eigentlich bestätigt, ist anfällig für das
Abliefern von Duplikaten. Für das gibt es Sequenznummern - also HDLC
macht das auf Ebene 2 mit Sequenzummern von 0-7 bzw 0-127 und selektiver
Bestätigung. TCP machts mit Sequence Numbers, die mit Ack bestätigt
werden ("habe bis hierher empfangen").
Ob das auf Ebene 2 oder auf der Transport-Ebene gemacht wird, ist eine
bisschen philosophisch und auch eine Performance-Frage. Irgendwo muss
es aber passieren. Bei der Internet-Architektur hat sich der Konsens
durchgesetzt, sowas auf der Transport-Ebene (TCP, Layer4) zu machen und
sich nicht auf zuverlässige Links zu verlassen.
(3) auch für banale Protokolle rate ich bezüglich Paketaufbau:
- die TLV (type-length-value) Codierung zu verwenden
(http://en.wikipedia.org/wiki/Type-length-value)
- alles "am Draht" ist in network byte order normiert
(http://en.wikipedia.org/wiki/Network_byte_order#Endianness_in_networking)
- alignment beachten. Also shorts auf Adressen modulo2 = 0, longs auf
modulo 4 = 0 etc. Notfalls padding. Ein paar Bytes extra sind wurscht
relativ zu dem Aufwand, den die Missachtung der Regel erzeugt.
TLV deswegen, weil ein TLV Parser "versionssicher" ist - wenn der Sender
neue Paketfelder sendet, erkennt und überspringt der Empfänger diese als
"unerkannt", macht aber ansonsten das richtige.
Network byte order: die Annahme, dass am anderen Ende immer dieselbe
Rechnerarchitektur (little- oder big-Endian) sitzt, gilt vielleicht nur
im Labor.
Alignment: Pakete zu parsen, die das nicht beachten, bricht einem die
Finger vor lauter unnötigem Hin-und Herkopieren. Siehe avrdude oder
Microsoft SMB.
(4) das diskutieren wir bei Interesse ein andermal. Bei 1m
RS232/CAN/USB/I2C fast egal.
executive summary:
- Paketaufbau mit SOF und EOF verwenden.
- Wenn die Link-Ebene SOF und EOF unterstützt, beide verwenden.
- bei zeichenorientierten Protokollen SOF und EOF als Zeichen definieren
und im netto-Paketn "escapen" (beim Senden verdoppeln, beim Empfangen
das Duplikat entfernen)
- Prüfsumme hinter EOF ansetzen. Prüfsummen haben mit dem Nettopaket
nichts zu tun, das ist eine Ebene tiefer.
- die ISR ist ein layer1 oder layer2 handler. Variante layer1: nur
Zeichen in den Ringpuffer einlesen. Variante layer2: ein Frame
Handler, der SOF, EOF und CRC versteht. Dann ist das interface zur
nächsthöheren Ebene nicht ein Ringpuffer von Zeichen, sondern
(konzeptuell) eine packet queue. In beiden Fällen hat die ISR keine
Ahnung vom Aufbau des Inhalts.
- Inhalt: auf der Ebene "darüber" (von mir aus in "main()") betrachten.
- TLV, byte order, alignment beachten.
so. genug habilitiert ;-)
-Michael
Moin Michael,
eine sehr gute Zusammenfassung, nur an einer Stelle muß ich
widersprechen:
Michael Haberler wrote:
> Bei Zeichen-orientierten Protokollen gibts diese Möglichkeit nicht, weil> die unterste Ebene eben nur Einheiten von iA 8 bits beherrscht. Also> alles, was asynchron über UARTs läuft,
Viele UARTs beherrschen die Übertragung von 9 Datenbits und zwar genau
zu dem Zweck, Anfang und Ende von Frames zu markieren, ohne auf eine
7-bit-Kodierung heruntergehen zu müssen.
Merkwürdigerweise wird diese Möglichkeit allerdings in der Praxis fast
nie verwendet.
Gruss
Andreas
9 Datenbits für's Framing von Paketen ist bei UARTs absolut nicht
selbstverständlich, es sei denn du arbeitest gezielt mit
Paritätsfehlern. Klassisch sind 5,6,7,8 Bits, nicht aber 9. Und so geht
es auch den PCs, was die Sache etwas umständlich macht (ebenso
beispielsweise LPC2000).
Was man bei den ganzen Spuk nicht ausser Acht lassen sollte: Bei manchen
seriellen Übertragungen ist eine sichere Synchronisation auf den
Byteanfang auch bei sauberer Leitung nicht unbedingt gewährleistet.
Beispielsweise nicht bei UARTs. Dort ist das über etwas Pause
automatisch herstellbar, bei anderen wie SPI wird es komplizierter.
Manchmal wrote:
> Weil ein PC Uart das zB nicht kann ? Und auch wenn es das koennte, mit> einem generischen Blockdevice Treiber obendrauf...
Vermutlich. Aber selbst bei Protokollen, die nur für die Kommunikation
innerhalb definierter Busteilnehmer gedacht ist, sieht man das 9. Bit
kaum.
>>Manchmal wrote:>> Weil ein PC Uart das zB nicht kann ? Und auch wenn es das koennte, mit>> einem generischen Blockdevice Treiber obendrauf...>Vermutlich. Aber selbst bei Protokollen, die nur für die Kommunikation>innerhalb definierter Busteilnehmer gedacht ist, sieht man das 9. Bit>kaum.
Wie haenge ich da einen PC zum Loggen und debuggen dran ?
Was gehen müsste: PC auf 8 Bits mit manueller Parität einstellen und
Paritätsfehler als Framing-Kennung einsortieren. Bei 9 Datenbits plus
Parität geht natürlich nichts mehr.
Beim PC hat man schon lange keinen Zugriff auf das Uart mehr.
Irgendwelche daemliche und duemmliche Software dazwischen mach alles
kaputt. Zugegeben, es ist schon eine Weile her, aber ich hatte mal ein
Projekt, da wurde waehrend dem Protokol von einem Device die Baudrate
umgeschalten. Vom normalen interaktiven 2400baud modus in einen download
modus mit 38400. Bei einem Uart sind das zwei register zugriffe, beim PC
(Pentium 166MHz), damals unter WinNT4 dauerte dieser vorgang geschlagene
1.4 sekunden. Da war der download schon halb durch. Und deshalb haben
all diese netten Waere-Gut-Features auf irgendwelchen Systemen keine
Zukunft. Weil der PC da etwas auf stur schaltet.
Geiert nicht so mit den Bits, Jungs. Wir schreiben 2009.
Die letzte Netzwerk-Architektur, wo "jedes Bit ausgenüzt wurde" (X.25),
wird grade noch zum Bankomatbetrieb verwendet. Die haben das anno Schnee
für die damaligen links "optimiert" und das ganze damit für heutige
links unbrauchbar gemacht. Das DFN hat Millionen versenkt, um das
Gegenteil zu beweisen, und hat auch aufgegeben.
--
zeichenorientiertes Framing ist ok, wenn man halt nichts anderes hat.
Man kann vielleicht einen esoterischen UART-Mode für Framing verwenden.
Aber spätestens beim Tracen oder beim Schreiben eines Framehandlers, wo
man keinen direkten HW-Zugriff mehr oder andere Hardware vor sich hat,
erkennt man, dass man sich ins Knie geschossen hat.
Eine Empfangsroutine, die im richtigen Zeitpunkt Framebreite umschaltet,
na gute Nacht. Es gibt sogar bescheuerte Protokolle, die schalten
unterwegs die Geschwindigkeit um.
also - zuerst korrekt, dann schnell machen - wenn überhaupt.
-Michael
ps: Autobaud ist schon ok, meinte ich nicht. Aber sonst lieber nicht mit
exotischen Zeichenformaten oder Geschwindkeiten "optimieren".
psps: und hier noch ein Klassiker für die Badewanne:
http://www.iuniverse.com/bookstore/BookDetail.aspx?BookId=SKU-000002499
@ Michael Haberler (mah) :
Kurze Frage zu
>- Prüfsumme hinter EOF ansetzen. Prüfsummen haben mit dem Nettopaket>nichts zu tun, das ist eine Ebene tiefer.
Meinst du damit, den Aufbau etwa so:
<SOF> <Nutzdaten> <EOF> <CRC>
oder eher so
<SOF> <Nutzdaten> <CRC> <EOF>
?
Matthias Lipinsky wrote:
> @ Michael Haberler (mah) :>> Kurze Frage zu>>>- Prüfsumme hinter EOF ansetzen. Prüfsummen haben mit dem Nettopaket>>nichts zu tun, das ist eine Ebene tiefer.>> Meinst du damit, den Aufbau etwa so:>> <SOF> <Nutzdaten> <EOF> <CRC>
So - für ein layer2 Frame.
> oder eher so>> <SOF> <Nutzdaten> <CRC> <EOF>
So nicht.
Meine Begründung ist ein bissl philosophisch. Prüfsummen über gesamte
Paketinhalte sind eine Funktion der darunterliegenden ISO-Schicht, und
vielleicht sogar optional in der Implementierung. Deswegen haben die im
Paket selbst nichts verloren. Wenn der Empfänger die Prüfsumme nicht
implementiert, wird es halt unsicherer, aber auf der Ebene darüber
stimmt das Paketformat noch. Variante 2 wär daher eine "layering
violation" (unter der Annahme dass alles zwischen SOF und EOF an die
"Ebene darüber" geht).
Das Thema war layer2 framing und da würd ichs mal so sehen. Die layer2
checksum hat im layer3 Nettopaket nix verloren, die wird eh vom layer2
handler ausgewertet.
Das heisst nicht, dass unterschiedliche Ebenen eigene Mechanismen zur
Fehlererkennung und Korrektur haben können. Also zB TCP checksums und
Ethernet CRC32. Oder Vorwärtsfehlerkorrektur bei DVB.
Bei IP mit TCP ists so: layer3 (IP) hat eine Prüfsumme nur für den
IP-header. TCP hat eine Prüfsumme fürs TCP Segment. Zusammen ist das
ganze Paket gesichert, auch wenn layer2 mal Fehler durchlässt. Daher
funktioniert das auch mit unzuverlässigen Links.
Aber IP selber hat keine Prüfsumme fürs ganze Paket (nur für den
Header), weil das iA eh der layer darunter macht und der Rest in TCP und
das wär dann doppelt gemoppelt. Und die Layer2 checksum ist für IP daher
auch egal und optional.
aber ich glaub ich drifte ein wenig ab..
-Michael
Michael Haberler wrote:
>> Meinst du damit, den Aufbau etwa so:>>>> <SOF> <Nutzdaten> <EOF> <CRC>>> So - für ein layer2 Frame.>>> oder eher so>>>> <SOF> <Nutzdaten> <CRC> <EOF>>> So nicht.>> Meine Begründung ist ein bissl philosophisch. Prüfsummen über gesamte> Paketinhalte sind eine Funktion der darunterliegenden ISO-Schicht, und> vielleicht sogar optional in der Implementierung. Deswegen haben die im> Paket selbst nichts verloren.
Ich denk, das ist wirklich eine philosophische Sache.
Die Idee hinter dem ganzen Schichtenmodell ist ja letztendlich, dass
jede drunterliegende Schicht, das 'Datenpaket' der drüberliegenden
Schicht in irgendeiner Weise beim Senden einpackt und auf der
Gegenseite, wenn das Paket den Stack hochwandert, die jeweils
korrespondierende Schicht dieses Einpacken wieder rückgängig macht.
Ob diese Schicht jetzt ein Enveolpe in der Form
<SOF> Paket <EOF> <CRC>
drüberlegt, oder ob das
<SOF> Paket <CRC> <EOF>
ist, dürfte aufs gleiche rauskommen. Mir wär die 2-te Variante
sympathischer, weil EOF dann tatsächlich EOF ist und nicht nachher noch
was kommt.
Ist aber philosophischer Natur.
>drüberlegt, oder ob das> <SOF> Paket <CRC> <EOF>>ist, dürfte aufs gleiche rauskommen. Mir wär die 2-te Variante>sympathischer, weil EOF dann tatsächlich EOF ist und nicht nachher noch>was kommt.
Würde ich auch so sehen. Es könnte ja weiterhin folgendes sein:
<SOF> Paket <CRC> <EOF>
|
|
\|/
Paket = <SOP> Daten <EOP> ( start/end of packet für die
darüberliegende Schicht )
ja, da ist was dran - das EOF packt die Prüfsumme robust mit ein und
macht das frame handling etwas einfacher.
Dafür ist man aber halt mit der gewählten Prüfsumme verheiratet. Wenn Du
die ausn Paket rauslasst, kannst den Layer darunter (mit anderer,
besserer oder gar keiner Prüfsumme) tauschen und nix fällt auf.
Man kann auch overkill bei der Flexibilität betreiben, ich stehe dazu
;-)
-Michael
>Man kann auch overkill bei der Flexibilität betreiben, ich stehe dazu>;-)
Vielleicht doch lieber so:
<SOF> Paket <EOF> <CRC_Alg><CRC>
Also vor der Prüfsumme den Berechnungsalgorithmus für selbige
mitschicken ;-)
Michael Haberler wrote:
> Dafür ist man aber halt mit der gewählten Prüfsumme verheiratet. Wenn Du> die ausn Paket rauslasst, kannst den Layer darunter (mit anderer,> besserer oder gar keiner Prüfsumme) tauschen und nix fällt auf.
Ja, darüber hab ich auch nachgedacht.
Und bin, für mich, zum Schluss gekommen, dass ich das in den meisten
Fällen gar nicht haben will. Letztendlich hab ich das nämlich schon
öfter beobachtet (auch bei mir selbst :-): Wenn eine Prüfsumme angegeben
werden kann oder auch nicht, dann entscheidet man sich erst mal für
'nicht'. Ist weniger Arbeit und schliesslich will man das Kind ja erst
mal ins Laufen bringen. Mit dem Effekt, dass es bei dem 'nicht' bleibt.
Ich weiß: traurig aber gängige Praxis - brauchen wir nicht weiter
diskutieren :-)
> Man kann auch overkill bei der Flexibilität betreiben
Flexibilität ist letztendlich immer eine Gratwanderung.
Karl heinz Buchegger wrote:
> Ob diese Schicht jetzt ein Enveolpe in der Form>> <SOF> Paket <EOF> <CRC>>> drüberlegt, oder ob das>> <SOF> Paket <CRC> <EOF>>> ist, dürfte aufs gleiche rauskommen. Mir wär die 2-te Variante> sympathischer, weil EOF dann tatsächlich EOF ist und nicht nachher noch> was kommt.
Das stimmt schon, allerdings muss man bei der zweiten Variante immer
überlegen, ob denn nun das EOF mit in der Prüfsumme ist, oder nicht.
Wenn es mit drin ist, ist die Prüfsummenberechnung etwas lästig. Ich
bevorzuge daher auch die erste Variante.
Vor einiger Zeit habe ich mal ein proprietäres Protokoll gesehen, bei
dem der Autor sich wohl nicht für eine Variante entscheiden konnte. Der
hat vor und hinter dem CRC ein EOF gesendet. War auch nicht so toll...
Gruss
Andreas
Andreas Vogt wrote:
> Das stimmt schon, allerdings muss man bei der zweiten Variante immer> überlegen, ob denn nun das EOF mit in der Prüfsumme ist, oder nicht.
Guuuuuuuutes Argument!
Andreas Vogt wrote:
> Vor einiger Zeit habe ich mal ein proprietäres Protokoll gesehen, bei> dem der Autor sich wohl nicht für eine Variante entscheiden konnte. Der> hat vor und hinter dem CRC ein EOF gesendet. War auch nicht so toll...
Ich vermute, da war ein Design-Komittee am Werk.
-Michael
Hallo,
um mal wieder zum Thema zu kommen. Ist folgendes Konzept so ok? (Es wird
von einer fehlerfreien Übertragung ausgegangen = Hobbybereich)
1. Master sendet fortlaufende 0xff zum Slave und wartet bis Slave mit
ACK antwortet.
2. Master schiebt seinen Datensatz rüber und zuletzt bestätigt Slave mit
ACK.
3. Master macht Verbindung wieder zu.
Slave schiebt alles artig über ISR in den Ringpuffer.
Main Routine prüft, ob mindestens 1 Byte im Puffer ist und ob Slave
Select wieder HIGH, da Slave Select = LOW Indiz für laufende Übertragung
ist. Prüft, ob dieses das Startbyte ist. Falls nicht, wird Byte
verworfen, holt sich nächstes Byte.
Ist es ein Startbyte zieht Main den Puffer leer und prüft, ob die
Struktur des Satzes stimmt, berechnet die Position der
Prüfsumme/Abschlussbytes und prüft, ob dieses ok ist, ob Endmarke da ist
usw.
Ist der Puffer unfertig gefüllt wartet Main N ms, ist er dann nicht
gefüllt stimmt was nicht und der Buffer wird leergeluscht und resettet.
Soweit ok?
Hallo alle,
interessanter Thread - vor allem wenn man bedenkt, das das Thema
trivial ist und zig Standardimplementierungen für das Problem
existieren.
Die meisten, die ich gesehen habe, arbeiten mit einem Ringbuffer.
@Karl heinz Buchegger:
Dein cli() und sei() kannst du dir sparen. Ringbuffer müssen nicht
synchronisiert werden, wenn ausser dem Interrupt niemand den
Schreibzeiger
modifiziert!!!
Ich würde es so machen:
interrupt
{
BYTE b;
while (ReadInterface(&b))
{
ByteFifoAdd(b);
}
}
main
{
BYTE b;
MSG msg;
DWORD lastByteTimestamp;
InitMessage(&msg);
//init hardware...
while(true)
{
if (ByteFifoGet(&b))
{
lastByteTimestamp = GetTickCount();
AddToMsg(&msg, b);
if ( IsHeaderComplete(&msg) )
{
if ( IsMessageComplete(&msg)
{
ProcessMessage(&msg);
}
}
}
else
{
//Timeout, wenn länger nix empfangen
timeNow = GetTickCount();
if (timeNow-lastByteTimestamp > TIMEOUT)
{
//disable receive interrupt
//Send Break Condition
//clear Bytefifo
InitMessage(&msg);
//enable receive interrupt
}
}
}
}
>Ringbuffer müssen nicht synchronisiert werden, wenn ausser dem Interrupt >niemand
den Schreibzeiger modifiziert!!!
@Experte
Du bist ein Byte-Experte. Laß den Schreibzeiger auf einem 8-Bitter mal
vom Typ short oder int sein. Schon geht es ohne Interruptsperre in die
Hose!
Mein Senf zum Thema:
Für die Datenübertragung nutze ich meist Datensätze im Intel-Hex-Format.
Es ist ein wenig geschwätzig, beginnt aber immer mit einem eindeutigen
Startzeichen, endet mit CR, hat eine leichte Prüfsumme und die
übertragenen Zeichen müssen im Bereich 30-39 und 41-46 liegen. Wenn das
zu langsam ist, nehme ich meist den Multiprozessormodus, der eine
eindeutige Adressierung (9.Bit) bietet.
Und dann wieder zum Thema, was im Interrupt zu bearbeiten ist. Sofern
eine Adressierung wie beim Multiprozessormodus gemacht wird, sollte die
auf jeden Fall im Interrupt gehandhabt werden. Ebenso auch bei anderen
Adressierungsarten. Wichtig dabei ist nämlich, daß bei neuer, eigener
Adresse die Ringpuffer gelöscht werden (kann/sollte/müßte), um klare
Startbedingungen für den neuen Datensatz zu schaffen.
Ausnahmen bestätigen die Regeln!
@Gasst
Na da hat ja einer aufgepasst - aber nicht so ganz wie mir schein, denn
int ist auf einem 8Bitter 8Bit breit.
Ansonsten ist die ganze Diskussion Kindergarten.
Der Vorteil des Ringbuffers ist, dass man uns,chronisiert lesen und
schreiben koennen moechte, ausser man machts wie gasst...
Und wer nicht weiss wies geht, liest irgend ein Buch oder in der howto
Abteilung in diesem Forum!!!
>dass ein int mindestens 16 Bit groß zu sein hat.
... und auf ein 8-Bitter mit zwei Bytezugriffen beschrieben/gelesen
wird, die von einem Interrupt problemlos unterbrochen werden können.
@Experte
Viel Spaß bei der Fehlersuche von sporadisch auftretenden Fehlern. Oder
noch besser, laß Deinen Kunden suchen :-)
Im Standard steht zwar ein Mindestwertebereich für "int" drin, es steht
aber nicht drin, dass du für alle im Interrupt verwendeten Variablen
mindestens "int" zu verwenden hast.
@gasst
Also wenn du deinen Ringbuffer so programmieren willst, dass
du Leser und Schreiber synchronisieren mußt, hast du grundlegend
was nicht verstanden. Im Normalfall nimmt man für Lese- und Schreibindex
einen
Typ, der der Datenbusbreite des entsprechenden Prozessors entspricht.
Da muss nix synchronisiert werden.
Wenn du, gasst, auf deinen 8-Bittern damit Probleme bekommst, weil du
den falschen Typ gewählt hast, weiß ich auch nicht...
...und jetzt komm nicht mit dem Frage-Argument "und was ist, wenn ich
mehr als 256 Elemente ablegen will..?"
...kriegste eh nich in den RAM von deinem 8Bitter.
>...kriegste eh nich in den RAM von deinem 8Bitter.
Da hast Du natürlich Recht. Ein 8-Bitter hat ja maximal 256 Byte RAM.
Hatte ich nicht dran gedacht.
Experte wrote:
> mehr als 256 Elemente ablegen will..?"> ...kriegste eh nich in den RAM von deinem 8Bitter.
Mumpitz, der ATmega1284P hat z.B. 16384 Byte internen SRAM.
Soll ich dann deinetwegen 16128 Byte ungenutzt lassen?
Viele 8-Bitter (8051, AVR) können 64kB Daten oder Code linear zugreifen.
Sie benötigen aber dazu 2 Zugriffe, um den Pointer zu laden.
Peter
Hallo,
ein PIC Spezi hier?
ich weiss nichtr genau, ob ich in der ISR das BF Bit abfragen sollte
oder nicht? Die ISR kommt ja nur, wenn ein Byte da ist. Andere Frage:
Was ist mit dem Overflow Bit? Muss das berücksichtigt werden? Wird bei
Stau auf der Datenrutsche gesetzt
1
#define DEFAULT_VALUE 0xff
2
uint8_tSPI_ByteToSend=DEFAULT_VALUE;// Von der Statemachine bestimmtes Sendebyte
3
4
#int_SSP
5
voidSPI_ISR()
6
{
7
staticuint8_tempfang;
8
9
LED_ROT=1;
10
11
// Daten des Masters abholen
12
if(SSPSTAT_BF)
13
{
14
empfang=SSPBUF;// Masterbyte holen
15
SSPBUF=SPI_ByteToSend;// Antwort in SPI einschreiben
Peter Dannegger wrote:
> Christian J. wrote:>> ein PIC Spezi hier?>> Und andere Threads besser nicht hijacken.
Ähm, ich glaube, diesen hier hat Christian J. selbst aufgemacht. ;)
Gruss
Andreas
Andreas Vogt wrote:
>> Und andere Threads besser nicht hijacken.>> Ähm, ich glaube, diesen hier hat Christian J. selbst aufgemacht. ;)
Ich bezog das auch mehr auf das Thema.
Man kriegt einfach mehr Antworten, wenn man einen neuen Thread macht,
wenn das Thema wechselt.
Peter
Hallo Peter,
also, nach 3 Nächten bin ich nun fertig, ich arbeite mit 2 Fifos, einen
für Empfang, einen fürs Senden des Antwortframes.
In der Hauptroutine stehen 3 Funktionen:
Check_Input_Fifo (Daten in globalen Struct extrahieren)
AnalzyeData (Was muss gemacht werden? Bereite Antwort vor)
SendData (Antwort senden)
Die ISR zieht sich aus dem tx_Fifo ihre Daten oder schreibt sie in den
rx_Fifo.
Was mich nur nervt ist, dass man wohl nicht drumherum kommt die ISR ab
und zu abzuschalten, damit sie nicht auf die Fifos zugreift während
diese gerade gefüllt oder abgearbeitet werden. Dann kann es passieren,
dass es zu "Leerbytes" im Antwortframe kommt, wenn der Master
schlichtweg in dem Moment anfragt wo der Antwortframe noch nicht ganz
zusammengebaut wurde.
Was ich suche ist eine anerkannte Struktur solcher Sachen als Vorlage,
die sich möglichst am ISO/OSI Modell orientiert und nicht zu umfangreich
sein sollte. ich möchte die SPI Routinen völlig unabhängig überall
nutzen können.
Hast Du da was? Oder jemand anders?
Christian J. wrote:
> Was mich nur nervt ist, dass man wohl nicht drumherum kommt die ISR ab> und zu abzuschalten, damit sie nicht auf die Fifos zugreift während> diese gerade gefüllt oder abgearbeitet werden. Dann kann es passieren,> dass es zu "Leerbytes" im Antwortframe kommt, wenn der Master> schlichtweg in dem Moment anfragt wo der Antwortframe noch nicht ganz> zusammengebaut wurde.
Das mit dem Empfang habe ich ja mit dem Framepuffer gelöst. Der wird
erst angefaßt, wenn das Frameende erkannt wurde.
Das Senden kann man so lösen, daß zwar der Sendepuffer mit den Daten
befüllt wird, aber der In-Index wird erst aktualisiert, wenn das letzte
Byte eines Frame im Puffer ist.
Der Sende-FIFO darf ja erst senden, wenn die In-/Out-Indexe
unterschiedlich sind.
Ich arbeite mit Index, statt Pointer, da meine Puffer <256 Byte sind und
somit der Code deutlich kleiner und auch keine Interruptsperre nötig ist
für den atomaren Zugriff.
Peter
Hallo Peter,
alles klar, verstehe ich so, dass ich zwei Flags einführen muss:
f_ValidDataInRXFIFO
f_ReadyToSendTXFIFO
Das letzte Flag ist für die ISR gedacht, damit die erst den Fifo
auslutscht, wenn der Datensatz darin voll zusammen gebaut wurde.
Beim RX FIFo lese ich immer aus, wenn delta(write,read) grösser 1 ist,
also mindestens ein 1 byte vorliegt. Kommen die anderen dann nicht
binnen Timeout wird alles resettet.
Ich sehe schon die nächste Nacht vor mir in der meine Frau nachts um 2
hochkommt und fragt wann ich ins Bett komme :-)