Простой последовательный протокол связи точка-точка
Мне нужен простой протокол связи между двумя устройствами (ПК и микроконтроллер). ПК должен отправить некоторые команды и параметры на микро. Микро должен передавать массив байтов (данные от датчика).
Данные должны быть защищены от шума (помимо проверки на четность, я думаю, что мне нужен другой метод исправления данных).
Есть ли стандартное решение для этого? (Мне нужна только идея, а не полное решение).
PS Любой совет приветствуется. PPS Извините за любые грамматические ошибки, я надеюсь, вы понимаете.
Редактировать 1. Я не решил, будет ли это протокол master / slave или обе стороны могут инициировать связь. ПК должен знать, когда Micro выполнил задание, и может отправлять данные. Он может непрерывно опрашивать микро, если данные готовы, или микро может отправлять данные, когда работа выполнена. Я не знаю, что лучше и проще.
Редактировать 2. Протокол аппаратного и физического уровня. Поскольку в ПК используется последовательный стандарт RS-232 C, я буду использовать асинхронную связь. Я буду использовать только сигналы RxD, TxD и GND. Я не могу использовать дополнительные провода, потому что микроконтроллер AFAIK их не поддерживает. Кстати, я использую чип AVR ATmega128.
Поэтому я буду использовать фиксированную скорость передачи данных, 8 бит данных, 2 стоповых бита без проверки на четность (или с?).
Протокол передачи данных. Это то, что мой вопрос в первую очередь касается. Спасибо за предложение протоколов HDLC, PPP и Modbus. Я буду исследовать это.
13 ответов
Я бы использовал HDLC. Я имел удачу с этим в прошлом. Я бы для серийного использования просто использовал Асинхронное кадрирование и забыл обо всех других элементах управления, поскольку это, вероятно, было бы излишним.
В дополнение к использованию HDLC для формирования пакета. Я форматирую свой пакет следующим образом. Вот как параметры передаются с использованием 802.11
U8 cmd;
U8 len;
u8 payload[len];
Общий размер каждого командного пакета составляет len +2
Затем вы определяете команды как
#define TRIGGER_SENSOR 0x01
#define SENSOR_RESPONSE 0x02
Другое преимущество заключается в том, что вы можете добавлять новые команды, и если вы правильно спроектируете свой парсер, чтобы игнорировать неопределенные команды, у вас будет некоторая обратная совместимость.
Таким образом, сложив все вместе, пакет будет выглядеть следующим образом.
// total packet length minus flags len+4
U8 sflag; //0x7e start of packet end of packet flag from HDLC
U8 cmd; //tells the other side what to do.
U8 len; // payload length
U8 payload[len]; // could be zero len
U16 crc;
U8 eflag; //end of frame flag
Затем система будет следить за последовательным потоком на наличие флага 0x7e, и, когда он будет там, вы проверяете длину, чтобы увидеть, является ли он pklen >= 4 и pklen=len+4, и является ли crc действительным. Примечание: не полагайтесь только на crc для небольших пакетов, вы получите много ложных срабатываний, также проверьте длину. Если длина или crc не совпадают, просто сбросьте длину и crc и начните с декодирования нового кадра. Если это совпадение, скопируйте пакет в новый буфер и передайте его в вашу функцию обработки команд. Всегда сбрасывайте длину и crc при получении флага.
Для вашей функции обработки команд возьмите cmd и len, а затем используйте переключатель для обработки каждого типа команды. Я также требую, чтобы определенные события отправляли ответ, чтобы система вела себя как удаленный вызов процедуры, управляемый событиями.
Так, например, сенсорное устройство может иметь таймер или отвечать на команду, чтобы выполнить чтение. Затем он отформатирует пакет и отправит его на ПК, и ПК ответит, что получил пакет. Если нет, то сенсорное устройство может повторно отправиться по тайм-ауту.
Также, когда вы выполняете передачу по сети, вы должны проектировать ее как сетевой стек, подобный модели OSI, так как точки Foredecker не забывают о вещах физического уровня. Мой пост с HDLC - это уровень канала передачи данных, а RPC и обработка команд - это уровень приложений.
Я прочитал этот вопрос несколько месяцев назад, у меня была точно такая же проблема, и я не нашел ничего достаточно эффективного для крошечного 8-битного микро с небольшим количеством оперативной памяти. Так вдохновленный CAN и LIN, я создал что-то для этой работы. Я назвал его MIN (сеть межконтроллерного соединения) и загрузил его на GitHub здесь:
https://github.com/min-protocol/min
Там есть две реализации: одна на встроенном C, другая на Python для ПК. Плюс небольшая тестовая программа "Hello World", в которой ПК посылает команды, а прошивка загорается светодиодом. Я писал о том, как настроить и запустить его на плате Arduino здесь:
https://kentindell.wordpress.com/2015/02/18/micrcontroller-interconnect-network-min-version-1-0/
МИН это довольно просто. Я исправил представление уровня 0 (8 бит данных, 1 стоповый бит, без контроля четности), но оставил скорость передачи открытой. Каждый кадр начинается с трех байтов 0xAA, который в двоичном виде равен 1010101010, что является хорошим импульсом для определения скорости автобода, если один конец хочет динамически адаптироваться к другому. Кадры имеют полезную нагрузку 0-15 байт с 16-битной контрольной суммой Флетчера, а также контрольным байтом и 8-битным идентификатором (чтобы сообщить приложению, что содержат данные полезной нагрузки).
Протокол использует символьную вставку, так что 0xAA 0xAA 0xAA всегда указывает начало кадра. Это означает, что если устройство выходит из режима сброса, оно всегда синхронизируется с началом следующего кадра (целью MIN было никогда не пропускать неполный или неправильный кадр). Это также означает, что нет необходимости иметь конкретные межбайтовые и межкадровые временные ограничения. Полная информация о протоколе находится в вики-репозитории GitHub.
Есть место для будущих улучшений с MIN. Я оставил здесь несколько хуков для передачи блочных сообщений (зарезервированы 4 бита управляющего байта) и для согласования возможностей более высокого уровня (зарезервирован идентификатор 0xFF), так что есть много возможностей для добавления поддержки для обычно требуемой функциональности.
Протоколы RS232 сложны. Предложение использовать HDLC является хорошим, но это не полное решение. Есть и другие вещи, которые вам нужно решить:
- Как будет определяться скорость передачи данных между двумя устройствами? Autobuad? Предопределено или установлено экспликация?
- Будете ли вы контролировать поток в программном или аппаратном обеспечении или в обоих? Обратите внимание, что если вы используете аппаратное управление потоком, вы должны убедиться, что кабели собраны правильно.
- Говоря о кабелях, это огромная проблема с RS233. В зависимости от устройства может потребоваться использовать прямой кабель, или перекрестный кабель, или вариант.
- Использование программного механизма управления потоком может быть эффективным, поскольку позволяет использовать самый простой кабель - всего три проводных (TX, RX и обычный).
- Вы выбираете 7 или 8-битное слово?
- Проверка четности HW или программного обеспечения.
Я предлагаю вам использовать 8 бит данных, без аппаратной четности, 1 стоповый бит и использовать программное управление потоком данных. Вы должны использовать autobaud, если ваше оборудование поддерживает это. Если нет, то автобод чертовски сложно сделать в программном обеспечении.
Здесь есть несколько хороших ответов, вот несколько полезных указателей:
Даже если ваши пакеты не разделены по времени, байт синхронизации является важным способом уменьшения количества мест, из которых вам нужно попытаться создать пакет. Вашим устройствам часто приходится иметь дело с кучей нежелательных данных (т. Е. С окончанием пакета в полете, когда они включены, или в результате аппаратного столкновения). Без синхронизирующего байта вам придется пытаться составить пакет из каждого полученного вами байта. Байт синхронизации означает, что только 1/255 байтов случайного шума может быть первым байтом вашего пакета. Также FANTASTIC, когда вы хотите отследить ваш протокол.
Наличие адреса на ваших пакетах или хотя бы небольшого упоминания "ведущий / ведомый" или "ПК / устройство" полезно, когда вы смотрите на пакеты с помощью инструмента отслеживания того или иного типа. Вы можете сделать это, имея другой байт синхронизации для ПК, чем УСТРОЙСТВО. Кроме того, это будет означать, что устройство не будет реагировать на свое собственное эхо.
Возможно, вы захотите посмотреть на исправление ошибок (например, Хэмминга). Вы упаковываете 8-битные данные в 12-битный защищенный байт. Любой из этих 12 битов может быть переброшен по маршруту, и исходные 8 битов могут быть получены. Полезно для хранения данных (используется на компакт-дисках) или там, где устройство не может легко повторно отправить (спутниковая связь, односторонняя радиосвязь).
Номера пакетов облегчают жизнь. Отправленный пакет содержит номер, ответы - тот же номер и флаг с надписью "ответ". Это означает, что пакеты, которые никогда не поступали (скажем, синхронизация повреждена), легко обнаруживаются отправителем, и в полнодуплексном режиме с медленным каналом две команды могут быть отправлены до получения первого ответа. Это также облегчает анализ протокола (третья сторона может понять, какие пакеты были получены, не зная базового протокола)
Наличие одного мастера - потрясающее упрощение. Тем не менее, в полнодуплексной среде это не имеет большого значения вообще. Достаточно сказать, что вы всегда должны делать это, если только вы не пытаетесь экономить электроэнергию или делаете что-то, управляемое на конце устройства (состояние ввода изменено, образец готов).
Мое предложение - это Modbus. Это эффективный и простой стандартный протокол для связи с устройствами, имеющими датчики и параметры (например, ПЛК). Вы можете получить спецификации на http://www.modbus.org/. Он существует с 1979 года и набирает популярность, у вас не будет проблем с поиском примеров и библиотек.
Вы можете взглянуть на Telemetry
и связанная с ним настольная реализация в python Pytelemetry
Основные особенности
Это протокол на основе PubSub, но в отличие от MQTT это протокол точка-точка, а не брокер.
Как и любой протокол pubsub, вы можете публиковать с одного конца на topic
и быть уведомленным на другом конце по этой теме.
На встроенной стороне публикация в теме так же проста, как:
publish("someTopic","someMessage")
Для номеров:
publish_f32("foo",1.23e-4)
publish_u32("bar",56789)
Этот способ отправки переменных может показаться ограниченным, но следующая веха намеревается добавить дополнительный смысл при разборе темы, выполнив такие действия:
// Add an indexing meaning to the topic
publish("foo:1",45) // foo with index = 1
publish("foo:2",56) // foo with index = 2
// Add a grouping meaning to the topic
publish("bar/foo",67) // foo is under group 'bar'
// Combine
publish("bar/foo:45",54)
Это хорошо, если вам нужно отправлять массивы, сложные структуры данных и т. Д.
Кроме того, шаблон PubSub великолепен благодаря своей гибкости. Вы можете создавать основные / подчиненные приложения, устройства к устройству и т. Д.
C библиотека https://badge.fury.io/gh/Overdrivr%2Ftelemetry
Библиотеку C очень просто добавить на любое новое устройство, если у вас есть приличная библиотека UART.
Вы просто должны создать структуру данных под названием TM_transport
(определяется Telemetry
) и назначьте 4 функциональных указателя read
readable
write
writeable
,
// your device's uart library function signatures (usually you already have them)
int32_t read(void * buf, uint32_t sizeToRead);
int32_t readable();
int32_t write(void * buf, uint32_t sizeToWrite);
int32_t writeable();
Чтобы использовать телеметрию, вам просто нужно добавить следующий код
// At the beginning of main function, this is the ONLY code you have to add to support a new device with telemetry
TM_transport transport;
transport.read = read;
transport.write = write;
transport.readable = readable;
transport.writeable = writeable;
// Init telemetry with the transport structure
init_telemetry(&transport);
// and you're good to start publishing
publish_i32("foobar",...
Библиотека Python https://badge.fury.io/py/pytelemetry
На стороне рабочего стола есть pytelemetry
модуль, который реализует протокол.
Если вы знаете Python, следующий код подключается к последовательному порту, публикуется один раз по теме foo
, печатает все полученные темы в течение 3 секунд, затем завершается.
import runner
import pytelemetry.pytelemetry as tm
import pytelemetry.transports.serialtransport as transports
import time
transport = transports.SerialTransport()
telemetry = tm.pytelemetry(transport)
app = runner.Runner(transport,telemetry)
def printer(topic, data):
print(topic," : ", data)
options = dict()
options['port'] = "COM20"
options['baudrate'] = 9600
app.connect(options)
telemetry.subscribe(None, printer)
telemetry.publish('bar',1354,'int32')
time.sleep(3)
app.terminate()
Если вы не знаете Python, вы можете использовать интерфейс командной строки
Pytelemetry CLI https://badge.fury.io/py/pytelemetrycli
Командная строка может быть запущена с
pytlm
Тогда ты можешь connect
, ls
(список) полученных тем, print
данные, полученные по теме, pub
(опубликовать) по теме или открыть plot
по теме для отображения полученных данных в режиме реального времени
Вот альтернативный протокол:
u8 Sync // A constant value which always marks the start of a packet
u16 Length // Number of bytes in payload
u8 Data[Length] // The payload
u16 Crc // CRC
Используйте RS232/UART, поскольку ПК (последовательный порт) и процессор (UART) уже могут справиться с этим с минимальной суетой (для сдвига уровня просто необходим чип MAX232 или аналогичный).
И используя RS232/UART, вам не нужно беспокоиться о главном / подчиненном, если это не актуально. Управление потоком доступно при необходимости.
Предлагаемое программное обеспечение для ПК: либо напишите свое собственное, либо Docklight для простого мониторинга и управления (ознакомительная версия бесплатна).
Для большей проверки ошибок простейшей является проверка на четность или, если вам нужно что-то более мощное, может быть, сверточное кодирование.
В любом случае, что бы вы ни делали: будьте проще!
РЕДАКТИРОВАТЬ: Использование RS232 с ПК еще проще, чем раньше, поскольку теперь вы можете получить преобразователи USB в RS232/TTL. Один конец подключается к USB-разъему вашего ПК и выглядит как обычный последовательный порт; другой выходит на сигналы 5 В или 3,3 В, которые могут быть подключены непосредственно к вашему процессору, без смещения уровня.
Мы использовали TTL-232R-3V3 от FDTI Chip, который отлично подходит для такого рода приложений.
Относительно проверок четности (как это было несколько раз здесь):
Они в основном бесполезны. Если вы обеспокоены тем, что один бит может быть изменен по ошибке, то весьма вероятно, что второй бит также может измениться, и вы получите ложный положительный результат проверки четности.
Используйте что-то легковесное, например CRC16, с таблицей поиска - его можно рассчитать по мере получения каждого байта, и в основном это просто XOR. Предложение Стива Мельникоффа отлично подходит для маленьких микро.
Я бы также предложил передавать данные, читаемые человеком, а не двоичные файлы (если производительность не является вашим первым приоритетом). Это сделает отладку и файлы журналов намного приятнее.
Мое единственное предложение: если вам нужна шумоустойчивость, вы можете использовать полнодуплексный RS-422/485. Вы можете использовать микросхему, подобную этой, на стороне AVR, а затем преобразователь RS-232->RS-422 на стороне ПК, например, 485PTBR. Если вы можете найти или изготовить экранированный кабель (две витые экранированные пары), у вас будет еще больше защиты. И все это невидимо для микро и ПК - никаких изменений программного обеспечения.
Что бы вы ни делали, убедитесь, что вы используете полнодуплексную систему, и убедитесь, что строки разрешения чтения / записи закреплены на ИС.
Вы не указываете точно, как ведет себя микроконтроллер, но будет ли все, что передано с микроконтроллера, прямым ответом на команду от ПК? Если да, то кажется, что вы можете использовать какой-нибудь протокол master/slave (это, как правило, самое простое решение). Если обе стороны могут инициировать связь, вам нужен более общий протокол канального уровня. HDLC - классический протокол для этого. Хотя полный протокол, вероятно, является излишним для ваших нужд, вы можете, например, по крайней мере использовать тот же формат фрейма. Вы также можете взглянуть на PPP, чтобы узнать, есть ли что-то полезное.
Может быть, этот вопрос может быть совершенно глупым, но кто-нибудь рассматривал возможность использования одного из протоколов X/Y/Z MODEM?
Основным преимуществом использования одного из вышеуказанных протоколов является большая доступность готовых реализаций в различных средах программирования.
MIN уже упоминался здесь (https://github.com/min-protocol/min), он действительно работает, когда следует ограничениям 8-битного контроллера. У него есть хост-части, реализованные на python и rust, и я недавно портировал его на java:https://github.com/milo1000/min-java .
SLIP и UDP. Шутки в сторону.
Все ПК и подобные устройства говорят об этом.
Есть хорошая книга и примеры из TCP Lean
Джереми Бентам незаметно получил PIC, работающий по протоколу TCP/IP. AVR так же хорош, как PIC, верно?
Я бы порекомендовал UDP вместо этого, это чертовски легко.