Баллада о передаче данных

Во первых строках моего текстоизлияния хочу сказать следующее: Понаписано об этом уже много, напишу и я свое виденье. Стандартные интерфейсы по передаче информации это замечательно, но для моих нужд они не достаточно обеспечивают всеудовлетваряющую (ну или почти) передачу данных. Сделаю попытку внести некоторые дополнения, дабы привести это к тому состоянию которое меня устраивает.
Имеется 2 или более устройств на достаточно большом (1–100 метров) расстоянии, между которыми надо передавать данные. Рассмотрев некоторые интерфейсы (rs232/422/485, I2C, Ethernet) пришел к выводу — что они либо не гарантируют однозначную передачу данных, много проводов тоже мне не понравилось, не дают ответ что информация принята. За основу решил взять интерфейс RS485 — из его плюсов он может «далеко идти», 2 провода, можно одновременно подключить кучу приборов, прост, (UART) есть почти на любом контроллере.
В моем случае для меня подходит классическая схема 1 ведущий остальные ведомые. Алгоритм обмена сообщениями такой: передача данных происходит циклами обмена, один цикл обмена состоит из сообщения которое передается от ведущего к ведомому, в ответ ведущий принимает сообщение от ведомого, все остальные молчат. На этой же основе реализовать запрос на получение данных от ведомого устройства.

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

По первому вопросу: предлагается такая схема: начальный байт, байт количества
передаваемых символов во всем сообщении, что-то еще, байт контрольной суммы (БКС), конечный байт.
image
Примечание: байт контрольной суммы считать по модулю 2;

На основе предложенной схемы можно судить что если ответ не вернулся, то ведомый — не доступен. При этом возможны варианты когда до ведомого доходит испорченное сообщение и он не отвечает на него, или сообщение до него доходит и он направляет ответ, но ответ портится и ведущий его игнорирует.
Для исправления этого было принято: если ответ не приходит (или приходит, но недостоверный), то повторно (кол-во раз без маразма) повторять текущий цикл обмена. Здесь может возникнуть следующая ошибка. Допустим мы направляем команду говорящую устройству, что нужно прибавить громкость на +1 единицу. Когда сообщение доходит до ведомого, он выполняет команду прибавить громкость и отправляет ответ «ок, я сделал как ты хотел», при этом может получится так что ответ портится и ведущий не понимает что команда уже исполнена, и отправляет сообщение повторно. В итоге по приеме команды на стороне ведомого громкость будет уже прибавлена на +2 единицы. Для избегания такого явления, принято ввести идентификатор (НС — номер сообщения) отличия сообщений. Если номер сообщения повторяется, то это повторное сообщение и указанную команду выполнять не надо, а просто отправить предыдущее ответное сообщение.
Так же здесь ввожу еще 2 параметра — это номер (код) устройства которому передаются данные и номер (субкод) обозначающий какую команду надо выполнять (либо какие данные лежат внутри сообщения).
image

В итоге сложу все вместе и пройду по алгоритму, на примере увеличения значения порога срабатывания реле по температуре на 5 градусов Цельсия и забора текущего показания температуры из ведомого устройства за 1 цикл обмена:

формирую передаваемые данные от ведущего:
image
При приеме сообщения ведомым смотрит 2 байт, где лежит кол-во отправленных байт, если кол-во отправленных байт равно кол-ву принятых — значит сообщение не потеряло байтов далее смотрим начальный байт (символ) если он = '$', а также конечный байт (символ) если он = '#' — то это сообщение от ведущего к ведомому.
Тут же рассмотрю возможные варианты сообщения от ведущего к ведомому с ошибками в начальном и конечном байтах, а также вариант с ошибкой кол-ва байт в сообщении. Оговорюсь сразу из 3 значений параметров буду считать правильными 2 и 3, т.е. при совпадении 2-х параметров из 3-х возможных считаю сообщение состоявшимся.
1. начальный байт = '$', кол-во принятых байт = 7(кол-во отправленных байт = 7), конечный байт не равен '#';
2. начальный байт не равен '$', кол-во принятых байт = 7(кол-во отправленных байт = 7), конечный байт = '#';
3. начальный байт = '$', кол-во принятых байт = 7(кол-во отправленных байт = 7, кол-во байт не равно 7), конечный байт = '#'.
Далее обсчитываем контрольную сумму оставшихся 3 байт (байты 3, 4, 5), если она совпадает с БКС продолжаем парсинг данных, смотрим для этого ли устройства эти данные и что надо по ней выполнить, в нашем случае код ведомого устройства 55 и субкод 2 говорит о том что надо прибавить еще 5 градусов к порогу срабатывания реле и в ответном сообщении отправить текущие данные по температуре. Проверяю НС если не равен предыдущему номеру сообщения то выполняю команду и к текущему значению порога срабатывания реле прибавляю 5 градусов. Если они равны (НС), то не выполняю указанные действия, далее перехожу к формированию ответного сообщения.
применение схемы ['$'][кол-во отправленных/принятых байт][…]['#'] — с большой вероятностью гарантирует что такая комбинация не сможет встретится в передаваемых данных, и спровоцировать лжесообщение.

Формирую передаваемые данные от ведомого на основе принятого сообщения
image

Принцип обработки следующий: смотрим 2 байт где лежит кол-во отправленных байт, если кол-во отправленных байт равно количеству принятых байт, а также начальный байт = '@' и конечный байт = '&' — то это сообщение от ведомого к ведущему. Если требуется использую механизм 2 из 3-х, аналогично описанному выше только уже для ответного сообщения (для символов '@' и '&'). Ведущий при приеме этого сообщения анализирует контрольную сумму 9 (с 3-го по 11-ый) байт, при совпадении контрольной суммы данные в сообщении считаются достоверными и продолжается дальнейший анализ данных. При совпадении кода, субкода и НС отправленного и принятого сообщения продолжаем анализ ответа на сообщение отправленное ведущим. Далее идет анализ принимаемых данных, в моем случае в 6-ом байте значение 1 — говорит о том что команда по прибавке 5 градусов к порогу срабатывания реле произведена успешно, остальные 5 байт говорят о текущих показаниях температуры 7-ой байт — флаг говорящий о достоверности передаваемой температуры (т.е. рассматриваю вариант ведомое устройство включено и отвечает, а датчик может не работать) и 4 байта типа float значения температуры.
Применение 2-х проверочных символов в начале и конце сообщения с большой вероятностью гарантирует при ошибке не спутать сообщения от ведомого и ведущего. Также случайные (не случайные) данные в канале не испортят обмена.

Немного о передаче данных ведомый ведомому, и централизованное сообщение всем ведомым от ведущего.
Сначала о последнем — передача от ведущего ведомым осуществляется назначением кода устройства 255, говорящего ведомым что это централизованное сообщение, далее остается только решить вопрос об общих субкодах, также можно группировать по кодам устройств т.е. назначить код устройства 254 и по этому коду 3 или 4 устройства будут принимать сообщение остальные ее игнорируют, естественно здесь не должна срабатывать часть по отправке ответов от ведомых устройств — т.е. не гарантированно что ведомые однозначно приняли эти сообщения!
О передаче данных ведомый ведомому, реализовать методом ведущий отправляет сообщение ведомому (ведомый1) от которого должна поступить информация другому ведомому (ведомый2), ведомый1 отправляет ответ ведущему при этом ведомый2 подслушивает этот ответ забирая данные себе. Опять же гарантии об однозначной доставки сообщения от ведомого1 к ведомому2 нет, этот надо учитывать!

Возможности интерфейса кол-во теоретически подключаемых устройств около 250, команд/типов данных до 248 для каждого устройства, длинна полезной информации в сообщении до 250 байт.

Поговорим о подводных каменьях:
Вся передача данных рассчитана на работу по времени т.е. следует соблюдать определенные задержки между сообщениями. Так-же рекомендую делать фиксированную задержку между отправленным сообщением ведущего и ответом ведомого для того чтобы ведомый успел сгенерировать данные и полностью передать отправить в канал.
Также важен момент организации ответов от ведомого, может так произойти что ведомый был занят и у него в канале оказались данные нескольких сообщений сразу, следует избегать ответов на устаревшие сообщения (т.к ведущий их уже не ждет) игнорируя их, выполняя команды только последнего актуального сообщения и давать ответ на него.
Отдельно хотелось бы выделить вопрос об синхронизации устройств по времени — следует учитывать что
синхронизация по времени ведомого при получении сообщения требует учитывать задержки времени на отправку данных в канал (при скорости 9600 сообщение 10 байт будет передаваться примерно 11 мсек) и важен момент срабатывания прерывания по окончанию приема данных на стороне ведомого, если прерывания нет то стоит учитывать время проверки прихода данных в буфер устройства, и т.п.
Так-же стоит отметить что повторная отправка цикла сообщения также добавляет нюансов, рекомендую на синхронизацию по времени использовать отправку сообщения без повторов, а формировать сообщения с новым НС.

P.S. У меня есть сомнения что я открыл здесь что-то новое, все это в той или иной мере где-то используется в разных интерфейсах! С легкой руки автора этой писанины и применения сего протокола в своих разработках, хочу дать название этому протоколу передачи данных «SRDB2».

© Geektimes