Пример передачи данных по протоколу Modbus на Delphi


Для примера будем использовать контроллер ОВЕН ПЛК100. Для использования Modbus ASCII или Modbus RTU нужно подключить контроллер к компьютеру через COM порт, а для Modbus TCP по сети.

В конфигурации ПЛК добавляем компонент Modbus(slave). В группе Modbus[FIX] добавляем компонент соответствующий той связи которая соединяет компьютер с контроллером. Далее в группе Modbus(slave) добавляем регистры как показано на картинке

Компонент Modbus c набором переменных в конфигурации контроллера
Переменные Modbus
  • wdvr - двухбайтовое без знаковое
  • intvr - двухбайтовое знаковое
  • dwvr - четырех байтовое без знаковое
  • dintvr - четырех байтовое знаковое
  • fltvr - четырех байтовое с плавающей точкой
  • str - изходящая строк
  • str_ - продолжение исходящей строки
  • str2 - входящая строка

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

Программа контроллера будет с периодом 100мс изменять значения всех переменных, что-бы данные изменения наблюдать в программе на Delphi

Создаем VCL приложение в Delphi
Программа на Delphi для примера Modbus соединения

Добавляем на форму

  • StatusBar с одним текстовым полем
  • KRTimer, в свойствах Interval=500
  • KRCOMPortConnector, в свойствах устанавливаем необходимые настройки подключения
  • KRSpeeInfo, в свойстве Component выбираем коннектор
  • KRModbusMaster, в свойствах выбираем коннектор
  • KRModbusClient, в свойствах выбираем компонент KRModbusMaster, устанавливаем адрес устройства единицу и тип Modbus протокола mbtACSII
  • Создаем Modbus переменную, для этого в структуре объектов на компоненте KRModbusClient1 открываем контекстное меню и выбираем пункт "Add". В свойствах переменной устанавливаем Interval=500, MCVarType=MCT_WORD, Name="mb000_wdvr"
  • Создаем Modbus переменную, в свойствах устанавливаем Interval=500, MCVarType=MCT_SMALLINT, Name="mb001_intvr", RegisterIndex=1
  • Создаем Modbus переменную, в свойствах устанавливаем Interval=500, MCVarType=MCT_DWORD, Name="mb002_dwvr", RegisterIndex=2
  • Создаем Modbus переменную, в свойствах устанавливаем Interval=500, MCVarType=MCT_INT, Name="mb004_dintvr", RegisterIndex=4
  • Создаем Modbus переменную, в свойствах устанавливаем Interval=500, MCVarType=MCT_SINGLE, Name="mb006_fltvr", RegisterIndex=6
  • Создаем Modbus переменную, в свойствах устанавливаем Interval=500, MCVarType=MCT_STRING, Name="mb008_str", RegisterIndex=8
  • Создаем Modbus переменную, в свойствах устанавливаем MCVarType=MCT_STRING, Name="mb012_str2", RegisterIndex=12, UpAftWrite=true, WaitForUpdates=true

Если нужно установить соединение по Modbus RTU, то в свойствах компонента KRModbusClient установить MBType=mbtACSII

Если нужно установить связи по Modbus TCP, то вместо коннектора KRCOMPortConnector нужно использовать KRTCPConnector и в свойствах компонента KRModbusClient установить MBType=mbtTCP

Пример передачи данных с контроллера ОВЕН ПЛК100 в программу на Delphi по протоколу Modbus
 
Поделиться:
0
Василий Иванов
21.10.2018 20:53
 
Здравствуйте, Руслан!
Столкнулся с такой проблемой. При подключении нескольких ModbusClient (у меня их 5), если от первых двух ответ не пришел, то третий и далее уже не опрашиваются. Пытался играть с интервалами - не помогло. Может, подскажете, какой параметр крутить? Здесь очень много таймингов (на ComPortConnection), но почти все они, как я понял, относятся к потоковой передаче. У меня обычный Modbus RTU с периодическим опросом.
Спасибо.
Показать полностью
5
Руслан Кандирал
21.10.2018 21:07
Василий Иванов
 
Здравствуйте, Василий!

Я так понял у вас несколько устройств сидят на RS485.
Вот тут я объяснял как сам боролся с этой проблемой
Показать полностью
0
Василий Иванов
22.10.2018 22:19
Руслан Кандирал
 
Свойство CountErrorsForReconnect ни в одном из Modbus компонентах не нашел.
Так же на событие OnError переменных ни одна ошибка не выходит.
Сделал так.
На таймер, на котором собственно уже сидела фиксация ошибок по свойству Error переменных с подкрашиванием панелек (я не использовал ваши поля для вывода), добавил смену свойства Interval проблемных переменных на большее число (3000). А в событии ValUpdated первой переменной для каждого клиента, снова вернул нормальный интервал 50.
При этом свойство ReconnectTime (коннектора) уменьшил до 10, иначе был опять долгий реконнект с задержками и кашей на выходе.
Проверил по скану, вроде работает более менее.
Еще почему-то иногда вылазит ошибка "Ошибка контрольной суммы". Помогла установка свойства ErrorCount переменных в 3. Видимо, оно отвечает за минимальное кол-во ошибок для их фиксации.
А вообще, я вам очень благодарен и восхищаюсь проделанной вами работой. Спасибо. По возможности обязательно отблагодарю материально.
P.S. Очень не хватает описания свойств компонентов. Все приходится выяснять методом тыка.
Показать полностью
5
Руслан Кандирал
23.10.2018 10:36
Василий Иванов
 
У вас старая версия компонентов. Более новые я выкидывал в комментариях. Вот последняя:
https://kandiral.ru/downloads/Kandiral_2...

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

Перед установкой нужно деинсталировать старые компоненты
Вот кратка инструкция по установки:
https://kandiral.ru/o_sajte.html#comment...

"Ошибка контрольной суммы" возникает из за того, что после отправки запроса ожидается ответ в течении времени WriteTimeout+ReadTimeout. Если за это время ответ не пришел или пришел не полностью, то возникнет ошибка "Нет ответа" или "Ошибка контрольной суммы". И потом та часть ответа которая не пришла на текущий запрос придет на следующий. Если такие ошибки возникают часто, то нужно поиграться с таймингами ReadTimeout и WaitRespTime. ReadTimeout - это время ожидания ответа, обработка ответа происходит если он пришел до ReadTimeout или после срабатывания ReadTimeout. WaitRespTime - это время ожидания между частями ответа. Например пришло какое-то количество байт ответа, сразу обработка не происходит, а идет опрос еще в течении времени WaitRespTime. И если за время WaitRespTime ни чего не приходит, то начинается обработка.


" Помогла установка свойства ErrorCount переменных в 3. Видимо, оно отвечает за минимальное кол-во ошибок для их фиксации. " - да вы правы. Как ни настраивай систему, все равно периодически ошибки проскакивают. Для этого я добавил такое свойство.
Показать полностью
0
Konstantin Mamberger
18.10.2018 11:26
 
Здравствуйте!
Пытаюсь прочитать по ModbusTCP, адрес 192.168.1.99 AError=-102
на форме KRTCPConnector1 ip=192.168.1.99
KRModbusMaster1 ссылается на KRTCPConnector1
KRModbusClient1 на KRModbusMaster1
на пойму что вставлять в адрес устройства в ReadHoldingRegisters
как не меняю всегда AError=-102
плиз пример чтения HoldingRegisters с записью пример увидел но то же что писать в адресе устройства
у меня слейв на ардуино с использованием
#include <ModbusIP_ENC28J60.h>

Показать полностью
5
Руслан Кандирал
18.10.2018 20:54
Konstantin Mamberger
 
Ошибка -102 это "Соединение не установлено.". То есть коннектор не может подключиться к устройству.

Что-бы получить сообщение о ошибке вызовите KRModbusMaster1.ErrorMsg(AError,s)
где s - это переменная в которую запишится область возникновения ошибки: "Ошибка обработки данных" - уровень протокола Modbus; "Ошибка передачи данных" уровень коннектора.

Например если вызвать
KRModbusMaster1.ErrorMsg(-102,s)
то функция вернет строку "Соединение не установлено.", а в s будет "Ошибка передачи данных"


В вашем случае проблема может быть на уровне протокола TCP/IP, не верно указан IP адрес или порт. Или на уровне физического подключения: кабель, порт. Или код контроллера.
Показать полностью
0
Андрей Савельев
25.09.2018 19:44
 
Здравствуйте, Руслан! Столкнулся со следующей проблемой: при присвоении значений регистрам через свойство Value компоента TKRMBRegister значение данного свойства меняется не сразу, а либо при обращении к любому другому компоненту TKRMBRegister либо при выходе из процедуры в которой производилось присвоение. Фактически для гарантированного появления значения в соответствующем регистре нужно присвоить значение свойству Value дважды. Подскажите, почему так происходит и как избежать подобного поведения?
Показать полностью
5
Руслан Кандирал
25.09.2018 19:56
Андрей Савельев
 
Здравствуйте, Андрей!

У KRMBRegister есть свойство UpdateType. Возможные значения:
vutAfter(по умолчанию) - обновлять значение после положительного ответа подчиненного устройства
vutBefore - обновлять значение сразу при присваивании свойству Value. То есть перед отправкой подчиненному устройству
vutAftUpdate - обновлять значение после отправки запроса чтения. То есть отправляется значение, затем отправляется запрос чтения и после положительного ответа обновляется значение.

Я думаю вам подойдет vutBefore
Показать полностью
0
Андрей Савельев
25.09.2018 20:30
Руслан Кандирал
 
Благодарю. А в случае отсутствия положительного ответа от устройства будет ли компонент делать повторные попытки присвоить значение регистру, или это нужно контролировать самому?
Показать полностью
5
Руслан Кандирал
26.09.2018 05:18
Андрей Савельев
 
Это нужно контролировать самому
Показать полностью
0
Андрей Савельев
14.09.2018 22:10
 
Руслан, Modbus функции 15 «Force Multiple Coils» и 16 «Preset Multiple Regs», судя по контексту, реализованы в методах WriteCoils и WriteHoldingRegisters компонента KRModbusMaster? Можно попросить Вас привести примеры их использования...
Показать полностью
5
Руслан Кандирал
15.09.2018 08:51
Андрей Савельев
 
Показать полностью
0
Андрей Юрашев
07.09.2018 14:30
 
Руслан, а подскажите на данный момент функция 05 Force Single Coil в вашей компоненте не реализована???
Показать полностью
5
Руслан Кандирал
08.09.2018 02:56
Андрей Юрашев
 
Реализована, но только на уровне KRModbusMaster.

Пример:
Показать полностью
0
Андрей Юрашев
08.09.2018 19:26
Руслан Кандирал
 
KRModbusMaster1.WriteCoil(mbtTCP -- ругается на переменную AMBType и не принимает не mbtTCP не mbtRTU.
Еще вопрос если нужно опрашивать 2 прибора то добавляем еще один KRModbusClient2 и заводим там переменные которые нужно опросить так???
У меня не взлетело -- опрос не идет -- по логам только по первому KRModbusClient1 идет опрос. Что где надо включить?
Показать полностью
5
Руслан Кандирал
09.09.2018 12:37
Андрей Юрашев
 
mbtTCP и mbtRTU описываются в KRModbus, то бишь данный модулю нужно указать в uses.

Если устройства подключаются по разным соединениям, то нужно для каждого устройства создавать KRModbusMaster, KRModbusClient и соответствующий коннектор.

Если несколько устройств сидят на одном коннекте(например RS485 через COM порт или TCP), то достаточно для каждого устройства создавать KRModbusClient где в свойстве Addres указывать соответствующий адрес устройства и подключать общий компонент KRModbusMaster. Но в таком случае есть проблемы с которой я сам столкнулся недавно. Дело в том, что задача коннекторя отправить запрос получить ответ и если возникают при отправке/приеме возникнут проблемы отключать соединение и делать реконнект с периодом не более чем указано в свойстве ReconnectTime. То есть если например от устройства не приходит ответ несколько раз подряд (количество указывается в свойстве коннектора CountErrorsForReconnect), то соединение разрывается и затем коннектор пытается заново подключиться с периодом ReconnectTime. Это дает возможность коннектору подхватывать связь с устройством при временном ее отсутствии. Но если к одному коннектору подключено более одного устройства и с одним из них пропадает связь, то коннектор начинает периодически отключаться и в результате возникают проблемы со связью с остальными устройствами.

Что-бы обойти эту проблему я сделал следующее:

Показать полностью
0
Андрей Юрашев
04.09.2018 23:38
 
При считывании переменной типа Float получаю совершенно некорректный результат. Проанализировав возвращаемый ответ, увидел, что от прибора приходит ответ в таком виде
RECV: 10 03 04 3E CC CC CD A2 70 -- соответственно сами данные 3E CC CC CD ,в переменной типа Float получаем значение -107607648 -- т.е. перед преобразованием ответ перестраивается в такой вид CC CD 3E CC.
На самом деле мой прибор отдает значение 0.4 , данные должны идти на преобразование в том же порядке что и пришли 3E CC CC CD.
Есть ли возможность в переменной указывать порядок прихода данных -- разные производители по разному отдают этот тип данных.
Или какой способ обойти эту ситуацию.
Решение в лоб перевести переменную обратно в HEX вид, переставить как нужно и опять во Float уж очень не комильфо))))
Показать полностью
5
Руслан Кандирал
05.09.2018 08:59
Андрей Юрашев
 
Возможности поменять последовательность регистров нет. Нужно будет добавить в будущем.
Есть два варианта решить эту проблему:
1) В вашей программе. Делать тип переменной DWORD и преобразовывать в FLOAT самостоятельно. То есть тот способ который " не комильфо"
2) Внести изменение в функции MBRegsToSINGLE в файле Automation\KRModbus.pas и перекомпилировать пакет Automation. Но в таком случае последовательность регистров будет только такая как нужно вам.

Изменения которые нужно внести в файл Automation\KRModbus.pas
Показать полностью
0
Андрей Юрашев
05.09.2018 10:18
Руслан Кандирал
 
Благодарю, Руслан.
Показать полностью
0
Андрей Савельев
03.09.2018 22:52
 
Подскажите пожалуйста, я работаю с контроллерами ЭЛМЕТРО-МВВ. Возможно ли обращение к регистрам контроллера через переменные компонента KRModbusClient?
Не совсем понятно, как сопоставлять адресацию регистров контроллера с адресацией переменных компонента, ведь существуют два вида ригистров: входные и хранения с независимыми арресациями для каждого вида, но адресация у переменных компонента как-будто бы сквозная...
Показать полностью
5
Руслан Кандирал
03.09.2018 23:01
Андрей Савельев
 
У регистров (TKRMBRegister) есть свойство ReadFunction. Для входящих регистров выбирайте mbrfReadInputRegisters, а для регистров хранения соответственно mbrfReadHoldingRegisters. Адрес в обоих случаях указывается в свойстве RegisterIndex
Показать полностью
0
Андрей Савельев
28.08.2018 19:13
 
Руслан, подскажите пожалуйста, куда сбрасывается результат работы метода ReadDiscretInputs компонента KRModbusMaster? Я поначалу считал, что он возвращатся в переменной AData CallBack метода, но покопавшись в исходника, вижу, что в переменной всегда возвращается nil.
Показать полностью
5
Руслан Кандирал
28.08.2018 19:39
Андрей Савельев
 
Результат передается в callback функцию.

Пример:

Показать полностью
0
Андрей Савельев
28.08.2018 22:05
Руслан Кандирал
 
Благодарю.
Показать полностью
0
Oleksiy Tokarchuk
22.04.2018 20:52
 
Использовал данный пример для чтения переменных с ПЛК Click.
На первой машине все работало нормально.
На второй машине установил все компоненты Kandiral, скомпилировал - запустилось.
Вернулся на первую машину с заведомо рабочим кодом - скомпилировал - error no connetcion на ком порту (другие программы к порту коннектятся)
Не подскажите, где искать причину?
Показать полностью
5
Руслан Кандирал
23.04.2018 05:23
Oleksiy Tokarchuk
 
Удачный коннект считается тогда, когда от устройства приходит ответ. То есть, возможно программа подключается к COM порту, но устройство не отвечает на запросы. С помощью событий коннектора OnSendAsync и OnRecvAsync вы можете определить идет отправка данных или нет и приходит ли ответ от устройства.
Показать полностью
0
Oleksiy Tokarchuk
23.04.2018 22:02
Руслан Кандирал
 
Спасибо. Помогло. Причину нашел. Дело было в неправильной настройке коннектора.
Еще небольшой вопрос.
У меня на KRCOMPortSets1.Init ругается undeclared identifier Init
Лень самому искать причину)
Подскажите в чем она?
Показать полностью
5
Руслан Кандирал
24.04.2018 13:31
Oleksiy Tokarchuk
 
При компиляции примера использовалась более новая версия компонентов. Я ее еще не выкладывал так как нужно сделать тесты под разные версии delphi. Вот я давал в комментариях более новую версию
https://kandiral.ru/o_sajte.html#comment...
Показать полностью

Новости сайта

06.02.2018 15:53
Добавлена программа SimpleADB
Программа SimpleADB разработана в среде Delphi XE для отладки Android устройств через утилиту adb
07.10.2017 23:57
Оптимизация библиотек Modbus для контроллеров Arduino
Сделал оптимизированный вариант библиотек Modbus под Arduino, с целью снизить размер занимаемой памяти для скетча
28.03.2017 23:51
Modbus Slave RTU/ASCII для микроконтроллеров Arduino
Реализация клиентской части протокола Modbus RTU и Modbus ASCII для контроллеров Arduino и пример использования с видео обзором

Популярные статьи

Modbus Slave на Arduino
Реализация клиентской части протокола Modbus RTU и Modbus ASCII для контроллеров Arduino
Пример передачи данных по протоколу Modbus на Delphi
Пример передачи данных с контроллера ОВЕН ПЛК100 в программу на Delphi по протоколу Modbus
Набор компонентов для Delphi
Набор компонентов для Delphi включает в себя компонент для работы с файловой системой, сетью, автоматизацией и другие.