Насколько безопасен \ n \ r в качестве стоп-байтов?


8

В моем сообщении UART мне нужно знать начальный байт и конечный байт отправленного сообщения. Стартовый байт легкий, но стоповый, не так уж и много. Я реализовал два стоп-байта в конце моего сообщения, то есть \ n и \ r (десятичное и десятичное). UART работает только со значениями 0-255 байтов, так насколько это безопасно? Я могу себе представить, хотя и с низкой вероятностью, что мое сообщение может содержать значения «10 и 13» друг за другом, когда они не являются байтами остановки.

Есть ли лучший способ реализовать это?


7
Чтобы отправить произвольные данные, вы должны либо использовать пакеты, либо заполнять байтами. В вашем случае вероятность появления паттерна в определенном месте равна 1/65536. Который получает значение 1, если у вас достаточно длинный случайный поток данных.
Oldfart

4
Можете ли вы предоставить контекст, пожалуйста. Стоповые биты являются частью связи UART, но останавливают байты? Это звучит как чисто программная проблема и зависит от того, что было согласовано отправителем и получателем.
Уоррен Хилл

2
@MariusGulbrandsen, если ваши данные действительно произвольны и не являются строго текстовыми (например, ASCII), тогда нулевое завершение не будет работать; вам придется реализовать пакет.
RamblinRose

4
BTW: Это обычная практика, чтобы поставить возврат каретки до подачи строки: "\x0D\x0A".
Адриан Маккарти

3
@AdrianMcCarthy Я думаю, что смысл в том, чтобы поменять его, чтобы свести к минимуму вероятность того, что он будет действительной последовательностью. Тем не менее, два конца строки Windows в строке дали бы вам, \r\n\r\nкоторый содержит \n\rпоследовательность в середине ...
Майк Карон

Ответы:


14

Есть разные способы предотвратить это:

  • Убедитесь, что вы никогда не отправляете комбинацию 10/13 в своих обычных сообщениях (только в качестве стоп-байтов). Например, чтобы отправить 20 21 22 23 24 25:

20 21 22 23 24 25 10 13

  • Escape 10 и 13 (или все не ASCII символы с escape-символом, например. Поэтому для отправки 20 21 10 13 25 26 send: (см. Комментарий / credits для: DanW)

20 21 1b 10 1b 13 25 26

  • Определите пакет при отправке сообщений. Например, если вы хотите отправить сообщение 20 21 22 23 24 25, вместо этого добавьте количество отправленных байтов, поэтому пакет:

<nr_of_data_bytes> <данные>

Если ваши сообщения не более 256 байт, отправьте:

06 20 21 22 23 24 25

Итак, после получения 6 байтов данных вы знаете, что это конец; Вам не нужно отправлять 10 13 впоследствии. И вы можете отправить 10 13 внутри сообщения. Если ваши сообщения могут быть длиннее, вы можете использовать 2 байта для размера данных.

Обновление 1: еще один способ определения пакетов

Другой альтернативой является отправка команд, которые имеют определенную длину и могут иметь много отклонений, например

10 20 30 (команда 10, которая всегда имеет 2 байта данных)

11 30 40 50 (команда 11, которая всегда имеет 3 байта данных)

12 06 10 11 12 13 14 15 (команда 12 + 1 байт для количества следующих байтов данных)

13 01 02 01 02 03 ... (Команда 13 + 2 байта (01 02 для 256 + 2 = 258 следующих байтов данных)

14 80 90 10 13 (Команда 14, за которой следует строка ASCII, заканчивающаяся 10 13)

Обновление 2: плохое соединение / потеря байтов

Все вышеперечисленное работает только тогда, когда линия UART правильно отправляет байты. Если вы хотите использовать более надежные способы отправки, есть также много возможностей. Ниже приведены несколько:

  1. Отправка контрольной суммы в пакете (проверьте в Google CRC: проверка циклическим избыточным кодом). Если CRC в порядке, получатель знает, что сообщение было отправлено нормально (с высокой вероятностью).
  2. Если вам необходимо отправить сообщение повторно, то необходимо использовать механизм подтверждения (ACK / ответ) (например, отправитель отправляет что-то, получатель получает поврежденные данные, отправляет NACK (не подтверждено), отправитель может затем отправить снова.
  3. Тайм-аут: Если получатель не получил ACK или NACK вовремя, сообщение необходимо отправить повторно.

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


1
«Убедитесь, что вы никогда не отправляете комбинацию 10/13 в своих обычных сообщениях (только в качестве стоп-байтов)». - Вы не сказали , как отправлять данные , которые действительно включают в себя комбинацию 10/13 - вам нужно , чтобы избежать его. Таким образом, «20 10 13 23 10 13» может быть отправлено как «20 1b 10 1b 13 23» с 1b в качестве вашего escape-персонажа.
Дан W

1
Обратите внимание, что, используя предложенное поле длины, у вас возникнут проблемы, если ваша последовательная связь не работает и теряет один байт. Все будет не синхронизировано.
Йонас Шефер

@DanW Если вы используете первый или 2 байта в качестве количества байтов данных, не имеет значения, являются ли 10 или 13 частью этих данных ... Так что 20 10 13 23 10 13 можно отправить как 06 20 10 13 23 10 13 где 06 - число следующих байтов данных.
Мишель Кейзерс

@MichelKeijzers - да, но это второе решение, которое вы упомянули. В вашем первом решении отсутствует объяснение escape-последовательностей для предотвращения передачи стоп-байтов.
Дан W

Оба подхода работают и обычно используются, но у них есть свои преимущества и недостатки, которые вы можете добавить, если захотите, хотя это выходит за рамки того, о чем просил ФП.
Дан W

13

Насколько безопасен \ n \ r в качестве стоп-байтов?

Если вы отправляете отправку произвольных данных ->, вероятно, недостаточно надежно.

Распространенным решением является использование экранирования:

Давайте определим, что символы 0x02 (STX - начало кадра) и 0x03 (ETX - конец кадра) должны быть уникальными в потоке передаваемых данных. Таким образом, начало и конец сообщения могут быть безопасно обнаружены.

Если один из этих символов должен быть отправлен внутри фрейма сообщения, он заменяется префиксом escape-символа (ESC = 0x1b) и добавлением 0x20 к исходному символу.

Оригинальный персонаж заменен на

0x02 -> 0x1b 0x22  
0x03 -> 0x1b 0x23  
0x1b -> 0x1b 0x3b  

Получатель полностью изменяет этот процесс: каждый раз, когда он получает escape-символ, этот символ удаляется, а следующий символ вычитается 0x20.

Это только добавляет некоторые накладные расходы на обработку, но на 100% надежно (при условии отсутствия ошибок передачи, которые вы могли бы / должны проверить, дополнительно внедрив механизм контрольной суммы).


1
Хороший ответ. Обычный escape-символ, используемый для протоколов ASCII, был '\x10'DLE (Data Link Escape). Некоторые страницы Википедии предполагают, что DLE часто использовался противоположным образом: сказать, что следующий байт был управляющим символом, а не байтом данных. По моему опыту, это вообще противоположное значение для побега.
Адриан Маккарти

2
Следите за тем, что ваш буфер в худшем случае удваивается. Если память действительно ограничена, это может быть не лучшим решением.
ТехноСам

1
@Rev Каково обоснование для добавления 0x20 к оригинальному персонажу? Разве схема спасения не сработает без этого?
Ник Алексеев

1
@NickAlexeev: проще / быстрее определить фактические границы фрейма, если вы удалите любое другое вхождение зарезервированных символов из потока. Таким образом, вы можете разделить прием и разбор кадров (включая неоткрывающийся). Это может быть особенно актуально, если у вас очень медленный контроллер без FIFO и / или высокой скорости передачи данных. Таким образом, вы можете просто скопировать входящие байты (между STX / ETX) в буфер кадра по мере их поступления, пометить кадр как завершенный и выполнить обработку с более низким приоритетом.
1.0

@TechnoSam: Хороший вопрос.
1.0

5

Вы знаете, ASCII уже имеет байты для этих функций.

  • 0x01: начало заголовка - начальный байт
  • 0x02: начало текста - конец заголовка, начало полезной нагрузки
  • 0x03: конец текста - конец полезной нагрузки
  • 0x04: конец передачи - стоп-байт
  • 0x17: конец блока передачи - сообщение продолжается в следующем блоке

У этого также есть коды для различного использования в полезной нагрузке.

  • 0x1b: escape (escape следующий символ - используйте в полезной нагрузке, чтобы указать, что следующий символ не является структурой, описывающей коды, используемые в вашем протоколе)
  • 0x1c, 0x1d, 0x1e, 0x1f: файл, группа, запись и разделитель единиц соответственно - используются в качестве байта одновременной остановки и запуска для частей иерархических данных

В вашем протоколе должна быть указана самая высокая степень детализации ACK (0x06) и NAK (0x15), чтобы отрицательно подтвержденные данные могли быть переданы повторно. Вплоть до этой тончайшей детализации целесообразно иметь поле длины сразу после любого (неэкранированного) индикатора запуска и (как объяснено в других ответах) целесообразно следовать за любым (неэкранированным) индикатором остановки с помощью CRC.


Я буду отправлять произвольные данные, я думаю, это могло бы сбить с толку использование «\ n \ r» в моем вопросе, когда я не отправляю данные ASCII. Несмотря на то, что мне нравится этот ответ, он очень информативен при отправке ASCII через UART
CK

@MariusGulbrandsen: Пока ваш протокол определяет, где находится полезная нагрузка и какие коды необходимо экранировать в каждом разделе полезной нагрузки, вы можете отправлять что угодно, а не только текстовые данные.
Эрик Тауэрс,

4

UART не является отказоустойчивым по своей природе - мы говорим здесь о технологиях 1960-х годов.

Корень проблемы в том, что UART синхронизируется только один раз на 10 битов, что позволяет много тарабарщины проходить между этими периодами синхронизации. В отличие, например, от CAN, который дискретизирует каждый отдельный бит несколько раз.

Любая двухбитовая ошибка, возникающая внутри данных, повредит кадр UART и пройдет незамеченным. Битовые ошибки в стартовых / стоповых битах могут обнаруживаться или не обнаруживаться в форме ошибок переполнения.

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

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

Наиболее профессиональный способ обеспечения безопасной передачи по UART - наличие 16-битной контрольной суммы CRC в конце пакета. Все остальное не очень безопасно и имеет высокую вероятность пропущенных ошибок.

Тогда на аппаратном уровне вы можете использовать дифференциал RS-422 / RS-485, чтобы радикально улучшить надежность передачи. Это необходимо для безопасной передачи на большие расстояния. Уровень UART TTL должен использоваться только для связи на борту. RS-232 не должен использоваться для каких-либо других целей, кроме обратной совместимости со старым материалом.

В целом, чем ближе к оборудованию механизм обнаружения ошибок, тем эффективнее он работает. С точки зрения эффективности, дифференциальные сигналы добавляют больше всего, с последующей проверкой ошибок кадрирования / переполнения и т. Д. CRC16 добавляет немного, а затем добавляет «традиционное шарлатанство UART».


7
Этот совет довольно косвенный - вы фактически не ответили на заданный вопрос. В частности, предложенные вами решения могут решить другие проблемы, но они не решают основной проблемы вопроса на этой странице , а именно путаницы между кадрами и полезными данными. Самое большее, ваше предложение отклонит допустимые данные, встраивающие байт кадрирования из-за CRC или подобного сбоя, без возможности сообщить о них.
Крис Страттон,

3
На самом деле, этот ответ ухудшает ситуацию. Оригинал имел только байты данных и стоп-байты. Это добавляет третью категорию, байты CRC. И, как представлено здесь, они могут принимать любое значение, включая {10,13}.
MSalters

1
@MSalters: CRC может быть в шестнадцатеричном коде ASCII, чтобы предотвратить эту проблему. Еще одна хитрость, которую я видел на RS485, - это установить бит 7 в байт начала / адреса.
Транзистор

Re "МОЖЕТ, что выборки каждый отдельный бит несколько раз." : Фактическая выборка значения бита производится только один раз на бит. Что вы здесь имеете в виду? Какая-то проверка ошибок, как у отправителя? Часы синхронизации?
Питер Мортенсен

Инвертирование контрольной суммы было сделано таким образом, чтобы при суммировании всего блока данных получился ноль, что немного проще для кодирования и немного быстрее для выполнения. Кроме того, CRC намного лучше, чем вы думаете, посмотрите в Википедии.
toolforger

0

... Я могу себе представить, хотя и с низкой вероятностью, что мое сообщение может содержать значения "10 и 13" друг за другом, когда они не являются байтами остановки.

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

Есть несколько способов подойти ко всему этому.

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

Длина отправляемого пакета

Отправьте длину пакета в начале вместо [или в дополнение к] завершающего символа в конце.

Плюсы: полезная нагрузка отправляется в эффективном двоичном формате.

минусы: нужно знать длину пакета в начале передачи.

Избежать специальных символов

Избегайте специальных символов при отправке данных полезной нагрузки. Это уже объяснено в предыдущем ответе .

Плюсы: отправителю не нужно знать длину пакета в начале передачи.

минусы: немного менее эффективны, в зависимости от того, сколько байтов полезной нагрузки необходимо экранировать.

Данные полезной нагрузки закодированы так, что они не могут содержать начальные и конечные символы

Полезная нагрузка пакета кодируется таким образом, что он не может содержать символы начала и окончания. Обычно это делается путем отправки чисел в виде их представления ASCII или Hex-ASCII.

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

минусы: низкая эффективность. Для одного байта данных полезной нагрузки отправляется несколько байтов.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.