выравнивание байтов при последовательной связи

StackOverflow https://stackoverflow.com/questions/9468615

Вопрос

Итак, я пытаюсь определить протокол связи для последовательной связи. Я хочу иметь возможность отправлять на устройство 4-байтовые числа, но не знаю, как убедиться, что устройство начинает воспринимать их с правильным байтом.

Например, если я хочу отправить

0x1234abcd 0xabcd3f56 ...

как мне убедиться, что устройство не начнет читать не в том месте и не получит первое слово как:

0xabcdabcd

Есть ли умный способ сделать это?Я подумал об использовании маркера начала сообщения, но что, если я захочу отправить выбранный номер в качестве данных?

Это было полезно?

Решение

Почему бы не отправить start-of-message байт, за которым следует length-of-data байт, если вы знаете, насколько большими будут данные?

Альтернативно, поступайте так же, как другие двоичные протоколы, и отправляйте только пакеты фиксированного размера с фиксированным заголовком.Допустим, вы отправите только 4 байта, тогда вы знаете, что у вас будет один или несколько байтов заголовка перед фактическим содержимым данных.

Редактировать: Я думаю, ты меня неправильно понимаешь.Я имею в виду, что клиент всегда должен рассматривать байты как заголовок или данные, не на основе значения, а скорее на основе позиции в потоке.Предположим, вы отправляете четыре байта данных, тогда один байт будет байтом заголовка.

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

Особенность этой настройки в том, что то, является ли байт байтом заголовка, определяется не фактическим содержимым байта, а скорее позицией в потоке.Если вы хотите иметь переменное количество байтов данных, добавьте в заголовок еще один байт, чтобы указать количество следующих за ним байтов данных.Таким образом, не имеет значения, отправляете ли вы то же значение, что и заголовок в потоке данных, поскольку ваш клиент никогда не будет интерпретировать его как что-то еще, кроме данных.

Другие советы

сетевая строка

Для этого приложения, возможно, относительно простой "сетевая строка"Формат адекватный.

Например, текст "Привет, мир!" кодирует как:

12:hello world!,

Пустая строка кодируется тремя символами:

0:,

который можно представить как последовательность байтов

'0' ':' ','

Слово 0x1234abcd в одной сетевой строке (с использованием порядок сетевых байтов), за которым следует слово 0xabcd3f56 в другой сетевой строке, кодируется как последовательность байтов

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

(Символ новой строки ' ' до и после каждой сетевой строки не является обязательным, но упрощает тестирование и отладку).

синхронизация кадров

как мне убедиться, что устройство не начнет читать в неправильном месте

Общее решение проблемы синхронизация кадров проблема в том, чтобы прочитать во временный буфер, надеясь, что мы начали чтение с нужного места.Позже мы выполняем некоторые проверки целостности сообщения в буфере.Если сообщение не удалось, проверка, что -то пошло не так, поэтому мы выбрасываем данные в буфере и начинаем все сначала.(Если это было важное сообщение, мы надеемся, что передатчик отправит его повторно).

Например, если последовательный кабель подключен в полпути через первую сеть, приемник видит байтовую строку:

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

Поскольку получатель достаточно умен, чтобы дождаться символа «:», прежде чем ожидать, что следующий байт будет действительными данными, получатель может игнорировать первое частичное сообщение, а затем правильно принять второе сообщение.

В некоторых случаях вы заранее знаете, какова будет допустимая длина сообщения;это еще больше упрощает приемнику обнаружение того, что он начал чтение не с того места.

отправка маркера начала сообщения в качестве данных

Я подумал об использовании маркера начала сообщения, но что, если я захочу отправить выбранный номер в качестве данных?

После отправки заголовка сетевой строки передатчик отправляет необработанные данные как есть, даже если они выглядят как маркер начала сообщения.

В обычном случае у ресивера уже есть кадровая синхронизация.Сантринг NetString уже прочитал «длину» и заголовок «:», поэтому анализатор 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, а не любые произвольные двоичные значения.

синхронизация кадров (снова)

Я подумал об использовании маркера начала сообщения, но что, если я захочу отправить выбранный номер в качестве данных?

Мы уже рассмотрели случай, когда у получателя уже есть кадровая синхронизация.Случай, когда приемник еще не имеет кадровой синхронизации, довольно неприятен.

Самое простое решение состоит в том, чтобы передатчик отправил серию безвредных байтов (возможно, новые линии или космические символы), длина максимально возможного допустимого сообщения, как преамбула непосредственно перед каждым сети.Независимо от того, в каком состоянии находится приемник, когда подключен последовательный кабель, эти безвредные байты в конечном итоге приводят приемник в состояние «ware_for_length».И затем, когда трансмиттер отправляет заголовок пакета (длина, за которой следует ":"), приемник правильно распознает его как заголовок пакета и восстановил синхронизацию рамы.

(На самом деле передатчику не обязательно отправлять эту преамбулу перед каждый пакет.Возможно, передатчик мог бы отправить его для 1 пакета из 20;тогда приемник гарантированно восстановит синхронизацию кадров за 20 пакетов (обычно меньше) после подключения последовательного кабеля).

другие протоколы

Другие системы используют простую контрольную сумму Fletcher-32 или что-то более сложное для обнаружения многих видов ошибок, которые не может обнаружить формат сетевой строки ( а, б ) и может синхронизировать даже без преамбулы.

Многие протоколы используют специальный маркер «начала пакета» и различные методы «экранирования», чтобы избежать фактической отправки буквального байта «начала пакета» в передаваемых данных, даже если реальные данные, которые мы хотим отправить, имеют это значение.( Последовательное заполнение служебных байтов, немного начинки, цитируемый-для печати и другие виды кодирование двоичного текста в текст, и т. д.).

Преимущество этих протоколов заключается в том, что получатель может быть уверен, что когда мы видим маркер «начало пакета», это фактическое начало пакета (а не какой-то байт данных, который случайно имеет то же значение).Это значительно упрощает обработку потери синхронизации — просто отбрасывайте байты до следующего маркера «начала пакета».

Многие другие форматы, включая формат netstring, позволяют передавать любое возможное значение байта в качестве данных.Таким образом, получатели должны быть умнее в обработке байта начала заголовка, который мощь быть фактическим началом заголовка или мощь быть байтом данных - но, по крайней мере, им не придется иметь дело с «экранированием» или с удивительно большим буфером, необходимым в худшем случае для хранения «фиксированного 64-байтового сообщения данных» после экранирования.

Выбор одного подхода на самом деле не проще другого — он просто переносит сложность в другое место, как и предсказывает теория водяного слоя.

Не могли бы вы бегло просмотреть обсуждение различных способов обработки байта начала заголовка, включая эти два способа, в Викибук по последовательному программированиюи редактирование этой книги, чтобы сделать ее лучше?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top