Frage

Ich versuche also, ein Kommunikationsprotokoll für die serielle Kommunikation zu definieren. Ich möchte 4-Byte-Zahlen an das Gerät senden können, bin mir aber nicht sicher, wie ich sicherstellen kann, dass das Gerät sie beim richtigen Byte empfängt.

Zum Beispiel, wenn ich versenden möchte

0x1234abcd 0xabcd3f56 ...

Wie stelle ich sicher, dass das Gerät nicht an der falschen Stelle mit dem Lesen beginnt und das erste Wort wie folgt erhält:

0xabcdabcd

Gibt es eine clevere Möglichkeit, dies zu tun?Ich habe darüber nachgedacht, eine Markierung für den Anfang einer Nachricht zu verwenden, aber was ist, wenn ich die Nummer senden möchte, die ich als Daten ausgewählt habe?

War es hilfreich?

Lösung

Warum nicht eine schicken? start-of-message Byte gefolgt von einem length-of-data Byte, wenn Sie wissen, wie groß die Daten sein werden?

Alternativ können Sie wie andere Binärprotokolle vorgehen und nur Pakete fester Größe mit einem festen Header senden.Angenommen, Sie senden nur 4 Bytes, dann wissen Sie, dass Sie vor dem eigentlichen Dateninhalt ein oder mehrere Bytes Header haben.

Bearbeiten: Ich glaube, du verstehst mich falsch.Ich meine damit, dass der Client Bytes immer entweder als Header oder als Daten betrachten soll, nicht basierend auf dem Wert, sondern vielmehr basierend auf der Position im Stream.Angenommen, Sie senden vier Datenbytes, dann wäre ein Byte das Header-Byte.

+-+-+-+-+-+
|H|D|D|D|D|
+-+-+-+-+-+

Der Client wäre dann eine ziemlich einfache Zustandsmaschine im Sinne von:

int state = READ_HEADER;
int nDataBytesRead = 0;
while (true) {
  byte read = readInput();
  if (state == READ_HEADER) {
    // process the byte as a header byte
    state = READ_DATA;
    nDataBytesRead = 0;
  } else {
    // Process the byte as incoming data
    ++nDataBytesRead;
    if (nDataBytesRead == 4) 
    {
      state = READ_HEADER;
    }
  }
} 

Das Besondere an diesem Setup ist, dass es nicht auf den tatsächlichen Inhalt eines Bytes ankommt, sondern auf die Position im Stream, ob es sich bei dem Byte um ein Header-Byte handelt.Wenn Sie eine variable Anzahl von Datenbytes haben möchten, fügen Sie dem Header ein weiteres Byte hinzu, um die Anzahl der darauf folgenden Datenbytes anzugeben.Auf diese Weise spielt es keine Rolle, ob Sie denselben Wert wie den Header im Datenstrom senden, da Ihr Client ihn niemals als etwas anderes als Daten interpretiert.

Andere Tipps

Netzschnur

Für diese Anwendung vielleicht das relativ einfache „Netzschnur„Format ist ausreichend.

Zum Beispiel der Text "Hallo Welt!" kodiert als:

12:hello world!,

Die leere Zeichenfolge wird als die drei Zeichen kodiert:

0:,

die als Reihe von Bytes dargestellt werden kann

'0' ':' ','

Das Wort 0x1234abcd in einem Netstring (unter Verwendung von Netzwerk-Byte-Reihenfolge), gefolgt vom Wort 0xabcd3f56 in einem anderen Netzstring, wird als Bytereihe kodiert

'\n' '4' ':' 0x12 0x34 0xab 0xcd ',' '\n'
'\n' '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

(Das Zeilenumbruchzeichen „ “ vor und nach jedem Netzstring ist optional, erleichtert aber das Testen und Debuggen.)

Frame-Synchronisation

Wie stelle ich sicher, dass das Gerät nicht an der falschen Stelle mit dem Lesen beginnt?

Die allgemeine Lösung für Frame-Synchronisation Das Problem besteht darin, in einen temporären Puffer einzulesen, in der Hoffnung, dass wir an der richtigen Stelle mit dem Lesen begonnen haben.Später führen wir einige Konsistenzprüfungen für die Nachricht im Puffer durch.Wenn die Nachricht den Scheck fehlschlägt, ist etwas schief gelaufen, also werfen wir die Daten im Puffer weg und beginnen von vorne.(Wenn es eine wichtige Nachricht war, hoffen wir, dass der Sender sie erneut sendet).

Wenn das Serienkabel beispielsweise in der Hälfte des ersten Netstrings angeschlossen ist, sieht der Empfänger die Byte -Zeichenfolge:

0xab 0xcd ',' '\n' '\n'  '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

Da der Empfänger klug genug ist, auf das „:“ zu warten, bevor er erwartet, dass es sich bei dem nächsten Byte um gültige Daten handelt, kann der Empfänger die erste Teilnachricht ignorieren und die zweite Nachricht dann korrekt empfangen.

In manchen Fällen wissen Sie im Voraus, wie lang die gültige(n) Nachricht(en) sein wird/sind;Dadurch kann der Empfänger noch einfacher erkennen, dass er an der falschen Stelle mit dem Lesen begonnen hat.

Senden der Start-of-Message-Markierung als Daten

Ich habe darüber nachgedacht, eine Markierung für den Anfang einer Nachricht zu verwenden, aber was ist, wenn ich die Nummer senden möchte, die ich als Daten ausgewählt habe?

Nach dem Senden des Netstring-Headers sendet der Sender die Rohdaten unverändert – auch wenn sie zufällig wie die Markierung für den Beginn der Nachricht aussehen.

Im Normalfall verfügt der Empfänger bereits über Frame-Sync.Der NetString -Parser hat bereits die "Länge" und den ":" -Header gelesen, so Byte oder die "," Fußzeile Byte.

Pseudocode

// netstring parser for receiver
// WARNING: untested pseudocode
// 2012-06-23: David Cary releases this pseudocode as public domain.

const int max_message_length = 9;
char buffer[1 + max_message_length]; // do we need room for a trailing NULL ?
long int latest_commanded_speed = 0;
int data_bytes_read = 0;

int bytes_read = 0;
int state = WAITING_FOR_LENGTH;

reset_buffer()
    bytes_read = 0; // reset buffer index to start-of-buffer
    state = WAITING_FOR_LENGTH;

void check_for_incoming_byte()
    if( inWaiting() ) // Has a new byte has come into the UART?
        // If so, then deal with this new byte.
        if( NEW_VALID_MESSAGE == state )
            // oh dear. We had an unhandled valid message,
            // and now another byte has come in.
            reset_buffer();
        char newbyte = read_serial(1); // pull out 1 new byte.
        buffer[ bytes_read++ ] = newbyte; // and store it in the buffer.
        if( max_message_length < bytes_read )
            reset_buffer(); // reset: avoid buffer overflow

        switch state:
            WAITING_FOR_LENGTH:
                // FIXME: currently only handles messages of 4 data bytes
                if( '4' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_COLON;
            WAITING_FOR_COLON:
                if( ':' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_DATA;
                    data_bytes_read = 0;
            WAITING_FOR_DATA:
                // FIXME: currently only handles messages of 4 data bytes
                data_bytes_read++;
                if( 4 >= data_bytes_read )
                    state = WAITING_FOR_COMMA;
            WAITING_FOR_COMMA:
                if( ',' != newbyte )
                    reset_buffer(); // doesn't look like a valid message.
                else
                    // otherwise, it looks good -- move to next state
                    state = NEW_VALID_MESSAGE;

void handle_message()
    // FIXME: currently only handles messages of 4 data bytes
    long int temp = 0;
    temp = (temp << 8) | buffer[2];
    temp = (temp << 8) | buffer[3];
    temp = (temp << 8) | buffer[4];
    temp = (temp << 8) | buffer[5];
    reset_buffer();
    latest_commanded_speed = temp;
    print( "commanded speed has been set to: " & latest_commanded_speed );
}

void loop () # main loop, repeated forever
    # then check to see if a byte has arrived yet
    check_for_incoming_byte();
    if( NEW_VALID_MESSAGE == state ) handle_message();
    # While we're waiting for bytes to come in, do other main loop stuff.
    do_other_main_loop_stuff();

Mehr Tipps

Wenn ich ein serielles Kommunikationsprotokoll definiere, stelle ich das Testen und Debuggen vor viel Dies ist einfacher, wenn das Protokoll immer für Menschen lesbare ASCII-Textzeichen anstelle beliebiger Binärwerte verwendet.

Frame-Synchronisation (wieder)

Ich habe darüber nachgedacht, eine Markierung für den Anfang einer Nachricht zu verwenden, aber was ist, wenn ich die Nummer senden möchte, die ich als Daten ausgewählt habe?

Wir haben bereits den Fall behandelt, dass der Empfänger bereits über eine Frame-Synchronisierung verfügt.Der Fall, dass der Empfänger noch keine Frame-Synchronisierung hat, ist ziemlich chaotisch.

Die einfachste Lösung besteht darin, dass der Sender eine Reihe von harmlosen Bytes (möglicherweise Neulinien oder Raumzeichen), die Länge der maximal möglichen gültigen Nachricht, als Präambel kurz vor jedem Netstring sendet.Unabhängig davon, in welchem ​​Zustand sich der Empfänger befindet, wenn das serielle Kabel angeschlossen ist, führen diese harmlosen Bytes den Empfänger schließlich in den Status "Waiting_for_Length".Und wenn der Tranmitter den Paketkopf (Länge gefolgt von ":") sendet, erkennt der Empfänger sie korrekt als Paketkopfer und hat die Rahmensynchronisierung wiederhergestellt.

(Es ist nicht wirklich notwendig, dass der Sender diese Präambel vorher sendet jeden Paket.Vielleicht könnte der Sender es für 1 von 20 Paketen senden;Dann stellt der Empfänger garantiert die Frame-Synchronisierung in 20 Paketen (normalerweise weniger) wieder her, nachdem das serielle Kabel angeschlossen wurde.

andere Protokolle

Andere Systeme verwenden eine einfache Fletcher-32-Prüfsumme oder etwas Komplizierteres, um viele Arten von Fehlern zu erkennen, die das Netstring-Format nicht erkennen kann ( A, B ) und kann auch ohne Präambel synchronisieren.

Viele Protokolle verwenden einen speziellen „Paketanfang“-Marker und verschiedene „Escape“-Techniken, um zu vermeiden, dass tatsächlich ein wörtliches „Paketanfang“-Byte in den übertragenen Daten gesendet wird, selbst wenn die tatsächlichen Daten, die wir senden möchten, dies tatsächlich tun dieser Wert.( Konsistentes Overhead-Byte-Stuffing, etwas Füllung, zitiert-druckbar und andere Arten von Binär-zu-Text-Kodierung, usw.).

Diese Protokolle haben den Vorteil, dass der Empfänger sicher sein kann, dass, wenn wir die Markierung „Paketanfang“ sehen, es sich um den tatsächlichen Anfang des Pakets handelt (und nicht um irgendein Datenbyte, das zufällig denselben Wert hat).Dies erleichtert den Umgang mit Synchronisationsverlusten erheblich – einfach Bytes bis zur nächsten „Paketanfangs“-Markierung verwerfen.

Viele andere Formate, einschließlich des Netstring-Formats, ermöglichen die Übertragung jedes möglichen Bytewerts als Daten.Daher müssen Empfänger beim Umgang mit dem Start-of-Header-Byte intelligenter sein könnte ein tatsächlicher Start-of-Header sein, oder könnte ein Datenbyte sein – aber zumindest müssen sie sich nicht mit „Escape“ oder dem überraschend großen Puffer befassen, der im schlimmsten Fall erforderlich ist, um nach dem Escape eine „feste 64-Byte-Datennachricht“ zu speichern.

Die Wahl eines Ansatzes ist wirklich nicht einfacher als der andere – sie verschiebt lediglich die Komplexität an eine andere Stelle, wie von vorhergesagt Wasserbetttheorie.

Würde es Ihnen etwas ausmachen, die Diskussion über verschiedene Möglichkeiten zur Handhabung des Start-of-Header-Bytes, einschließlich dieser beiden Möglichkeiten, noch einmal zu überfliegen Wikibook zur seriellen Programmierungund bearbeiten dieses Buch, um es besser zu machen?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top