Найкращі практики та зразки протоколу зв'язку


19

Кожен раз, коли я розробляю послідовний протокол, який буде використовуватися між двома ардуїно, я відчуваю себе трохи схожим на винахід колеса. Цікаво, чи існують якісь найкращі практики чи зразки, яких люди дотримуються. Це питання менше стосується фактичного коду, а більше щодо формату повідомлень.

Наприклад, якщо я хотів сказати ардуїно спалахнути, то перший світлодіод 3 рази я можу надіслати:

^L1,F3\n
  • '^': запускає нову команду
  • 'L': визначає команду, (L: націлює цю команду на світлодіод)
  • '1': націлювання на перший світлодіод
  • ',': Роздільник командного рядка, нове значення у цьому повідомленні, яке слід подати
  • 'F': Flash-команда
  • '3': 3 рази (спалах світлодіода тричі)
  • '\ n': Закінчення команди

Думки? Як ви зазвичай підходите до написання нового послідовного протоколу? Що робити, якщо я хотів би надіслати запит від arduino 1 до arduino 2, а потім отримати відповідь?

Відповіді:


13

Існує маса способів написання послідовного протоколу в залежності від того, яку функціональність ви хочете отримати і скільки перевірки помилок вам потрібно.

Деякі найпоширеніші речі, які ви бачите в протоколах до точки:

Кінець повідомлення

Найпростіші протоколи ASCII просто закінчують послідовність символів повідомлення, часто \rабо так, \nяк це друкується при натисканні клавіші введення. Бінарні протоколи можуть використовувати 0x03або інший загальний байт.

Початок повідомлення

Проблема лише з закінченням повідомлення полягає в тому, що ви не знаєте, які інші байти вже отримані під час надсилання повідомлення. Потім ці байти будуть префіксними для повідомлення та спричинити його неправильне тлумачення. Наприклад, якщо Arduino просто прокинувся зі сну, у серійному буфері може бути сміття. Щоб обійти це, у вас є початок послідовності повідомлень. У вашому прикладі ^, у двійкових протоколах часто0x02

Помилка перевірки

Якщо повідомлення може бути пошкоджено, нам потрібна перевірка помилок. Це може бути контрольна сума чи помилка CRC чи щось інше.

Втеча персонажів

Можливо, контрольна сума додає до керуючого символу, такого як байт "початок повідомлення" або "кінець повідомлення", або повідомлення містить значення, рівне контрольному символу. Рішення полягає у введенні символу втечі. Символ втечі розміщується перед модифікованим символом управління, щоб фактичний символ управління не був присутній. Наприклад, якщо початковий символ дорівнює 0x02, використовуючи символ втечі 0x10, ми можемо надіслати значення 0x02 у повідомленні як пара байтів 0x10 0x12 (байтовий символ управління XOR)

Номер пакету

Якщо повідомлення пошкоджене, ми можемо попросити повторне надсилання або повторне повідомлення, але якщо було надіслано кілька повідомлень, може бути відтворено лише останнє повідомлення. Замість цього пакету можна надати номер, який перекидається після певної кількості повідомлень. Наприклад, якщо це число 16, передавальний пристрій може зберігати останні 16 відправлених повідомлень, і якщо такі були пошкоджені, приймаючий пристрій може запросити повторне використання, використовуючи номер пакета.

Довжина

Часто в двійкових протоколах ви бачите байт довжини, який повідомляє пристрою, що приймає, скільки символів у повідомленні. Це додає ще один рівень перевірки помилок, як якщо б не було отримано правильну кількість байтів, то сталася помилка.

Ардуїно специфічний

Придумуючи протокол для Arduino, перший погляд на те, наскільки надійний канал зв'язку. Якщо ви надсилаєте більшість бездротових носіїв інформації, XBee, WiFi тощо, тут уже вбудовані перевірки помилок та повторні спроби, і, таким чином, немає сенсу вносити їх у свій протокол. Якщо ви відправляєте через RS422 за пару кілометрів, тоді це знадобиться. Що я б включив - це початок повідомлення та кінець символів повідомлення, як у вас є. Моя типова реалізація виглядає приблизно так:

>messageType,data1,data2,…,dataN\n

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

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


Чи не можливо, що сміття перед реальним початком коду повідомлення може містити початок коду управління повідомленнями? Як би ти справився з цим?
CMCDragonkai

@CMCDragonkai Так, це можливість, особливо для однобайтових кодів управління. Однак якщо у вас з'явиться код управління запуском на півдорозі розбору повідомлення, це повідомлення відкидається, а розбір перезавантажується.
геометрікал

9

Я не маю жодної офіційної експертизи щодо серійних протоколів, але я їх використовував досить багато разів і більш-менш зупинявся на цій схемі:

(Заголовок пакета) (байт ідентифікатора) (дані) (контрольна сума fletcher16) (Футбольний пакет)

Зазвичай я роблю заголовок 2 байта і колонтитул 1 байт. Мій аналізатор скидає все, коли побачить новий заголовок пакету, і спробує розібрати повідомлення, якщо він бачить колонтитул. Якщо контрольна сума не вдасться, вона не викине повідомлення, але продовжуйте додавати, поки символ футера не знайдеться і контрольна сума не стане успішною. Таким чином, колонтитул повинен бути лише одним байтом, оскільки зіткнення не порушують повідомлення.

Ідентифікатор довільний, іноді довжина секції даних є нижньою нибкою (4 біти). Біт другої довжини міг би бути використаний, але я зазвичай не турбуюсь, оскільки довжина не повинна бути відомою для правильного розбору, тому бачити правильну довжину для даного ідентифікатора - це лише більше підтвердження того, що повідомлення було правильним.

Контрольна сума fletcher16 - це 2-байтна контрольна сума майже такої ж якості, що і CRC, але це набагато простіше у виконанні. деякі деталі тут . Код може бути таким же простим:

for(int i=0; i < bufSize; i++ ){
   sum1 = (sum1 + buffer[i]) % 255;
   sum2 = (sum2 + sum1) % 255;
}
uint16_t checksum = (((uint16_t)sum1)<<8) | sum2;

Я також використовував систему дзвінків і відповідей для критичних повідомлень, де ПК надсилатиме повідомлення кожні 500 мс або близько того часу, поки не отримає ОК-повідомлення з контрольною сумою всього оригінального повідомлення як даних (включаючи оригінальну контрольну суму).

Ця схема, звичайно, не дуже підходить для введення в термінал, як це було б у вашому прикладі. Ваш протокол здається досить хорошим, оскільки обмежений ASCII, і я впевнений, що це легше для швидкого проекту, який ви хочете мати можливість безпосередньо читати та надсилати повідомлення. Для великих проектів добре мати щільність бінарного протоколу та захищеність контрольної суми.


Оскільки "[ваш] аналізатор скидає все, коли побачить новий заголовок пакету", мені цікаво, чи це не створює проблем, якщо випадково заголовок зустрінеться всередині даних?
humanityANDpeace

@humanityANDpeace Причиною відмови є те, що коли пакет відрізається, він ніколи не буде правильно розбиратися, тож коли ви вирішите його сміття та рухаєтесь далі? Найпростіше, і на мій досвід досить хороше, рішення - скинути поганий пакет, як тільки з'явиться наступний заголовок. Я використовував 16-бітний заголовок без проблем, але ви можете зробити це довше, якщо впевненість важливіша, пропускна здатність.
BrettAM

Тож те, що ви називаєте заголовком, є дещо магічною 16-бітовою комбінацією. тобто 010101001 10101010, правда? Я погоджуюся, що це лише 1/256 * 256 зміни, щоб потрапити, але він також забороняє коли-небудь використовувати цей 16-бітний всередині ваших даних, інакше він неправильно трактується як заголовок, і ви відкинете повідомлення, правда?
humanityANDpeace

@humanityANDpeace Я знаю, що це через рік, але вам потрібно ввести послідовність втечі. Перед відправкою сервер перевіряє корисне навантаження на наявність будь-яких спеціальних байтів, потім видаляє їх іншим спеціальним байтом. Клієнтська сторона, ви повинні зібрати початковий корисний вантаж. Це означає, що ви не можете надсилати пакети фіксованої довжини і ускладнює реалізацію. Існує багато стандартів послідовних протоколів, щоб вибрати для цього всі адреси. Ось дуже гарна прочитана тема .
RubberDuck

1

Якщо ви дотримуєтесь стандартів, ви можете поглянути на ASN.1 / BER TLV-кодування. ASN.1 - мова, що використовується для опису структур даних, створена спеціально для зв'язку. BER - метод TLV кодування даних, структурованих за допомогою ASN.1. Проблема полягає в тому, що кодування ASN.1 в кращому випадку може бути складним. Створення повноцінного компілятора ASN.1 - сам по собі проект (і особливо складний в цьому, думайте, місяці ).


Напевно, краще зберігати лише структуру TLV. TLV в основному складається з трьох елементів: тега, довжини та поля значення. Тег визначає тип даних (текстовий рядок, рядок октету, ціле число тощо) та довжину довжини значення .

У BER T також позначає, чи є значенням набір самих структур TLV (побудований вузол) або безпосередньо значення (примітивний вузол). Таким чином ви можете створити дерево у двійковій формі, подібно до XML (але без накладних витрат XML).

Приклад:

TT LL VV
02 01 FF

являє собою ціле число (тег 02) з довжиною значення 1 (довжина 01) і значенням -1 (значення FF). У ASN.1 / BER цілі числа підписують великі ендіакальні числа, але ви, звичайно, можете використовувати власний формат.

TT LL (TT LL VV, TT LL VV VV)
30 07  02 01 FF  02 02 00 FF

являє собою послідовність (список) довжиною 7, що містить два цілих числа, одне зі значенням -1 і одне зі значенням 255. Два цілих цілих кодування разом складають значення послідовності.

Ви можете просто кинути це в інтернет-декодер, не так добре?


Ви також можете використовувати невизначену довжину в BER, що дозволить вам передавати дані. У цьому випадку вам потрібно правильно розібрати дерево. Я вважаю це розширеною темою, вам потрібно знати про ширину спочатку та глибину першого розбору для одного.


Використання схеми TLV в основному дозволяє думати про будь-яку структуру даних та кодувати її. ASN.1 йде набагато далі, надаючи вам унікальні ідентифікатори (OID), вибір (подібний до C-об'єднань), включає в себе інші структури ASN.1 тощо, але це може бути надмірним для вашого проекту. Напевно, найвідоміші сьогодні визначені структури ASN.1 - це сертифікати, які використовує ваш браузер.


0

Якщо ні, то ви висвітлюєте основи. Ваші команди можуть створювати і читати як люди, так і машини, що є великим плюсом. Ви можете додати контрольну суму для виявлення неправильно сформованої команди або пошкодженої під час руху, особливо якщо ваш канал містить довгий кабель або радіозв'язок.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.