Часть 2, приоритеты и базовые сигналы
Часть 1,введение (отправил в политоту из-за двусмысленных высказываний, ибо было сложно удержаться)
Приоритеты команды
Итак, вы решили взяться за благое дело и сэкономить пару дефицитных рупей на найме программиста и запилить всё своими руками. Что же для этого надо?
1) скачайте среду разработки
2) откройте среду разработки3) закройте среду разработки и идите искать программиста
Для начала, давайте проясним одну вещь. То что я рассказываю не является аксиомой. Это концепция архитектуры, позволяющая сделать максимально гибкое и устойчивое решение, позволяющее безболезненно изменять различные прослойки кода внутри проекта, не роняя завод.
Итак, помните, я говорил что всё есть сигнал? Я наврал. Всё есть объект.
Вход - это объект.
Выход - это объект.
Бутылка - тоже объект, но более высокого порядка.
А дальше мы начинаем собирать пирамидку из кубиков:
- базовые сигналы нижнего уровня, дискретные и аналоговые
- простые устройства
- сложные устройства
- технологические участки
- технологические линии
- производственные участки
...
- планирование производства
Вот всё что лежит до троеточия и будем рассматривать. Начнём с базиса, но перед этим - а кто здесь главный? Ты, я? оператор? директор? да пошёл ты в жопу, директор! Главная здесь ОНА:
Мне лень рисовать пирамидки маслоу, поэтому обойдёмся блок-схемами. ГОСТы - для слабаков! Главное, чтобы было понятно...
УХ, ебать! Но это лёгкий вариант, извините.
Глобальные переменные
Обратите внимание на "Глобальные переменные". Что в них входит? А всё, что проходит красной нитью через весь код и существует в единственном экземпляре:
- Аварийный останов
- Общий сброс аварий
- Общий режим эмуляции
- Общий перевод в автоматический режим всех устройств (очень полезно, если оператор любит пошарить ручками где надо и забывает что делал в недрах нашей системы)
Как оно выглядит в объявлении переменных? Вот так:
All_Block:BOOL; //аварийная остановка
All_Reset :BOOL; //сброс всех аварий
All_Sim :BOOL; //общий режим эмуляции
All_Auto :BOOL; //перевести всё в автоматический режим
sys :system; //системные переменные
sec :REAL; //длительность одной секунды
Обратите внимание, на две последние переменные. Это наша опора и поддержка, которую мы будем гонять вместе с первой четвёркой по всем проектам.
sec - длительность секунды в часах, да, я знаю что считать в REAL повышает нагрузку на CPU, но пардон, у вас мощности позволяют то в 2к2+ году
sys - структура системных переменных, которая собирает в себя базовые компоненты, реализация которых отличает у разных производителей:
- флаг TRUE
- флаг FALSE
- импульсы с разным весом, которые живут ровно 1цикл программы
- прочая мелочёвка
Опс, опять что-то новенькое. 1 цикл программы. Да-да, ваш код всегда исполняется от начала и до конца сверху вниз, слевана право. За всякие go_to я лично вырываю руки, ибо нехер.
Итого, на текущий момент у меня в sys лежит:
Дану ёб твою мать, скажете вы, что это за херня? И будете правы!
- x - не потому что хуй, а потому что ON и OFF зарезервированы системой и их нельзя просто так применять;
- Hand - флаги, что хотя бы одно устройство данного типа переведено в ручной режим;
- Alm - флаги, что хоть одно устройство данного типа находится в аварии;
- Any - сборный флаг для всех флагов, привязанных к типам устройств;
- V - Valve, а не то что вы подумали. Клапан, заслонка, задвижка;
- M - не мудак, к сожалению, а Motor. Вентилятор, транспортёр, всё что вращается - всё Motor. Я иногда делю на MD и MDA, дикрестное и дискретно-аналоговое управления, но это личные заморочки, не более;
- DTL - DataTimeL.. хз что за L, типовая структура формата год-месяц-дата-час-минута-секунда, которую мы будем использовать при работе с расписанием и всем, что требует проверки системного времени.
Ну наконец-то! А где код-то? Хрена вам, а не код. Рано.
На очереди ещё одна структура, которая будет всегда и везде, обеспечивая работу вон той страшной картинки наверху, обычно я называю её CMD:
УправлениеУра! Наконец-то! Мы добрались! И теперь...
Видите вон там странный Alarm, которого раньше не было? Видите? И он есть!
А всё почему? Да потому что...
1) мы делаем универсальный код, который будет применим для всего и вся
2) программа - это не только программа, это ещё функциональный блоки функции
В чём же разница? Если кратко - функциональный блок может иметь свои внутренние, приватные, переменные, а функции - работает только с внешними и временными.
СтопЭ. временные и внутренние - какая в пень разница?
Внутренняя - сохраняет своё значения при переходе на новый цикл программы.
Временная - теряет своё значение может содержать случайную величину при переходе на новый цикл программы.
Таким образом, если вы внутри функции делаете А+Б=С и по значению С принимаете решение КАЖДЫЙ раз вызывая функцию, то можно использовать временную. А если делаете А+Б = С один раз и больше к этому не возвращаетесь, только читая С, то нужна внутренняя переменная. И опять таки... если вы хотите сохранить значение С при перезапуске контроллера - будьте добрый, сделать её энергонезависимой, т.е. Retain.
Вернёмся к Alarm. Для функциональных блоков (FB) и функций (FC) есть следующие виды переменных:
- входные - можно только читать
- выходные - можно только записывать
- входные-выходные - как тугая попка трапа, можно и читать и изменять значение
Это очень важно, когда у вас одна и та же внешняя переменная передаётся разныеFC/FB а вы сидите и хлопаете глазками, почему значение теряется посреди программы. Да потому что вы проебались и привязали не к тому типу. Наиболее наглядно сие видно в графических языках:
Слева- входные (input) и входно-выходные (input-output) переменные, справа -выходные.
Фух, разобрались, теперь вызовем нашу страшную функцию:
Драйвера, устройства и состояния
Расширим очко овертона терминологию: драйвер. Нет, это не виндовый драйвер. Но близко. Это некий алгоритм и набор переменных, описывающий работу базового или типового устройства. Входа, выхода, насоса, клапана. Оно беспечивает автомномный контроль:
- безопасности
- аварий
- ручного и автоматического управления
- наработки
Включает в себя все необходимые для жизни параметры и настройки, коих дофига и больше. Но это мы рассмотрим потом. Или не рассмотрим. Если микроскоп вдруг сломается.
Выглядит управление дискретным входом следующим образом:
Итак, для работы драйвера нам необходимы:
- CMD - структура команд, рассмотрена выше
- cfg - структура параметров
- state - структура состояний
Дискретный вход слишком прост и отдельный набор состояний ему не нужен. Состояния включают в себя коды аварий, подсчёт наработки, вспомогательные плюшки для удобства отображения оператору "что тут происходит, мамочки, почему оно встало?!"
use_NC...
NC - нормальной закрытый сигнал
NO - нормально открытый сигнал
Например, у вас есть дверь. Стоит датчик, контролирующий что "дверь закрыта".
Есликогда она закрыта сигнал = 0, а при открытии двери сигнал = 1, это нормально открытый сигнал.
Если когда она закрыта сигнал = 1, а при закрытии двери сигнал = 0, то это нормально закрытый сигнал.
Применение того или иного типа сигнала должно быть продиктовано соображениями безопасности. Например, аварийный стоп - должен быть нормально закрытым сигналом. Есть сигнал - всё хорошо, пропал - всё плохо. Почему так? Потому что Вася уронил топор на кабель и сигнал пропал - вы должны отключить всю систему, чтобы бедного Васю не намотало, превратив в инвалида пожизненно. Да-да, безопасность Васи - Ваша ответственность!
А если это не критичный сигнал, требующий контроля целостности сигнальной линии, то можно смело ставить нормально открытый датчик. Но это теория, а на практике ныне - что найдёте, то и поставите. Сорян.
УУУ, сука, сколько буков то! А это только начало! И я не ответил на главный вопрос - на кой хер нам вообще различать на уровне драйвера тип сигнала? В алгоритме основном поправим, да и делов то. НЕТ! Хватит! Не усложняйте себе жизнь!
Вам, как программисту, должно быть глубочайше насрать какой там тип датчика -сработал это всегда = 1, не сработал это всегда = 0. И именно эту задачу унификации решает драйвер дискретного входа. УНИ-ФИ-КА-ЦИИ, а не загрузки процессорного времени бесполезным хламом. Так менеджеру и ответите, что вы ускоряете дальнейший цикл разработки, ага.
ТаймерыАвтор, ты заебал, где код драйвера? Да вот он, только работать он у вас не будет:
А почему - угадаете? Не, вы не тупые, вы умные. Просто я хитрожопый и вместо системных таймеров использую самописные. Нахуа-хуа? Да потому что системные кривые. И их мало. И каждый системный таймер можно использовать только один раз(если они аппаратные, как в старых ПЛК).
Почему использовать системный таймеры, это грех:
- если вы измените во время работы таймера уставку времени и она окажется меньше, чем уже прошедшее время с момента запуска таймера, то он встанет раком
- нет паузы
- нет сброса
- нет контроля % отсчитанного времени
- и, самое главное, время в формате Time, которое очень неудобно выводить на панель оператора
Самописный таймер решает все эти задачи. Как вы уже догадались, там потребуется структура переменных для него и FC, вот они для самого простого таймера, который используется в драйвере выше:
И весь этот охреневший объём кода вам нужен в промышленном контроллере только для того, что проверить нажатие кнопочки или срабатывание маленького геркончика. Очень мило, не правда ли?)
Благодарю за терпение, забыл важную картинку и не смог отредактировать пост. В прошлом варианте поста были вопрос на тему, почему pulse_01s и pulse_1s имеют тип BOOL и как вообще эта мерзость должна работать. Не зря в начале было сказано об архитектуре, именно её мы затронем третьей части историй после кружечки кофе и предложим своё видение "как оно должно работать", что, естественно, не претендует на истину и даже не носит рекомендательного характера, потому что код каждого программиста индивидуален и уникален, но если мы сможем писать хотя бы так, чтобы сосед слева мог его прочитать - это уже хорошо.
Подробнее
ооооо Глобальные переменные Внутренние переменные Управление из алгоритма Управление с НМ1 Внешние сигналы Ограничение уровней доступа на НМ1 [а] Администратор 0 Оператор [п~] Пользователь Без ограничений Приоритетность команд Приоритет управляющих сигналов и режимов работы от наивысшего (7) к низшему (0 - автоматический режим). Аварийный Останов НЕТ - А Принудительный режим ДА НЕТ НАЛИЧИЕ ЛЮБОГО ИЗ УСЛОВИИ 1 1 НЕТ _А ]_ 1 1 | Сервисный выключатель по месту | Местный ! установки оборудования. : 1 1 режим ДА- В автоматическом режиме транслируется в команду ручного режима для обеспечения безударного перехода в ручной режим. -нет АВТО ПУСК ПУСК- -ДА—I Датчик Сервисное Технологическая Неисправность безопаности обслуживание блокировка оборудования — 6 — 5 4 -стоп- I------------------------------------ ; Цифровой код режима работы. -{Используется при построении иерархии команд в коде. : Режим управления с игнорированием ] аварий и блокировок. Используется для 1 отладки и тестирования оборудования. I Работает только с ручным режимом. Технологическая блокировка -1 алгоритмическая защита от запуска \ исполнительного механизма при риске "¡повреждения оборудования. Например. I включения насоса на закрытую задвижку. Местный режим - управление физическим выключателем с дверцы шкафа или пульта по месту установки обордования. _• Ручной режим - управление по команде 1 оператора с НМ1. ОСТАНОВ <- <■ -стоп—
1 2 3 4 5 € 7 3 3 10 11 12 13 14 15 16 17 13 13 20 21 22 23 24 TYPE system : STRUCT pulse_01s :BOOL, pulse_ls :BCDL, xON :BOOL, xOFF :BCXDL, Hand_DI :BOOL, Hand_D0 :BCDL, Hand_AI :BOOL, Hand_A0 :BOOL, Hand_M :BOOL, Hand_V :BOOL, Hand_Axis :BOOL, Hand_Any :BOOL, Alm_AI :BOOL, Alm_M :BCDL, Alm_V :BOOL, Alm_Axis :BCDL, Alm_Logick :BOOL, Alm_Any :BOOL, Start :BOOL, DTL :DTL; END_STRUCT END TYPE //импульс весом //импульс весом //всегда TRUE //всегда FALSfJ 100 мс 1 секунда
1 2 3 4 S € 7 3 s 10 11 12 TYPE CMD : STRUCT Start_A :BOOL, Start_M :BOOL. Hand :BOOL. Lock :BOOL. Force :BOOL. Vector :BOOL. ResetOp :BOOL. Reset :BOOL, END_ STRUCT END TYPE //управление в автоматическом режиме //управление в ручном режиме //режим управления, 0 - автоматический, 1 - ручной //технологическая блокировка //принудительный режим с игнорированием аварий //выбор направления, 0 - вперёд, 1 - назад //Reset Operation Time - сброс времени межсервисного интервала //сброс аварии конкретно этого устройств
FUNCTION drv_CMD : BOOL VAR_INPUT Alarm :BOOL; END_VAR VAR_IN_OUT CHD :CHD; END_VAR VAR END VAR // Опредление режима работы драйвера IF NOT All_Block THEN //He принудительный режим IF NOT CMD.Force THEN IF NOT CHD.Lock AND NOT Alarm THEN //Автоматический режим IF NOT CHD.Hand THEN CHD.Start_H := CHD.Start_A; END_IF; ELSE // Технологическая блокировка или авария CHD.Start_H := FALSE; END_IF; END_IF; ELSE // Аварийная блокировка CHD.StartJi := FALSE; END IF;
"«•оссл/и^рв1* "SseLCChjOt f eFE " n9Zjr*Lbu*m wrCAetualJ5p««dr — 1ч» a Lu ii On —Switch OÍÍ “ — P9ÜUT« .Actuad Sp«M» “Pebiule "Eag¿.ae* Huyauc_Or. — rK Oa' Prc5C5_3í>eed_?.síic "pl!_Pr**et_Sp*ed ? bsd " -eftohftd* SBC
2S 30 31 // Управлявшее слово drv_CMD(Alarm := stare.Alarm, CMD := CMD); IF CMD.Hand THEN sys.Hand_V := TRUE; END IF
ооооо Глобальные переменные Ограничение уровней доступа на НМ1 Сигнал Внутренние переменные [а] Администратор 0 У BOOL = неопределён Управление из алгоритма 0 Оператор 0 У BOOL = TRUE Управление с НМ1 (ГГ) Пользователь 0 У BOOL = FALSE Внешние сигналы Без ограничений 0 У Значение Дискретный вход Общий алгоритм обработки сигнала с дискретного входа ПЛК с учётом следующих сценариев: - выбор типа датчика N0/1^0 - фильтрация дребезга - задержка срабатывания
TYPE cfg_DI : STRUCT use_NC :BOOL; //выбор типа сигнала, 0 - нормально открытый, 1 - нормально закрытый T_ON :INT; //время фильтрации дребезга на включение сигнала T_OFF :INT; //время фильтрации дребезга на выключение сигнала END_STRUCT END TYPE
1 2 3 4 5 € 7 3 5 10 11 12 13 14 15 FUNCTION BLOCK drv DI VAR_INPÜT Signal ENDJVAR VÄR_IN_OUT :BOOL; CMD cfg END_VAR VÄR_OUTPUT :CHD; :cfg_DI; Result END_VÄR VAR :BOOL; Clock :ARRAY [0..1] OF Time_S; END VAR
1 2 3 4 5 € 7 3 5 10 11 12 13 14 15 16 17 13 13 20 21 22 23 24 25 2€ 27 23 23 30 31 32 33 34 35 3€ 37 33 35 40 41 42 43 44 45 4€ 47 43 45 50 51 52 53 54 55 5€ 57 // ДИСКРЕТНЫЙ ВХОД // Сигналы от датчиков, например: // - уровень // - проток // - etc //глобальные команды IF All_Auto THEN CMD.Hand := FALSE; END_IF; // Фильтрация сигнала от датчика - включение IF cfg.T_ON > 0 AND Signal THEN Clock[1].Start := TRUE; Clock[0].Start := FALSE; IF Clock[1].Q THEN CMD.Start_A := Signal; END_IF; ELSIF Signal THEN CMD.Start_A := TRUE; Clock[1].Start := FALSE; Clock[0].Start := FALSE; END_IF; // Фильтрация сигнала от датчика - отключение IF cfg.T_0FF > 0 AND NOT Signal THEN Clock[1].Start := FALSE; Clock[0].Start := TRUE; IF Clock[0].Q THEN CMD.Start_A := Signal; END_IF; ELSIF NOT Signal THEN CMD.Start_A := FALSE; Clock[1].Start := FALSE; Clock[0].Start := FALSE; END_IF; // Нормально замкнутный контакт IF cfg.use_NC THEN CMD.Start_A := NOT CMD.Start_A; END_IF; // Спредление режима работы драйвера (контроль входа не должен прерываться при нажатии аварийной остановки, //его нельзя заблокировать или сбросить аварию которой нет, поэтому используется упрощённый вариант управления) IF NOT CMD.Hand THEN CMD.Start_M := CMD.Start_A; END_IF;| IF CMD.Hand THEN sys.Hand_DI := TRUE; END_IF; // Результат работы блока Result := CMD.Start_M; // Бызов и обработка таймеров Clock[l].SP := cfg.T_0N; Clock[0].SP := cfg.T_0FF; drv_Time_S {Clock[0]); drv_Time_S (Clock[1]);
1 ТУРЕ Т1те_3 : 2 БТЮТСТ 3 Бгагг :ВООЬ; //управление таймером 4 0 :ВООЬ; //выход таймера 8 Раизе :ВООЬ; //пауза € Иезег :ВООЬ; //сброс текущего времени 7 БР :ШГ; //ЗебРотпб - уставка, время, которое надо отсчитать запуска таймер^ 8 АР :ЮТ; //ActualPoint - аутальное время, которое прошло с момента 5 ЕЖ)_5ТИиСТ 10 £N0 ТУРЕ
FUNCTION drv_Time_S : bool VAR_IN_OUT T :|liine_S; END_VAR VAR END VAR // Таймер с краткостью 1 секунда IF T.Start AND NOT T.Reset THEN // Работа таймера IF T.AP < T.SP THEN IF NOT T.Pause AND sys.pulse_ls THEN T.AP := T.AP + 1; END_IF; T.Q := FALSE; ELSE // Заданное время достигнуто T.Q := TRUE; END_IF; ELSE // Выключение и сброс таймера T.AP := 0; T.Q := FALSE; T.Reset := FALSE; END IF;
песочница,geek,Прикольные гаджеты. Научный, инженерный и айтишный юмор,АСУ ТП,программирование,реактор образовательный,длиннопост