Пример передачи данных по протоколу Modbus на Delphi
Для примера будем использовать контроллер ОВЕН ПЛК100. Для использования Modbus ASCII или Modbus RTU нужно подключить контроллер к компьютеру через COM порт, а для Modbus TCP по сети.
В конфигурации ПЛК добавляем компонент Modbus(slave). В группе Modbus[FIX] добавляем компонент соответствующий той связи которая соединяет компьютер с контроллером. Далее в группе Modbus(slave) добавляем регистры как показано на картинке

Переменные Modbus
- wdvr - двухбайтовое без знаковое
- intvr - двухбайтовое знаковое
- dwvr - четырех байтовое без знаковое
- dintvr - четырех байтовое знаковое
- fltvr - четырех байтовое с плавающей точкой
- str - изходящая строк
- str_ - продолжение исходящей строки
- str2 - входящая строка
Продолжению входящей строки нет необходимости присваивать имя, так как напрямую к этой переменной обращаться не будем.
Программа контроллера будет с периодом 100мс изменять значения всех переменных, что-бы данные изменения наблюдать в программе на Delphi
Создаем VCL приложение в Delphi

Добавляем на форму
- 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 |
Как вы смотрите на модернизацию ваших Modbus компонентов? С целью ускорения и улучшения их работы.
Если вы не против, могу прислать Вам свой готовый вариант сделанный из ваших исходников. Вот вкратце что я сделал. Переписал KRThread с использованием событий и WaitForMultiplyObject. Теперь там нет задержек Sleep и Application.ProcessMessages. Переписал KRThreadQueue с использованием событий и критических секций. Теперь события для активации параллельного потока генерирует очередь. Применил эти новые объекты только для компонентов Modbus.
Старался всё сделать максимально аккуратно, чтобы по минимуму затрагивать ваш исходный код.
Но всё таки осмелился переделать принцип с очередями и потоками :)
Остался только один параллельный поток в Коннекторе и в нем же две очереди - входная и выходная. Изолирование данных между процессами происходит в очередях.
Теперь запрос формируется в Мастере (основной поток) и упаковывается в помощнике Мастера (основной поток) -> поступает во входную очередь Коннектора и сигнализирует параллельному потоку Коннектора.
Параллельный поток Коннектора отправляет запрос и получает ответ (через COM-BT-TCP) и добавляет полученный ответ в выходную очередь. Далее через Synchronize происходит извлечение ответа из выходной очереди и вызов Callback функций уже в основном потоке, благодаря чему они стали безопасными.
В результате сократился исполняемый код и время отклика. С параллельными потоками и изолированием данных между ними я дружу давно, так что вряд-ли где-то с ними накосячил.
Я проверил свой вариант на Modbus RTU через COM-порт. Проверить остальное сейчас возможности нет, хотя переделка затронула и BT и TCP коннекторы.
Готов выслать вам свой вариант, если вам это интересно.
Спасибо за внимание и понимание :)
Ещё только начал разбираться. Сейчас такое понимание: Каждый регистр имеет три таймера. Основной таймер Interval через обработчик Message заставляет Мастера добавлять в очередь запрос. Таким образом очередь пополняется запросами. Это всё в основном потоке. Кроме этого в Мастере работает параллельный поток который извлекает запросы из очереди и передает их в Коннектор и в коннекторе есть
две свои очереди - входных запросов и выходных ответов. Коннектор и его очереди работает в основном потоке. Кроме этого в коннекторе есть свой параллельный поток, который и обрабатывает очереди.
Если я правильно понял, тогда вопрос: какой смысл в параллельном потоке Мастера, если он только передает запрос из одной очереди в другую?
TKRThread может находится в активно и неактивном (пауза) состоянии. Изменяется состояние свойством Active
В активном режиме циклично вызывается метод KRExecute, в неактивном KRExecutePaused
При переходи из активного режима в неактивный один раз вызывается KRExecutePausedFirst
В основном, когда я использую TKRThread , то в KRExecute создаются необходимые объекты(соединения) и идет работа с ними, а в KRExecutePausedFirst удаляются(останавливается) все что было сделано(активировано) в KRExecute. KRExecutePaused использую редко.
Каждый регистр имеет три таймера.
Если вы имеете ввиду таймер из TKRVariable
то да. Обработчик таймеров находится тут
TKRVariable.TmWP
Первый таймер для обновления по Interval. Второй и третий для синхронизации с основным потоком при вызове событий OnValUpdated и OnError
Если я правильно понял, тогда вопрос: какой смысл в параллельном потоке Мастера, если он только передает запрос из одной очереди в другую?
В очередь мастеру попадает информация о запросе(TMBPack), а поток мастера извлекает эту информацию, формирует на её основе пакет и отправляет этот пакет в очередь коннектора. Ответный пакет коннектор возвращает мастеру, который проверяет пакет на ошибки, вытаскивает, при необходимости, необходимые данные и вызывает CallBack
Вы могли бы привести код, как вы видите использование WaitForSingleObject вместо таймаута WaitRespTime?
function TKRAsyncComPort.Read(var Buffer; Count: Integer): Integer;
который использует
function TKRAsyncComPort.WaitForAsync(var AsyncPtr: PKRACPAsync): Integer;
и тут всё хорошо:
WaitForSingleObject(AsyncPtr^.Overlapped.hEvent, INFINITE);
только можно таймаут задать вместо INFINITE
Тут функция ReadFile выполняется асинхронно, тоесть возвращается сразу и пустая. А потом когда порт считает нужное количество байт возникнет событие AsyncPtr^.Overlapped.hEvent - значит в буфере готовы данные в нужном количестве.
Мне кажется такая реализация более правильная, хотя я не спец в COM-портах, могу ошибаться.
-------------------------
В принципе ваша реализация чтения ответа с порта, хоть и не обычная, но жизнеспособная. И насколько разобрался, большой WaitRespTime не должен снижать скорость, если пакеты не разорванные. Мне только одно не понравилось: Если порт не отвечает (порт же намного медленнее компьютера) то цикл while начинает постоянно долбить COMPort.InputCount и ClearCommError().
Там хотя бы задержку добавить если InputCount = 0.
Вот это место имею ввиду:
while(getTickCount-tm<FWriteTimeout+FReadTimeout)do begin
if FCOMPort.InputCount > 0 then begin
.................................................
end;
if tm0<getTickCount then break;
end;
Тут если порт не сразу отвечает сыпется в порт ClearCommError(). миллион раз в секунду.
KRComPort - синхронный
KRAsyncComPort - асинхронный
Я использую синхронный режим для контроля всех таймингов самостоятельно. Какова разница в скорости(эффективности), я как-то об этом не задумывался. Может, если будет время, попробую по экспериментировать с асинхронным режимом.
Разве не нужно в этом Clear делать Dispose для всех Queue.List.Item ?
А вот те классы, которые используют эту очередь должны подчищать за собой память. При закрытии приложения это не особо важно, так как windows очистит всю память, которая выделялась под это приложение.
Да, вы правы, это не есть правильно нужно будет поправить.
reg00.Value:=55;
time:=time+1;
label43.Caption:=floattostrF(time,FFfixed,4,1);
проблема в том что в данном виде он вызывает торможение программы, то есть в лейбл счетчик выводится периодически с явными задержками. Если убрать первую строчку все работает как часы.
Связи с контроллером нет, тестирую программу в офисе.
В чем может быть проблема?
Слишком мало информации для понимания проблемы. Как именно вызывается данный код?
Если reg00 это TKRMBRegister, то строка
reg00.Value:=55;
просто помещает данные для Modbus запроса в очередь на обработку.
Программа выполняется по таймеру2(основная часть) раз в 1сек.
В таймере 2 мы отправляем посылки на modbus rtu (без ваших компонентов).
В таймере 3 принимаем посылки modbus rtu + считываем регистры modbusTCP через ваши компоненты
В таймере 4 шлем посылки управления modbus rtu + вашими кимпонентами шлем посылки управления. И именно в этой части программа жутко тормозит.
Какая версия Delphi?
За последний месяц все компоненты касающиеся протокола Modbus очень сильно изменились. Удалось почти в двое увеличить скорость работы и при этом снизить нагрузку на ЦП
На сайте обновления выложу скорее всего в феврале (нужно изменить описание тех модулей которые были изменены)
Сейчас у меня готовы библиотеки до версии XE7, сегодня к вечеру(ночи) сделаю до 10.3
Могу выслать вам обновления на почту
Interval=500, MCVarType=MCT_WORD, Name="myReg1", RegisterIndex=1. В соответствии с регистром меняю нужное поле TKRField.
С одним регистром работает всё хорошо - читаю двухбайтное значение из устройства. Как только добавляю ещё регистры тоже для чтения двух байт - начинаются проблемы. Начинает очень плохо читать и врать. В мониторе видно, что на два или три запроса приходит один ответ. Выходит что запросы на чтение отправляются не дождавшись ответа. И ответ получается не на тот запрос.
Пожалуйста, помогите разобраться.
P.S. В дельфи я не новичёк.
В коннекторе есть следующие свойства
ReadTimeout и WriteTimeout. По факту есть только таймаут на чтение (максимальное время ожидания ответа) и оно равно сумме этих двух свойств. :Желательно значение этих свойств увеличить по максимуму, как минимум по 500 или лучше по 1000, а там как покажет практика. По сути, так как мы заранее знаем длину ответного пакета, то большое значение таймаута ни как на скорость не повлияет если только нет реальных проблем со связью.
И есть свойство WaitRespTime - таймаут между частями пакетов. В последовательных соединениях (в TCP коннекторе это свойство не имеет значения) пакеты могут фрагментироваться. То есть может прийти часть пакета, а затем через определенный промежуток времени следующая часть. Вот WaitRespTime и есть таймаут между этими частями. Это свойство влияет на скорость работы и чем оно ниже тем быстрее скорость обмена пакетами. Но при низком значении может быть нестабильная работа протокола. Скорее всего именно из-за этого параметра у Вас и проблема с разсинхроном пакетов. Тут можно изначально выставить большое значение (300-500), убедится в стабильной работе и затем постепенно пробовать уменьшать пока не будет достигнуто оптимальное значение.
Я правильно понимаю, что чем больше работающих одновременно экземпляров TKRMBRegister в клиенте, тем медленнее будет общий результат. И что для ускорения нужно одним запросом читать сразу несколько нужных регистров в массив. И потом уже из массива брать нужные значения. Или как то по другому решать проблему?
Посмотрел исходники. Получается что запросы на чтение/запись попадают в очередь для асинхронной обработки в параллельном потоке. А можно как-то контролировать эту очередь, чтобы не переполнить её. И как-нибудь приоритетно что-то записать/прочитать. Без очереди так сказать :)
В принципе, да. Сам алгоритм приема/передачи пакетов можно посмотреть в процедуре TKRCOMPortThread._exec
Register.Interval - это свойство работает следующим образом: отправляем запрос; ждем ответ или таймаут; обрабатываем ответ; простаиваем время заданное в Register.Interval и начинаем с начала. То есть мы не забиваем очередь через каждые 200мс (если Register.Interval=200) и фактическая скорость обновления получается равна "Время получения ответа"+Register.Interval (время формирования запроса и обработки ответа можно не учитывать так как эти процессы происходят относительно быстро)
KRTimer.Interval - это интервал обновления значений в визуальных элементах. На передачу данных ни как не влияет.
На скорость влияет не количество TKRMBRegister, а как часто обновляются значения этих регистров. То есть, регистров может быть много, но некоторые могут обновляться через секунду другие через две. То есть, там где значение изменяется часто, опрашиваем часто, другие реже. Некоторые регистры я опрашиваю только при подключении (значение которых не меняется в контроллере) и при их изменении через программу.
Для обновления данных одним запросом нужно использовать TKRModbusMaster. Я во многих комментариях это описывал. Или использовать TKRMBRegisterс типом MCT_ARRAY
Очередей используется три и три потока (не считая основного). Очередь используется для синхронизации между потоками, а потоки нужны в любом случае. В основном потоке делать такие вещи нельзя, так как это снизит скорость передачи данных и будет тормозить основной интерфейс. Два потока и две очереди находятся в коннекторе, вот статья по этой теме:
и один поток и очередь в TKRModbusMaster. Для контроля заполнения очередей есть компоненты KRConnectorQueueBar и KRMBQueueBar
Посоветуйте пожалуйста как это убрать.
Все компоненты, где используются потоки нужно останавливать в FormCloseQuery, как в примере выше
Причем без компонента StatusBar1 - invalid floating.
C ним - ошибка записи памяти по адресу.
Upd: исправил остановкой всех своих таймеров. Просто странно раньше они ошибок не выдавали.
Убедитесь что именно событие OnCloseQuery, а не OnClose или OnDestroy
Если это не помогает, то нужно в событии OnCloseQuery самостоятельно сбрасывать компоненты(объекты) и писать информацию в логи, что-бы определить в каком именно классе возникает ошибка.
То есть индексы зависят только от устройства к которому вы подключеетесь.
Каждый TKRMBRegister обновляется отдельным запросом.
Для увеличения скорости в коннекторе и в KRModbusMaster свойство WaitTime установите единицу. Это пауза между запросами. Ещё можно снизить значение WaitRespTime в KRModbusMaster, но при слишком низком значении может быть разсинхронизация пакетов, что приведет к появлению ошибок. То есть желательно это свойство по немного уменьшать и проверять связь на стабильность.
Чтение всех регистров одним запросом можно реализовать на уровне класса TKRModbusMaster, то есть без использования KRModbusClient, KRMBRegisters. Про работу с KRModbusMaster я писал в нескольких комментариях, например тут
Возникла необходимость опрашивать датчик, подключаемый через COM порт с периодом 40 мс. В свойстве компонента TKRMBRegister.Interval выставил значение 40, но посмотрев передаваемую информацию через com port toolkit обнаружил, что команды на обновление данных отправляются фактически через 95 мс, а порой пауза доходит до 130 мс. Так же почему-то свойство Value объекта KRMBRegister часто остается необновленным. Подскажите, что и где можно покрутить, чтобы опрос датчика осуществлялся с необходимым периодом?
Таймер повторного обновления включается после получения ответа. То есть фактический период обновления составляет Interval+"Время на обработку предыдущего пакета"
Для приблизительно одинакового периода между запросами лучше Interval установить в ноль, а обновление делать по таймеру. Например есть таймер с интервалом 40
Еще в коннекторе и в KRModbusMaster значения свойства WaitTime установите в еденицу
На основе данного проекта реализован опрос ТРМ202, ТРМ138, МВ110-8А. Все работает замечательно.
НО только с китайским преобразователем RS485-USB на чипе CH340.
Ни с преобразователем АС4 Овен(чип Silicon Labs CP210x), ни с преобразователем Bolid(чип XR21B1411) программа не может связаться с приборами. Конфигураторы Овен связываются с приборами при использовании любого преобразователя.
Прошу помощи.
Проверил программой - связи нет.
Добавить TKRValueListEditor, наследника TValueListEditor, но совсеми "плюшками" TKRField, чтобы редактировать сразу множество регистров в одном компоненте.
И есть вопрос: если мне необходимо соединяться с несколькими устройствами, как мне лучше поступить, менять в run-time свойство Addres у KRModbusClient и переподключаться или завести несколько KRModbusClient (около 1,5 десятка - датчики и модули управления)?
Если вам нужно одновременно работать с несколькими приборами, то конечно нужно создавать отдельно KRModbusClient для каждого. Если приборы(набор регистров) одинаковые, то KRModbusClient можно на клонировать через copy/past вместе с переменными, единственное, что так как каждая переменная это компонент(от класса TComponent), то имена одинаковые быть не могут, их(имена) нужно будет откорректировать.
Менять адрес - это, я думаю, подойдет только в том случае, если вы работаете с устройствами поочередно. Вообще, в переподключении (свойство Active) нет необходимости, но нужно убедится, что все пакеты от предыдущего устройства уже обработаны. Нужно все таки KRModbusClient выключить (Active:=false), затем сделать задержку через Application.ProcessMessages (время задержки подобрать на практике), затем поменять адрес и включить.
Или хотя бы просто цифрами указывать без редактора....
Конечно если я сам создаю устройство (программирую контроллер) то я сам в какой последовательности запишу, в такой и прочту. Но вот если устройство произведено кем то другим и в инструкции к нему указывается, что например, показания датчика давления находятся во входных регистрах по адресу 8, порядок чтения - старшим байтом вперед, то мне уже остается править значение перед выводом в поле (хорошо что у вашего компонента KRField есть нужное событие). Но было бы гибче, да и логичнее, настраивать это в свойстве переменной. Т.к. поле KRField может менять "переменную-хозяина", допустим, в run-time режиме.
А если свободный порядок байт? Т.е. аналог на скрине тому, что если сниму все чекбоксы, то передвигая байты стрелками влево/вправо могу сам задавать нужную комбинацию.
Я пытался присвоить значение своих переменных вашим(modbus-переменным), но появлялась ошибка Unit1.pas(71): E2010 Incompatible types: 'TKRMBRegister' and 'Integer'
Я всё к предыдущему вопросу....именно при закрытии одной программы (не важно какой именно) теряется связь с контроллером(из всех программ на delphi и связь ПЛК с панелью СП310(RS232), через codesys по ethernet связь остаётся(вижу что переменные передаваемые панелью СП310 не обновляются, через непонятное время начинают обновляться), как будто ПЛК делает сброс всех соединений...)...может как-то некорректно закрывается TCP соединение...???
KRTimer1.Enabled:=FALSE;
KRModbusClient1.Active:=FALSE;
KRModbusMaster1.Active:=FALSE;
KRTCPConnector1.Active:=FALSE;
Для решения данной ситуации пытаюсь реализовать следующее:
1) сделать закрытие сетевого соединения (как будто выдернул ethernet из ПК);
2) KRTimer1.Enabled:=FALSE;
KRModbusClient1.Active:=FALSE;
KRModbusMaster1.Active:=FALSE;
KRTCPConnector1.Active:=FALSE;
3) восстановить сетевое соединение и закрыть программу...
тогда вторая программа на 2 ПК не разрывает соединение с контроллером...
Провел тест. Всё нормально. Вот видео
Вот исходники
У Вас всё нормально работает....но у меня почему-то не хочет...может контроллер что-то глючит овен 110-32......вообщем реализовал так как писал:
1) закрытие сетевого соединения (как будто выдернул ethernet из ПК);
2) KRTimer1.Enabled:=FALSE;
KRModbusClient1.Active:=FALSE;
KRModbusMaster1.Active:=FALSE;
KRTCPConnector1.Active:=FALSE;
3) восстановить сетевое соединение и закрыть программу...
Ок. Я протестирую и отпишусь.
Спасибо за предыдущий ответ...разбираюсь с программой в контроллере..
Возник ещё - вопрос: возможно ли из двух приложений на делфи (с разных ПК) опрашивать один и тот же контроллер по TCP?
Опробовал следующее: в одной программе опрашиваю порт 502(стандартный), в другой 503 ...когда обе программы проконнектились до контроллера всё нормально, но стоит закрыть программу на одном ПК, на втором теряется связь...на некоторое время...потом восстанавливается..
У меня была похожая ситуация. Только на одном компьютере было запущено две программы, которые подключались к одному контроллеру. Вроде проблем не было.
Вот скрин настроек подключения в codesys
Программист из меня так себе, пытаюсь сделать программку для связи дельфи - контроллер по modbus tcp, и застопорился я на вашем замечательном наборе компонентов, который я скачал, но не знаю как подключить к Delphi 10.3. Пробовал Components-install new-KRmodbusМaster и вылезает окно KRConnector с кучей ошибок на незнакомые элементы. Несовместимость с 10.3 или я делаю что то не так?
И сделал инструкцию
Давно уже собирался обновить набор компонентов и инструкцию по установке. Через несколько часов выложу текстовую инструкцию. Видео обзор сделаю вечером или даже ближе к ночи.
Есть ли у вас эти компоненты для работы с Modbus RTU для старого дельфи 2007го года ?
(Delphi® 2007 for Win32® Version 11.0.2804.9245). Надо вставить в старый проект работу с модбасом...
Компоненты тестировались на Delphi с версии XE (ver 15). А что мешает откомпилировать старый проект под Delphi XE?
Возникает ещё такая ситуация....возможно у Вас было что-то подобное:
Пытаюсь подключиться к ПЛК ОВЕН через ваши компоненты...пишет - не удалось соединиться...
при этом через Codesys подключается всё норм...через несколько попыток - удаётся соединиться...
после этого пытаюсь соединиться через Codesys(происходит ошибка подключения, вторая попытка удачно), но после этого сбрасываются переменные modbus в 0, которые до переменной которую опрашиваю...
Codesys и Modbus работают на разных портах и должны работать параллельно не зависимо друг от друга. У меня на видео видно как программа опрашивает данные по Modbus и при этом работает Codesys и тоже опрашивает переменные. Но так как у Вас сбрасываются регистры Modbus, могу предположить, что где-то программа зацыкливается и контроллер перезагружается.
я всё с той же проблемой рассинхронизации пакетов....по TCP сделал WaitRespTime=100...более менее стало стабильно...но всё равно ...например, сегодня возникла рассинхронизация...как при такой ситуации правильно пересбросить соедининение?
Отловить рассинхронизацию можно по одному из регистров, который постоянно обновляется:
Думаю в будущем что-то сделаю для решения этой проблемы.
Пробовал сделать, как Вы описывали через события OnError и OnValUpdated переменной...только не понял что за строки(функция) в событии таймера:
if not connected then begin
setGState(100); ?????
connected:=true; ?????
закоментил данные строки...но ничего не помогло..
В вашем случае, я думаю, лучше писать логи в файл. Как минимум мониторинг пакетов. А потом анализировать данные в логах.
Код который Вы привели это часть моей программы.
setGState(100); Выводит сообщение по полученному коду
Переменная connected нужна для определения момента подключения/отключения к устройству.
Ошибка обработки данных: неверный идентификатор пакета
лог
29.04.2019 12:18:18.855 SEND: 6A 00 00 00 00 06 01 03 00 0B 00 01
29.04.2019 12:18:18.857 RECV: 69 00 00 00 00 05 01 03 02 00 02
29.04.2019 12:18:19.056 SEND: 6B 00 00 00 00 06 01 03 00 50 00 01
29.04.2019 12:18:19.058 RECV: 6A 00 00 00 00 05 01 03 02 00 A7
29.04.2019 12:18:19.256 SEND: 6C 00 00 00 00 06 01 03 00 00 00 01
29.04.2019 12:18:19.258 RECV: 6B 00 00 00 00 05 01 03 02 00 00
т.е. получается рассинхронизация...пакетов...а как её победить возможно?
WriteTimeout=1000
ReadTimeout=1000
WaitRespTime=50
если не поможет то увеличивайте тайминги. Read и Write таймауты больше 2000 делать нет смысла. WaitRespTime не должен быть больше ReadTimeout. Для начала пробуйте увеличивать WaitRespTime.
WaitOutTime - это простой в исходящей очереди при отправке результата в колбеки. Тоже можно ставить небольшое значение.
Эти свойство нужно увеличивать если программа сильно грузит систему.
Пожалуйста подскажите, можно ли через KRMBRegister считывать сразу большое количество регистров, например в строку или поток, с последующим парсингом их в значения типа Single?
вызовет ошибку в строке 10. Есть, как минимум, три решения:
1) Присваивать значения простым переменным, а в основном потоке работать с этими переменными. Это так как я показал в предыдущем комментарии и еще тут
2) Через сообщения windows.
3) Через таймер, функция SetTimer . Это способ используется в TKRVariable . Принцип такой, в callback сохраняются значения в переменные как в варианте 1, а затем запускается таймар с небольшим интервалом, который вызывает событие OnValUpdated. То есть, тут мы сохраняем значения и фиксируем момент обновления и при этом не останавливаем поток.
У вас переменные для опроса добавляются через выпадающее меню элемента KRModbusClient1 ( mb000_wdvr). Если возможность подтянуть переменные из конфигурационного файла? Или каким либо образом автоматизировать импорт переменных?
Нет. Такого я не делал.
Вот пример как это можно сделать:
Вот INI файл, который у меня получился
Немогу подключится через переходник COM порт - RS485 к FX3U. Параметры Modbus RTU 19200 8bit 0 0 настроил. В окне MonitoringForm ничего нет. При попытке использовать OnSendAsync и OnRecvAsync ошибка Undeclared identifier 'PKRBuffer'
Должны отображаться, как минимум, отправляемые пакеты. Если вообще ничего нет, значит вы не запустили процесс опроса подчиненного устройства.
"При попытке использовать OnSendAsync и OnRecvAsync ошибка Undeclared identifier 'PKRBuffer'"
Нужно подключить модуль KRTypes в uses
Столкнулся с такой проблемой. При подключении нескольких ModbusClient (у меня их 5), если от первых двух ответ не пришел, то третий и далее уже не опрашиваются. Пытался играть с интервалами - не помогло. Может, подскажете, какой параметр крутить? Здесь очень много таймингов (на ComPortConnection), но почти все они, как я понял, относятся к потоковой передаче. У меня обычный Modbus RTU с периодическим опросом.
Спасибо.
Я так понял у вас несколько устройств сидят на RS485.
Вот
Так же на событие OnError переменных ни одна ошибка не выходит.
Сделал так.
На таймер, на котором собственно уже сидела фиксация ошибок по свойству Error переменных с подкрашиванием панелек (я не использовал ваши поля для вывода), добавил смену свойства Interval проблемных переменных на большее число (3000). А в событии ValUpdated первой переменной для каждого клиента, снова вернул нормальный интервал 50.
При этом свойство ReconnectTime (коннектора) уменьшил до 10, иначе был опять долгий реконнект с задержками и кашей на выходе.
Проверил по скану, вроде работает более менее.
Еще почему-то иногда вылазит ошибка "Ошибка контрольной суммы". Помогла установка свойства ErrorCount переменных в 3. Видимо, оно отвечает за минимальное кол-во ошибок для их фиксации.
А вообще, я вам очень благодарен и восхищаюсь проделанной вами работой. Спасибо. По возможности обязательно отблагодарю материально.
P.S. Очень не хватает описания свойств компонентов. Все приходится выяснять методом тыка.
Там есть CountErrorsForReconnect и недавно я еще разделил ошибки на два типа. Ошибки при отправке пакета явно сигнализируют о том, что есть проблемы со связью, там сейчас установлен жесткий предел 7, после чего происходит реконнект. Реконнект по ошибкам при получении ответа зависит от свойства CountErrorsForReconnect.
Перед установкой нужно деинсталировать старые компоненты
Вот кратка инструкция по установки:
"Ошибка контрольной суммы" возникает из за того, что после отправки запроса ожидается ответ в течении времени WriteTimeout+ReadTimeout. Если за это время ответ не пришел или пришел не полностью, то возникнет ошибка "Нет ответа" или "Ошибка контрольной суммы". И потом та часть ответа которая не пришла на текущий запрос придет на следующий. Если такие ошибки возникают часто, то нужно поиграться с таймингами ReadTimeout и WaitRespTime. ReadTimeout - это время ожидания ответа, обработка ответа происходит если он пришел до ReadTimeout или после срабатывания ReadTimeout. WaitRespTime - это время ожидания между частями ответа. Например пришло какое-то количество байт ответа, сразу обработка не происходит, а идет опрос еще в течении времени WaitRespTime. И если за время WaitRespTime ни чего не приходит, то начинается обработка.
" Помогла установка свойства ErrorCount переменных в 3. Видимо, оно отвечает за минимальное кол-во ошибок для их фиксации. " - да вы правы. Как ни настраивай систему, все равно периодически ошибки проскакивают. Для этого я добавил такое свойство.
Пытаюсь прочитать по 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>
Что-бы получить сообщение о ошибке вызовите KRModbusMaster1.ErrorMsg(AError,s)
где s - это переменная в которую запишится область возникновения ошибки: "Ошибка обработки данных" - уровень протокола Modbus; "Ошибка передачи данных" уровень коннектора.
Например если вызвать
KRModbusMaster1.ErrorMsg(-102,s)
то функция вернет строку "Соединение не установлено.", а в s будет "Ошибка передачи данных"
В вашем случае проблема может быть на уровне протокола TCP/IP, не верно указан IP адрес или порт. Или на уровне физического подключения: кабель, порт. Или код контроллера.
У KRMBRegister есть свойство UpdateType. Возможные значения:
vutAfter(по умолчанию) - обновлять значение после положительного ответа подчиненного устройства
vutBefore - обновлять значение сразу при присваивании свойству Value. То есть перед отправкой подчиненному устройству
vutAftUpdate - обновлять значение после отправки запроса чтения. То есть отправляется значение, затем отправляется запрос чтения и после положительного ответа обновляется значение.
Я думаю вам подойдет vutBefore