5

そのため、シリアル通信用の通信プロトコルを定義しようとしています.4バイトの数値をデバイスに送信できるようにしたいのですが、デバイスが正しいバイトでそれを取得し始めることを確認する方法がわかりません.

たとえば、送信したい場合

0x1234abcd 0xabcd3f56 ...

デバイスが間違った場所から読み取りを開始して、最初の単語を次のように取得しないようにするにはどうすればよいですか。

0xabcdabcd

これを行う賢い方法はありますか?メッセージの開始にマーカーを使用することを考えましたが、選択した番号をデータとして送信するにはどうすればよいですか?

4

2 に答える 2

7

start-of-messageデータのサイズがわかっている場合は、1バイトの後に 1 バイトを送信してみませんlength-of-dataか?

または、他のバイナリ プロトコルと同様に、固定ヘッダーを持つ固定サイズのパッケージのみを送信します。4 バイトだけを送信するとすると、実際のデータ コンテンツの前に 1 バイト以上のヘッダーがあることがわかります。

編集:あなたは私を誤解していると思います。つまり、クライアントは、値ではなくストリーム内の位置に基づいて、バイトを常にヘッダーまたはデータのいずれかと見なす必要があるということです。たとえば、4 バイトのデータを送信する場合、1 バイトがヘッダー バイトになります。

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

クライアントは、次のような非常に基本的なステート マシンになります。

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

このセットアップに関する問題は、バイトがヘッダー バイトであるかどうかを決定するのは、バイトの実際の内容ではなく、ストリーム内の位置です。可変数のデータ バイトが必要な場合は、ヘッダーに別のバイトを追加して、それに続くデータ バイト数を示します。この方法では、クライアントがデータ以外のものとして解釈することは決してないため、データ ストリームでヘッダーと同じ値を送信しても問題ありません。

于 2012-02-27T16:47:16.377 に答える
5

ネットストリング

このアプリケーションでは、おそらく比較的単純な「netstring」形式で十分です。

たとえば、「He​​llo World!」というテキスト。次のようにエンコードします。

12:hello world!,

空の文字列は、次の 3 つの文字としてエンコードされます。

0:,

これは一連のバイトとして表すことができます

'0' ':' ','

1 つのネット文字列の 0x1234abcd という単語 (ネットワーク バイト オーダーを使用) と、それに続く別のネット文字列の 0xabcd3f56 という単語は、一連のバイトとしてエンコードされます。

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

(各ネット文字列の前後の改行文字 '\n' はオプションですが、テストとデバッグを容易にします)。

フレーム同期

デバイスが間違った場所から読み取りを開始しないようにする方法

フレーム同期の問題に対する一般的な解決策は、正しい場所から読み取りを開始できることを期待して、一時バッファーに読み取ることです。後で、バッファ内のメッセージに対していくつかの一貫性チェックを実行します。メッセージがチェックに失敗した場合、何か問題が発生したため、バッファ内のデータを破棄して最初からやり直します。(重要なメッセージである場合は、送信者が再送信することを願っています)。

たとえば、シリアル ケーブルが最初のネット文字列の途中に差し込まれている場合、受信者は次のバイト文字列を認識します。

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

レシーバーは、次のバイトが有効なデータであると期待する前に「:」を待機するほどスマートであるため、レシーバーは最初の部分的なメッセージを無視して、2 番目のメッセージを正しく受信することができます。

場合によっては、有効なメッセージの長さがどのくらいになるかが事前にわかっています。これにより、受信者が間違った場所で読み取りを開始したことをさらに簡単に検出できます。

メッセージ開始マーカーをデータとして送信

メッセージの開始にマーカーを使用することを考えましたが、選択した番号をデータとして送信するにはどうすればよいですか?

netstring ヘッダーを送信した後、送信側は未加工データをそのまま送信します。たとえそれがたまたまメッセージ開始マーカーのように見えたとしてもです。

通常の場合、受信機にはすでにフレーム同期があります。ネット文字列パーサーはすでに「長さ」と「:」ヘッダーを読み取っているため、ネット文字列パーサーは未加工のデータ バイトをバッファー内の正しい場所に直接配置します。たとえそれらのデータ バイトが「:」ヘッダーのように見えたとしてもです。バイトまたは「,」フッター バイト。

疑似コード

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

その他のヒント

シリアル通信プロトコルを定義するとき、任意のバイナリ値ではなく、常に人間が読める ASCII テキスト文字をプロトコルで使用すると、テストとデバッグがはるかに簡単になることがわかりました。

フレーム同期 (再度)

メッセージの開始にマーカーを使用することを考えましたが、選択した番号をデータとして送信するにはどうすればよいですか?

受信機がすでにフレーム同期している場合については、すでに説明しました。受信機がまだフレーム同期していない場合はかなり厄介です。

最も簡単な解決策は、送信機が一連の無害なバイト (おそらく改行または空白文字) を送信することです。これは、可能な最大有効メッセージの長さであり、各ネット文字列の直前のプリアンブルとして送信されます。シリアル ケーブルが差し込まれているときの受信機の状態に関係なく、これらの無害なバイトが最終的に受信機を "WAITING_FOR_LENGTH" 状態に追いやります。そして、送信機がパケットヘッダ(長さの後に「:」が続く)を送ると、受信機はそれをパケットヘッダとして正しく認識し、フレーム同期を回復しています。

(送信機がすべてのパケットの前にそのプリアンブルを送信する必要はありません。おそらく、送信機は 20 パケットのうちの 1 つでそれを送信できます。受信機は、シリアル ケーブルが切断された後、20 パケット (通常はそれ以下) でフレーム同期を回復することが保証されます。差し込む)。

その他のプロトコル

他のシステムは、単純な Fletcher-32 チェックサムまたはより複雑なものを使用して、ネット文字列形式では検出できない多くの種類のエラー ( ab ) を検出し、プリアンブルがなくても同期できます。

多くのプロトコルは、特別な「パケットの開始」マーカーを使用し、さまざまな「エスケープ」手法を使用して、実際に送信したい実際のデータに「パケットの開始」バイトが含まれている場合でも、実際に送信データで文字通りの「パケットの開始」バイトを送信しないようにします。その値。( Consistent Overhead Byte StuffingBit Stuffingquoted-printable、およびその他の種類のバイナリからテキストへのエンコーディングなど)。

これらのプロトコルには、受信者が「パケットの開始」マーカーを見たときに、それが実際のパケットの開始であることを確認できるという利点があります (偶然に同じ値を持つデータバイトではありません)。これにより、同期の喪失の処理がはるかに簡単になります。次の「パケットの開始」マーカーまでバイトを破棄するだけです。

netstring 形式を含む他の多くの形式では、任意の可能なバイト値をデータとして送信できます。したがって、レシーバーは、実際のヘッダーの開始である可能性があるか、データ バイトである可能性があるヘッダーの開始バイトをより賢く処理する必要がありますが、少なくとも「エスケープ」や最悪の場合、エスケープ後に「固定の 64 バイト データ メッセージ」を保持するために、驚くほど大きなバッファが必要でした。

ウォーターベッド理論で予測されているように、1 つのアプローチを選択することは、他のアプローチよりも単純ではありません。

シリアル プログラミング ウィキブックで、これら 2 つの方法を含むヘッダー開始バイトを処理するさまざまな方法の議論に目を通し、その本を編集して改善していただけませんか?

于 2012-06-23T19:00:30.687 に答える