выравнивание байтов при последовательной связи
-
13-11-2019 - |
Вопрос
Итак, я пытаюсь определить протокол связи для последовательной связи. Я хочу иметь возможность отправлять на устройство 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-байтового сообщения данных» после экранирования.
Выбор одного подхода на самом деле не проще другого — он просто переносит сложность в другое место, как и предсказывает теория водяного слоя.
Не могли бы вы бегло просмотреть обсуждение различных способов обработки байта начала заголовка, включая эти два способа, в Викибук по последовательному программированиюи редактирование этой книги, чтобы сделать ее лучше?