Modbus. Что это и с чем его едят
Modbus это протокол передачи данных по типу клиент-сервер. обеспечивающий передачу данных между устройствами подключенным к различным шинам или сетям. Разработан был в далеком далеком 1979 году и с тех пор обрел неплохую популярность среди миллионов автоматических устройств. Поддержка протокола происходит и по сей день.
Как и свойственно клиент-серверному общению, коммуникации происходят по правилу запрос-ответ, сообщения снабжаются функциональными кодами, которые и говорят о его типе. Передача может происходить по нескольким типам сетей:
- TCP/IP
- Асинхронные последовательные линии передач (EIA/TIA-232-E, EIA-422, EIA/TIA-485-A, оптоволокно, радио и пр.
Структура пакета
Протокол Modbus описывает простейший типа пакета PDU (Protocol data unit), который лежит в основе всех пакетов Modbus. В зависимости от типа передачи, пакет может дополняться дополнительными полями уже на уровне ADU (Application data unit).
Клиент создает ADU пакет и инициирует передачу, после получения пакета сервер разбирает пакет и по функциональному коду уже решает, что делать с данными. Функциональные коды могут быть в пределах от 1 до 255, где с 128 по 255 коды зарезервированные под ошибки. Поле данных же содержит дополнительную информацию для сервера которая используется в соответствии с функциональным кодом. Это может быть адрес регистра, количество пунктов которые будут обработаны или количество байт в поле. В каких то типах вопросов поле данных может отсутствовать вообще (нулевой длинны).
Если сервер успешно принял пакет и обработал данные, то он возвращает ответ в той форме, в которой этого требует действие декларированное функциональным кодом. Например, если клиент хочет изменить какой либо регистр сервера, то он отправляет соответствующий функциональный код и необходимые и если сервер успешно обработал запрос, то он возвращает точно такой же пакет, который и получил. Но если что то пошло не так, то сервер генерит ошибку, состоящая из функционального кода ошибки и самого кода ошибки:
Размер PDU ограничен по размеру. Для последовательной линии передачи данных это = 256 байт - Адрес сервера (1 байт) - CRC (2 байта). Получается, что размер пакета ограничен 253 байтами. Следовательно:
- RS232/R485 размер ADU = 253 байта + Адрес сервера (1 байт) + CRC (2 байта) = 256 байт
- TCP размер ADU = 253 байта + MBAP (7 байт) = **260 байт **
Протокол Modbus использует big-endian порядок байтов для адреса и данных. Это значит, что длинное число отправленное в пакете начинается с левого байта к правому. Например, значение 0x1234 разобьется на два байта и будет отсылаться в порядке, сначала 0x12, а затем 0x34.
Modbus основан на модели данных из серии таблиц, которые имеют различные характеристики
Адресация элементов таблицы задается 16-значным адресом начиная с 0, что позволяет каждой таблице содержать до 2^16 = 65536 элементов. Очевидно предположить, что все данные обрабатываемые с помощью Modbus (биты и регистры) должны быть расположены в памяти устройства. Но физический адрес в памяти не стоит путать ссылкой на данные. Единственным требованием является описание указанных адресов с физическим адресом в устройстве.
Такое представление типов данных можно сравнить с привычными нами таблицами регистров. Различием является только лишь то, что мы сами придумываем таблицу регистров для нашего сервера.
Обработка пакета PDU на сервере должна выглядеть примерно так:
Возврат ошибки от сервера должен выглядеть как PDU пакет, где в функциональным кодом является код ошибки + 0x80. А код исключения должен описывать причину ошибки.
Распределение функциональных кодов
Все функциональные код протокола Modbus можно разделить на три категории:
Публичные функциональные коды:
Четко определенные функциональные коды, подтвержденные сообществом Modbus.org, публично задокументированы и имеют в своем распоряжении тест соответствия, включают в себя как указанные функциональные коды так и не указанные, зарезервированные для использования в будущем.
Функциональные коды указанные пользователем:
Существует два диапазона таких кодов. от 65 до 72 и от 100 до 110. Пользователь волен выбирать свои действия которые сервер будет совершать получив пакет с данным кодом.
Зарезервированные функциональные коды:
Эти коды в настоящее время используются некоторыми компаниями для устаревших продуктов и не доступны для публичного использования.
Описание функциональных кодов
0x01 - Чтение регистров флагов
Этот код используется для чтения от 1 до 2000 регистров флагов по очереди. Пакеты такого запроса будут выглядеть следующим образом:
Функциональный код: 1 байт - 0x01
Адрес первого флага: 2 байта - от 0x0000 до 0xFFFF
Количество флагов: 2 байта - от 1 до 2000 (0x7D0)
Ответ же будет таким
Функциональный код: 1 байт - 0x01
Количество флагов: 1 байт - n штук
Флаги: n байт
В случае ошибки ответ будет таким
Функциональный код: 1 байт - функциональный код+0x80
Код ошибки: 1 байт - или 01 или 02 или 03 или 04
По данной картинке можно понять какой код будет говорить о какой ошибке.
0x02 - Чтение дискретных входов
Эта функция работает аналогично 0x01
0x03 - Чтение регистров хранения
Здесь нам дозволено читать до 125 значений. Структура запроса и ответа аналогична 0x01 за тем лишь исключением, что принимать мы будем не биты а по 2 байта
0x04 - Чтение входных регистров
Аналогично 0x03
0x05 - Запись регистра флага
Здесь мы можем изменить значение регистра флага. С нуля на единичку или наоборот. Для единички используется 0xFF00, а для ноля 0x0000
Функциональный код: 1 байт - 0x01 Адрес флага: 2 байта - от 0x0000 до 0xFFFF Состояние флага: 2 байта - либо 0x0000, либо 0xFF00
В случае успеха, мы получим точно такое сообщение, в случае провала код ошибки 0x85. Алгоритм работы будет нескольки отличаться:
0x06 - Запись одного регистра
Записывает данные в один регистр.
Функциональный код: 1 байт - 0x08 Адрес регистра: 2 байта - от 0x0000 до 0xFFFF Состояние регистра: 2 байта - от 0x0000 до 0xFFFF
Блок-схема работы аналогична 0x05
0x07 - Чтение сигналов состояния
Запрос состоит из одного функционального кода, а ответом служит тот же функциональный код с одним байтом ответа. Схема работы такова:
0x08 - Диагностика (Только для последовательно линии)
Созданная для того, чтобы проверить связь между клиентом и сервером. Пример пакета:
Функциональный код: 1 байт - 0x08 Под-функция: 2 байта Данные : N x 2 байта
В качестве Под-функции указывается тип диагностики:
- 00 - Вернуть отосланные данные
- 01 - Перезапустить линию передачи
- 02 - Вернуть регистр диагностики
- 03 - Изменить ASCII код разделителя
- 04 - Включить режим только чтения
- 10 - Очистить счетчики и диагностические регистры
- 11 - Вернуть количество принятых сообщений на линии
- 12 - Вернуть количество ошибок полученных на линии
- 13 - Вернуть количество исключений полученных на линии
- 14 - Вернуть количество сообщений отправленных сервером
- 15 - Вернуть количество не отвеченных сообщений
- 16 - Вернуть количество NAK (Negative Acknowledge) сообщений
- 17 - Вернуть количество сообщений полученных сервером, когда он был занят
- 18 - Вернуть количество переполнений
- 20 - Сбросить счетчик переполнения и флаг
Блок-схема работы:
0x0B - Вернуть кол-во событий
Используется чтобы получить с сервера два байта статуса и кол-во событий. Пример:
Функциональный код: 1 байт - 0x0B
Ответ:
Функциональный код: 1 байт - 0x0B Статус: 2 байта - от 0x0000 до 0xFFFF Код-во событий: 2 байта - от 0x0000 до 0xFFFF
2 байта статуса означают, что если предыдущая команда все еще обрабатывается на удаленном устройстве, то ответ будет 0xFFFF, иначе 0x0000
0x0C - Вернуть лог событий
Возвращает лог событий с момента запуска сервера. Нормальный ответ будет содержать функциональный код, один байт содержащий длину следующего сообщения, два байта статуса, два байта с количеством событий, два байта с количеством сообщений и события длинной в один байт. Более подробное описание этой функции можно посмотреть в документации с сайта modbus.org.
0x0F - Записать несколько регистров флагов
Имеет такую структуру:
Функциональный код: 1 байт - 0x0F Адрес первого флага: 2 байта - от 0x0000 до 0xFFFF Количество байт для изменения: 2 байта - от 0x0000 до 0xFFFF Кол-во байт с данными: 1 байт - N Данные для записи: N байт
Ответ:
Функциональный код: 1 байт - 0x0F Адрес первого флага: 2 байта - от 0x0000 до 0xFFFF Количество байт для изменения: 2 байта - от 0x0000 до 0xFFFF
Например, возьмем такой пакет:
Функциональный код: 1 байт - 0x0F Адрес первого флага: 2 байта - 0x0013 Количество байт для изменения: 2 байта - 0x000A Кол-во байт с данными: 1 байт - 0x02 Данные для записи: 1 байт - 0xCD Данные для записи: 1 байт - 0x01
Разложив данные для записи в бинарное число, мы получим, что 0xCD01 = 1100 1101 0000 0001. Количество байт под изменение 0x000A - 10 штук, начиная с адреса 0x0013. Изменение в этом случае будет происходить сначала в первом байте, а затем во втором. Тем самым получим такие флаги:
0xCD:
- 1 - на адрес 0x1B
- 1 - на адрес 0x1A
- 0 - на адрес 0x19
- 0 - на адрес 0x18
- 1 - на адрес 0x17
- 1 - на адрес 0x16
- 0 - на адрес 0x15
- 1 - на адрес 0x14
0x01:
- 0
- 0
- 0
- 0
- 0
- 0
- 0 - на адрес 0x1D
- 1 - на адрес 0x1C
Алгоритм работы похожий как и с последовательной записью
0x10 - Запись нескольких регистров
Позволяет записать от 1 до 123 регистров на удаленное устройство.
Функциональный код: 1 байт - 0x10
Адрес первого регистра: 2 байта - от 0x0000 до 0xFFFF
Количество регистров для изменения: 2 байта - от 0x0001 до 0x007B
Кол-во байт с данными: 1 байт - 2*N
Данные для записи: N*2 байт
Ответ:
Функциональный код: 1 байт - 0x10
Адрес первого регистра: 2 байта - от 0x0000 до 0xFFFF
Количество регистров для изменения: 2 байта - от 0x0001 до 0x007B
В результате у нас последовательно будут заполняться регистры по типа того, что было в 0x0F
0x11 - Сообщить ID сервера
Используется для чтения описания типа, настоящего статуса и другой информации с устройства
Функциональный код: 1 байт - 0x11
Ответ:
Функциональный код: 1 байт - 0x11
Количество байт в сообщении: 1 байт
ID сервера: Длинна задается устройством
Индикатор запуска: 1 байт - 0x00 = Выключен, 0xFF = Включен
Прочая информация: Длинна задается устройством
0x14 - Чтение из файла
Файлы организованы в виде записей. Каждый файл может содержать до 10000 записей, адресованных от 0000 до 9999 или от 0x0000 до 0x270F.
Функциональный код: 1 байт - 0x14
Количество байт: 1 байт - от 0x07 до 0xF5
Подзапрос x - Тип: 1 байт - 06
Подзапрос x - Номер файла: 2 байта - от 0x0001 до 0xFFFF
Подзапрос x - Номер записи: 2 байта - от 0x0000 до 0x270F
Подзапрос x - Длинна записи: 2 байта - N
Подзапрос x+1 - ... и т.д.
Ответ:
Функциональный код: 1 байт - 0x14
Длинна ответа: 1 байт - от 0x07 до 0xF5
Подзапрос x - Длинна ответа: 1 байт - от 0x07 до 0xF5
Подзапрос x - Тип: 1 байт - 06
Подзапрос x - Данные: N x 2 байт
Подзапрос x+1 - ... и т.д.
0x15 - Запись в файл
Такие же правила как и в 0x14 только происходит запись
Функциональный код: 1 байт - 0x15
Количество байт: 1 байт - от 0x09 до 0xFB
Подзапрос x - Тип: 1 байт - 06
Подзапрос x - Номер файла: 2 байта - от 0x0001 до 0xFFFF
Подзапрос x - Номер записи: 2 байта - от 0x0000 до 0x270F
Подзапрос x - Длинна записи: 2 байта - N
Подзапрос x - Данные: N x 2 байт
Подзапрос x+1 - ... и т.д.
Ответ:
Функциональный код: 1 байт - 0x15
Длинна ответа: 1 байт - от 0x09 до 0xFB
Подзапрос x - Тип: 1 байт - 06
Подзапрос x - Данные: N x 2 байт
Подзапрос x - Номер файла: 2 байта - от 0x0001 до 0xFFFF
Подзапрос x - Номер записи: 2 байта - от 0x0000 до 0x270F
Подзапрос x - Длинна записи: 2 байта - N
Подзапрос x - Данные: N x 2 байт
Подзапрос x+1 - ... и т.д.
0x16 - Запись в регистр по маске
Помогает изменить регистр с использованием AND-маски и OR-маски. Результат будет примерно таким
(Исходное значение AND and-Маска) OR (or-Маска AND (NOT and-маска))
Например:
Исходное значение - 0001 0010
and-Маска - 1111 0010
or-Маска - 0010 0101
NOT and-Маска - 0000 1101
Результат - 0001 0111
Структура пакета:
Функциональный код: 1 байт - 0x16
Адресс регистра: 2 байт - от 0x0001 до 0xFFFF
And-Маска: 2 байт - от 0x0001 до 0xFFFF
Or-Маска: 2 байт - от 0x0001 до 0xFFFF
В качестве ответа пакет будет полностью дублирован
0x17 - Чтение/Запись нескольких регистров
Функция комбинирует одну операцию чтения и одну операцию записи в одной транзакции.
Функциональный код: 1 байт - 0x17
Адресс регистра для чтения: 2 байт - от 0x0001 до 0xFFFF
Количество регистров для чтения: 2 байт - от 0x0001 до 0x007D
Адресс регистра для записи: 2 байт - от 0x0001 до 0xFFFF
Количество регистров для записи: 2 байт - от 0x0001 до 0x007D
Количество байт: 1 байт - 2 x N
Регистры для записи: N x 2 байт
Ответ:
Функциональный код: 1 байт - 0x17
Количество байт: 1 байт - 2 x N
Байты для чтения: N x 2 байт
0x18 - Чтение данных из очереди
Функция позволяет читать очередь FIFO (первый вошел - первый вышел) из регистра на удаленном устройстве.
Функциональный код: 1 байт - 0x18
Адресс очереди: 2 байт - от 0x0001 до 0xFFFF
Ответ:
Функциональный код: 1 байт - 0x18
Количество байт: 2 байт
Количество элементов очереди: 2 байта
Ну и в заключение описание кодов ошибок, которое я тупо скопировал с википедии:
- 01 — Принятый код функции не может быть обработан
- 02 — Адрес данных, указанный в запросе, не доступен
- 03 — Величина, содержащаяся в поле данных запроса, является недопустимой величиной
- 04 — Невосстанавливаемая ошибка имела место, пока подчинённый пытался выполнить затребованное действие.
- 05 — Подчинённый принял запрос и обрабатывает его, но это требует много времени. Этот ответ предохраняет главного от генерации ошибки тайм-аута.
- 06 — Подчинённый занят обработкой команды. Главный должен повторить сообщение позже, когда подчинённый освободится.
- 07 — Подчинённый не может выполнить программную функцию, принятую в запросе. Этот код возвращается для неудачного программного запроса, использующего функции с номерами 13 или 14. Главный должен запросить диагностическую информацию или информацию об ошибках от подчинённого.
- 08 — Подчинённый пытается читать расширенную память, но обнаружил ошибку паритета. Главный может повторить запрос, но обычно в таких случаях требуется ремонт.