Когда следует переключаться с ASCII на расширенные последовательные протоколы?

Все мои микроконтроллерные устройства, которые обмениваются данными с ПК через UART, используют строки ASCII для отправки команд и получения данных (как это реализовано в Arduino). Это то, что я понял, когда начал копаться в электронике, и мне всегда было достаточно посылать голые струны. Однако я заметил, что большинство устройств, с которыми я сталкивался, используют сложные бинарные протоколы, которые включают функциональные коды, адреса и проверку ошибок CRC.

Когда приемлем базовый обмен данными ASCII, а когда следует рассмотреть более продвинутые возможности, такие как Modbus? Используют ли коммерческие устройства такой код ASCII? Промышленный?

Краткий ответ: когда это нужно вашему приложению. Да, коммерческие устройства используют ASCII. Возьмите GPS NMEA в качестве примера. (И снова я направлю сюда свой вопрос )
@EugeneSh.: Стоит отметить, что в NMEA есть поле контрольной суммы, и отбрасывание одного пакета выборки из-за сбоя контрольной суммы (что случается чаще, чем вы думаете) обычно не является критическим сбоем. Это вполне может быть не так для других протоколов... и существует множество используемых бинарных протоколов GPS (например, Garmin) для приложений, в которых это действительно может быть критично (или в которых частота дискретизации выше 1 Гц является обязательной). требуется, для которого NMEA слишком многословен). Хотя на самом деле это только укрепляет вашу точку зрения.

Ответы (8)

  1. ASCII и CRC не исключают друг друга. ASCII — это кодировка, а CRC — для проверки ошибок.

  2. ЧТО-НИБУДЬ может быть отправлено как ASCII. Мы, старики, наверняка помним UUEncoding, который превращает что угодно в строку ASCII.

  3. A) Для меня это обычно вопрос скорости и эффективности. Отправка большого 32-битного числа с помощью ASCII может занять много времени, но для отправки его в двоичном виде по последовательному протоколу требуется всего 4 байта.

    B) Отправка NUMBERS через ASCII означает, что вы должны преобразовать число в ASCII, что является явным дополнительным шагом (это часть того, что делает «printf»).

  4. Если вы каким-то образом потеряете свое место, облажаетесь, потеряете формат, получите неправильный порядок байтов и т. д., бинарный протокол связи, безусловно, может облажаться. Если вы отправляете ASCII, может быть проще восстановиться после ошибок, просто войдя и ПОСМОТРЕВ на поток данных.

+1 за «ASCII - это кодировка». Это не протокол; протоколы могут быть построены поверх ASCII.
Автоматическое восстановление после сбоя по сути не проще для текстового протокола, чем для бинарного, но проверка и отладка, безусловно, могут быть.
@НикДжонсон - абсолютно. Как только вы откроете файл в шестнадцатеричном редакторе, чтобы увидеть, что вы можете восстановить, вы уже находитесь в FUBAR, насколько SOP идет.
@nickjohnson, это не совсем так. ASCII дает вам множество вариантов внеполосного кадрирования / разделителя для облегчения синхронизации и восстановления, что потребует дополнительного экранирования, заполнения битов, временного интервала или других трюков, если канал используется для двоичных данных полной ширины.
@ChrisStratton Но вы не получаете ничего из этого автоматически; вы должны написать свой код, чтобы знать об этом.
Такие вещи, как разбиение на новую строку, фактически бесплатны во многих функциях последовательного доступа, и даже там, где это не так, они тривиальны. Вставка и удаление кадров в 8-битных двоичных данных, напротив, более сложны, а также означают, что закодированный размер количества данных зависит от их содержимого.
Я всегда отдаю предпочтение ASCII при написании протоколов из-за всех очевидных преимуществ (удобочитаемость, журналируемость и т. д.). Есть два случая, когда двоичный код имеет больше смысла: во-первых, если важна скорость и вам нужен двоичный файл, чтобы втиснуть в поток как можно больше данных, а во-вторых, незначительно, если вы намеренно пытаетесь запутать или даже зашифровать данные. stream, чтобы воспрепятствовать или предотвратить обратный инжиниринг. При этом я занимался реверс-инжинирингом бинарных протоколов, и это меня больше раздражало, чем мешало.
@J ... Я добавлю, что место для хранения на принимающей стороне когда-то было реальной проблемой, но на самом деле это уже не так.

Вот некоторые мысли по этому поводу:

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

На простейшем уровне можно сказать, что простой протокол связи имеет три уровня: физический, транспортный и прикладной. (Есть модели с большим количеством, например, OSI с 7 или TCP/IP с 4. Количество уровней не очень важно в контексте этого вопроса.)

Прикладной уровень — это уровень, с которым вы имеете дело непосредственно в своем коде, и в центре внимания находится вопрос. Что касается транспортного уровня, байт, который вы передали ему в send_data, является просто двоичным шаблоном, но вы можете интерпретировать его в коде своего приложения как букву «А». Вычисление CRC или контрольной суммы будет одинаковым независимо от того, считаете ли вы байт «A», 0x41 или 0b01000001.

Транспортный уровень — это уровень пакетов, на котором у вас есть заголовки сообщений и проверка ошибок, будь то CRC или базовая контрольная сумма. В контексте прошивки у вас может быть такая функция, как send_data, где вы передаете ей байт для отправки. Внутри этой функции он помещается в пакет, который говорит: «Эй, это обычное сообщение, требует подтверждения, а контрольная сумма — 0x47, текущее время — X». Этот пакет отправляется через физический уровень на принимающий узел.

На физическом уровне определяются электроника и интерфейс: разъемы, уровни напряжения, синхронизация и т. д. Этот уровень может варьироваться от пары дорожек, передающих сигналы TTL для базового UART на печатной плате, до полностью изолированной дифференциальной пары, как в некоторых CAN реализации.

На принимающем узле пакет поступает на физический уровень, распаковывается на транспортном уровне, после чего ваш двоичный шаблон становится доступным для прикладного уровня. Прикладной уровень принимающего узла должен знать, следует ли интерпретировать этот шаблон как «A», 0x41 или 0b01000001, и что с ним делать.

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

Протоколы Ascii также могут включать контрольную сумму. Я сталкивался с вариациями Hex-as-ascii, использующими представление чисел ascii.
@ЕвгенийШ. Уточнил этот момент
Не придираться, но TCP не четыре уровня; он считается подходящим для четвертого уровня модели OSI. Последовательная связь не совсем соответствует модели OSI.
@batsplatsterson Это придирки, и совершенно не относящиеся к тому, о чем я говорю.

Еще не упомянутый момент заключается в том, что независимо от того, используется ли ASCII или двоичный протокол, отправка символа стирания перед каждым пакетом гарантирует, что даже если линейный шум или ошибки кадрирования появятся до начала пакета, все символы после стирания будут удалены. out будет правильно кадрирован при отсутствии дополнительных шумов. В противном случае, если кто-то отправляет пакеты непрерывно и не включает никаких символов, которые гарантированно добьются ресинхронизации, возможно, что один сбой может испортить все, что последует, до следующей паузы в передаче. Символ 0xFF удобен, потому что он гарантирует, что любой получатель сможет выполнить повторную синхронизацию со следующим символом.

(*) 0xFF — называется стиранием, потому что кто-то, кто набирает ошибочный символ при вводе данных на бумажную ленту, может нажать кнопку «сдвинуть ленту назад» и нажать стирание, чтобы заменить ошибочно набитый символ на 0xFF, что игнорироваться большинством получателей).

Одним из преимуществ отправки строк ASCII является то, что управляющие коды могут использоваться для обозначения начала/конца сообщения. например, STX (символ 2) и ETX (символ 3) могут сигнализировать о начале и окончании передачи. В качестве альтернативы вы можете добавить простой перевод строки, чтобы отметить конец передачи.

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

Многие бинарные протоколы ДЕЙСТВИТЕЛЬНО резервируют одну или несколько битовых комбинаций в качестве управляющих кодов, но они также включают механизм перехода для обработки этих кодов, когда они появляются в данных.
Вы можете зарезервировать любой шаблон, чтобы пометить все, что вы хотите в двоичном формате. Например, я работаю над проектами с быстрым потоком данных и медленным потоком данных, выходящим на один и тот же UART. Я зарезервировал самый большой отрицательный int32 в качестве флага для моих медленных данных и просто насытил мои отрицательные данные самым большим отрицательным значением + 1.
Согласовано. Надеюсь, я пояснил это в отредактированном ответе.

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

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

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

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

Я использовал HDLC в сетях RS-485 без проблем, и PPP также использует его.

Было бы неплохо, если бы вы указали, почему вы предлагаете это вместо Modbus.

ASCII через UART является наиболее популярным отчасти потому, что:

  • Это удобочитаемо для человека при отладке (мне еще предстоит увидеть логический анализатор, который не декодирует ASCII).

  • Это очень легко реализовать, у вас есть таблица ASCII через быстрый Google, которая хорошо стандартизирована.

  • Он имеет встроенную синхронизацию со стартовыми/стоповыми битами.

  • Практически весь мир любителей использует ASCII вместо последовательного, поэтому любые новые методы должны будут иметь дело с этим, а это ни в коем случае не просто.

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

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