Как создавать блоки с помощью Delphi?

В рамках данной задачи с помощью среды разработки Delphi XE7 будет создан новый блок, который представляет собой ПИД-регулятор. ПИД-регулятор очень часто используется в системах автоматического управления для формирования управляющего сигнала с целью получения необходимых точности и качества переходного процесса. Создание такого блока может значительно упростить проектирование АСУ ТП. Достигается существенный прирост скорости расчета, а также возможность по одному и тому же алгоритму обсчитывать несколько типовых объектов.

Приступим к созданию заготовки, которая должна упростить процесс разработки нового блока. Первым шагом станет создание нового проекта в SimInTech. Для этого необходимо нажать по вкладке «Файл», далее «Новый проект», затем «Схема автоматики». Перед вами откроется пустое окно для создания модели.

Чтобы ориентироваться в SimInTech, перед началом работы над задачей рекомендуется пройти упражнения, представленные в разделе справки poshagovoe_rukovodstvo.dita#.

Далее необходимо перенести на схему блок, который на ваш взгляд наиболее близок по структуре создаваемому. На основе этого блока мы будем делать собственный. Пусть это будет «Усилитель» (Рисунок 1).

Рисунок 1. Звено «Усилитель» добавлено на схему

Теперь необходимо отредактировать данный элемент под создаваемый блок: указать необходимое количество входов/выходов, указать какие параметры задаются через свойства блока. Для этого выделите блок и нажмите «Правка», далее «Изменить блок». Перед вами откроется окно реактирования блока (Рисунок 2). Обратите внимание, что для возможности редактирования блока необходимо включить режим разработчика. Это осуществляется из пункта главного меню «Вид».

Рисунок 2. Окно редактирования блока

Выполним настройку свойств блока. Удалим имеющиеся свойства и добавим шесть новых. Среди них:

Отметим, что создаваемый ПИД-регулятор должен быть векторным, поэтому в графе «Тип данных» везде указываем «Массив». Переходим на вкладку «Общие». Укажем в графе «Тип объекта» - «ПИД1», а в графе «Имя объекта» - «PID1». Этим действием мы подчеркиваем, что создаем новый блок для SimInTech. Также на этом этапе необходимо изменить изображение блока. Это тоже осуществляется из вкладки «Общие».

Рисунок 3. Изменены имя и тип объекта

Рисунок 4. Изменено графическое изображение блока

Перейдем на вкладку «Порты» и добавим еще один входной порт. Важно выбрать тип связи – «Математическая связь». Расположение зададим «Снизу».

Рисунок 5. Добавлен новый входной порт

Перейдем к вкладке «Расчёт», отвечающей непосредственно за исполнение самого блока. В графе «Расчётный шаблон» в первой строке необходимо указать имя библиотеки, в которой будет создаваться класс блока, а во второй строке имя класса внутри библиотеки. Библиотеку назовем, например, «REG_LIB.dll», а имя класса – «TPID1». В последней строке укажем имя объекта – «PID1».

Рисунок 6. Вкладка «Расчёт»

Можем нажать по кнопке «Ok». Система должна выдать оповещение об ошибке. Так и должно быть, потому что указанную расчетную библиотеку, которая имплементирует математику блока, мы еще не сделали. На этом создание заготовки для нового блока закончено. Сохраните эту схему в удобную для вас директорию.

Описание расчетной библиотеки в среде разработки Delphi XE7

В рамках данной задачи, для простоты будем делать библиотеку на основе уже существующих исходных кодов. В директории …\SimInTech\Source\MBTY находятся исходные коды библиотек, которые уже включены в состав системы. В качестве основы для своей библиотеки мы возьмем, например, библиотеку GIDRO_LIB.

В этой же директории …\SimInTech\Source\MBTY создаем новую папку REG_LIB и переносим в нее содержимое папки GIDRO_LIB.

Открываем среду разработки Delphi XE7. Для разработки блоков для SimInTech достаточно иметь «пустую» версию программы Embarcadero Delphi XE7 буз установки компонентов, потому что сами библиотеки блоков сторонних компонентов не требуют. После запуска программного комплекса необходимо открыть файл «gidro_lib.dproj» из созданной нами папки REG_LIB и пересохранить его под новым именем. Для этого нажимаем по меню «File», далее «Save Project As» и сохраняем в ту же директорию …\SimInTech\Source\MBTY\REG_LIB в соответствии с тем именем библиотеки, которое мы указали во вкладке «Расчёт» создаваемого блока, то есть reg_lib. После этого все ненужные файлы, относящиеся к библиотеке «gidro_lib», за исключением файла gidro_lib_Icon, из папки REG_LIB можно удалить.

Возвращаемся к среде разработки и открываем файл Info.pas из той же директории. Этот файл описывает таблицу блоков, которые принадлежат данной библиотеке. Нетрудно заметить, что на текущий момент библиотека состоит из пятнадцати элементов. Удаляем все, кроме одного, из которого будем делать блок ПИД-регулятора. Теперь библиотека состоит только из одного класса, имя которого «TNS» (Рисунок 7).

Рисунок 7. Таблица классов

Далее имя класса нужно задать именно тем, которое мы вводили при создании заготовки, а именно: «TPID1» (Рисунок 8).

Рисунок 8. Изменено имя класса

Создание блока происходит следующим образом: при инициализации расчета, ядро программы согласно конфигурации, описанной во вкладке «Расчет» блока, загружает библиотеку (в нашем случае это «REG_LIB.dll»). Затем в этой библиотеке вызывает функцию «CreateObject» (Рисунок 9). Эта функция создает класс блока (в нашем случае «TPID1»), который унаследован от базового класса «TRunObject». Классы блоков описаны в файле «Blocks.pas». Они сделаны по одному и тому же образцу.

Рисунок 9. Функция «CreateObject»

Откроем вышеупомянутый файл «Blocks.pas». Оставим в этом файле лишь один класс, так как эту библиотеку мы используем только как шаблон (Рисунок 10).

Рисунок 10. Файл «Blocks.pas»

Далее переименуем класс «TNS» в необходимый нам «TPID1». Для этого в инспекторе классов нажимаем по кнопке «Rename» (Рисунок 11). И это же имя необходимо перенести в таблицу классов, по которой присходит непосредственно создание классов внутри библиотеки (Рисунок 12).

Рисунок 11. Переименовываем класс в «TPID1»

Рисунок 12. Таблица классов

Все классы расчетных блоков унаследованы от базового класса «TRunObject». Базовый класс и флаги вызова описаны в файле «RunObjts», находящемся в директории …\SimInTech\Source\Root. Откроем этот файл. Посмотрим на сам класс блока «TRunObject». Он обладает следующими параметрами: массивы размерностей входов и выходов «cU», «cY»; массивы входов и выходов «U», «Y»; параметры, касающиеся динамических и алгебраических переменных (Рисунок 13).

Рисунок 13. Параметры базового класса

Далее идут различные системные ссылки и флаги. Их описание приведено на Рисунок 14.

Рисунок 14. Описание системных ссылок

Также класс обладает набором методов, которые необходимы для его реализации. Во-первых, это constructor и destructor. Они позволяют создать/уничтожить нужные нам величины внутри блока.

Функции «GetParamID», «GetOutParamID», «ReadParamID», «WriteParamID» позволяют блоку общаться с пользователем, то есть получать или наоборот отдавать какие-либо данные помимо входов и выходов (Рисунок 15). Эти функции осуществляют привязку параметров, которые мы задаем в свойствах блока, к внутренним переменным блока.

Следующие функции и процедуры вызываются уже непосредственно в процессе инициализации и расчета (Рисунок 16):

Рисунок 15. Функции для работы с параметрами блока

Рисунок 16. Функции и процедуры, вызываемые на этапе инициализаци и расчета

Рисунок 17. Флаги вызова run-функции блока

Рисунок 18. Флаги вызова info-функции блока

Теперь можем перейти непосредственно к описанию собственного блока. Возвращаемя к файлу «Blocks.pas» создаваемой библиотеки.

Для создания блока нам будет достаточно описать три функции: «GetParamID», «InfoFunc», «RunFunc».

Первым делом в описании класса «TPID1» укажем глобальные переменные, которые нам понадобятся для создания блока, а также функции и процедуры, необходимые для описания работы блока (Рисунок 19). Вводим шесть переменных, которые будут описывать коэффициенты и начальные условия. Важно, чтобы имена переменных совпадали с именами свойств, заданных при создании заготовки. Блок векторный, следовательно каждая переменная должна быть массивом действительных чисел. Все числа, используемые в блоках, являются числами типа Double. Также для описания блока нам понадобятся вышеупомянутые функции «GetParamID», «InfoFunc», «RunFunc» и конструктор «Create» для создания переменных внутри блока.

Рисунок 19. Описание класса «TPID1»

Создадим с помощью конструктора «Create» внутри блока шесть массивов для коэффициентов регулятора (Рисунок 20). Теперь с помощью функции «GetParamID» осуществим привязку внешних свойств блока к внутренним переменным (Рисунок 21). Если значение строки «ParamName» совпадает с заданным именем, то ядро возвращает указатель на эту переменную и тип данных этой переменной.

Рисунок 20. Создаем внутренние массивы блока для коэффициентов регулятора

Рисунок 21. Функция «GetParamID»

Перейдем к описанию info-функции, которая отвечает за сортировку блока. Для этой функции нас интересуют следующие флаги вызова:

В рамках текущей задачи создания векторного блока ПИД-регулятора размерность массива выхода должна быть равна размерности входа блока. Это условие выполняется за счет вызова info-функции флагом «i_GetCount», в котором прописано соответствующее равенство размерностей (Рисунок 22).

В общем блок ПИД-регулятора можно представить в виде комбинации трех блоков, два из которых являются динамическими (интегрирующее и инерционно-дифференцирующее звено) и один – алгебраическим (усилитель). Поэтому количество переменных состояния блока должно в два раза превышать количество входных сигналов. В связи с этим для удобства математического описания блока рекомендуем ввести два локальных массива переменных состояния. Один будет отвечать за интегрирующее звено, а другой за инерционно-дифференцирующее звено. Но обязательно должно быть прописано объединение этих локальных массивов в один для передачи его решателю (это бует сделано позже при описании run-функции блока). Объявляем массивы для локальных дифференциальных переменных состояния и для их производных с помощью оператора «var». Также внутри конструктора создаем внутренние массивы для этих переменных (Рисунок 23).

Рисунок 22. Функция «InfoFunc»

Рисунок 23. Создание массивов для локальных переменных состояния

Далее нужно задать размерности массивов «xdif1», «xdif2», «fdif1», «fdif2». Это можно сделать, например, при описании флага «i_GetCount». Размерности этих массивов должны соответствовать размерности входа.

Рисунок 24. Задание размерностей для массивов локальных переменных состояния

Можем приступить к описанию последней и самой главной расчетной функции – run-функции. Как упоминалось ранее, для run-функции нас будут интересовать следующие флаги:

Предварительно введем переменные, которые понадобятся для организации циклов, а также напишем простую функцию, которая при вызове run-функции будет следить за значениями коэффициентов регулятора, не допуская их равенства нулю (Рисунок 25). Таким ообразом, функция сравнивает значения коэффициентов с нулем, и в случае равенства нулю коэффициента выдает сообщение об ошибке и останавливает расчет.

Рисунок 25. Функция проверки равенства нулю коэффициентов регулятора

Можем перейти к описанию первого флага – «f_InitState». Здесь должны быть заданы начальные значения для переменных состояния и выходов блока. В рамках данного учебно-демонстрационного примера подробно на математике блоков останавливаться не будем. Приведем лишь уравнения динамики сосставляющих ПИД-регулятора:

где xi(t) – вектор входных сигналов, yi(t) – векторы выходных сигналов составляющих регулятора, kpi – вектор коэффициентов усиления П-составляющей, kii – вектор коэффициентов усиления И-составляющей, kdi – вектор коэффициентов усиления Д-составляющей, tdi – вектор постоянных времени Д-составляющей.

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

При вызове run-функции флагом «f_InitState» сначала производится проверка коэффициентов на равенство нулю, далее происходит вычисление начальных значений локальных переменных состояния и выхода блока, а затем значения локальных переменных состояния записываются в один массив для передачи ядру (Рисунок 26).

Рисунок 26. Описание флага «f_InitState»

Далее опишем флаги «f_UpdateOuts», «f_GoodStep» (Рисунок 27). При вызове run-функции этими флагами необходимо передать ядру значения выходов блока. Для динамических блоков это может быть зависимость выходов от переменных состояния и входов, а для алгебраических - непосредственно зависимость выходов от входов. Поскольку блок будет непрерывным, то данные флаги должны быть реализованы одинаково. Сначала распределяем полученный от решателя массив дифференциальных переменных состояния по массивам локальных переменных состояния «xdif1» и «xdif2», а затем записываем выражение для выхода блока, не забывая проверить на равенство нулю коэффициенты регулятора.

Рисунок 27. Описание флагов «f_UpdateOuts», «f_GoodStep»

Перейдем к описанию последнего флага «f_GetDeri» (Рисунок 28). При вызове run-функции этим флагом мы должны передать ядру значения производных переменных состояния, то есть массив «fdif». В нашем случае опять для удобства сначала записываем выражения для производных локальных переменных состояния, а затем объединяем их в один массив и передаем ядру.

Таким образом, на первом этапе вычисляется все, что прописано во флаге «f_InitState». Далее происходит вычисление выходов блока на пробных шагах. Затем после обновления выходов как на пробных шагах, так и на основных шагах по флагу «f_GetDeri» ядру передаются значения производных переменных состояния, чтобы на следующем шаге решатель сформировал массив переменных состояния.

Рисунок 28. Описание флага «f_GetDeri»

На этом описание run-функции блока также как и описание всего блока завершено. Теперь необходимо скомпилировать написанную библиотку. Выбираем тип платформы «32-bit Windows» (Рисунок 29) и нажимаем «Build reg_lib» из пункта главного меню «Project» (Рисунок 30).

Рисунок 29. Выбираем тип платформы

Рисунок 30. Компилируем написанную библиотеку

Если все было сделано верно, система не должна выдать никаких оповещений об ошибках (Рисунок 31).

Рисунок 31. Библиотека сформирована

Проверка работоспособнсти блока и отладка кода с использванием среды разработки Delphi XE7

Открываем созданную ранее заготовку и собираем простую схему для проверки работоспособности блока (Рисунок 32). Подаем на вход пять сигналов. Коэффициенты регулятора задайте произвольно. Запускаем модель на расчет и убеждаемся в корректной работе блока (Рисунок 33).

Рисунок 32. Схема для проверки работоспособности блока

Рисунок 33. Проверка работоспособности блока

Теперь осущетсвим запуск среды разработки Delphi XE7 в отладочно режиме. Закрываем SimInTech. В пункте главного меню среды разработки «Run» нажимаем по строчке «Parameters» и в графе «Host application» указываем ссылку на модуль графической оболочки C:\SimInTech\bin\mmain.exe (Рисунок 34).

Рисунок 34. Подготовка к запуску среды разработки в отладочном режиме

Запускаем расчет среды разработки с отладкой нажатием кнопки «F9». В результате должно появиться окно SimInTech. Открываем созданную ранее для проверки работоспособности блока схему и запускаем расчет в SimInTech. Возвращаемся к среде разработки и в нужном месте кода устанавливаем Breakpoint (Рисунок 35). Переносим интересующую нас величину, например, третий выход блока в «Watch List» (Рисунок 36) и можем наблюдать текущее значение этой величины. Таким образом, нажмая кнопку «F8», в пошаговом режиме осуществляется отладка кода с помощью среды разработки Delphi XE7.

Рисунок 35. Ставим Breakpoint в интересующем месте

Рисунок 36. «Watch List» для просмотра текущих значений интересующих величин

Процедура добавления соданного блока к панели инструментов, а также создания библиотечных файлов для SimInTech описана в примере с генерацией кода на языке Си непосредственно в SimInTech.