Modbus: простыми словами о популярном протоколе для M2M-взаимодействия
Modbus — это сетевой протокол прикладного уровня, широко используемый в промышленном производстве для обмена данными между устройствами (Machine-to-Machine, M2M).
С момента разработки в 1979 году он не теряет своей популярности. Согласно статистике HMS Industrial Networks в 2021 году Modbus занимает 10% мирового рынка промышленных сетей (по 5% приходится на Modbus RTU и Modbus TCP).
В статье расскажем об основных особенностях протокола Modbus, его преимуществах и недостатках, а также наиболее частых сценариях использования.
Базовые принципы работы Modbus
Modbus использует архитектуру Master-Slave, которая относительно недавно была переименована разработчиком в Client-Server. Согласно этому подходу в сети выделяется клиентское (ведущее) устройство, которое периодически отправляет запросы на серверные (ведомые) устройства с целью чтения или записи их параметров.
Все запросы может инициировать только клиентское устройство: передача сообщений от серверных устройств без предварительного опроса со стороны клиента в протоколе не предусмотрена.
Пакет данных Modbus включает в себя постоянную часть PDU (Protocol Data Unit), общую для всех реализаций протокола и состоящую из кода функции и данных. Кроме этого, возможен ряд специфических полей, которые будут различаться в зависимости от физического уровня сети — чаще всего это адрес серверного устройства и контрольная сумма для выявления ошибок. С учетом дополнительных полей полный пакет Modbus носит название ADU (Application Data Unit). Рассмотрим более подробно каждое поле пакета ADU в обобщенном виде. Особенности, присущие различным вариантам протокола, будут описаны в следующем разделе.
- Адрес серверного устройства (Additional address). Определяет, по какому адресу следует отправить клиентский запрос. Может принимать значения в диапазоне от 1 до 247. Адрес 0 используется для широковещательной передачи данных от клиента всем серверным устройствам (ответ сервера при этом не предусмотрен), а адреса 248–255 считаются зарезервированными.
В некоторых реализациях протокола поле игнорируется — например, в Modbus TCP, где чаще всего применяется стандартная IP-адресация.
- Код функции (Function code). Определяет, какое действие необходимо выполнить серверному устройству. Значения кодов функций лежат в диапазоне от 1 до 255, причем коды от 128 до 255 зарезервированы для сообщений об ошибках. Код 0 не используется.
Для кодов из диапазонов 65-72 и 100-110 пользователи могут реализовать собственные функции (User-Defined Function Codes). Некоторые коды, например 9, 10, 13 и другие, зарезервированы определенными поставщиками для своего оборудования и закрыты для общего использования (Reserved Function Codes). Не входящие в эти два подмножества коды относятся к публичным (Public Function Codes) — это задокументированные функции, находящиеся в открытом доступе.
- Данные (Data). Данные, необходимые для выполнения выбранной функции на серверном устройстве. Чаще всего это адреса регистров для чтения или записи, их количество и так далее. Длина и формат поля зависят от кода функции. Некоторые функции не требуют передачи данных.
- Контрольная сумма (Error check). Содержит рассчитанное при помощи специального алгоритма число для проверки целостности пакета. В качестве алгоритма для расчетов используется CRC-16 или LRC-8. В некоторых реализациях протокола поле отсутствует — например, в Modbus TCP, где контроль целостности пакета обеспечивается средствами протокола TCP/IP.
Рассмотрим передачу пакетов в Modbus. Протокол обеспечивает клиент-серверное взаимодействие в режиме Request/Response. Клиент инициирует запрос в серверное устройство, передавая в PDU код функции и данные. В зависимости от физического уровня сети в пакете могут быть дополнительные поля, рассмотренные выше.
Если обработка запроса проходит без ошибок, то сервер возвращает пакет, содержащий исходный код функции и запрошенные данные.
При возникновении ошибки серверное устройство возвращает в качестве данных код исключения, а вместо исходного кода функции — его значение, увеличенное на 128 (0x80 в шестнадцатеричной системе HEX).
Также предусмотрены тайм-ауты на стороне клиента во избежание длительного ожидания ответа от вышедших из строя устройств.
Разновидности Modbus: ASCII, TCP и RTU
Modbus — это протокол прикладного (седьмого) уровня модели OSI (Open Systems Interconnection model). Он не зависит от нижележащих уровней и может использоваться совместно с другими протоколами, например Ethernet TCP/IP или UDP/IP, а в качестве физической среды для передачи сигналов применять последовательные интерфейсы RS-232, RS-422, RS-485, оптоволокно, радиоканалы и другое.
Опишем отличия наиболее известных реализаций протокола Modbus: RTU, ASCII и TCP.
Modbus RTU (Remote Terminal Unit). Это разновидность протокола, которая в качестве физического уровня сети чаще всего использует последовательный интерфейс RS-485, реже — RS-232 и RS-422. По сути, все эти интерфейсы определяют связь с помощью витых пар, но различаются характеристиками вида максимальной длины кабеля, количества узлов и так далее.
Формат пакета Modbus RTU в целом совпадает с обобщенной формой, описанной ранее: дополнительные поля не используются. Контроль целостности пакетов ведется с помощью алгоритма CRC-16.
Важная особенность Modbus RTU в том, что для разделения пакетов должны использоваться временные паузы продолжительностью не менее чем произведение 3,5*t, где t — время передачи одного байта в текущей сети. А передача байтов данных в пределах одного пакета производится последовательно с промежутком времени между соседними байтами не более 1,5*t, иначе передача будет считаться ложной. Эти правила не дают использовать Modbus RTU в медленных, например модемных, сетях.
Modbus ASCII. Это разновидность протокола, также работающая поверх интерфейсов RS-232/RS-485, но для кодирования сообщений использующая ASCII-символы.
По сравнению с Modbus RTU в формате пакета добавляются еще два поля — специальные символы для отметки начала и конца сообщения: двоеточие и символы возврата каретки / перевода строки. Временные паузы между пакетами не нужны. Для проверки целостности применяется алгоритм LRC-8.
В целом этот вариант протокола сейчас используется крайне редко — из-за сложностей кодирования и большого размера сообщений. Однако он может стать хорошей альтернативой Modbus RTU на линиях с сетевыми задержками и оборудовании с менее точными таймерами.
Modbus TCP. Это реализация ModBus в сетях Ethernet. Работает поверх TCP/IP стека.
В отличие от Modbus RTU и ASCII, в Modbus TCP соединение устанавливается с конкретным устройством средствами TCP/IP. Поэтому адрес в пакете Modbus чаще всего игнорируется, а широковещательная рассылка сообщений не используется. Однако адрес может потребоваться, если соединение устанавливается со шлюзом, который, в свою очередь, выводит на сеть RS485 — чтобы далее общаться с устройствами уже на языке Modbus.
Контроль целостности пакетов также обеспечивается средствами протокола TCP/IP, поэтому нет необходимости в его Modbus-реализации.
Наряду с адресом в заголовке пакета Modbus TCP присутствует ряд дополнительных полей:
Чаще всего заполняется нулями. Необходим для случаев, когда клиентское устройство отправляет несколько сообщений, не дожидаясь ответа на предыдущие, чтобы затем связать ответы с запросами.
Мы рассмотрели только открытые и самые распространенные реализации протокола Modbus. Но их гораздо больше, например MODBUS Plus — проприетарный протокол от Schneider Electric, поддерживающий режим Multi-Master.
Регистры и функции Modbus
Так как Modbus предназначен для работы с промышленной автоматикой, обмен данными с Modbus-устройствами происходит через регистры. Они делятся на входы и выходы. Входы можно только читать, а выходы — читать и писать. Бывают 1-битные регистры Modbus для описания дискретных входов/выходов (Discrete Inputs и Coils) и 16-битные регистры для аналоговых входов/выходов (Input Registers и Holding Registers).
Доступ к регистрам осуществляется с помощью 16-битного адреса. Первому элементу в каждой группе регистров соответствует адрес 0. То есть адрес любого регистра может принимать значения из диапазона 0-65535 (0x0000-0xFFFF в HEX-формате). При этом спецификация протокола не определяет, что физически из себя представляют адресные пространства и по каким внутренним адресам устройства должны быть доступны регистры. В общем случае значения регистров с одинаковым адресом, но разными типами отличаются друг от друга.
В документации ряда производителей на некоторые, особенно старые устройства адреса регистров могут быть указаны в других форматах — где адресация начинается не с нуля и первая цифра адреса определяет тип регистра. Например, Input Register с адресом 0 может быть описан как 30001, а Holding Register — как 40001. В таких случаях в пакетах данных следует передавать адреса в стандартном формате Modbus независимо от способа представления их в документации. Для получения верного адреса достаточно вычесть смещение, соответствующее типу регистра. В некоторые программные пакеты заложена автоматическая корректировка адресов.
Тип регистров | Назначение | Размер | Доступ | Стандартный адрес | Примеры нестандартных адресов |
Coils | Регистры флагов, обозначающие текущее состояние выхода устройства. Например, при включенном реле значение 1. | 1 бит | Чтение и запись (выход) | 0-65535 (0x0000-0xFFFF в HEX-формате) | 00001-09999 или 000001-065536 |
Discrete Inputs | Дискретные входы, описывающие состояние входа устройства. Например, при поданном напряжении значение 1. | 1 бит | Чтение (вход) | 0-65535 (0x0000-0xFFFF в HEX-формате) | 10001-19999 или 100001-165536 |
Input Registers | Регистры ввода, предназначенные для чтения настроек (например, текущего значения температуры). | 16 битов | Чтение (вход) | 0-65535 (0x0000-0xFFFF в HEX-формате) | 30001-39999 или 300001-365536 |
Holding Registers | Регистры, предназначенные для хранения настроек с возможностью их чтения и записи. | 16 битов | Чтение и запись (выход) | 0-65535 (0x0000-0xFFFF в HEX-формате) | 40001-49999 или 400001-465536 |
Для работы с каждым типом регистров определены функции чтения и записи. Наиболее часто используемые функции описаны ниже.
Код | HEX-код для PDU | Название функции | Тип данных | Назначение |
1 | 0x01 | Read Coils | Coils | Чтение значений нескольких регистров флагов |
2 | 0x02 | Read Discrete Inputs | Discrete Inputs | Чтение значений нескольких дискретных входов |
3 | 0x03 | Read Holding Registers | Holding Registers | Чтение значений нескольких регистров хранения |
4 | 0x04 | Read Input Registers | Input Registers | Чтение значений нескольких регистров ввода |
5 | 0x05 | Write Single Coil | Coils | Запись одного регистра флагов |
6 | 0x06 | Write Single Register | Holding Registers | Запись одного регистра хранения |
15 | 0x0F | Write Multiple Coils | Coils | Запись нескольких регистров флагов |
16 | 0x10 | Write Multiple Register | Holding Registers | Запись нескольких регистров хранения |
Для каждой функции в спецификации протокола Modbus определена структура PDU: какие данные и в каком порядке должны использоваться в запросах и ответах. Рассмотрим формирование пакетов Modbus RTU на примере функции Read Coils с кодом 1. Эта функция, кроме передачи собственного кода, требует наличия в запросе адреса первого Coil-регистра и количества регистров, которые необходимо прочитать. В случае успешного выполнения запроса в ответе будут возвращены код функции, число байт, необходимое для вывода запрошенных Coil-регистров, и статус всех этих регистров.
Предположим, нам нужно обратиться к серверному устройству с адресом 1 и прочитать 19 его Coil-регистров с номерами 20–38. Адресация регистров ведется с 0, поэтому адрес первого нужного нам регистра будет 0x13 (это 19 в HEX-системе). Требуемое для чтения количество регистров также будет равно 0x13 (для чтения запрошено 19). В качестве адреса и кода функции указываем 01. Контрольная сумма формируется по алгоритму CRC-16 на основе других полей пакета.
В случае отсутствия ошибок в ответе вернутся без изменений адрес серверного устройства и код функции. Для расчета числа байтов, которые потребуются для возврата состояния регистров, нужно разделить запрошенное количество регистров на 8 и к результату прибавить 1, если остаток от деления не равен 0. В нашем случае результат деления 19 на 8 равен 2, но остаток положительный — поэтому для вывода регистров потребуется 2+1=3 байта. Это значение будет указано в ответе после кода функции. И далее будут следовать 3 байта, описывающие состояние выбранных регистров. Например, первый байт будет описывать состояние 8 Coil-регистров с номерами 27-20. Если в поле, к примеру, содержится HEX-значение CD — статус соответствующих 8 регистров такой: 1100 1101.
Если в процессе обработки запроса на серверном устройстве возникнет ошибка (например, обнаружен несуществующий адрес регистра), то в ответе будет содержаться измененный код функции, равный исходному коду плюс смещение 0x80 — в нашем примере 0x81, и код исключения — в нашем примере 03, что значит неверный формат запроса. С полным перечнем возможных исключений можно ознакомиться в документации.