Рассказ о том, как мы прикрутили к Modbus быстрое сканирование шины, события и разрешение коллизий адресов.

Из статьи вы узнаете, зачем это нам было нужно, с какими проблемами мы столкнулись в процессе и что у нас в итоге получилось.

Задача

Периферийные устройства Wiren Board подключаются к шине RS-485 и работают по протоколу Modbus RTU — это старый промышленный протокол передачи данных, поддержка которого есть почти во всех контроллерах или программном обеспечении верхнего уровня.

Контроллер и некоторые периферийные устройства Wiren Board
Контроллер и некоторые периферийные устройства Wiren Board

Но в Modbus RTU есть недостатки, которые вытекают из его архитектуры:

  • проблема коллизий адресов — если на шине два устройства с одинаковым адресом, то надо физически отключить одно из них от шины и поменять второму адрес на свободный;

  • долгое сканирование шины — надо перебирать все 247 адресов на всех возможных параметрах соединения;

  • долгий период опроса одного регистра в устройстве — если на шине много устройств и в каждом по паре сотен регистров, то в зависимости от скорости и расположения регистров, пауза между опросами одного и того же регистра может достигать нескольких секунд. Это в конечном счёте приводит к медленной реакции системы.

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

Но на что нужно быстро реагировать в системах диспетчеризации и автоматизации инженерных систем? В основном, на действия пользователя. Например, нажал пользователь на выключатель и свет сразу включился. Здесь очень важно максимальное время между действием и реакцией на него — если система обычно реагирует за 10 мс, но иногда за 10 с, то пользователь это заметит и начнет жаловаться.

А раз есть понятные проблемы — можно попытаться их решить.

Поиск решения

Первое, что пришло в голову — сменить интерфейс и протокол устройств, например, делать устройства с CAN-шиной.

Но этот вариант нам не подходил:

  • Мы хорошо умеем делать устройства с Modbus RTU и RS-485 — под это заточены наши процессы в разработке и производстве.

  • Наши клиенты часто используют устройства Wiren Board со своими контроллерами и поддержка Modbus RTU есть почти везде.

  • К нашему контроллеру часто подключают устройства сторонних производителей, и выбор устройств с RS-485 и Modbus RTU кратно шире, чем с тем же CAN.

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

Трансивер RS-485 в устройстве WB-MR6C v.2
Трансивер RS-485 в устройстве WB-MR6C v.2

Поэтому нам было важно сохранить наш основной протокол Modbus RTU, а значит единственное решение — разработка расширения протокола, используя заложенные разработчиками протокола Modbus возможности.

Итак, требования к решению:

  • программное, без модификации схемотехники — пользователи недавно выпущенных устройств смогут получить доступ к расширению через обновление прошивки;

  • совместимое с классическим Modbus RTU — наши устройства должны оставаться Modbus RTU устройствами и уметь работать на одной шине с Modbus-устройствами других вендоров, а также с контроллерами, которые о расширении ничего не знают;

  • простое — это позволит без проблем поддержать его в стороннем софте верхнего уровня, что важно партнёрам, которые используют наше железо и свой софт;

  • открытое — мы не любим закрытые протоколы, которые привязывают пользователей к производителю железа.

Железо и софт для экспериментов

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

  • Компьютер с Linux с преобразователем WB-USB485, можно взять другой преобразователь или просто контроллер Wiren Board. А также утилита wb-modbus-scanner, она же является референсной реализацией мастера для работы с расширением. Собранная версия для компьютера с Linux лежит в корне репозитория.

  • Модуль счётных входов WB-MCM8 и реле WB-MR6C v.2. Важно, чтобы на модулях была последняя прошивка с поддержкой событий. Если это не так — обновите её описанным в документации способом.

  • Логический анализатор, например, клон Saleae Logic Analyzer.

  • Программа PulseView под Linux или Saleae Logic под WIndows. Надо включить декодер протокола UART. Настройки семплирования: 10М / 2 МГц. Команды подавались сразу после запуска захвата — это позволило целиком записывать запрос мастера, что не удавалось с установленным триггером на спад уровня. 

Мы подключались к входу трансивера RS-485 одного из слейв-устройств на шине.

Стенд: ноутбук с Linux, преобразователь WB-USB485, логический анализатор и два Modbus-устройства Wiren Board с поддержкой расширения Быстрый Modbus
Стенд: ноутбук с Linux, преобразователь WB-USB485, логический анализатор и два Modbus-устройства Wiren Board с поддержкой расширения Быстрый Modbus
Ещё фото стенда и скриншот настроек
Подключение устройств
Подключение устройств
Подключение логического анализатора к WB-MCM8
Подключение логического анализатора к WB-MCM8
Скриншот захвата процесса сканирования
Скриншот захвата процесса сканирования

Классический Modbus RTU

Прежде чем разбираться с расширением, вспомним, как работает классический Modbus RTU. Полное описание протокола читайте в нашей Вики или официальной документации.

Modbus — это протокол, который служит для обмена данными между устройствами автоматизации.

В устройствах Wiren Board данные передаются по последовательным линиям связи RS-485 с использованием протокола Modbus RTU, который использует полудуплексный режим передачи данных и работает по принципу «клиент-сервер» или «мастер-слейв»:

  • Мастер — это клиент, контроллер;

  • Слейв — это сервер, модуль ввода-вывода или просто «устройство».

По спецификации протокола каждый слейв на шине имеет свой уникальный адрес от 1 до 247, адрес 0 используется для широковещательной передачи команд. Есть ещё зарезервированные адреса с 248 по 255, которые в протоколе не используются.

Мастер периодически по очереди опрашивает слейвы, которые ему отвечают. Мастер не имеет адреса, а передача сообщений от слейва без запроса мастера в протоколе не предусмотрена.

Если при выполнении команды возникла ошибка, то слейв возвращает её код. Если слейв не ответил — мастер выжидает установленный таймаут и переходит к опросу следующего устройства.

Пакет данных протокола Modbus RTU
Пакет данных протокола Modbus RTU
Modbus-транзакция, прошедшая без ошибок
Modbus-транзакция, прошедшая без ошибок

Обмен данными в Modbus RTU происходит через регистры, всего их четыре вида. Для каждого вида есть свои функции чтения/записи.

Тип

Размер

Функции

Чтение

Запись

Coils — регистры флагов

1 бит

0x01

0x05 — одного

0x0F — нескольких

Discrete Inputs — дискретные входы

1 бит

0x02

Holding Registers — регистры хранения

16-битное слово

0x03

0x06 — одного

0x10 — нескольких

Input Registers — регистры ввода

16-битное слово

0x04

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

В ответе слейва содержится информация: адрес устройства; код функции; количество передаваемых байт; сами данные и контрольная сумма (CRC).

Пример классического Modbus-запроса

Например, запросим у устройства Wiren Board с адресом 20 его modbus-адрес, хранящийся в holding-регистре 128:

  • 0x14 — адрес устройства в HEX, 20 в DEC;

  • 0x03 — прочитать один holding-регистр;

  • 0x00 0x80 — адрес первого регистра в HEX, 128 в DEC;

  • 0x00 0x01 — запрашиваем один регистр;

  • 0x87 0x27 — контрольная сумма.

Ответ слейва:

  • 0x14 — адрес устройства в HEX, 20 в DEC;

  • 0x03 —функция, которую выполнили;

  • 0x02 — количество передаваемых байт;

  • 0x00 0x14 — данные в регистре, 20 в DEC;

  • 0xB5 0x88 — контрольная сумма.

Пример запроса Modbus-адреса устройства Wiren Board и ответ в классическом Modbus RTU
Пример запроса Modbus-адреса устройства Wiren Board и ответ в классическом Modbus RTU

Расширение Быстрый Modbus

Как мы уже видели выше, в Modbus есть концепция функций. Функций много, 15 из них используется, а остальные зарезервированы за производителями железа. Поэтому производители могут добавить отсутствующую в стандартном Modbus функциональность, просто используя заложенные в протоколе возможности.

Функции, описанные в спецификации протокола Modbus
Функции, описанные в спецификации протокола Modbus

Мы взяли свободную функцию 0x46, с помощью которой реализовали возможности нашего расширения. Для отправки широковещательных команд мы используем зарезервированный адрес 0xFD (253). Контрольная сумма рассчитывается точно так же, как в обычном Modbus RTU.

Важный момент — если ПЛК или SCADA-система ничего не знают о Быстром Modbus, они будут общаться с новыми «быстрыми» устройствами в обычном режиме, с функциями записать/прочитать регистры. То же справедливо и для контроллеров Wiren Board — если устройства-слейвы не поддерживают Быстрый Modbus, они будут опрашиваться «по старинке».

Быстрый Modbus — это не отдельный режим или новый протокол — это просто расширение стандартного протокола Modbus RTU, которое добавляет новые возможности. Если функции доступны — ими можно пользоваться, если нет — всё будет работать как обычно.

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

Расширение «Быстрый Modbus» открыто и документировано, вы можете использовать его в своих устройствах и программном обеспечении. Для разработчиков устройств мы предлагаем платный фреймворк, реализующий протокольную часть классического Modbus RTU с нашим расширением и поддержкой обновления прошивок по шине RS-485. Но это не обязательно, вы можете поддержать у себя наше расширение самостоятельно.

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

Арбитраж

Физика процесса

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

Мы вдохновились концепцией арбитража в CAN и сделали свой вариант по RS-485 с учётом особенностей шины. И в RS-485 и в CAN для передачи данных в шины используются специальные микросхемы — трансиверы (передатчики), но устроены они по-разному.

В CAN есть две линии (CANH и CANL), которые растягиваются двумя транзисторами: CANH может быть подтянута к питанию через транзистор Q1, а CANL — прижата к GND через транзистор Q2. 

Когда на шине вообще ничего не происходит, то транзисторы закрыты и потенциалы между линиями выравниваются через терминирующие резисторы.  Приёмники детектируют такое состояние как рецессивное.

В момент передачи доминантного состояния оба транзистора открываются и линия CANH подтягивается к питанию, а линия CANL прижимается к GND. По разности потенциалов между линиями, приёмники понимают, что на шине доминантное состояние.

Передача разных состояний двумя передатчиками в CAN. Резисторы — это сопротивление линии
Передача разных состояний двумя передатчиками в CAN. Резисторы — это сопротивление линии

Как видно из рисунка выше, если соединить два передатчика CAN вместе и одновременно передать разные состояния, то на шине будет установлено доминантное состояние. И тот, кто передает рецессивное, узнает об этом. Получается, что мы можем одновременно разными передатчиками выставлять на шине разные состояния.

Сам арбитраж в CAN устроен так: устройства передают в шину побитно свой идентификатор, выставляя на шине то рецессивное, то доминантное состояние. Устройство, выставившее в шину доминантное состояние, арбитраж выигрывает, а рецессивное — проигрывает. Атомарной единицей арбитража здесь является бит.

В RS-485 тоже есть две линии (A и B), но передатчик имеет отдельную пару транзисторов, чтобы подтянуть линии A и B к питанию (Q1 и Q2) и отдельную пару для прижима к GND (Q3 и Q4) — это нужно, чтобы иметь возможность явно передавать в линию логический ноль и единицу, что повышает помехоустойчивость. 

Когда на шине вообще ничего не происходит, то есть передатчик выключен и все четыре транзистора внутри него закрыты —  линии растягиваются резисторами failsafe bias. Такое состояние приёмники детектируют как логическую единицу.

В момент передачи включается передатчик и транзисторы подтягивают одну из линий к питанию, а другую прижимают к GND — состояния транзисторов зависят от того, что мы передаём:

  • логическая единица — Q1 подтягивает B к питанию, а Q4 прижимает A к GND;

  • логический ноль — Q2 подтягивает A к питанию, а Q3 прижимает B к GND.

Если следовать идеям CAN-шины, напрашивается закономерный способ слушать эхо во время передачи в надежде, что какое-то из состояний на линии RS-485 окажется доминантным — однако этого не произойдёт. 

Одновременная передача логического 0 и 1 передатчиками RS-485. Приёмники на схеме не указаны
Одновременная передача логического 0 и 1 передатчиками RS-485. Приёмники на схеме не указаны

На картинке выше два передатчика подключены к общей шине RS-485 и передают одновременно разные состояния:

  • в первом передатчике открыты транзисторы Q1a и Q4a — на шину передаётся логическая единица;

  • во втором передатчике открыты транзисторы Q2b и Q3b — на шину передаётся логический ноль.

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

Это значит, что мы не можем использовать состояния 0 и 1 как доминантные и рецессивные при включённых передатчиках. Поэтому нам не оставалось другого выхода, как принять молчание за рецессивное состояние, а передачу одного из состояний — за доминантное.

В процессе исследований мы столкнулись ещё с одной проблемой, которая нам не давала скопировать принцип арбитража CAN с атомарностью в один бит — Linux в мастере. Хотя он и не участвует в арбитраже, но после запроса ждёт ответа от слейвов. И если принимаемые посылки не будут иметь формат USART фрейма то приемник в Linux будет детектировать ERROR FRAME, а это усложнит работу с шиной.

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

Осциллограмма арбитража на шине RS-485
Осциллограмма арбитража на шине RS-485

Доминантное состояние у нас передаётся значением 0xFF с использованием обычной передачи USART и аппаратным управлением драйвера шины. 0xFF — это максимальное число, которое можно передать в одном байте и передаётся оно импульсом шириной 1 бит во время передачи старт-бита. Остальное время посылка передается с состоянием 1 (IDLE ) на шине. Это оказалось удобно, так как если возникнет какая-то рассинхронизация передатчиков слейвов, она будет небольшая.

Рецессивное состояние — это молчание в течение арбитражного окна, то есть передатчик выключен. Этим мы обходим аппаратное ограничение шины RS-485, в которой невозможно одновременно передавать разные состояния.

Арбитражное окно — это интервал времени, которое требуется для приёма 12 бит (одного UART-фрейма с двумя стоп-битами и битом чётности) плюс запас времени на обработку прерывания в микроконтроллере слейва. Запас времени на прерывание ограничен снизу 50 мкс и кратен времени передачи 1 бита. 

Рецессивные и доминантные состояния шины в момент арбитража
Рецессивные и доминантные состояния шины в момент арбитража

За одно арбитражное окно передаётся один бит идентификатора: 0 — доминантным состоянием, а 1 — рецессивным. 

Если устройство должно передавать доминантное состояние, то по началу арбитражного окна оно отправляет в шину 0xFF. Если на шине уже идет передача — устройство молчит, чтобы не передавать посылку, которая рассинхронизировалась. В этом арбитражном окне такое устройство проиграть не может. Чужая передача обнаруживается с помощью флага BUS BUSY, который есть в аппаратном блоке USART и выставляется, если на шине обнаружен чужой стартовый бит.

Если же устройство должно передать рецессивное состояние — оно молчит в течение всего арбитражного окна и слушает шину. Если из шины за время арбитражного окна был принят байт — другое устройство передало доминантное состояние и арбитраж проигран.

Логика процесса

Во время арбитража устройство-слейв передаёт по одному биту друг за другом арбитражное слово, которое состоит из приоритета и уникального идентификатора.

Приоритет сообщения — это 4 бита: 0 (0b0000) — наивысший, 15 (0b1111) — низший.  

Уникальный идентификатор зависит от команды, отправленной мастером:

  • Сканирование — (28-битное число) младшие 28 бит уникального серийного номера, что позволяет нам не обращать внимание на коллизии modbus-адресов. Чтобы устройства с поддержкой расширения от разных производителей смогли работать на одной шине, мы раздаём производителям блоки серийных номеров, а для DIY выделили отдельный диапазон — он указан в документации на Гитхабе.

  • Опрос событий —modbus-адрес (8-битное число). Устройства на шине уже настроены, коллизии адресов отсутствуют, нет смысла тратить время на арбитраж по серийным номерам.

В итоге арбитражное слово имеет длину 12 бит (4+8) при событиях или 32 бита (4+28) при сканировании. 

Биты идентификатора передаются по порядку, начиная с MSB (most significant bit, самый старший бит в слове) и заканчивая LSB (least significant bit, самый младший бит в слове).

Передача битов арбитражного слова ведётся по одному в течение арбитражного окна. К моменту завершения передачи арбитражного слова остаётся только одно устройство — победитель арбитража.

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

Почему для сканирования используется только 28 бит серийного номера

Если вы сейчас возьмёте серийный номер любого нашего устройства и переведёте в двоичную систему счисления, то у вас получится 32-битное число. Но как уже сказали, мы используем только младшие 28 бит серийника, так куда деваются старшие?

Старшие четыре бита серийного номера мы просто отбрасываем, чтобы вместе с битами приоритета уложиться в 32-битное арбитражное слово: 28+4 = 32. У нас всегда в старших четырёх битах серийного номера единицы, поэтому мы ничего от такого подхода не теряем.

При сканировании используются младшие 28 бит серийного номера устройств. Старшие четыре отбрасываются. Мы специально выдаем серийники так, чтобы это не влияло на уникальность.
При сканировании используются младшие 28 бит серийного номера устройств. Старшие четыре отбрасываются. Мы специально выдаем серийники так, чтобы это не влияло на уникальность.

Пример того, как происходит арбитраж при сканировании шины
Пример того, как происходит арбитраж при сканировании шины

Тайминги

Чтобы понять природу выбранных таймингов, вспомним, как выглядит посылка UART.

Каждый бит данных, а также стартовый и стоповый биты имеют одинаковую длительность, которая равна 1 с  / baudrate. Для формирования арбитражных окон мы используем отдельный аппаратный таймер, который настраивается таким образом, чтобы он инкрементировался с той же скоростью, с которой один бит передаётся по UART. Поэтому все временные интервалы у нас кратны времени передачи одного бита.

Ожидание начала арбитража — это время между запросом мастера и первым арбитражным окном. Сверху ограничено временем передачи 3.5 символов по стандарту Modbus — это нужно, чтобы успеть на мастере выключить передатчик и включить приёмник для получения ответов от устройств на шине. Снизу ограничено 800 мкс и кратно времени передачи целого бита. Нижняя граница подобрана экспериментально с разумным запасом и нужна для того, чтобы устройства успели обработать запрос и понять, надо ли им участвовать в арбитраже и с каким приоритетом.

Длительность арбитражного окна равна времени, необходимого для передачи одного фрейма в 12 бит (8 бит данные, 1 бит чётности, 2 стоп-бита и 1 старт-бит) плюс 50 мкс, округлённые до времени передачи целого бита — это время, необходимое устройствам обработать доминантное состояние на шине и решить, играют ли они дальше арбитраж.

Таймаут на приём ответа — это время, которое ждёт мастер, чтобы понять, что на шине нет устройств, поддерживающих расширение Быстрый Modbus, и начать опрос классическим поллингом. Рассчитывается так: ожидание начала арбитража + длительность арбитражного окна * количество бит в арбитраже.

Формирование таймаута на приём ответа
Формирование таймаута на приём ответа
Пример расчёта таймаута

Например, рассчитаем таймаут ожидания ответа на скорости 115200 бит/с:

  • Время передачи 1 бита на заданной скорости =  (1с/115200 бит/с) * 1000000 = 8.681 мкс

  • Ожидание начала арбитража = 3.5 * ( 1 старт + 8 данные + 1 четность + 2 стоп ) = 42 бита * 8.681 мкс = 364.602 мкс Получилось меньше требуемых 800 мкс, а ждать надо не менее 800 мкс, по этому таймер настраивается на ROUNDUP(800 / 8.681) = 93 бита. Время составит 93 * 8.681 = 807.333 мкс

  • Длительность арбитражного окна = (12 бит * 8.681 мкс) + ROUNDUP(50 мкс / 8.681 мкс) * 8.681 мкс = 104.172 + 68.681 = 156.258 мкс

Количество бит в арбитраже при сканировании 32, а в событиях 12. Получаем таймаут на приём ответа, который выжидает мастер:

  • Сканирование: 807.333 мкс + 156.258 мкс * 32 = 5800.256 мкс ≈ 5.8 мс

  • События: 807.333 мкс + 156.258 мкс * 12 = 2675.096 мкс ≈ 2.7 мс

Сканирование

Принцип

Классическое сканирование шины в Modbus — это последовательный перебор всех адресов со всеми настройками подключения, что занимает больше 15 минут: 247 адресов, 8 скоростей, 3 варианта чётности и 2 варианта стоповых битов. А если на шине есть устройства с одинаковыми адресами, то отличить одно от другого становится невозможным.

С помощью Быстрого Modbus перебор всех возможных комбинаций скорости, чётности и стоповых битов на одной шине происходит всего за ~3.5 секунды. Нам не надо перебирать все 247 адресов на шине и ждать таймаут на каждый запрос — этим мы экономим время.

В общем случае в наших устройствах это происходит так:

  1. Мастер отправляет в шину широковещательную команду «Начать сканирование».

  2. Слейвы проводят между собой арбитраж — победитель передаёт данные о себе: серийный номер и modbus-адрес.

  3. Мастер обращается к слейву по серийному номеру и вычитывает из него модель.

  4. После вычитки модели мастер отправляет в шину команду «Продолжить сканирование».

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

  6. Мастер снова отправляет в шину команду «Продолжить сканирование». И так продолжается до тех пор, пока на шине не останется неотсканированных устройств. Когда это случится — от устройства с наименьшим идентификатором придёт сообщение «Конец сканирования».

Фактически процесс выглядит так: запросы «Есть кто?», арбитраж, ответы победителей «Я тут!» и финального «Больше никого нет»
Фактически процесс выглядит так: запросы «Есть кто?», арбитраж, ответы победителей «Я тут!» и финального «Больше никого нет»

Если на команду «Начать сканирование» никто не ответил, мастер выжидает таймаут и переключает настройки связи. Таймаут маленький, перебирать все адреса не нужно, поэтому перебор настроек происходит очень быстро.

Для сканирования предусмотрены функции:

  • 0x01 — начать сканирование, отправляет мастер;

  • 0x03 — ответ на сканирование, отправляет один из слейвов;

  • 0x02 — продолжить сканирование, отправляет мастер;

  • 0x04 — конец сканирования, отправляет один из слейвов.

Блок схема

Возьмём два устройства и подключим их к шине, у наших серийные номера:

  1. WB-MCM8 — 4265607340 (dec) = 0xFE4000AC

  2. WB-MR6C v.2 — 4275217318 (dec) = 0xFED2A3A6

Просканируем шину с помощью утилиты wb-modbus-scanner:

# wb-modbus-scanner -d /dev/ttyRS485-1 -D -b 115200
Serial port: /dev/ttyRS485-1
Use baud 115200
	send SCAN INIT	-> :  FD 46 01 13 90
	<- :  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 46 03 FE 40 00 AC 14 E8 3A
	read DEVICE MODEL
	-> :  FD 46 08 FE 40 00 AC 03 00 C8 00 14 91 BA
	<- :  FD 46 09 FE 40 00 AC 03 28 00 57 00 42 00 4D 00 43 00 4D 00 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C5 25
Found device ( 1) with serial   4265607340 [FE4000AC]  modbus id:  20  model: WBMCM8         	 
	send SCAN NEXT	-> :  FD 46 02 53 91
	<- :  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 60 03 FE D2 A3 A6 F1 B4 49
	read DEVICE MODEL
	-> :  FD 46 08 FE D2 A3 A6 03 00 C8 00 14 8A AF
	<- :  FD 46 09 FE D2 A3 A6 03 28 00 57 00 42 00 4D 00 52 00 36 00 43 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CE 86
Found device ( 2) with serial   4275217318 [FED2A3A6]  modbus id: 241  model: WBMR6C         	 
	send SCAN NEXT	-> :  FD 46 02 53 91
	<- :  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 46 04 D3 93
End SCAN

При выполнении команды мы включили отладку параметром -D, чтобы было видно отправляемые и принимаемые байты, которые мы разберём ниже.

Начало сканирования

Мастер отправляет в шину команду «Начать сканирование», которая фактически звучит: «Есть кто?». Приняв эту команду, все устройства-слейвы на шине считают себя неотсканированными.

Неотсканированные устройства будут пытаться отправлять ответ с одинаковым приоритетом (0b0110), поэтому в этот раз арбитраж выиграет устройство с наименьшим серийным номером.

Команда «Начать сканирование»
FD 46 01 13 90
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x01 — субкоманда начала сканирования;

  • 0x13 0x90 — контрольная сумма.

Начало сканирования и арбитраж
Начало сканирования и арбитраж

Ответ победителя арбитража

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

Здесь важно, что устройство указывает в ответе свой серийный номер, то есть в случае коллизии modbus-адресов на шине мы можем это увидеть и сменить modbus-адрес, обратившись к устройству по серийному номеру.

Приоритет обоих устройств одинаков (0110), поэтому второе проигрывает по серийному номеру
Приоритет обоих устройств одинаков (0110), поэтому второе проигрывает по серийному номеру
Ответ устройства с информацией о себе
FD 46 03 FE 40 00 AC 14 E8 3A
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x03 — субкоманда ответа на сканирование;

  • 0xFE 0x40 0x00 0xAC — серийный номер устройства (big endian);

  • 0x14 — modbus-адрес устройства;

  • 0xE8 0x3A — контрольная сумма.

Запрос, арбитраж и ответ победителя
Запрос, арбитраж и ответ победителя

Чтение регистров по серийному номеру

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

Здесь мы обращаемся к устройству по серийному номеру, используем стандартную команду «читать регистр», но упаковываем запрос в специальный пакет с функцией эмуляции стандартной команды 0x08.

Чтение регистров по серийному номеру
FD 46 08 FE 40 00 AC 03 00 C8 00 14 91 BA
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x08 — субкоманда эмуляции стандартных запросов;

  • 0xFE 0x40 0x00 0xAC — серийный номер устройства к которому обращаемся (big endian);

  • 0x03 — обычный запрос: стандартный код функции работы с регистрами;

  • 0x00 0xC8 0x00 0x14 — тело запроса: прочитать 20 регистров (0x14) начиная с 200 (0xC8);

  • 0x91 0xBA — контрольная сумма.

Запрос регистров с моделью устройства и ответ
Запрос регистров с моделью устройства и ответ

Устройство, получившее адресованный ему запрос, отвечает пакетом с функцией 0x09 и вкладывает в тело стандартный ответ на Modbus-команду.

Ответ на запрос чтения регистра
FD 46 09 FE D2 A3 A6 03 28 00 57 00 42 00 4D 00 52 00 36 00 43 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CE 86
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x09 — субкоманда эмуляции ответа на стандартный запрос;

  • 0xFE 0x40 0x00 0xAC — серийный номер устройства к которому обращаемся (big endian);

  • 0x03 — обычный ответ: стандартный код функции работы с регистрами;

  • 0x28  0x00  0x57  0x00  0x42  0x00  0x4D  0x00  0x43  0x00  0x4D  0x00  0x38  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00 — тело ответа;

  • 0xCE 0x86 — контрольная сумма.

Целиком запрос мастера и ответ слейва
Целиком запрос мастера и ответ слейва

Продолжение сканирования

Итак, мастер получил ответ от выигравшего арбитраж устройства и считал из него дополнительную информацию. Далее мастер отправляет команду «Продолжить сканирование», которая звучит как «Есть ещё кто?». 

Команда «Продолжить сканирование»
FD 46 02 53 91
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x02 — субкоманда продолжения сканирования;

  • 0x53 0x91 — контрольная сумма.

Устройства снова разыгрывают арбитраж, который побеждает неотсканированное устройство с наименьшим серийным номером. Победитель арбитража отвечает «Я тут», а мастер вычитывает из него модель.

Арбитраж выиграло устройство с большим приоритетом
Арбитраж выиграло устройство с большим приоритетом

Этот цикл «Продолжить сканирование» → «Арбитраж» → «Ответ устройства» → «Запрос модели» → «Ответ устройства» повторяется до тех пор, пока все устройства на шине не будут отсканированы.

Продолжение сканирования, арбитраж и ответ очередного неотсканированного устройства
Продолжение сканирования, арбитраж и ответ очередного неотсканированного устройства

Конец сканирования

Важный момент — на каждый запрос мастера «Продолжить сканирование» в арбитраже участвуют все устройства на шине, но сообщения у неотсканированных устройств с высоким приоритетом (0b0110), а у отсканированных — с низким (0b1111). Это значит, что пока на шине есть ещё неотсканированные устройства, их ответы всегда выигрывают арбитраж: ведь приоритет их сообщений был выше. 

Но как только на шине будут отсканированы все устройства, на очередную команду «Продолжить сканирование» арбитраж выиграет сообщение от устройства, которое уже было отсканировано.

Этот арбитраж выиграет устройство с наименьшим серийным номером (по оставшимся битам арбитражного слова). Но неважно, что за устройство выиграло этот арбитраж: все отсканированные устройства пытались отправить одно и то же сообщение «Конец сканирования». Получив такой ответ, мастер может быть уверен, что ни одного неотсканированного устройства на шине не осталось. Такой подход избавляет мастер выжидать таймаут.

Остались только отсканированные устройства, поэтому сообщения от одного из них выиграло арбитраж. Второе устройство проиграло по серийному номеру
Остались только отсканированные устройства, поэтому сообщения от одного из них выиграло арбитраж. Второе устройство проиграло по серийному номеру
Сообщение «Конец сканирования»
<- FD 46 04 D3 93
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x04 — субкоманда завершения сканирования;

  • 0xD3 0x93 — контрольная сумма.

Продолжение сканирования, арбитраж и ответ победителя — «Больше никого нет»
Продолжение сканирования, арбитраж и ответ победителя — «Больше никого нет»

События

Принцип

Расширение Быстрый Modbus позволяет быстро опрашивать события, возникшие в устройствах без поочередного опроса каждого из них. Здесь, в отличие от сканирования, мы работаем по modbus-адресам устройств, поэтому на шине не должно быть коллизий адресов, как и при работе классического Modbus.

В общем случае происходит это так:

  1. Мастер отправляет в шину широковещательную команду «Есть у кого что?».

  2. Слейвы проводят между собой арбитраж по приоритету и адресу — победитель передаёт пакет с имеющимися у него событиями.

  3. Мастер принимает пакет с событиями и отправляет в шину пакет с командой «Есть у кого что?».

  4. Если событий ни у кого не осталось, в арбитраже выигрывает устройство с наименьшим modbus-адресом и сообщает мастеру, что событий на шине больше нет.

  5. Мастер снова отправляет в шину команду «Есть у кого что?» и всё повторяется снова: арбитраж, ответ устройств с событиями.

Блок-схема
Блок схема запроса событий
Блок схема запроса событий

Для работы с событиями предусмотрены функции:

  • 0x10 — запрос мастером событий у слейвов;

  • 0x11 — передача событий от слейва к мастеру;

  • 0x12 — ответ слейва, если событий на шине нет;

  • 0x18 — настройка событий мастером в слейве.

Есть 4 типа событий, аналогичные типам регистров и пятый системный:

  • 1 — coil;

  • 2 — discrete;

  • 3 — holding;

  • 4 — input;

  • 15 — системное событие.

По умолчанию в устройствах Wiren Board все события, кроме события с низким приоритетом «Я перезагрузился», отключены. Включение происходит изменением приоритетов, которые влияют на те 4 бита приоритета сообщения в момент арбитража:

  • 0 — событие не активно;

  • 1 — событие с низким приоритетом;

  • 2 — событие с высоким приоритетом.

Приоритеты задаются для каждого события: если у устройства сработает приоритетное событие, оно выйдет на арбитраж с меньшим значением битов приоритета и выиграет арбитраж первым. Если таких устройств окажется несколько, они будут выигрывать арбитраж по очереди, начиная с наименьшего modbus-адреса. Подробнее о процедуре арбитража читайте в разделе «Арбитраж».

Также у мастера есть возможность запрашивать события у устройств, начиная с определённого modbus-адреса. Это инструмент защиты от случаев, когда какое-то устройство с маленьким адресом спамит в шину событиями, не давая другим устройствам выиграть арбитраж.

Процесс запроса событий и ответов
Процесс запроса событий и ответов

Чтобы гарантировать доставку событий от устройства к мастеру, предусмотрено подтверждение событий. В пакете с событиями есть поле флага, который может быть 0 или 1. Слейвы в каждом пакете с событиями инвертируют этот флаг относительно предыдущего, отправленного ими же. Таким образом, не существует двух пакетов подряд от одного устройства с одинаковым значением флага. Флаги от разных устройств между собой не связаны.

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

Слейв, который в запросе событий увидел свой modbus-адрес и верный флаг, сбрасывает отправленные события и выходит на арбитраж с новыми. Если флаг пришёл неверный — он добавляет новые события к уже отправленным, выходит на арбитраж и после победы отправляет новый пакет с тем же флагом.

При опросе событий мастер руководствуется стандартной формулой времени ожидания, в которой ожидается 12 арбитражных окон: передача 4 бит приоритета и 8 бит modbus-адреса.

Подтверждение событий и чередование флагов
Подтверждение событий и чередование флагов

Настройка событий в устройстве

Включим отправку события изменения счётчика коротких нажатий восьмого входа устройства WB-MCM8 с адресом 20:

#  wb-modbus-scanner -d /dev/ttyRS485-1 -D -i 20 -r471 -t 4 -c 1 -b 115200
Serial port: /dev/ttyRS485-1
Use baud 115200
   -> :  14 46 18 05 04 01 D7 01 01 69 EA
   <- :  14 46 18 01 01 41 1C

Параметры команды:

  • -D — выводить отладку, то есть байтики;

  • -b 115200 — скорость шины;

  • -i 20 — modbus-адрес устройства;

  • -r471 — 471 регистр устройства;

  • -t 4 — тип регистра, Input;

  • -c 1 — включить событие с приоритетом 1.

Если сейчас снять с устройства питание и подать снова, то событие будет отключено, — надо включить его заново, если оно нам нужно. В ПО контроллера Wiren Board включением событий занимается драйвер wb-mqtt-serial, который включает нужные события автоматически при получении от устройства события «Я перезагрузился». 

При настройке событий нужно учитывать, что не все регистры устройств Wiren Board поддерживают события — это сделано, чтобы избежать спама неважными событиями, например изменением напряжения питания. Номера регистров, для которых доступны события, описаны в документации на наши устройства. У драйвера в контроллере Wiren Board есть шаблон устройства с указаниями, какие события нужно включить, поэтому пользователю ничего настраивать не нужно.

Конечно, при разработке своих устройств с использованием расширения Быстрый Modbus вы можете реализовать своё поведение. Например, сохранять настройку событий при перезагрузке и как-то усреднять значения, которые постоянно изменяются, чтобы не было спама. Никаких ограничений на уровне расширения протокола нет.

Команда мастера звучит так: «Устройство с адресом 20, включи событие для регистра 471 с приоритетом 1».

Команда настройки событий
14 46 18 05 04 01 D7 01 01 69 EA
  • 0x14 — адрес устройства;

  • 0x46 — команда управления разрешением передачи событий;

  • 0x18 — субкоманда, настройка событий;

  • 0x05 — длина списка настроек равна 5 байтам;

  • 0x04 — тип регистра;

  • 0x01 0xD7 0x01 — адрес регистра (big endian); 

  • 0x01 — количество регистров подряд ;

  • 0x01 — приоритет события;

  • 0x69 0xEA — контрольная сумма.

В одном запросе можно включить события сразу нескольких регистров устройства, тогда команда будет звучать так: «Устройство с адресом N, включи события в X регистрах, начиная с регистра Y и для каждого установи такие-то приоритеты». Примеры смотрите на Гитхабе в разделе Функция настройки отправки событий.

Ответ устройства на команду изменения настроек событий звучит так: «Я устройство с адресом 20, включил события с такими-то приоритетами».

Ответ устройства на команду настройки событий
14 46 18 01 01 41 1C
  • 0x14 — адрес устройства.

  • 0x46 — команда управления разрешением передачи событий.

  • 0x18 — субкоманда управления разрешением передачи событий изменения значения регистра.

  • 0x01 — длина списка значений настройки.

  • 0x01 — флаги разрешения отправки событий, мы настраиваем один регистр, у нас флаг 1 — включено событие в одном регистре.

  • 0x41 0x1C — контрольная сумма.

В ответе опущены поля «тип регистра», «адрес регистра» и «количество регистров подряд», а флаги разрешений (приоритеты), упаковываются в битовые маски. 

Команда настройки события и ответ слейва
Команда настройки события и ответ слейва

Аналогичным образом включим событие на состояние первого выхода второго устройства — реле WB-MR6C v.2 с адресом 241, регистр 0 тип Coil, приоритет 2:

#  wb-modbus-scanner -d /dev/ttyRS485-1 -D -i 241 -r0 -t 1 -c 2 -b 115200
Serial port: /dev/ttyRS485-1
Use baud 115200
	-> :  F1 46 18 05 01 00 00 01 02 A2 BB
	<- :  F1 46 18 01 01 0C CA

Параметры:

  • -D — выводить отладку, то есть байтики;

  • -b 115200 — скорость шины;

  • -i 241 — modbus-адрес устройства;

  • -r0 — 0 регистр устройства;

  • -t 1 — тип регистра, Coil;

  • -c 2 — включить событие с приоритетом 2.

Структуру пакетов настройки событий мы разбирали выше, повторяться не будем.

После настройки сгенерируем события в устройствах:

  • замкнём и разомкнём 8-й вход WB-MCM8 — это увеличит счётчик срабатывания входа;

  • включим канал 1 реле WB-MR6C v.2, для этого замкнём вход 1 этого реле.

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

Запрос событий и ответы устройств

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

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

Раунд 1

Воспользуемся нашей утилитой и запросим события у устройств на шине. Так как подтверждать нам нечего, в флаге подтверждения и адреса устройства выставляем 0 (-e 0):

# wb-modbus-scanner -d /dev/ttyRS485-1 -D -e 0 -b 11520
Serial port: /dev/ttyRS485-1
Use baud 115200
	send EVENT GET	-> :  FD 46 10 00 FF 00 00 C8 9A
	<- :  FF FF FF FF FF FF F1 46 11 00 02 09 01 01 00 00 01 00 0F 00 00 10 64
	device: 241 - events:   2   flag: 0   event data len: 009   frame len: 017
Event type:   1   id: 	0 [0000]   payload:      	1   device 241
Event type:  15   id: 	0 [0000]   payload:      	0   device 241

Параметры команды:

  • -D — выводить отладку, то есть байтики;

  • -b 115200 — скорость шины;

  • -e — подтвердить события с флагом 0, а если надо подтвердить события с флагом 1 — используется -E. После параметра следует адрес устройства, которому надо подтверждать события. У нас таких нет, поэтому указываем 0.

Запрос событий мастером
FD 46 10 00 FF 00 00 C8 9A
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x10 — субкоманда запрос событий от устройств;

  • 0x00 — минимальный modbus-адрес устройства которое выйдет на арбитраж;

  • 0xFF — максимальная длина поля данных с событиями в пакете, которую ожидает мастер, по стандарту длина всего пакета не должна превышать 256 байт;

  • 0x00 — адрес устройства, от которого был получен предыдущий пакет с событиями. Нужно для подтверждения доставки, у нас подтверждать нечего, поэтому здесь ноль;

  • 0x00  — флаг предыдущего полученного пакета для подтверждения приёма;

  • 0xC8 0x9A — контрольная сумма.

После того, как мастер запросил события, устройства играют между собой арбитраж с учётом битов приоритета.

В первом раунде у нас арбитраж выиграло реле WB-MR6C с адресом 241, а WB-MCM8 с адресом 20 проиграло. Это произошло из-за того, что у реле мы настроили событие с высоким приоритетом, и оно успело произойти до запроса событий мастером. То есть реле выиграло арбитраж по битам приоритета, а не по серийному номеру.

Первый раунд арбитража победило устройство с приоритетным событием
Первый раунд арбитража победило устройство с приоритетным событием
Ответ устройства с адресом 241
F1 46 11 00 02 09 01 01 00 00 01 00 0F 00 00 10 64
  • 0xF1 — 241, адрес устройства;

  • 0x46 — команда работы с расширенными функциями;

  • 0x11 — субкоманда передачи событий от устройства;

  • 0x00 — флаг этого пакета для подтверждения получения;

  • 0x02 — количество не сброшенных событий;

  • 0x09 — длина поля данных всех событий в байтах до контрольной суммы;

  • событие 1 — 0x01 0x01 0x00 0x00 0x01;

  • событие 2 — 0x00 0x0F 0x00 0x00;

  • 0x10 0x64 — контрольная сумма.

В одном ответе содержатся все события, произошедшие в устройстве и не подтверждённые мастером. 

Информация о первом событии (изменение состояния выхода) содержит 0x01 0x01 0x00 0x00 0x01:

  • 0x01 — длина дополнительных данных события (payload);

  • 0x01 — 1, тип события — coil;

  • 0x00 0x00 — идентификатор события или номер регистра (big endian);

  • 0x01 — дополнительные данные (payload) (формат little endian).  То есть содержимое регистра 0 с типом 1.

Информация  втором событии (Я перезагрузился) содержит 0x00 0x0F 0x00 0x00:

  • 0x00 — длина дополнительных данных события.

  • 0x0F — 15,  тип события — системное.

  • 0x00 0x00 — идентификатор события или номер регистра (big endian).

  • дополнительных данных нет.

Событие «Я перезагрузился» возникло сразу после подачи питания и мы его не подтверждали — поэтому оно пришло вместе с событием, которые мы сгенерировали сами — включён выход 1.

Раунд 2

После обработки ответа слейва, мастер снова отправляет в шину запрос, но дополнительно указывает, что устройству с адресом 241 надо подтвердить события с флагом 0 (-e 241):

# wb-modbus-scanner -d /dev/ttyRS485-1 -D -e 241 -b 115200
Serial port: /dev/ttyRS485-1
Use baud 115200
	send EVENT GET	-> :  FD 46 10 00 FF F1 00 8D 0A
	<- :  FF FF FF FF FF FF FF FF 14 46 11 00 02 0A 02 04 01 D7 01 00 00 0F 00 00 7A DA
	device:  20 - events:   2   flag: 0   event data len: 010   frame len: 018
Event type:   4   id:   471 [01D7]   payload:      	1   device 20
Event type:  15   id: 	0 [0000]   payload:      	0   device 20
Запрос событий с подтверждением
FD 46 10 00 FF F1 00 8D 0A
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x10 — субкоманда запроса событий от устройств;

  • 0x00 — минимальный modbus-адрес устройства которое выйдет на арбитраж;

  • (0xFF — максимальная длина поля данных с событиями в пакете, которую ожидает мастер, по стандарту длина всего пакета не должна превышать 256 байт;

  • 0xF1 — 241, адрес устройства, от которого был получен предыдущий пакет с событиями. Нужно для подтверждения доставки событий;

  • 0x00  — флаг предыдущего полученного пакета для подтверждения;

  • 0x8D 0x0A — контрольная сумма.

Устройство с адресом 241 молча подтверждает у себя отправленные события и в следующем пакете с событиями уже выставит флаг подтверждения 1. Также оно выходит на арбитраж с другими устройствами с сообщением об отсутствии событий, которое отправляется с самым низким приоритетом (4 бита приоритета равные 0b1111).

Во втором раунде побеждает уже устройство WB-MCM8 с адресом 20,  но тоже из-за битов приоритета.

Второй раунд арбитража выигрывает устройство с обычным событием из-за того, что больше событий ни у кого нет
Второй раунд арбитража выигрывает устройство с обычным событием из-за того, что больше событий ни у кого нет
Ответ устройства с адресом 20
14 46 11 00 02 0A 02 04 01 D7 01 00 00 0F 00 00 7A DA
  • 0x14 — 20, адрес устройства;

  • 0x46 — команда работы с расширенными функциями;

  • 0x11 — субкоманда передачи событий от устройства;

  • 0x00 — флаг этого пакета для подтверждения получения;

  • 0x02 — количество не сброшенных событий;

  • 0x0A — 10, длина поля данных всех событий в байтах до контрольной суммы;

  • событие 1 — 0x02 0x04 0x01 0xD7 0x01 0x00 0x00;

  • событие 2 — 0x00 0x0F 0x00 0x00;

  • 0x7A 0xDA — контрольная сумма.

В одном ответе содержатся все события, произошедшие в устройстве и не подтверждённые мастером. 

Информация о первом событии (изменился счётчик входа) содержит 0x02 0x04 0x01 0xD7 0x01 0x00:

  • 0x02 — длина дополнительных данных события (payload);

  • 0x04 — 4, тип события — input;

  • 0x01 0xD7 — 471, идентификатор события или номер регистра (big endian);

  • 0x01 0x00 — дополнительные данные (payload) (little endian). То есть содержимое input-регистра 471.

Информация о втором событии (Я перезагрузился) содержит 0x00 0x0F 0x00 0x00:

  • 0x00 — длина дополнительных данных события;

  • 0x0F — 15, тип события — системное;

  • 0x00 0x00 — идентификатор события или номер регистра (big endian);

  • дополнительных данных нет.

Событие «Я перезагрузился» возникло сразу после подачи питания и мы его не подтверждали — поэтому оно пришло вместе с событием, которые мы сгенерировали сами — изменился счётчик входа 1.

Раунд 3

После обработки ответа от устройства, мастер снова отправляет в шину запрос, но дополнительно указывает, что устройству с адресом 20 надо подтвердить события с флагом 0:

# wb-modbus-scanner -d /dev/ttyRS485-1 -D -e 20 -b 115200
Serial port: /dev/ttyRS485-1
Use baud 115200
	send EVENT GET	-> :  FD 46 10 00 FF 14 00 C7 9A
	<- :  FF FF FF FF FF FF FD 46 12 52 5D
NO EVENTS
Запрос мастера с подтверждением
FD 46 10 00 FF 14 00 C7 9A
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x10 — субкоманда запроса событий от устройств;

  • 0x00 — минимальный modbus-адрес устройства которое выйдет на арбитраж;

  • 0xFF — максимальная длина поля данных с событиями в пакете, которую ожидает мастер, по стандарту длина всего пакета не должна превышать 256 байт;

  • 0x14 — 20, адрес устройства, от которого был получен предыдущий пакет с событиями. Нужно для подтверждения доставки событий;

  • 0x00  — флаг предыдущего полученного пакета для подтверждения;

  • 0xC7 0x9A — контрольная сумма.

Устройство с адресом 20 молча подтверждает у себя отправленные события и в следующем пакете с событиями уже выставит флаг подтверждения 1. Также оно выходит на арбитраж с другими устройствами с сообщением об отсутствии событий.

Третий раунд арбитража выигрывает устройство с наименьшим серийным номером так как приоритеты сообщений одинаковые
Третий раунд арбитража выигрывает устройство с наименьшим серийным номером так как приоритеты сообщений одинаковые
Ответ устройства с адресом 20 —  «Событий больше нет»
FD 46 12 52 5D
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x12 — субкоманда передачи сообщения об отсутствии событий;

  • 0x52 0x5D — контрольная сумма.

После получения сообщение об отсутствии событий, мастер снова посылает запрос новых событий, но без подтверждения. И так по кругу. Наш драйвер wb-mqtt-serial отправляет запросы событий каждые 50 мс, что позволяет организовать практически мгновенную доставку событий.

Разрешение коллизий адресов на шине

Кроме сканирования шины и работы с событиями, расширение Быстрый Modbus позволяет обратиться к устройству по серийному номеру и записать значение в регистр. Эту возможность можно использовать для разрешения коллизий modbus-адресов на шине — просто назначаете свободный modbus-адрес одному из устройств с одинаковыми адресами и готово:

# wb-modbus-scanner -d /dev/ttyRS485-1 -D -b 115200 -s 4265607340 -i 200
Serial port: /dev/ttyRS485-1
Use baud 115200
Chande ID for device with serial   4265607340 [FE4000AC] New ID: 200
	-> :  FD 46 08 FE 40 00 AC 06 00 80 00 C8 DC 35
	<- :  FD 46 09 FE 40 00 AC 06 00 80 00 C8 8D F0
Команда смены modbus-адреса по серийному номеру
FD 46 08 FE 40 00 AC 06 00 80 00 C8 DC 35
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x08 — субкоманда эмуляции стандартных запросов;

  • 0xFE 0x40 0x00 0xAC — серийный номер устройства к которому обращаемся (big endian);

  • 0x06 — обычный запрос: стандартный код функции записи в регистр;

  • 0x00 0x80 0x00 0xC8 — тело запроса: записать в регистр 128 (0x00 0x80) число 200 (0x00 0xC8);

  • (2 байта) 0xDC 0x35 — контрольная сумма.

Ответ устройства
FD 46 09 FE 40 00 AC 06 00 80 00 C8 8D F0
  • 0xFD — широковещательный адрес;

  • 0x46 — команда работы с расширенными функциями;

  • 0x09 — субкоманда эмуляции ответа на стандартный запрос;

  • 0xFE 0x40 0x00 0xAC — обычный PDU: серийный номер устройства к которому обращаемся (big endian);

  • 0x06 — обычный ответ: стандартный код функции записи в регистр;

  • 0x00 0x80 0x00 0xC8 — тело ответа: записал число 200 в регистр 128;

  • 0x8D 0xF0 — контрольная сумма.

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

Пример смена modbus-адреса по серийному номеру
Пример смена modbus-адреса по серийному номеру

Одновременная работа устройств с Быстрым Modbus и классическим Modbus RTU

Так как расширение базируется на классическом Modbus, а арбитраж между устройствами происходит только после запроса мастера, то ничто не мешает мастеру работать на одной шине с «быстрыми» и «медленными» устройствами.

Мастер просто чередует запросы классического Modbus с широковещательными запросами Быстрого Modbus. Устройства, которые не поддерживают расширение, просто игнорируют незнакомый широковещательный адрес и команды.

Для иллюстрации мы запустили опрос устройств через наш драйвер wb-mqtt-serial, который умеет одновременно опрашивать устройства обычным поллингом и по событиями. Драйвер каждые 50 мс отправляет в шину запрос событий, читает их, а остальное время работает с устройствами обычным поллингом. Если от устройства пришло событие «Я перезагрузился», драйвер отправляет ему пакет с настройками событий.

Работа на одной шине устройств без поддержки и с поддержкой Быстрого Modbus
Работа на одной шине устройств без поддержки и с поддержкой Быстрого Modbus

Что дальше

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

Ещё мы подумываем расширить возможности самих периферийных устройств, например позволить им обмениваться сообщениями между собой без контроллера, — это позволит строить по-настоящему распределённые системы автоматизации поверх классической и надёжной шины RS-485. Техническая возможность есть, в железе мы уже пробовали.

Также будем помогать партнёрам внедрять это расширение в свои устройства и программное обеспечение верхнего уровня с открытым исходным кодом.

Заключение

В статье мы рассказали о своём расширении «Быстрый Modbus», о причинах его создания, основных принципах и решаемых с его помощью проблемах.

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

Нам удалось добиться хорошего распределения времени реакции на события. Вот пример нашей гистограммы. Мы подключили много реле к одному контроллеру на одну шину, скрипт через GPIO дёргал входы реле и измерял задержку от отправки импульса до получения обновления в MQTT. Результаты занесли в таблицу и построили гистограмму.

Гистограмма распределения времени реакции в Быстром Modbus
Гистограмма распределения времени реакции в Быстром Modbus

Расширение реализовано на программном уровне с применением заложенных в оригинальный протокол возможностей, поэтому мы добились полной совместимости наших устройств с протоколом Modbus. Если мастер, опрашивающий устройства ничего не знает про наше расширение — он будет работать с ними в рамках классического Modbus.

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

Поделитесь в комментариях, а как бы вы решили стоящие перед нами задачи с учётом ограничений, которые у нас были?

Если вы разработчик, приходите к нам работать, мы ищем программиста и инженера-электронщика — вакансии на hh.ru. Много интересных задач, отлаженные процессы разработки, дружный коллектив и возможность делать устройства, которыми пользуются десятки компаний и тысячи частных пользователей. Есть удалёнка или офисы в Москве и Астане (Казахстан).

Комментарии (16)


  1. NutsUnderline
    09.11.2023 07:11
    +4

    Нужен заголовок "делаем CAN из modbus" и картинка про 15 стандартов в качестве КДПВ. Не могу не отметить что разжевано все очень подробно.


    1. wofs Автор
      09.11.2023 07:11
      +1

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

      Поэтому мы старались найти решение, которое не будет привязывать пользователя к нам. У пользователя нашего оборудования будет стандартная шина RS-485 с протоколом Modbus RTU и он сможет добавить туда устройства сторонних производителей или поставить контроллер другого вендора без переделки физики.

      Не могу не отметить что разжевано все очень подробно.

      Спасибо, этой статьёй мы старались ответить на все вопросы по работе расширения, которые задавали нам с момента его анонса в апреле этого года: https://habr.com/ru/companies/wirenboard/articles/737402/


  1. MTom
    09.11.2023 07:11
    +1

    Очень подробное описание! Быстрый Modbus использую уже некоторое время, теперь стал лучше понимать, как он работает :)


  1. Sun-ami
    09.11.2023 07:11

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


    1. wofs Автор
      09.11.2023 07:11

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

      Почему не получится? Мы можем обратиться к каждому устройству с его параметрами связи по серийному номеру и прописать нужные настройки, например, 115200 8N2, а заодно и сменить адрес, если есть дубли.

      а уже в слэйвах делать автонастройку на любые поддерживаемые параметры обмена.

      Распишите, пожалуйста, подробнее эту мысль. Что такое «автонастройка в слейве»?
      У нас там есть регистры, в которые мастер может записать настройки связи. То есть логика сейчас такая: ищем всех слейвов на всех вариантах настроек связи и потом делаем всем одинаковые настройки.

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

      На самом деле в новых прошивках нет проблемы со стоп-битами — устройства умеют работать и 1 и с 2 стоповыми битами из коробки и им не важно, что там хочет мастер.

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

      Насчёт коллизий адресов - при автоматическом назначении адресов по описанному алгоритму коллизий между устройствами, поддерживающими новый протокол быть не должно.

      Мы на заводе записываем в устройства адреса из диапазона 1-247, наклейку с которым мы потом клеим на корпус устройства. Это позволяет в большинстве случаев получить в инсталляции устройства с разными адресами и ничего перенастраивать не нужно. Но иногда могут попасться пара устройств с одинаковыми адресами.

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

      Коллизии возможны, если подключить к работающей шине устройство, которое не поддерживает автоматическое назначение адресов. Как в этом случае автоматически обнаружить эту коллизию?

      Стандартно: подключили новое устройство в инсталляцию, сканируем шину, что-то делаем с его настройками, если нужно.


      1. Sun-ami
        09.11.2023 07:11

        Мы можем обратиться к каждому устройству с его параметрами связи по серийному номеру и прописать нужные настройки

        Так, конечно, можно делать. Но если в инсталляции есть хотя бы одно устройство, не поддерживающее быстрый Modbus - тогда всё равно придётся делать полное сканирование старым методом, и наличие быстрого сканирование у новых устройств совсем не ускорит цикл сканирования, а даже немного его замедлит - к обычному сканированию добавится быстрое. Я думал, что речь идёт о поиске параметров обмена до первого корректного обмена - он действительно будет быстрее.

        Что такое «автонастройка в слейве»?

        Мастер ведёт обычный обмен, или, если не нужно - периодически отправляет специальный короткий пакет сканирования со своими параметрами и специальным адресом, а слэйв пытается принять любой пакет с любым адресом, перебирая параметры обмена до первого успешного приёма пакета.


  1. Teemon
    09.11.2023 07:11

    Ещё не прочитал но одобряю. Вы молодцы.Появление быстрого модбаса наверное послужило причиной выбрать ваш контпрлле а не Овен/Сименс .

    Единственное что уже дождусь вайренборд 8.

    Вопрос: а почему в опциях не было использование модбас тсп? Скорости хорошие, сразу топология звезда, что удобно. Может быть можно было бы в poe тогда вообще красиво было бы.

    Я же правильно понимаю, что modbus tcpможет работать не поллингом а прямым соединением до каждого устройства?.. правда в некоторых контроллерах типа Siemens s7-1200 можно поднять всего 8 соединений помоему. Но у вас же Линукс, вы можете и 8000 соединений поднять?)

    Единственное что тсп это удорожание устройства, а в вашем случае вы программно сделали, типа не увеличивая цену)


    1. wofs Автор
      09.11.2023 07:11

      ... а почему в опциях не было использование модбас тсп?
      ... сразу топология звезда, что удобно.

      С виду выглядит хорошо, но есть нюансы :)

      Тысячи инсталляций сейчас собраны с шиной RS-485 и Modbus RTU. Это значит нам придётся выпускать и поддерживать две линейки устройств, чтобы уже собранные инсталляции можно было обслуживать и менять там со временем оборудование.

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

      Siemens s7-1200 можно поднять всего 8 соединений помоему.

      И это одна из причин — использование наших устройств с другими контроллерами. Нередко на объекте уже стоит контроллер другого вендора и при модернизации добавляют наши устройства.


      1. Teemon
        09.11.2023 07:11

        Шина 3 провода а по факту 5, т.к. питание, короче та же самая витая пара))

        Но в целом мне кажется, рано или поздно вы перейдёте на Ethernet , особенно если пойдете в промку)


        1. wofs Автор
          09.11.2023 07:11

          Шина 3 провода а по факту 5
          С питанием 4 провода: A, B и GND, +24В.

          Но в целом мне кажется, рано или поздно вы перейдёте на Ethernet , особенно если пойдете в промку)

          Я не понял о промке :) В шкафу удобно использовать шину RS-485 — занимает меньше места, требует меньше провода. Если речь про отправку данных из шкафа куда-то наверх, то маленький конвертер протоколов в 2 юнита решает задачу.

          Ethernet занимает много места, стоит дороже обычного клеммника, требует специального инструмента для обжима и навыков. А в месте с PoE ещё и ощутимо увеличит стоимость устройств, так как PoE модули довольно дорогие.


    1. alekseykostromin
      09.11.2023 07:11
      +1

      Количество, поддерживаемых TCP соединений, зависит как от аппаратной, так и от программной реализации, т.е. на одной и той же аппаратуре один софт может на 200 подключениях уже лаговать, когда другой обрабатывает их тысячами.


  1. ruraspb
    09.11.2023 07:11

    А не проще ли было делать чтение буферированым. Если в адресном пространстве есть запрещеные адреса отдавать там нули и ложь. Тогда мастер на первом проходе поймёт кого нет. А на следующих просто будет читать только тех кто есть. И скорость вырастет и не надо городить велосипед


    1. wofs Автор
      09.11.2023 07:11

      Я так понимаю, речь о сканировании шины? Расскажите, пожалуйста, свою идею подробнее — с виду кажется сложнее, чем сделали мы.


      1. Teemon
        09.11.2023 07:11

        Я так понял прочитать все слейвы которые из конфигурации. Те кто не ответил - исключать из поллинга, чтобы по тайм-ауту не ждать


        1. wofs Автор
          09.11.2023 07:11

          Я так понял прочитать все слейвы которые из конфигурации...

          Это просто — сейчас так и работает: если слейв есть в конфигурации и не ответил, то мы его не опрашиваем некоторое количество циклов, потом пытаемся снова. Если он так и молчит, исключаем его из опроса. Так работало почти всегда у нас так без всяких расширений.

          Проблема возникает, когда в конфигурации драйвера никого нет — тогда единственный путь, стучаться по каждому адресу и ждать таймаут на ответ устройства. Здесь наша итоговая цель проста: автоматическое конфигурирование устройств на шине и добавление их в конфигурацию драйвера.

          Сейчас в софте контроллера Wiren Board есть только табличка устройств на шине, но это не конечный вариант.

          Сканирование шины RS-485 в веб-интерфейсе контроллера Wiren Board
          Сканирование шины RS-485 в веб-интерфейсе контроллера Wiren Board


  1. alekseykostromin
    09.11.2023 07:11

    В modbus tcp можно организовать так называемые unsolicited messages и таким образом организовать спонтанную передачу данных (по изменению), возможно ли как-то модернизировать последовательные устройства или сеть из них чтобы реализовать такой режим? ради любопытства спрашиаю...