Главная » Без рубрики » Клиент Modbus TCP для Simatic S7-1200 и S7-1500

Клиент Modbus TCP для Simatic S7-1200 и S7-1500

Что такое клиент Modbus TCP?

Клиент Modbus TCP — это узел, который формирует запросы к серверу: запрашивает данные, передаёт уставки и команды. В терминологии Modbus RTU ему соответствует понятие «мастер» (ведущее устройство). Однако в TCP-версии протокола допускается наличие нескольких клиентов одновременно — в отличие от RTU, где мастер всегда один.

Почему клиент программировать сложнее?

Если для сервера достаточно одного вызова функционального блока и одного блока данных, то с клиентом всё несколько сложнее.

Во-первых, клиент может (и на практике так и происходит) обмениваться данными с несколькими серверами.

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

В-третьих, нельзя забывать про порядок байт в словах. Разные аппаратные платформы используют разные схемы (little-endian и big-endian), и при несовпадении байты приходится переворачивать вручную.

Именно поэтому клиента имеет смысл программировать на языке SCL (ST в терминологии МЭК 61131-3) и «заворачивать» всю обработку в функциональный блок.

Практический пример: общение с двумя серверами

Для большей реалистичности рассмотрим случай, когда контроллер общается с двумя серверами Modbus TCP, отправляя несколько запросов к каждому.

Создание функционального блока

Первым делом создаём функциональный блок ModbusClient на языке SCL и добавляем вызов его экземпляра в OB1.

В первую очередь создадим функциональный блок ModbusClient на языке SCL и добавим вызов его экземпляра в OB1.
В первую очередь создадим функциональный блок ModbusClient на языке SCL и добавим вызов его экземпляра в OB1.

В области статических переменных (STAT) функционального блока прописываем две структуры TCON_IP_v4 — по одной на каждое соединение с сервером. Это два разных соединения, и каждое требует отдельного описания. В данном примере конфигурируемые соединения не используются.

Объявлено две структуры для связи с двумя серверами
Объявлено две структуры для связи с двумя серверами

Заполнение структур TCON_IP_v4

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

InterfaceId — идентификатор сетевого интерфейса контроллера. Клиент Modbus работает на первом интерфейсе. Его ID можно посмотреть в конфигурации устройства — в нашем случае он равен 64. Важно: нужен идентификатор именно интерфейса, а не его портов.

Его ID равен 64. Нужен идентификатор именно интерфейса, а не его портов.
Его ID равен 64. Нужен идентификатор именно интерфейса, а не его портов.

ID — идентификатор соединения. Не путать с идентификатором интерфейса и с «номером» Modbus-устройства. Это внутренний логический номер коннекшена, который программист назначает самостоятельно в диапазоне от 1 до 4096. У каждого соединения должен быть свой уникальный идентификатор. Назначаем ID = 1.

ConnectionType — тип соединения: TCP или UDP. По умолчанию значение 0x0B (11 в десятичной системе) — оставляем TCP.

ActiveEstablished — выставляем в «истину». Поскольку мы работаем как клиент, именно наша сторона должна инициировать соединение.

RemoteAddress — IP-адрес первого сервера. Пусть будет 192.168.43.100.

RemotePort — порт, на котором сервер Modbus TCP ожидает запросы. Стандартный порт — 502.

LocalPort — оставляем равным нулю.

Соединение с первым сервером выглядит следующим образом.
Соединение с первым сервером выглядит следующим образом.

Вторая структура заполняется аналогично, с другим ID и другим IP-адресом.

Вторая структура заполняется аналогично. С другим ID и другим IP адресом.
Вторая структура заполняется аналогично. С другим ID и другим IP адресом.

Первый запрос: читаем один регистр

Теперь пробуем прочитать один регистр хранения с сервера. Для этого перетаскиваем функциональный блок MB_CLIENT из библиотеки в программу. При перетаскивании появляется диалог создания экземпляра — выбираем мульти-экземпляр и корректируем имя.

Для начала надо перетащить FB MB_CLIENT из библиотеки в программу.
Для начала надо перетащить FB MB_CLIENT из библиотеки в программу.
После перетаскивания появится диалоговое окно создания экземпляра.
После перетаскивания появится диалоговое окно создания экземпляра.
Промежуточный итог.
Промежуточный итог.
Вызов FB
Вызов FB

Параметры вызова MB_CLIENT:

REQ — активирует выполнение опроса. Пока REQ = TRUE, клиент читает данные с сервера или записывает их на сервер.

DISCONNECT — разрывает соединение.

MB_MODE — режим работы клиента. В совокупности со входом MB_DATA_ADDR определяет, какая функция Modbus TCP используется. Для чтения одного или нескольких регистров хранения значение MODE должно быть равно 0.

MB_DATA_ADDR — адрес в адресном пространстве протокола Modbus TCP. В примере указан 40001 — это «первый регистр хранения».

MB_DATA_LEN — количество читаемых или записываемых величин. В нашем случае — единица.

MB_DATA_PTR — переменная или структура данных, куда записывается прочитанное значение. Переменная может находиться в любом блоке данных. В примере объявлена локальная статическая переменная SingleHR типа INT — её размер (2 байта) совпадает с размером одного регистра хранения Modbus. Если размерности не совпадают, вызов завершится ошибкой.

CONNECT — уже созданная структура TCON_IP_V4.

Диагностика ошибок.

После компиляции и загрузки программы сервер не отвечает. Никаких ответов нет.

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

Дело в том, что флаги успешного (DONE) или неуспешного (ERROR) вызова блока «живут» всего один цикл сканирования программы. Заметить такое быстрое изменение невозможно. Поэтому по флагу ошибки копируем статус вызова в отдельную переменную, а по флагу успешного выполнения — обнуляем статус. Также добавляем флаги управления запросом и соединением.

В описанном случае код ошибки был 80C6. В документации на блок MB_CLIENT этой ошибки нет, но поиск по справочной системе указал на функцию TCON (аналогичные ошибки можно искать среди TSEND, TRECEIVE и похожих блоков). Описание: «The connection partner cannot be reached (network error)».

Причина оказалась банальной: программа-имитатор Modbus не была прописана в разрешениях встроенного брандмауэра Windows. Разрешение было настроено только для частных сетей, а интерфейс программатора был определён как публичная сеть. После изменения настроек брандмауэра обмен заработал.

Чтение вещественной переменной.

Усложним задачу: попробуем считать с сервера одну вещественную переменную (REAL). Это 4 байта, то есть 2 регистра. В вызове увеличиваем количество читаемых регистров до 2 и меняем указатель на прочитанные данные — теперь это внутренняя статическая переменная типа REAL.

Чтение с сервера одной вещественной переменной.
Чтение с сервера одной вещественной переменной.

Важный нюанс при горячей загрузке.

Если загружать изменённое прикладное ПО «на горячую», с переинициализацией переменных функционального блока ModbusClient в тот момент, когда контроллер уже опрашивает сервер, обмен может прекратиться. На выходе блока появится статус 80A3. Это связано с вмешательством во внутренние структуры обмена из-за переинициализации всего блока. В некоторых случаях это приводило к полной невозможности коммуникаций до перезапуска контроллера переключателем старт/стоп.

Чтобы избежать такого зависания, необходимо сначала «поднять» флаг Srv1Disconnect, провести изменения в переменных функционального блока (имеются в виду переменные интерфейсной части, а не программный код), выполнить загрузку с переинициализацией, а затем вручную возобновить обмен.

Проблема порядка байт (endianness).

Сервер Modbus хранит в двух регистрах вещественную переменную со значением 0.666. Клиент вместо этого числа читает 1.663175E+38 — сильно отличающееся значение. Причина — разный порядок байт в словах и порядок самих слов в двойном слове.

Инструкция SWAP меняет порядок байт в двойном слове, но в данном случае она не помогает — значение всё равно неправильное. Это означает, что сервер отдаёт регистры в «неправильном» порядке, а байты внутри регистров — в «правильном». Информация оказывается «перемешанной».

Решение: складываем результат чтения двух регистров в массив из 4 байт, объявляем ещё один массив из 4 байт для манипуляций. Функция Deserialize «собирает» массив из байт в конкретное значение — в данном случае в вещественную переменную. После одной итерации получаем корректное значение.

Функция Deserialize «складывает» массив из байт в какое-либо конкретное значение, в данном случае — в вещественную переменную.
Функция Deserialize «складывает» массив из байт в какое-либо конкретное значение, в данном случае — в вещественную переменную.

Частая ошибка: Unit ID.

В протоколе Modbus RTU все ведомые устройства (слейвы) имеют уникальный адрес в сети. Мастер, формируя запрос, указывает этот адрес — однобайтовое поле в заголовке пакета.

В протоколе Modbus TCP адресом устройства является его IP-адрес. Однако поле Device ID в заголовке сохранилось — и это часто вносит путаницу.

Согласно спецификациям, «обычный» сервер Modbus TCP должен игнорировать это поле. Unit ID учитывается только для устройств, преобразующих Modbus RTU в Modbus TCP (шлюзы, конвертеры протоколов и т.п.).

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

Если столкнулись с таким поведением, откройте экземпляр функционального блока клиента Modbus и найдите в его статических переменных байтовую величину MB_Unit_ID. По умолчанию её значение равно 0xFF (255). «Нормальные» серверы его игнорируют — им достаточно самого факта установления TCP-соединения. Если же сервер «ненормальный», установите здесь вручную Unit ID вашего устройства.

Поставьте тут вручную Unit ID вашего устройства.
Поставьте тут вручную Unit ID вашего устройства.

В статических переменных есть и другие полезные настройки: таймауты, количество повторных попыток запроса и т.д. — их можно изучить во встроенной справке.

Работа с несколькими запросами к одному серверу

Теперь вернёмся к исходной задаче: считываем с первого сервера 3 вещественных переменных (6 регистров), начиная с адреса 40001, и записываем одну переменную (начальный адрес 40011). Предполагаем, что порядок слов и байт «правильный».

Шесть регистров — это шесть слов данных и три вещественных переменных. Можно читать информацию в локальный массив байт, а потом средствами дополнительной обработки представлять их в виде трёх вещественных величин (тем же Deserialize), но это лишняя работа.

Гораздо удобнее сразу «разложить» читаемую информацию в собственную структуру. В блоке данных Data создаём структуру из трёх полей типа REAL. Важно: блок данных Data должен быть «стандартным» или «неоптимизированным», иначе будет возникать ошибка опроса (например, 818B). Содержание структуры зависит от того, в каком порядке и в какой форме сервер отдаёт данные.

Структура, состоящая из трех полей типа REAL.
Структура, состоящая из трех полей типа REAL.
Блок данных Data должен быть «стандартным» или «неоптимизированным».
Блок данных Data должен быть «стандартным» или «неоптимизированным».
Программа приобретает следующий вид.
Программа приобретает следующий вид.

Организация последовательных запросов.

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

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

В итоге получается следующая программа:
В итоге получается следующая программа:

Добавление второго сервера

Для опроса второго сервера достаточно подготовить хранилище для локальных переменных (от сервера и к серверу) и дополнить существующую программу. Отличия минимальны: тот же принцип, но другие переменные (включая экземпляр функционального блока MODBUS_CLIENT) и другие запрашиваемые/записываемые данные.

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

Со вторым сервером настроено отдельное соединение и есть отдельный экземпляр функционального блока Modbus. Его можно вызывать «одновременно» с коммуникациями первого сервера, поэтому в общей программе предусмотрено два независимых оператора CASE.

Со вторым сервером настроено отдельное соединение.
Со вторым сервером настроено отдельное соединение.

Дальнейшие доработки

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

На этом описание работы с протоколом Modbus TCP завершается. В следующий раз рассмотрим программирование протокола Modbus RTU.

Menu