Имитация модулей ввода-вывода для удобной отладки
Приветствую :)
Прошу пнуть в нужном направлении, дальше я сам :)
У меня есть СПК и два модуля МУ и МВ.
Несмотря на то, что они стоят рядом на столе - коммутировать их выходы в процессе отладки совершенно неудобно.
Хочу написать имитатор этих модулей.
Требования к нему:
1) графический интерфейс с возможностью переключать состояния входов мышкой
2) работа по модбасу, чтоб не надо было в спа каждый раз что-то перенастраивать для отладки. Подключился к живым модулям - работаем с живыми, подключился к имитатору - работаем с ним.
Здесь, на форуме, есть пример от многоуважаемого товарища Сергея Карпеш(а), но он создал эту имитацию в Опен Складе.
Я же вот думаю - почему бы в самой КодеСис не попробовать сделать такой эмулятор? Система плюс-минус уже знакома, графический интерфейс есть, возможность запуска на виртуальном контроллере есть.
Единственное, я не особо пока разбираюсь в модбасе. В какой-то сторонней проге у меня получилось создать устройство с нужными регистрами и успешно их читать в СПК. Но в той проге значения тоже вводить неудобно.
Отсюда вопрос: какой общий порядок действий должен быть?
Я создал проект СПК107, там создал модбас девайс, прописал ему адрес как у реального модуля, регистры, вроде бы, прописал.
Через эмулятор компортов создал связать двух виртуальных ком-портов.
Запустил два виртуальных контролёра:
1) с моим рабочим проектом
2) с модбас девайсом этим
Симулятор ком-портов увидел, что к нему подключены два устройства.
Но модуль ввода Овена в моём проекте не загорается зелёным даже. Адреса прописаны верно, скорости выставлены одинаково.
Может, для успешного соединения обязательно нужно корректно прописать регистры в имитируемом устройстве?
Или вообще вся эта эпопея обречена на провал по каким-то причинам, до которых я ещё не успел дойти?
Заранее спасибо за подсказки :)
Решено для моего случая. Но для себя - читайте пост ниже
Спасибо всем за ответы. Я почему-то не увидел, что тему уже опубликовали. И сам уже себе помог :))
Более суток ковырял эту тему, разобрался с регистрами и прочим.
Модуль ввода не подключался по причине того, что он хочет запросить в сумме 29 слов, а я создавал устройство слейва, где не было столько регистров.
Модулю вывода для счастливой жизни достаточно всего одного регистра, потому он и работал без проблем.
Ещё раз поясню мои мучения:
У меня семь контакторов шнеков управляются по показаниям весов. Для отладки мне надо имитировать ответ контактора о замыкании, вовремя тыкать туда-сюда эти самые контакторы, плюс имитировать процесс увеличения веса...
Вручную это делать очень неприятно и сложно.
Приходится загрублять все тайминги, чтоб успевать руками нащёлкать нужные состояния в MasterOPC Universal Modbus Server (естественно, я про него знаю).
Но при загрублении таймингов сильно уходят показания измеряемой скорости насыпания. Плюс - я постоянно в голове держу не отладку, а порядок тыкания кнопочек.
У меня было открыто три окна:
Кодесис с отлаживаемым проектом.
Кодесис с имитацией весового модуля.
Модбас этот описи сервер универсал с имитацией модулей через теги.
и вот бегаешь между окнами. То там забыл включить, тот тут переключить. У сервера этого модбасного ещё и интерфейс не очень фредли - чтоб значение поменять - надо открыть окно ввести значение, закрыть окно... Я как бы автоматизацией занимаюсь, а тут прям ручной труд во всей красе.
В общем, одолев устройство ввода выделением большего количества памяти, я наткнулся на следующую проблему - в одном проекте кодесис на один порт нельзя прикрутить два слейва. Значит, нужно два порта, а потом эти порты прикручивать к одному порту отлаживаемого проекта. Тяжко.
В общем и целом - я набрёл на ФБ OCL.MB_SerialSlave, который в коде, без лишних бубнов поднимает слэйв устройство. Опять же - на одном порту две штуки поднять нельзя, но можно сделать так, чтоб этот слэйв отзывался на любой ID. А дальше мне тупо повезло, что в двух моих модулях не пересекаются адреса хранения данных о состоянии выходов.
Соответсвенно - по наводке Евгения Кислова я просто завёл слэйв с общим адресным пространством для обоих своих модулей.
И вот щас всё работает. Теперь я в этот же проект могу добавить свой имитатор весового модуля, чтоб он сам включался по выходам модуля вывода. И ответку контакторов я теперь тоже могу автоматизировать, а не руками тыкать, поглядывая в таблицу на соответствие контакторов и выводов... И расположить все кнопки управления я теперь тоже могу так, как они физически на заводе стоят, а не в столбик из этих тегов в этом модбас матьего описи сервере)
Если кому интересно - вот код с максимально подробным описанием:
Код:
(* ДИСКЛЕЙМЕР.
Спасибо Евгению Кислову за пинок в нужное время в нужном направлении :)
Работа примера основана на примере 5.9.2 из документа
CODESYS V3.5 Настройка обмена по протоколу Modbus Руководство пользователя 04.08.2023 версия 3.2
Под шаблоном я буду иметь в виду готовый шаблон Модбас устройства от компании Овен.
ОБРАЩАЮ ВНИМАНИЕ!!!!!!! Весь этот финт ушами с двумя слейвами одовременно возможен лишь потому, что
конкретно у этих двух приборов не пересекаются адреса хранения состояний выходов.
Если адреса пересекаются - то нужно будет определять, какое устройство сейчас долбится к нам за данными
и класть в буфер данные для СЛЕДУЮЩЕГО по адресу устройства. Потому что ФБ выдаёт адрес устройства, которому уже всё отдал.
По словам Евгения - мастер опрашивает устройства по порядку, так что можно нахитрить в этом направлении.
Примерный алгоритм: составляем массив адресов своих устройств, на первой итерации баним их всех, чтоб не отдать кому-то не то.
Запоминаем, кто к нам долбится, смещаемся по своему массиву вправо от полученного адреса и кладём в буфер нужные СЛЕДУЮЩЕМУ устройству данные.
Разбаниваем все устройства, работаем. Но это прокатит только если между адресами имитируемых устройств нет адресов реальных устройств.
Иначе всё собъётся. Короче, к сути.
В примере реализован функционал имитации работы модуля ввода MV110_16_D_DN и модуля вывода МУ110-16R_K
Эти два названия я взял из названий готовых шаблонов модулей из пакета от Овена.
По факту у меня дискретные приборы, и меня интересовали лишь две возможности для отладки:
1) для модуля вывода - заиметь на визуализации лампочку, которая загоралась бы, когда выход модуля активен
2) иметь на визуализации кнопку, по нажатию которой имитируется замыкание входа модуля ввода.
Счётчики, ШИМы, показания датчиков мне не нужны, поэтому в этом примере они не реализованы. Но по идее - частично обработать инфу возможно.
*)
PROGRAM PLC_PRG
VAR
fbComControl: OCL.COM_Control; // ФБ управления портом COM1
fbModbusSerialSlave: OCL.MB_SerialSlave; // ФБ для реализиации модуля вЫвода
usiID: USINT;
(* Чо ваще тут происходит
в ходе экспериментов выяснилось, что шаблон модуля ввода MV110_16_D_DN работает только в случае, когда под него выделено не менее 29 байт:
см.инструкцию от модуля, "Таблица А1 - Регистры Modbus".
Он опрашивает регистр с адресом 51, забирая оттуда битовую маску значений входов. Это как раз одно слово для 16 входов.
И ещё ему ну прям никак не обойтисть без доступа к регистрам с адресами 64..79, откуда он берёт значения счётчиков импульсов.
Мне они не нужны, но без выделения под них памяти ничего не работает (имитируемый модуль висит в офлайн или мигает туда-сюда в попытках подключения).
Так как регисты в памяти должы быть расположены последоватьельно - нужно будет выделить 79-51+1=29 слов памяти.
(+1 - потому что 2-1=1, но по факту количество 2)
Шаблон модуля вЫвода MV110_16_D_DN прекрасно работает при одном выделенном для него слове.
Повторю - меня инстересует только состояние дискретных выходов, поэтому минимально необходимое количество WORD для моего случая = 1.
Насколько я понял - считывание состояния происходит с того же регистра (могу ошибаться).
Итого мы имеем: шаблон ввода хочет иметь дело с регистром 50 (1 шт). Шаблон вывода - с регистрами 51-79 (29 шт).
На самом деле - на данном этапе адресация регистров не важна. Важно именно их количество. В моём случае 30 шт.
Везение заключается в том, что у двух этих модулей не пересекаются адреса нужных нам регистров.
Соответственно - их можно расположить в памяти в таком порядке:
IN - это будет регист модуля ввода
IN01..16 - регистры счётчиков модуля ввода, без которых он жить не может
OUT - регист модуля вывода.
01 OUT
02 IN
03
04
05
06
07
08
09
10
11
12
13
14
15 IN01
16 IN02
17 IN03
18 IN04
19 IN05
20 IN06
21 IN07
22 IN08
23 IN09
24 IN10
25 IN11
26 IN12
27 IN13
28 IN14
29 IN15
30 IN16
Чтобы не путаться с нулевой адресацией и количеством - адресацию массива данных я буду начинать от 1, а не от 0.
*)
awSlaveData: ARRAY [1..30] OF WORD; // буфер данных Modbus Slave
(* в моём случае это не нужно, потому что у меня нет регистров, которые читаются и пишутся обеими участниками обмена.
Если такое есть - нужно тут в коде писать данные в такие регистры не циклично, а по необходимости.
Иначе цикличная запись будет постоянно затирать принимаемую инфу. Так делать фу.
xWrite: BOOL; // команда записи данных из программы в регистры Modbus Slave
fbWriteEdge: R_TRIG; // триггер для однократной записи
*)
axIN: ARRAY [1..16] OF BOOL; // массив состояний входов модуля ввода. Привязаны к тумблерам
axOUT: ARRAY [1..16] OF BOOL; // массив состояний выходов модуля вЫвода. Привязаны к лампочкам
END_VAR
Цитата:
// поднимаем COM порт с нужными настройками
fbComControl
(
xEnable := TRUE,
udiComPort := 33,
udiBaudrate := 19200,
udiByteSize := 8,
eParity := OCL.COM_PARITY.EVEN,
eStopBit := OCL.COM_STOPBIT.ONE
);
//запускаем слейва с адресом 255. Это позоляет ему отвечать на запрос с любым ID.
fbModbusSerialSlave
(
(* насколько я понял - начальный адрес является обманом для опрашивающего устройства.
Когда оно просит регистр с адресом 50 - мы ему подсовываем свой самый первый регистр,
и все остаются довольны. То есть - это смещение адресации на уровне нашего кода тут.
Если бы не было этого параметра - пришлось бы тут в коде создавать область памяти,
в которой первые 49 слов банально не использовались бы.
А так - у нас шаблон модуля вывода хочет видеть данные на 50 адресе,
шаблон модуля ввода - на 51, и мы просто указываем смещение в 50:
чтоб вместо 50 выдать то, что лежит у нас в первой ячейке, а вместо 51 - то, что во второй.
50 51 64 ... 79 - шаблоны модулей думают, что читают по этим адресам
OUT IN IN01 IN16 - вот эти данные
1 2 15 30 - на самом деле - данные у нас лежат вот с такими индексами.
*)
c_uiStartAddr:=50,
xEnable :=fbComControl.xActive,
hCom := fbComControl.hCom,
usiSlaveId := 255,
pData := ADR(awSlaveData),
szSize := SIZEOF(awSlaveData)
);
// сорян за хардкод :) Уже некогда разбираться с преобразованиями
// в первой ячейке у нас лежат состояния вЫходов (которые обманным образом транслируются под видом регистра с адресом 50)
axOUT[1]:=awSlaveData[1].0;
axOUT[2]:=awSlaveData[1].1;
axOUT[3]:=awSlaveData[1].2;
axOUT[4]:=awSlaveData[1].3;
axOUT[5]:=awSlaveData[1].4;
axOUT[6]:=awSlaveData[1].5;
axOUT[7]:=awSlaveData[1].6;
axOUT[8]:=awSlaveData[1].7;
axOUT[9]:=awSlaveData[1].8;
axOUT[10]:=awSlaveData[1].9;
axOUT[11]:=awSlaveData[1].10;
axOUT[12]:=awSlaveData[1].11;
axOUT[13]:=awSlaveData[1].12;
axOUT[14]:=awSlaveData[1].13;
axOUT[15]:=awSlaveData[1].14;
axOUT[16]:=awSlaveData[1].15;
// во второй - состояния входов
awSlaveData[2].0:=axIn[1];
awSlaveData[2].1:=axIn[2];
awSlaveData[2].2:=axIn[3];
awSlaveData[2].3:=axIn[4];
awSlaveData[2].4:=axIn[5];
awSlaveData[2].5:=axIn[6];
awSlaveData[2].6:=axIn[7];
awSlaveData[2].7:=axIn[8];
awSlaveData[2].8:=axIn[9];
awSlaveData[2].9:=axIn[10];
awSlaveData[2].10:=axIn[11];
awSlaveData[2].11:=axIn[12];
awSlaveData[2].12:=axIn[13];
awSlaveData[2].13:=axIn[14];
awSlaveData[2].14:=axIn[15];
awSlaveData[2].15:=axIn[16];