Соловей!.. Ведь, слушайте, ведь вот пичуга! Ну, смотреть не на что!.. Ну, мелочь пузатая!.. А ведь как, подлец, природу украшал!.. Что делал, мерзавец!.. Э-тю-тю-тю-тю-тю-тю, тю-тю-тю!..
Райкин А. Люди и манекены
Рассмотрим алгоритм, который заимствован из несложного проекта системы управления прессом. В сам проект вникать не будем, а рассмотрим лишь его небольшую и, пожалуй, самую простую часть – управление валками. На пульте управления есть кнопка «Валки» (на рис. 1 сигнал X6), при нажатии на которую посылается сигнал, который то прижимает, то отпускает валки. Преобразуем алгоритм управления валками в автоматную форму и посмотрим, что из этого получится.
Код исходного проекта, реализующий поставленную задачу, приведен на рис. 1. Верхняя цепь данного фрагмента задает текущий режим системы управления, а нижняя - собственно управление валками.
Для начала в целях структуризации нового проекта создадим функциональный блок (ФБ). Он представлен на рис. 2. Отталкиваясь от исходного кода, определим модель его поведения в форме конечного автомата. Он показан на рис. 3. Код реализации этого автомата на языке LD демонстрирует рис. 4. Создан он в рамках подхода к реализации конечных автоматов, который описан в предыдущих статьях автора [1].
Ну и что? – скажет обычный нормальный программист. Была всего одна цепь, а теперь шесть!? Ради чего был затеян «вертеж»? Попробуем в этом, как раз, и разобраться. Для этого приведем исходный алгоритм управления валками (см. рис. 1) к эквивалентной автоматной форме. Именно это позволит провести объективное сравнение решений.
На первом этапе нарисуем блок-схему, которая, если следовать ГОСТ-у (его, заметим, пока не отменили), входит в перечень документов на программу. Блок-схема, соответствующая разбираемому коду, представлена на рис. 5а. Ну, и что? – вторит упрямо все тот же «нормальный программист». Зачем она (блок-схема), когда алгоритм программы в голове, а «изваять» код программы, как в данном случае, буквально минутное дело. Пусть блок-схемы рисуют те, кто не умеет программировать и/или только этому учится. И с этим сложно пока спорить.
Но мы и не будем этого делать, а … продолжим дальше. Итак, блок-схема, которая представлена на рис. 5а, это предположительно то, что в голове программиста. А код, который приведен на рис. 1, это то, что у него «на языке». Но, как мы далее убедимся, он не совсем точно отражает полет мысли программиста, скрывая некоторые нюансы поведения и проектирования программы.
Во-первых, программист использует промежуточное понятие «режим». На наш взгляд это лишнее. Но не это самая большая тайна. Во-вторых, он, вероятнее всего, «забудет» сказать, что текущее значение режима, определяемое значением переменной D10, в конечном итоге почему-то зависит от переменной D12. И это, заметим, происходит явно за границами приведенного кода. Вроде бы, мелочь, но мелочь, которую необходимо пояснить и обязательно знать. Но мы еще упустили механизм, который неявным образом превращает алгоритм из чисто последовательного в циклический. Но и это еще не все. А зачем переменная M11?... Ответ на этот вопрос мы получим ниже.
Пока же продолжим попытки нахождения эквивалентного блок-схеме автомата. И это уже легко сделать, используя известную процедуру преобразования любой граф-схемы алгоритма (ГСА) в эквивалентный ей автомат (подробнее о процедуре разметки ГСА см. [2]). Результат приведен на рис. 5б. Его можно сделать компактнее, что и демонстрирует рис. 5в. Хотя… нечто подобное можно проделать и с блок-схемой. Но, с другой стороны, автомат фактически сразу выявляет неполноту исходной блок-схемы. Хотя бы потому, что управляющие автоматы должны иметь, как правило, циклическую форму.
Имея эквивалентный автомат, уже можно объективно сравнить идею, заложенную в исходном алгоритме, с идеей, представленной автоматом на рис. 3. Последний, как минимум, свободен от «недомолвок» исходного алгоритма. При этом по виду ФБ (рис. 2) видно, что управление идет напрямую от кнопки «Валки». Т.е. не используется «режим». Но важнее другое. Алгоритм на рис. 3 цикличен изначально. Нет флага M11, который в исходном алгоритме реализует некие вспомогательные функции. Да и к чему он, если его роль легко исполняют состояния автомата? В конце концов, автомат на рис.3 явно компактнее и возможно более понятен, чем на рис. 5б (подробнее на эту тему см. далее).
Наблюдая текущее состояние автомата на рис. 3, можно судить об активности процесса управления: если автомат находится в исходном состоянии 0 – он "спит", ожидая команды от кнопки, в противном случае - реализует функцию управления. Можно даже сказать, что он при этом делает - зажимает или освобождает валки. Хотя, продолжая минимизировать автомат, его «рабочие» состояния 1, 2 можно вполне "склеить".
Засады ПЛК
Пополним рубрику «засады ПЛК», начало которой положено предыдущей статьей [3]. На панели оператора есть два поля – поле «План», которое отражает значение переменной D20, и поле «Факт» - значение переменной D22. Каждая штамповка уменьшает на единицу значение поля «План» и настолько же увеличивает поле «Факт». По завершении задания в первом поле будет 0, во втором – значение, первоначально занесенное в поле «План». В целях минимизации действий оператора хотелось бы кнопкой «Сброс» очищать поле «Факт», одновременно перенося его значение в поле «План».
Нет ничего проще - см. код на рис. 7. Код исполняется слева направо и сверху вниз и это здесь учтено. Однако, … нажатие на кнопку «Сброс» приводит к одновременному очищению обоих полей. В чем дело?
Теперь-то, когда понятно в чем дело, то проблема, как говорится, "не стоит и выеденного яйца». Если забыть, что ПЛК неявно подразумевает цикличность работы, то это, как в данном случае, вполне может стать "засадой". Дело в том, что нажатие на кнопку «Сброс» приводит к многократному, на время удержания кнопки, циклическому исполнению кода. В результате первый цикл очищает текущее значение переменной D22, а все последующие заносят его в D20. Простейший выход – запускать код передним фронтом сигнала кнопки «Сброс».
Только не спешите утверждать, что это очевидно. Для постоянно программирующего для ПЛК это, скорее всего, так и есть, но только не для того, кто только недавно оторвался от «обычного языка программирования», в котором нет и в помине понятия фронтов переменных. И то, что почти неведомо обычному программисту, для программиста на ПЛК – обыденное дело.
Лично я, даже зная о фронтах, предпочитаю их не использовать. А все для того, чтобы не скрывать действия, которые легко пропустить, анализируя код (что и произошло). Есть и другая причина - переносимость кода. Для некоторых типов ПЛК, например, внутри ФБ использование фронтов сигналов запрещено. Поэтому лучше дождаться сначала установки сигнала, а затем его сброса. Правда, понадобится промежуточное состояние, что немного увеличит объем кода, но зато такой код будет верно отражать реальный процесс исполнения алгоритма. Собственно во многом именно об этом и шел разговор выше.
Выводы
Используйте автоматное программирование. Технически для этого нужно совсем немного: 1) завести два массива для состояний - текущих и теневых, 2) вставить копирование теневого массива состояний в массив текущих состояний и 3) использовать простой и прозрачный метод кодирования автоматов. Правда, надо предупредить, что это будет все же не стопроцентное автоматное программирование, но ... «лиха беда – начало». Процентов, скажем, девяносто - это тоже неплохо.
Наиболее сложное в этом деле – научиться «мыслить автоматно». А, используя выше сказанное, этого уже не избежать. Для освоения технологии автоматного программирования необходима постоянная, непрерывная практика рисования автоматов. Просто держать их в голове – не вариант. Можно понять тех, кто, имея опыт, не отобразит последние корректировки графа автомата (а кто не слаб?), но исходную модель лучше рисовать в обязательном порядке. Особенно на начальных этапах освоения технологии. И, конечно, желательно освоить удобный графический редактор. У меня, напомню, это Visio. Он удобен для рисования графов автоматов, входит в состав “офиса» и с ним нет проблем от слова совсем. При этом, если верить рекламе, есть и бесплатные аналоги данного редактора.
Так что в путь... Дорога проторена. Использование же других "тропинок", скорее всего, заведет не туда. Осталось пожелать удачи, а я со своей стороны, чем смогу, тем и помогу. Но, как мне представляется, информация о том, куда и как «топтать тропу», дана в полном объеме. И пусть мы в рассматриваемом случае (язык LD) имеем технологию в нескольку усеченном варианте, но, зато, в таком виде она может быть реализована на любом языке программирования. Здесь важнее будет не кодирование, а накопление опыта настоящего параллельного программирования и использования правильных автоматов. И компасом в этом деле должна служить точная наука, а не некие отрывочные знания об автоматах или ни чем не подкрепленная вера в некие околонаучные автоматные практики.
PS
Обмен опытом
Можно автомат на рис. 5б еще уменьшить. Результат такой минимизации показан на рис. 5г, а его код – на рис. 7. Сравним и его с исходным кодом, чтобы еще раз вникнуть в суть отличий. Однако, если убрать операторы работы с состояниями, а две цепи кода на рис. 8 «слить» в одну, то получим фактически код на рис. 1. И сразу возникает вопрос – зачем преобразовывать код в автомат, если отличий столь мало?
Но отличия все же есть. Состояния. С их помощью алгоритм естественным образом разбивается на такты, а за состояниями можно наблюдать. Отдельный такт, это отрезок времени, на котором организуется параллельное исполнение автоматов. В рамках обычного программирования такого попросту нет. Обычный проект – это один огромный автомат без памяти (т.е. с одним состоянием), у которого переходы в форме дуг, представленных цепями проекта. И если в исходном проекте автоматов не было вовсе, то в новом их стало четыре. А это четыре параллельных взаимодействующих процесса. Если кратко, то это уже, какая ни есть, но сеть автоматов, которая работает по своим автоматным законам. Их ПЛК ни как не учитывает. А если их не учитывать, то результат будет непредсказуемым. Как в современном параллельном программировании [4].
Проверить сказанное просто. Достаточно удалить все операторы, связанные с состояниями, из кода на рис. 8. Результат такой «вивисекции» представлен на рис. 9. Тестирование показало, что результаты работы после этого не изменились. Таким образом, путем применения обратных преобразований, мы убедились в правильности проведенной конвертации программы. Ну и, конечно, в том, что обычная программа в ПЛК - это всего лишь один большой-пребольшой автомат без памяти.
На этом же коде можно еще раз рассмотреть работу с фронтами сигналов. Если убрать фронт у переменной bInЗажимРазжим, то программа превратится в не работающую программу. Чтобы заставить ее правильно работать и при этом не использовать фронт, необходимо в начало следующей цепи вставить условие – ложное значение упомянутой переменной.
Литература
Вот, как просто! Автоматы в деле. Для ПЛК фирмы DELTA. [Электронный ресурс], Режим доступа: https://habr.com/ru/post/685098/, свободный. (дата обращения 18.10.2022)
Баранов С.И. Синтез микропрограммных автоматов. – Л.: Энергия, 1979. – 232с.
Автоматы в деле. Штабелер. Засады ПЛК. [Электронный ресурс], Режим доступа: https://habr.com/ru/post/685098/, свободный. (дата обращения 18.10.2022)
Эдвард А. Ли. Проблемы с потоками. [Электронный ресурс], Режим доступа: http://www.softcraft.ru/parallel/pwt/, свободный. (дата обращения 18.10.2022)