Найти в Дзене

Архитектура ППО для ПЛК. Часть 1. Сущность и вариант использования.

Архитектура ППО — важная тема при создании программного обеспечения, но ей не всегда удается уделить должное внимание в сфере АСУТП. В наше время системы усложняются, увеличивается количество связей между компонентами и возникает потребность правильной и удобной организации системы. В данной статье будут затронуты основы работы ПЛК и типы управления для системы, дано определение архитектуры ППО, рассмотри элементы системы, которые формируют фундамент и разберем один из примеров рефакторинга проекта. Типы управления В зависимости от того, что будет является стартовой точкой для различных алгоритмов я выделяю два типа управления: Система непрерывного управления Данный тип системы используется там, где технологический процесс очень сильно растянут по времени и имеет большое количество контуров регулирования.
Такой способ управления имеет свои особенности написания логики компонентов и их взаимодействие.
Обычно при такой реализации на входе отображаются все возможные свойства данного компо
Оглавление

Архитектура ППО — важная тема при создании программного обеспечения, но ей не всегда удается уделить должное внимание в сфере АСУТП.

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

В данной статье будут затронуты основы работы ПЛК и типы управления для системы, дано определение архитектуры ППО, рассмотри элементы системы, которые формируют фундамент и разберем один из примеров рефакторинга проекта.

Типы управления

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

  • Система непрерывного управления
  • Командная система управления

Система непрерывного управления

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

Из набора таких данных и триггеров, для активации и деактивации, собирается логика процесса.
Оператор задает допустимые пределы регулирования, значения при которых будет происходит переход между состояниями и иногда вмешивается в процесс с ручным регулированием.
Взаимодействие между компонентами системы происходит путем передачи значений с выходов одного блока на вход другого.

Командная системы управления

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

Архитектура ППО

Архитектура прикладного программного обеспечения — эта структура системы, разделенная на различные компоненты, описывающая их организацию и определяющая методы взаимодействия между ними, которая сохраняет возможность изменения системы, упрощает ее разработку и сопровождение.
Архитектура ППО описывает объект автоматизации, процессы, которые происходят с этим объектом, устройства, используемы в этих процессах, различные виды воздействия на всю систему и взаимодействие объектов внутри системы.

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

Давайте начнем с минусов построения архитектуры ППО, если подходить к этому вопросы строго по учебнику.
ЭТО ДОЛГО! Очень долго! А иногда и больно. Если у вас не масштабируемые проекты и очень сжатые сроки, то не стоит за неделю до ПНР говорить, что сейчас мы все сделаем и пытаться пересобрать проект, ориентируюсь на различные принципы построения системы, пытаясь уместить в проект новые подходы.
Скорее всего, опираясь на свой опыт, вы уже спокойно отделяете слои, отдельно происходит обработка входных и выходных сигналов, отдельно происходит обработка различных механизмов, и как-то передаются данные от одного функционального блока к другому. Тут принятие поспешных решений может навредить.
Кому же это нужно?
Во-первых, для систем со сложной логикой, где внутри логики завязаны много различных компонентов. В таком случае при правильном подходе получиться легко масштабировать проект или изменять различные детали реализации, так как компоненты будут как минимум слабо связаны, также не будет путаницы при внесении изменений, так как будут четко определенны границы ответственности каждого компонента. Разумеется задуматься об архитектуре стоит и при работе в команде.

Элементы системы

Для построения архитектуры требуется определить минимально возможные компоненты, оперируя которыми и будет проектироваться система. Можно выделить несколько основных: контроллеры, презентаторы, сущности, варианты использования. Каждый из них реализует определенную логику системы. Основными компонентами являются сущность и вариант использования.

Сущность

Основной компонент системы — сущность.
Сущность — это объект, который содержит в себе или имеет простой доступ до тех данных и правил, которые имеют решающее значение для системы и будут существовать в отсутствие автоматизированной системы управления.
Под сущностью я буду понимать программную реализацию какого-либо физического объекта, который бы и без ППО существовал бы. Датчики, исполнительные механизмы, различные конструкции, которые участвуют в технологическом процессе — это сущности.
Сущность должна быть меньше всего подвержена изменениям и является основой для построения системы.
В объект, который считается сущностью, включены ее основные данные и основные правила сущности.
Сущность является высокоуровневым компонентом, который
не должен зависеть от других компонентов.

Теперь немного к практическому уровню.
К сущностям у нас могут относиться:

  • Дискретные cигналы(не обязательно дискретный сигнал является каналом дискретного модуля).
  • Аналоговые сигналы
  • Клапаны
  • Насосы
  • Емкости
  • Датчики
    Сущности хранят только те данные, которые непосредственно к ним относятся.
    Условный клапан не знает в каком режиме она работает, ручной или автоматический. Он лишь знает что у него есть команда и также он знает, что он может быть открытым и закрытым.
    При чем, если мы возьмем какой-нибудь датчик давления, который отдает свои данные на канал AI модуля, то мы его смело можем поделить на две сущности. Аналоговый сигнал и сам датчик давления. Аналоговый сигнал обрабатывает всю информацию что связана с сами сигналом и его значением. Это не отмасштабированные показания, состояние этого сигнала, а такая сущность как датчик давления уже хранит в себе значение давления и сигналы об аварийном высоком или аварийном низком значении, но датчик давления не хранит в себе пределы для масштабирования и не производит масштабирование сигнала.
    Обычной сущностью в системе может быть структура данных. Или структура данных и набор функций, который с ней связан. Возможно вы все эти данные упакуете в функциональный блок.

Вариант использования

Вариант использования описывает правила, которые характерны для данной системы. Вариант использования описывает способы использования конкретной автоматизированной системы. Он определяет какие команды и сигналы должна получить система, какие выходные данные и сигналы системы должна выдать и действия, которые необходимо совершить.
Вариант использования взаимодействует с сущностями. Вариант использования знает когда клапан надо открыть или до какого уровня набрать емкость. Вариант использования знает какой режим работы у нас сейчас есть, он знает есть ли блокировка на механизме или нет.
Если вернутся к примеру с датчиком давления, то именно вариант использования будет знать пределы масштабирования и значения для формирования аварийных сигналов, и в варианте использования будет происходить масштабирование сигнала.
Вариант использования подвержен изменениям. Именно в этом слое и только в нем придется вносить изменения, если технологи решат, что-то попробовать.
Вариант использования не обязательно должен быть одним функциональным блоком, но должен реализовывать лишь один вариант.

Логика АС

Совокупность сущностей и вариантов использования формируют логику автоматизированной системы или бизнес логику.
Это представление в коде поведения системы без привязки к ПЛК, модулям ввода вывода и конкретным каналам. Логика является основой и она не обращает внимание на детали.

Теперь к примеру. Посмотрим на практике, а как это все должно работать. За основу будет взята задача разработки системы позиционирования. В систему входят: драйвер ШД, энкодер, датчик нулевого положения.

Изначально код выглядит так:

FUNCTION_BLOCK fbDriver
VAR_INPUT
Setting: udtDriverSetting;
Driver: REFERENCE TO udtDriverData;
EncoderPozition: REFERENCE TO DINT;
END_VAR
VAR_OUTPUT
Enable: BOOL;
PWM: UDINT;
CCW:BOOL;
END_VAR
VAR
Corrdinate: ARRAY[0..6] OF DINT;
Direction: INT;
END_VAR
Corrdinate[0]:=Setting.Pozition_1;
Corrdinate[1]:=Setting.Pozition_2;
Corrdinate[2]:=Setting.Pozition_3;
Corrdinate[3]:=Setting.Pozition_4;
Corrdinate[4]:=Setting.Pozition_5;
Corrdinate[5]:=Setting.Pozition_6;
Corrdinate[6]:=Setting.Pozition_7;
IF Driver.Pozition < Corrdinate[Driver.CurrentPozition] THEN
CCW:=FALSE;
Direction:=1;
ELSE
CCW:=TRUE;
Direction:=-1;
END_IF
IF Driver.xManualCommand AND Driver.Pozition <> Corrdinate[Driver.CurrentPozition] THEN
PWM:=DINT_TO_UDINT((Corrdinate[Driver.CurrentPozition] - Driver.Pozition)*Direction);
Driver.Pozition:=Corrdinate[Driver.CurrentPozition];
END_IF
IF NOT Driver.xManualCommand THEN
PWM:= 0;
END_IF
IF Driver.ZeroPozitionCommand THEN
EncoderPozition:=0;
Driver.ZeroPozitionCommand:=FALSE;
END_IF
Enable:=Driver.Enable;

Основные структуры, которые мной были использованы

TYPE udtDriverData :
STRUCT
Pozition: DINT;
CurrentPozition: INT;
ZeroPozitionCommand: BOOL;
Enable: BOOL;
xManualCommand: BOOL;
END_STRUCT
END_TYPE

TYPE udtDriverSetting :
STRUCT
Pozition_1: DINT;
Pozition_2: DINT;
Pozition_3: DINT;
Pozition_4: DINT;
Pozition_5: DINT;
Pozition_6: DINT;
Pozition_7: DINT;
END_STRUCT
END_TYPE

Разберем основные ошибки допущенные мной.
Во первых — это сборная солянка. Не смотря на то, что блок называется fbDriver, он также отвечает за обработку энкодера, за формирование сигналов для дискретного выхода, обрабатывает данные с панели оператора и даже реализует логику позиционирования. Примерно так выглядит код, который пишется за несколько часов до вылета. По этому я переписал этот код.
Стоит тут добавить, что я переписывал его не опираясь на чистое ООП, но сделал так, чтобы все стало понятнее и проще, использую некоторые приемы ООП. Я не использовал интерфейсы, чтобы сильно утонуть в слоя абстракции, но они тут требуются.
Из одного FB я получил 3.
1) fbDriver — отвечает за работу драйвера ШД. Формирует разрешающий сигнал на управления, команду старт/стоп, показывает направление вращения.
2) fbStepGenerator — отвечает за формирование шагов и он просто был убран из системы позиционирования, потому что он не относится к этому компоненту.
3) fbPositionSystem — логика использования данных компонентов.

Начну разбор с fbDriver

FUNCTION_BLOCK fbDriver
VAR_INPUT
Driver: REFERENCE TO udtDriverData;
END_VAR

На входе в функциональный блок у меня есть только ссылка на структуру данных необходимого мне драйвера. Можно даже ее выкинуть и все поместить в тело ФБ, но я пока к такому не готов(тут просто надо будет написать свойства для фб и все это должно быть сокрыто интерфейсом).
Далее у меня идет ряд методов, которые формируют логику работы

METHOD PUBLIC CCW
VAR_INPUT
END_VAR
THIS^.Driver.xCCW:=TRUE;
END_METHOD

METHOD PUBLIC CW
VAR_INPUT
END_VAR
THIS^.Driver.xCCW:=FALSE;
END_METHOD

METHOD DisableDriver : WORD
VAR_INPUT
END_VAR
THIS^.Driver.xEnable:=FALSE;
END_METHOD

METHOD PUBLIC EnableDriver : WORD
VAR_INPUT
END_VAR
THIS^.Driver.xEnable:=TRUE;
END_METHOD

METHOD PUBLIC StartDriver
VAR_INPUT
END_VAR
THIS^.Driver.xCommand:=TRUE;
END_METHOD

METHOD PUBLIC StopDriver
VAR_INPUT
END_VAR
THIS^.Driver.xCommand:=FALSE;
END_METHOD

Структура данных, которые мне необходимы

TYPE udtDriverData :
STRUCT
xEnable: BOOL;
xCommand: BOOL;
xCCW: BOOL;
END_STRUCT
END_TYPE

Именно эти данные я буду использовать для привязки к дискретным выходам.
Вторая часть отвечает за то, а как же нам позиционировать все это?
Система позиционирования должна управлять драйвером ШД, на основе команд и показаний энкодера.
Моя логика заключатся в двух командах — поставить систему «домой» и занять нужную позицию.
Также система позиционирования должна понимать, а имеет ли она право работать, не приходит ли какая-нибудь блокировка. Это вариант использования драйвера шагового двигателя в конкретной системе.

FUNCTION_BLOCK fbPositionSystem
VAR_INPUT
PositionSystem: REFERENCE TO udtPositionSystem;
Driver: REFERENCE TO fbDriver;
HomePosition: BOOL;
udiEncoderPosition: UDINT;
END_VAR
VAR_OUTPUT
END_VAR

VAR
_uiSetHomePositionStage : UINT;
_uiSetPositionStage: UINT;
END_VAR

METHOD PUBLIC LockSystem : WORD
VAR_INPUT
END_VAR

THIS^.PositionSystem.xLock:=TRUE;
END_METHOD

METHOD PUBLIC SetHomePostion: WORD
VAR_INPUT
END_VAR
IF THIS^.PositionSystem.xLock THEN
SetHomePostion:=16#FF;
ELSE
SetHomePostion:=THIS^._setHomePosition(THIS^.HomePosition);
END_IF
END_METHOD

METHOD PRIVATE _setHomePosition : WORD
VAR_INPUT
xHomePostion: BOOL;
END_VAR

CASE THIS^._uiSetHomePositionStage OF
0:
THIS^.Driver.EnableDriver();
THIS^._uiSetHomePositionStage :=10;
10:
THIS^.Driver.StartDriver();
THIS^._uiSetHomePositionStage:= 20;
20:
IF xHomePostion THEN
THIS^.Driver.StopDriver();
THIS^.Driver.DisableDriver();
THIS^._uiSetHomePositionStage:=30;
END_IF
30:
THIS^._uiSetHomePositionStage:=0;
THIS^.PositionSystem.udiHomePosition:=THIS^.udiEncoderPosition;
_setHomePosition:=16#01;
RETURN;
END_CASE
END_METHOD


METHOD PUBLIC SetPosition : WORD
VAR_INPUT
udiPosition: UDINT;
END_VAR

IF THIS^.PositionSystem.xLock THEN
SetPosition:=16#FF;
RETURN;
ELSE
SetPosition:=THIS^._setPosition(udiPosition);
END_IF
END_METHOD

METHOD PRIVATE _setPosition : WORD
VAR_INPUT
udiPosition: UDINT;
END_VAR

CASE THIS^._uiSetPositionStage OF
0:
THIS^.Driver.EnableDriver();
THIS^._uiSetPositionStage:=10;
10:
THIS^.Driver.StartDriver();
THIS^._uiSetPositionStage:=20;
20:
IF THIS^.udiEncoderPosition >= THIS^.PositionSystem.udiHomePosition+udiPosition THEN
THIS^.Driver.StopDriver();
THIS^.Driver.DisableDriver();
THIS^._uiSetPositionStage:=30;
END_IF
30:
THIS^._uiSetPositionStage:=0;
_setPosition:=16#01;
RETURN;

END_CASE
END_METHOD

METHOD PUBLIC UnlockSystem : WORD
VAR_INPUT
END_VAR

THIS^.PositionSystem.xLock:=FALSE;
END_METHOD
END_BLOCK

Из необычных вещей которые стоит заметить в методах — это метод SetPosition. Метод SetPosition принимает на вход позицию, на которую мы должны переместиться.
Для чего это требуется. Во-первых, чтобы я мог скормить этому методу любую позицию из любого источника. Во-вторых, выставить на указанную позицию и произвести действия для высчитывания данной позиции — разные задачи и разная зона ответственности. Логику работы позиции будет иметь какой-нибудь третий компонент, который будет обладать всеми необходимыми данными.
Пускай считает тот, кто владеет данными.

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

-2

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

Итог

Теперь у нас есть представление, что такое сущность и что такое вариант использования, он же use case, а также как они взаимодействуют. Далее нам предстоит разобраться как логике автоматизированной системы взаимодействовать с миром и ответить на главный вопрос, как же правильно передавать данные по modbus.

Если вы прочли до этого момента, то у меня будет просьба, оставьте свое мнение по этой статье. С чем вы не согласны, с чем точно не согласны, где у вас появились вопросы. Это поможет в дальнейшем дополнить статью.

Почта для связи и предложений: info@engcore.ru

Группа в ТГ: https://t.me/wtfcontrolsengineer