Forum: Mikrocontroller und Digitale Elektronik Datenempfang in ISR - Welche Strategie?


von Christian J. (elektroniker1968)


Lesenswert?

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

von Michael H. (mah)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@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

von Christian J. (elektroniker1968)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Stefan E. (sternst)


Lesenswert?

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.

von Matthias L. (Gast)


Lesenswert?

Was spricht dagegen, einfach den ankommenden Datenstrom in einem Array 
(zeischen)zuspeichern und später (in main) auszuwerten?

Also etwa so:
1
ISR ( irgendwas empfangen)
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:
1
if ( ich hab was empfangen )
2
{
3
  // Header prüfen
4
  Puffer[0];
5
  // ...
6
7
  // Chksumme prüfen
8
  Puffer[n];
9
 ...

von 8421 (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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:
1
union combuf{
2
  u8 d[1];
3
  struct{
4
    u8 comstart;
5
    u8 length;
6
    u8 command;
7
    u8 dat[COM_DATALEN];
8
  }com;
9
};
10
11
union combuf com;
12
13
14
void command( void )
15
{
16
  static u8 idx = 0;
17
  u8 rec;
18
19
  if( !kbhit() )                        // no new byte received
20
    return;
21
  rec = ugetchar();                     // get byte
22
  com.d[idx] = rec;                     // store it
23
  if( com.com.comstart == COMSTART )    // byte 0 = command start
24
    idx++;
25
  if( idx >= 2 ){                       // length byte received
26
    if( com.com.length > sizeof(union combuf) ){        // above buffer limit
27
      idx = 0;
28
      return;
29
    }
30
    if( idx >= com.com.length ){
31
      if( get_crc() )
32
        uputchar( CRC_ERR );            // CRC error
33
      else
34
        exec();                         // execute command
35
      idx = 0;                          // wait for next command
36
    }
37
  }
38
}


Peter

von Christian J. (elektroniker1968)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von 8421 (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
  char c = 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
  Schreibe Zeichen in Ringbuffer( UDR );
4
}
5
6
....
7
int main()
8
{
9
   ...
10
11
   while( 1 ) {
12
13
     ....
14
15
     if( Zeichen in Ringbuffer ) {
16
       cli();
17
       c = Hole Zeichen aus Ringbuffer;
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.

von Christian J. (Gast)


Lesenswert?

Leute,

ich Danke Euch für diese ausführlichen Ausführungen! :astrein-smiley:
Werde ich heute abend mal umsetzen.

von Christian J. (Gast)


Lesenswert?

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.

von 8421 (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Andreas V. (tico)


Lesenswert?

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

von Matthias L. (Gast)


Lesenswert?

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?

von Andreas V. (tico)


Lesenswert?

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

von Matthias L. (Gast)


Lesenswert?

>Aber die ersten beiden Bytes..

Natürlich, aber was muss, das muss...

von Thomas W. (wagneth)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Andreas V. (tico)


Lesenswert?

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

von Christian J. (elektroniker1968)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von Andreas V. (tico)


Lesenswert?

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
1
  if( com.com.comstart == COMSTART )    // byte 0 = command start
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(union combuf) )
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

von Peter D. (peda)


Lesenswert?

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

von 8421 (Gast)


Lesenswert?

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

von Michael H. (mah)


Lesenswert?

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

von Andreas V. (tico)


Lesenswert?

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

von Manchmal (Gast)


Lesenswert?

Weil ein PC Uart das zB nicht kann ? Und auch wenn es das koennte, mit 
einem generischen Blockdevice Treiber obendrauf...

von (prx) A. K. (prx)


Lesenswert?

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.

von Andreas V. (tico)


Lesenswert?

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.

von Manchmal (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Manchmal (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

Du musst kein Windows oder Linux verwenden. Es gibt ja FreeDOS.

von Michael H. (mah)


Lesenswert?

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

von Matthias L. (Gast)


Lesenswert?

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


?

von Michael H. (mah)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Matthias L. (Gast)


Lesenswert?

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

von Michael H. (mah)


Lesenswert?

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

von Matthias L. (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Andreas V. (tico)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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!

von Michael H. (mah)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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?

von Experte (Gast)


Lesenswert?

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

von Gasst (Gast)


Lesenswert?

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

von Experte (Gast)


Lesenswert?

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

von Stefan E. (sternst)


Lesenswert?

Experte wrote:

> denn int ist auf einem 8Bitter 8Bit breit.

Nö. Der C Standard schreibt vor, dass ein int mindestens 16 Bit groß zu 
sein hat.

von Gasst (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Experte (Gast)


Lesenswert?

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

von Matthias L. (Gast)


Lesenswert?

>mehr als 256 Elemente..
>...kriegste eh nich in den RAM von deinem 8Bitter.

mit WinZip schon ;-)

von Gasst (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

Peter, dein Ironiedetektor müsste mal geputzt weden ;-).

von Experte (Gast)


Lesenswert?

Er denkt tatsächlich nur in Bytes...
256 Elemente != 256 Bytes, gasst

von Christian J. (elektroniker1968)


Lesenswert?

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_t  SPI_ByteToSend = DEFAULT_VALUE;   // Von der Statemachine bestimmtes Sendebyte
3
4
#int_SSP
5
void SPI_ISR()
6
{
7
  static uint8_t empfang;
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
16
17
      if (FIFO_Entries < MAXSIZE)    // Ringpuffer füllen
18
        FIFO_Write(empfang);
19
20
    SPI_ByteToSend = DEFAULT_VALUE;  // Sendebyte zurücksetzen
21
  }
22
  SSPCON_SSPOV = 0;
23
24
  LED_ROT = 0;
25
}

von Peter D. (peda)


Lesenswert?

Christian J. wrote:
> ein PIC Spezi hier?

Welcher denn?

PIC10,12,16,18,24,30,32,33 ...

Und andere Threads besser nicht hijacken.


Peter

von Andreas V. (tico)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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?

von Peter D. (peda)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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

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.