Змейка на ПЛК. Наш ответ Сименсу

Здравствуйте.

Недавно мне прислали ссылку на статью, где был показан пример реализации простой и в то же время культовой игры «Змейка» в контроллере семейства Siemens s7-300. И я подумал: все знают о таких монстрах, как Siemens, ABB и т.д. Но современные отечественные разработки остаются в тени.

В этой статье я покажу, как за полчаса реализовать алгоритм игры «Змейка» на российской АСУ ТП «КВИНТ 7», разработанной в НИИТеплоприборе. И для большего интереса игра будет целиком реализована на языке технологического программирования FBD, которому уделяется незаслуженно мало внимания.

Итак, начнем:

Наша задача — написать игру «Змейка» за полчаса.
Для реализации задумки нам понадобятся:
  • Компьютер с установленным ПО КВИНТа 7
  • Контроллер Р-400
  • Патчкорд для связи контроллера с компьютером
  • Лицензия на оперативные средства (для игры) и средства проектирования (чтобы написать саму игру) КВИНТа 7

Небольшое пояснение
Хотя КВИНТ 7 и обладает полной виртуализацией всей АСУ ТП, и сколь угодно сложный проект можно реализовать на одном компьютере, но мы будем делать все по взрослому.


Сам алгоритм игры можно разделить на несколько больших блоков:
  • Генератор (для формирования тактовой частоты)
  • Управление (для того, чтобы можно было играть)
  • Голова змейки (тот элемент, которым мы будем управлять)
  • Хвост змейки (без него игра теряет смысл, ведь цель игры собрать как можно более длинный хвост)
  • Еда (те ячейки, которые змейка будет «есть» и увеличивать длину хвоста)
  • Дополнительные проверки (позволяют сделать игру удобнее и логичнее)


Генератор

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

Принцип действия следующий. На выходе алгоблока "И1 " каждый цикл меняется логическое значение 1<->0.
Дальше на алгоблоке "Слож1" идет подсчет количества единиц, которое сравнивается с заданной нами величиной (в данном примере 50 т.к. время цикла стоит 10 мсек, то за 1 секунду счетчик насчитает 50 включений).
При равенстве показаний счетчика и нашей заданной величины отматываем назад накрученное на счетчике значение (алгоритм "Выч1") и выдаем тактовый импульс длительностью 10 мсек (1 цикл контроллера) с выхода "=" алгоритма "Сравн1". Последний алгоритм выбора служит для обнуления счетчика по сигналу сброса.
Для внимательных читателей
Внимательный читатель наверняка заметил, что при первом включении счетчик отсчитает 50 импульсов не за 1 секунду, а за 980 мсек. потому, что мы ведем подсчет по переднему фронту, а не по заднему. Но при этом все остальные тактовые импульсы он будет выдавать ровно через 1 секунду. Конечно исправить эту ситуацию очень просто, однако я намеренно оставил такую версию реализации генератора именно для этого вопроса.

После того, как генератор сделан, свернем его в компактный макрос и оставим пока дожидаться своего часа.


Управление

Тут все предельно просто. У нас уже есть генератор. Змейка непрерывно ползет по полю, меняя лишь направление своего движения. Само поле представляет собой двумерный массив, каждая ячейка которого задается координатами Х и У. При движении вправо координата Х увеличивается на единицу с каждым шагом, а координата У не меняется. При движении влево координата Х уменьшается при постоянной координате У. Для движении вверх-вниз аналогично, только меняться будет координата У, а координата Х будет константой.
Следовательно ставим два реверсивных счетчика (по одному на каждую координату), которые в зависимости от команды одновременно с приходом нового тактового импульса будут либо добавлять, либо отнимать единицу от текущей координаты головы змейки. В итоге получаем следующую схему.

Небольшое пояснение по поводу избыточности схемы
Опять таки внимательные читатели могут задать вопрос — почему данная схема реализована не оптимальным способом, а с задействованием дополнительных ненужных алгоритмов. Все очень просто. Эти, пока бесполезные, алгоритмы включены в схему заранее и понадобятся нам на этапе «причесывания» программы. Чтобы лишний раз не возвращаться и не править исходную конструкцию, лучше ввести их заранее. Для чего они нужны расскажу в спойлере ниже

Принцип действия
Алгоритм "РучСелектор1" служит для ввода команд змейки. Он имеет 4 выхода которые соответствуют четырем направлениям движения (вправо-влево-вверх-вниз). Далее стоит еще один алгоритм селектирования "РучСелектор2". Т.к. команды управления могут поступать в любой момент времени, используем его для синхронизации команд управления с тактовыми импульсами. т.е. будем с первого селектора передавать значение на второй только по приходу тактового импульса. На самом деле очевидно, что данный алгоритм избыточен, т.к. мы проводим синхронизацию с тактовыми импульсами непосредственно на счетчиках (это видно из схемы), однако данный алгоритм был оставлен мной в программе исключительно в целях отладки. Далее стоят 4 алгоритма "И" для синхронизации счетчиков с тактовыми импульсами, о чем я написал выше. По соответствующему сигналу с алгоритмов "И" выбирается увеличение или уменьшение нужной нам координаты на единицу. Т.к. алгоритм селектирования работает по схеме «1 из n», следовательно в каждый момент времени логическая единица будет только на одном из выходов селектора. А это значит, что мы можем просто сложить приращение соответствующей координаты и полученное значение отправить на реверсивный счетчик данной координаты, реализованный как и в случае с генератором на алгоритме суммирования с обратной связью.

На этом схема управления завершена. Пока не будем сворачивать ее в макрос т.к. мы ее еще немного подправим на последнем этапе.

Голова и хвост змейки

Как говорилось ранее, голова змейки это ячейка массива с определенными координатами Х и У. Но пока у нас этого массива нет. Кроме того, заглянув немного в будущее (а именно в создание хвоста змейки), становится понятно, что эта ячейка массива должна быть с памятью. Самый простой логический элемент с памятью это триггер. Их существует много разновидностей, каждая из которых применяется для своих целей. В данном примере мы отдадим предпочтение «RS-триггеру». Для добавления красоты в игру (например раскрашивать «змейку» и «еду» разными цветами, выделять цветом голову змейки и т.д.) логической ячейки памяти недостаточно, ведь нам придется хранить в ней уже не 2, а как минимум 4 состояния. Т.е. придется делать ячейку памяти либо из нескольких триггеров, либо в другом формате — например хранить в ячейке целое число. Но это несколько усложнит программу, а одна из целей всего этого действа — уложиться в полчаса.
Сделаем для нашей змейки поле 20 на 20. Т.е. нам понадобится 400 триггеров. Проблема решается просто. делаем одну ячейку с одним триггером и сворачиваем ее в макрос. Делаем двадцать макросов ячеек и сворачиваем их в новый, большой макрос. Ставим двадцать больших макросов и получаем 400 ячеек памяти.
Сам макрос ячейки будет иметь 5 входов и 4 выхода.

Входы:
  1. Вход (логический признак, находится ли «голова» змейки в этой ячейке памяти)
  2. Такт (тактовые импульсы для синхронизации с главным генератором)
  3. Длина (длина хвоста змейки)
  4. Сброс (логический признак нажатия на кнопку сброс для запуска новой игры)
  5. Еда (логический признак, находится ли «еда» для змейки в этой ячейки)

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

Выходы:
  1. Выход (логический признак, закрашивать эту ячейку на поле или нет)
  2. GameOver (логический признак конца игры если змейка наступила себе на хвост)
  3. Ням_ням (логический признак того, что змейка наступила на ячейку с едой и нужно генерировать новую еду для змейки)
  4. Еще_попытка (логический признак того, что выброшенная случайным образом ячейка с едой попала на поле, уже занятое змейкой и нужно заново генерировать координаты новой случайной клетки)

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

Принцип работы алгоритма ячейки
На вход поступает сигнал, что змея наступила на эту ячейку. Их него выделяется импульс длиной 10 мсек. и по алгоритму "И" складывается с тактовым импульсом. Если оба условия выполнены, т.е. в одном цикле пришел тактовый импульс и сигнал о том, что змея появилась в этой ячейке, то триггер "RS1" взводится. Это главный триггер этой ячейки памяти. В принципе для обработки головы змеи достаточно сбрасывать триггер следующим тактовым сигналом. Все остальное, это обработка хвоста, и разных ситуаций.

1. обработка ситуации «змея наступила себе на хвост». тут все просто за исключением маленькой тонкости. Мы складываем по И входной сигнал и состояние триггера. Если оба сигнала равны единице, то пришел голова наступила на клетку, занятую хвостом. Это нарушение правил и мы выдаем сигнал «GameOver» по которому игра заканчивается. Тонкость заключается в том, что сигнал с триггера нужно брать не текущий, а в предыдущем цикле. Для этого здесь использована обратная связь (отображается пунктирной линией).

2. обработка «еды». тут тоже все просто. Получаем сигнал о том, что в этой клетке должна находится еда. Складываем его по И с главным триггером. Если оба сигнала единицы, значит клетка еды выпала на поле, уже занятое змеей. В этом случае выдаем на выход сигнал «Еще_попытка», который дает команду генерировать новые случайные координаты.

3. обработка ситуации «змей съела еду». Если условия 2 у нас не выполнились, т.е. клетка с едой попала на свободное поле, то взводится триггер еды "RS2". Далее мы просто ждем сигнала о том, что змея наступила на эту клетку. Как только такой сигнал получен — сбрасываем триггер еды (еда съедена) и выдаем сигнал «Ням-ням» по которому увеличиваем длину змеи на единицу и одновременно посылаем команду генерировать новые координаты для еды.

4. «обработка хвоста». Идея заключается в том, чтобы хранить состояние ячейки до тех пор, пока весь хвост змеи не уползет с нее. Для этого ставим счетчик (алгоритм "Слож1") и считаем сколько тактовых импульсов у нас пришло за то время, как главный триггер был взведен. Сравниваем это значение с длиной змеи и как только они становятся равны — сбрасываем триггер. Для простоты и отладки здесь сравнивается не значение счетчика с длиной. А разница между ними с нулем. Один из моментов, на который стоит обратить внимание, это то, что из после вычитания из длины показаний счетчика мы к получившейся разнице добавляем единицу. Это сделано для того, чтобы компенсировать подсчет первого такта, который происходит одновременно с попаданием змеи на клетку.


«Еда»


Здесь самая большая проблема заключается в получении случайного числа. Т.к. команды «random» у нас нет. В интернете есть куча способов генерации псевдослучайного числа. В данном примере реализован один из них. Берется некое большое меняющееся число — UpTime контроллера в секундах. И делится на постоянно меняющийся делитель (реализован на интеграторе). Берется остаток от частного и считается, что это и есть случайное число. На самом деле конечно оно никакое не случайное, и с первого взгляда можно заметить, что вероятность выпадения маленьких чисел больше, чем больших именно за счет «гуляния» делителя в некой области, но если взять допустим не целые от остатка, а скажем сотые или тысячные от остатка. Привести их к диапазону 1-20, то получим число, значение которого уже будет очень похоже на случайную величину.

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

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

1. Проверка на выход за границы поля.
Тут все зависит от реализации игры. Вспоминаем, что на втором этапе «Управление» у нас был счетчик, в котором находились координаты головы змейки по Х и по У. Просто сравниваем эти координаты с 0 и с 21 (т.к. поле у нас размером 20 на 20 и координатами 1-20). Если любое из условий выполнилось, то выставляем признак «Конец игры» (которые должен складываться по ИЛИ с нашим признаком «GameOver»). Можно сделать как в игре «Pac-man» чтобы при выползании змеи за пределы поля она бы появлялась с другой стороны. Для этого достаточно обнулять соответствующую координату.

2. Проверка на корректность команды.
Наша змея не может мгновенно развернуться на 180 градусов. Следовательно добавим небольшую проверку, которая не позволит подавать команды противоположной направленности друг за другом.

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

3. Команды «Старт» и «Сброс».
Сделаны для удобства. Команда «Старт» взводит триггер, который подает сигнал на наш генератор. Команда «Сброс» сбрасывает этот триггер, останавливая генератор. Одновременно сброс сбрасывает все ячейки памяти и данные во всех счетчиках.
Итоговая программа. рисунок очень большой


Делаем интерфейс и играем

От отведенного нам времени на игру осталось пара минут, которых как раз хватит чтобы прикрутить графический интерфейс к игре.
Рисуем поле 20х20 из квадратиков. Каждый квадратик соответствует своей ячейке памяти. Задаем в свойствах чтобы при значении 0 соответствующей ячейки памяти квадратик был серый, а при 1 — зеленый (или любые понравившиеся вам цвета).

Поиском картинок в интернете находим красивую рамочку для поля и какой нибудь рисунок змеи, подходящий по смыслу.
Рядом с полем помещаем кнопки управления и привязываем управление мышкой и с клавиатуры клавишами WASD.
Неподалеку размещаем кнопки «Старт» и «Сброс». И большую надпись «GameOver», которая будет появляться при проигрыше.

Компилируем и загружаем программу в контроллер. Запускаем станцию оперативного управления и играем.
Вот такая мнемосхема будет у оператора


Подводим итоги

В данной статье мы немного рассмотрели принципы программирования на языке FBD для контроллеров ПТК КВИНТ 7. Не прилагая больших усилий мы реализовали игру «Змейка». Т.к. язык FBD ориентирован по большей части на технологов, то разобраться в принципах работы и начать писать свои программы может любой человек, знакомый с элементарной логикой (например логикой работы алгоритмов И, ИЛИ, триггеров и счетчиков). При этом не нужно иметь огромный опыт в программировании или схемотехнике.

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

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

Разумеется путем нехитрых манипуляций можно улучшить программу, например увеличивая со временем скорость движения змейки. Или ввести параметр «голод», по которому змейка, не получающая в течение определенного интервала времени не получает новую еду, ускоряется. Кроме того, внимательные читатели могли обратить внимание на функцию запоминания длины змейки при переходе ее в новую клетку (а тут специально заложена небольшая погрешность для привлечения внимания) и подумать, что это неправильно т.к. обычно змейка удлиняется с головы, а не с хвоста. Сделать можно все, что угодно. Это лишь вопрос желания и времени.

Спасибо всем, дочитавшим до конца. Надеюсь это было интересно.
Поделиться публикацией

Комментарии 24

    +10
    Мсье знает толк.
      0
      Зачётно, хотя функциональная схема выглядит ужасающей мешаниной линий и блоков. Все-таки декомпозиция — это альфа и омега любого разработчика.
      Еще интересно было бы увидеть статистику по технологической программе: сколько функциональных блоков, какой размер конечного кода, какое время цикла выполнения в контроллере и т.п. Если уж заниматься писеметрией, то с цифрами в руках.
        +3
        Вот картинка со статистикой.
        Скрытый текст
        +4
        АСУТП рулит!!! Инженеры тоже умеют делать крутые штуки!!!
          +3
          Теперь нужна змейка полностью на LAD'е.
            +2
            Не, использовать интерфейс АСУТП — это «читерство», я считаю. Надо ставить модули ввода-вывода в достаточном количестве, четыреста выходов завести на светодиоды, а кнопки — на входы — и вот тогда получится действительно змейка на ПЛК, а компьютер вообще можно будет выключить.
              0
              >четыреста выходов завести на светодиоды

              жесть какая, панель оператора нужна для графики.
                +6
                Только хардкор! Впрочем настоящий хардкор — это вот на таком поле в змейку поиграть при пусконаладке:


                (фотку у Каганова стащил)
                  +3
                  Это не поле светодиодов. Это кассеты со сборками ТВЭЛов реактора РБМК.
                  +1
                  Вот сюда надо змейку:
                  image
                  0
                  На самом деле это сделать гораздо проще, чем написать программу.
                  Единственная загвоздка тут в 400 диодов и кто будет их распаивать и привинчивать проводки к модулям ЦДП.

                  Но вы же понимаете, что без АРМа управлять змейкой все равно не получится. Так что чисто на контроллер перейти не удастся.
                  Хотя с другой стороны у контроллера есть 4 кнопки, которые в теории можно привязать к командам.

                  Или кстати можно 4 кнопки завести на модули ДЦП и с них подавать команды. Тогда остается проблема только с распайкой.
                    0
                    Да, я это и имел ввиду — кнопки завести на входы. Если, кстати, взять ПЛК типа GE Fanuc — там индикаторы выходов сгруппированы в блоки светодиодов (8x4, кажется) — тогда можно сами ДЦП модули в качестве индикаторов использовать.
                      0
                      Т.к. модули маленькие, то места для светодиодов не нашлось. Да и сами модули имеют всего лишь 16 дискретных выходов.
                      Так что без паяльника и отвертки тут не обойтись. Но это уже не столь интересно, т.к. все уже готово, нужно только припаять и прикрутить 808 проводков.
                  +1
                  Здесь должна быть шутка про змейку для Чака Норриса, но масштаб АСУ ТП «КВИНТ 7» заворожил и лишил чувства юмора. Автор крут.
                    +2
                    Какая тонкая ирония.
                    Соглашусь с вами, действительно получился перебор.
                    Но это только потому, что реально обидно, когда все знают «буржуйские» системы и превозносят их до небес, в то время как отечественные разработки порой превосходят иностранные аналоги, но о них знает лишь очень и очень узкий круг специалистов.
                      +2
                      Действительно о них ничего не слышал. А чем они Сименс превосходят? Хотел сам посмотреть, пошел по ссылкам а там даже скриншотов нет. :-(
                        0
                        В двух словах не расскажешь.
                        Хотел кинуть ссылку на брошюру с сайта НИИТеплоприбора, но потом увидел какая она ужасная в плане оформления.
                        Но если интересно, более подробную информацию можно найти тут.
                    +1
                    Очень приятно увидеть ответную статью, даже был в небольшом, но приятном шоке =)

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

                    А так — у вас FBD почему то уже больше напоминает CFC — язык непрерывных схем. Я все хочу до него добраться, но не получется.

                    Может, напишете статью об истории вашей АСУТП? Было бы интересно почитать!

                    UPD: Оказывается, вы правоприемники Ремконта! Приятный сюрприз — изучал их в университете, наши преподаватели в его навесном шкафу коньяк прятали =)
                      +1
                      UPD: Оказывается, вы правоприемники Ремиконта! Приятный сюрприз — изучал их в университете, наши преподаватели в его навесном шкафу коньяк прятали =)
                      Небось коньяк «Квинт».

                      Если честно, то на придумывание схемы хвоста ушло конечно же больше времени (где то в обед мне прислали ссылку на ваш пост и взяли на «слабо», и в течение вечера пока было свободное время я прикидывал как это можно сделать попроще). Уж очень не хотелось ставить 400 триггеров. Но перебирая в уме различные варианты все равно упирался в то, что без кучи запоминающих элементов не обойтись (была перспективная идея хранить не целиком хвост змеи в памяти, а только координаты точек перегиба, но в пределе длина змеи может составлять 400 клеток и точек перегиба может быть столько же, а т.к. динамической памяти в программе нет, то этот вариант отпал).
                      Ну а когда решение было принято, то набросать программу было уже делом техники, ведь все блоки сделаны предельно примитивно, а поддержка макросов позволяет за пару минут сделать 400 ячеек памяти. Так что фактически я написал один элемент и свернул в макрос «Ячейка_памяти». Затем сделал макрос «Упаковка» в который поместил 20 макросов «Ячейка_памяти», а затем поставил в задачу 20 макросов «Упаковка» и все готово.
                        +1
                        Это и не FBD. Там есть поддержка типов данных, в том числе структур. Так же данные делятся на потенциальные (мгновенное значение) и команды (буферизуемые значения). Так же имеется встроенная поддержка качества значений. Можно проводить обратные связи, они выделяются зеброй. При этом в качестве начальных значений берутся значения типа данных по умолчанию (прописывается в самом типе).
                        В общем там вагон всего, еще я планировал впилить туда контроль порядка выполнения в виде связей и поддержку условий. Тогда был бы полный Франкенштейн из FBD, SFC и обычных блок-схем.
                          0
                          Это в таком виде и есть CFC от сименса) радует продуманность
                            0
                            Даже то, что реализовано на данный момент, полностью покрывает все задачи в АСУ ТП. Ну а чего в стандартных схемах нет, можно добавить в виде своих плюшек макросами.
                            Хотя слова «поддержка условий» звучат заманчиво.

                            PS. Спорим, что я с трех попыток угадаю кто написал комментарий выше? А скорее всего даже с одной.
                              0
                              С одной :)
                              А почему на 2м скрине значения «нет» подозрительно голубые?
                                0
                                Все предельно просто.
                                Т.к. я сначала написал прогу, а потом уже писал текст и вырезал картинки, то картинку с генератором я вырезал из программы со связями. А когда писал текст, то по ходу рассказа он должен был идти отдельно. Мне было очень лень запускать пилон, ставить отдельно макрос и делать новую картинку, поэтому я просто в паинтбраше стер входящие на алгоблок связи и дописал значения по умолчанию веселым голубым цветом. Если присмотреться, то там даже его номер (восьмой) остался как в главной программе.

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

                        Самое читаемое