Язык функциональных блоковых диаграмм (FBD) и его применение
Одним из популярных языков программирования ПЛК — программируемых логических контроллеров, является графический язык функциональных блоковых диаграмм FBD — Function Block Diagram. Этот язык, наряду с другими языками стандарта МЭК 61131-3, такими как например язык лестничной логики (LD), использует в своей архитектуре подобие электронной схемы.
Написанная на данном языке программа для контроллера состоит из некого списка цепей, которые одна за другой выполняются сверху — вниз. Кроме того, здесь имеется возможность присвоения отдельным цепям меток, в этом случае станет доступно использование инструкций перехода на метку, дабы изменять последовательность исполнения цепей, и создавать условия и циклы.

Таким образом, программа, написанная на графическом языке FBD, представляет собой набор связанных друг с другом функциональных блоков, выходы и входы которых соединены линиями связи. Линии связи отражают определенные программные переменные, через которые происходит обмен данными от блока — к блоку.
Отдельный блок несет на себе конкретную функцию (логическое «и», «не», счетчик и т. д.), при этом один блок может иметь несколько выходов и входов. Изначально значения переменных задаются константами или со специальных входов, а выходы их связываются дальше с другими переменными программы или с выходами ПЛК.
На рисунке приведен пример программы, написанной на языке функциональных блоковых диаграмм FBD. Как видите, такое изображение программы очень наглядно отражает алгоритм, что и делает данный язык довольно простым и удобным для разработки ПО для ПЛК.
В процессе программирования на языке FBD применяются как стандартные блоки из библиотек, так и блоки, сами написанные на FBD или на иных языках стандарта МЭК 61131-3. Блок представляет собой элемент программы, своего рода подпрограмму, функциональный блок или функцию (логическое «НЕ», «ИЛИ», «И», таймер, счетчик, триггер, математическая операция, обработка аналогового сигнала и т. д.).
Из таких блоков графически составляются выражения, образующие цепи: к выходу одного блока присоединяется следующий блок, далее — еще блок, и так образуются цепи. По ходу цепи порядок выполнения блоков соответствует порядку их соединения, а результат выполнения цепи либо подается на выход ПЛК, либо записывается в какую-то внутреннюю переменную.

Рассмотрим кусочек программы, написанной на языке FBD: В умножить на 4, затем поделить на А, и записать результат в переменную result. В псевдокоде это будет выглядеть так: result := B*4/A. Возможно также добавление к блокам специальных управляющих входов EN и выходов ENO, для управления вызовами отдельных блоков: логический ноль, поданный на вход EN, запретит вызов данного блока, а выход ENO в случае ошибки сообщит о ней, и прервет тем самым выполнение цепи до конца.
Как видите, язык FBD до крайности нагляден, удобен, и потому прост в освоении даже специалистами — прикладниками, не имеющими специальной подготовки по информатике. Код выполняется последовательно, структура команд внутри кода проста, поэтому программа транслируется очень быстро и задача выполняется надежно. Используя FBD, можно решать очень сложные задачи на ПЛК.

Есть различные модификации языка программирования FBD, отличающиеся наличием тех или иных ограничений или расширений.
Например, существует разновидность FBC, допускающая применение только чистых функций с одним выходом без промежуточных переменных — модификация для функционального программирования.
Или модификация CFC (Continuous Function Chart), позволяющая установить порядок выполнения диаграмм не просто последовательной цепочкой, а по усмотрению разработчика ПО. С CFC разработчик получает больше свободы, хотя код получается более длинным.
Пример языка FBD в STEP 7:

Язык функциональных блок-схем FBD, использующий графический стиль потока данных, является наиболее широко используемым языком программирования ПЛК.
Ниже показан результат опроса в группе Программируемые контроллеры. Вопрос звучал следующим образом: » Какие языки программирования ПЛК вы используете чаще всего?». При ответе на вопрос допускалась возможность множественного выбора.
По результатам опроса язык FBD напбрал 52,14%, а его разновидность СFC — 10,71%, в сумме два языка набрали 62,85%, что значительно больше, чем другие языки программирования ПЛК. У ближайшего конкурента языка LD — 35%.

Преимущество языка FBD перед языком релейных диаграмм LD возрастает с увеличением сложности алгоритма управления. Алгоритм управления написанный в FBD, позволяет легче, чем в LD, отслеживать изменения в программе и искать возможные ошибки во время отладки.
В целом, принципы работы обоих языков очень похожи. Своей популярностью они обязаны прозрачности обозначений, благодаря которой код относительно небольших программ можно быстро понять, даже без особых знаний программирования.
Смотрите также по этой теме:
Курс по программированию контроллеров:
Structured Text
Представляем книгу по Structured Text (ST) МЭК 61131-3. Автор — Сергей Романов
Программирование микроконтроллеров AVR
на графическом языке функциональных блоков FBD
Язык FBD очень прост в освоении и удобен как для схемотехников, не имеющих специальной подготовки в области программирования, так и для начинающих осваивать мир электроники, автоматики и робототехники. Весь процесс программирования в FBD-системе заключается в соединении линиями связи готовых элементарных блоков. Практически каждый функциональный блок в Horizont-Configurator имеет графическое изображение, принятое при разработке функциональных схем электронных устройств. Блоки представлены в виде прямоугольников, внутри которых имеется обозначение функции, и являют собой операции над входными переменными.
Линии связи, которыми соединяют функциональные блоки, указывают распространение сигнала. Функциональная диаграмма системы управления, построенная в Horizont-Configurator, имеет сходство с электрическими схемами и может быть откомпилирована в файл прошивки для микроконтроллера AVR.
Система программирования, позволяющая строить программу из набора стандартных логических элементов, очень удобна, если Вы хотите сделать BEAM-робота, но предварительно решили проверить логику разработанной вами схемы. Нарисовать схему в программе Horizont-Configurator гораздо быстрее, чем собрать ее даже на макетной плате. Использование Horizont-Configurator также удобно при отладке или проверке различных вариантов схемы BEAM-робота на реальной платформе. Конечно, при исполнении BEAM-схемы на микроконтроллере потеряются ее аналоговые свойства, но основная логика работы будет вполне видна. Особенно удобно использовать Configurator при разработке многотактных конечных автоматов, являющихся центром управляющей системы BEAM-роботов со сложным поведением.
Для того, чтобы начать работу с Horizont-Configurator, необходимо скачать установочный файл по ссылке horizont-configurator_for_avr_setup.exe) и установить программу к себе на компьютер.
Установив Configurator, можно загрузить примеры проектов, посмотреть на работу программы, откомпилировать примеры (меню «Проект» -> «Конфигурация -> «Создать файл конфигураций», затем меню «Проект» -> «Конфигурация -> «Собрать проект»). Пока среда Horizont-Configurator еще находится в стадии разработки, но уже представляет собой удобный и гибкий инструмент, с помощью которого можно создавать самые разнообразные прошивки для микроконтроллеров AVR.
- Порты микроконтроллера (сразу конфигурируются как входы или выходы)
- Логические блоки (НЕ, И, ИЛИ, Исключающее ИЛИ)
- Логические константы
- Триггеры (RS, SR, D, DC, RTRIG, FTRIG)
- Логический генератор
- Генератор импульсов
- Блоки для работы с числами (Арифм. операции, Сравнение и т.д.)
- Таймеры
- Счетчики
- Индикаторы
- Элементы памяти
В меню Файл выберем «Новый проект», дадим ему название и зададим тип микроконтроллера, например, ATMega8. Нажмем несколько раз «применить» в появляющихся окнах.
После проделанных действий у нас появится поле для нашего проекта, а в левой панели необходимые элементы. Теперь перетащим элементы на рабочее поле. Нам понадобятся: Вход PinD1 из раздела «Порт D», Выходы PinC1, PinC2, Pin3, PinC4 из раздела «Порт C», две логические константы из раздела «Логические блоки» и Логическое НЕ из того же раздела. Развернуть элементы зеркально можно в правой панели «Свойства» с помощью изменения свойства «Turn» на «180 град».
Соединим элементы сигнальными линиями. В панели инструментов есть кнопки переключения между режимами выделения и рисования соединительных линий. Если объекты отказываются соединиться, то можно поступить следующим образом: нарисуйте соединительную линию, а затем придвиньте к ней элемент (они соединятся).
Зададим значения для логических констант последовательно выделив их и изменив значение «Value» в правой панели «Свойства» на «true».
Осталось откомпилировать получившуюся функциональную диаграмму. В меню Проект выберем «Конфигурация -> «Создать файл конфигураций», а затем «Конфигурация -> «Собрать проект». После проделанных действий у нас появится файл с прошивкой для микроконтроллера. Перейти в папку с получившимися файлами можно выбрав «Конфигурация -> «Открыть созданный проект». Среди списка файлов можно без труда найти файл «имя проекта.hex» — это и есть файл прошивки. Загрузить файл в микроконтроллер можно, например, с помощью программы-загрузчика PonyProg2000.
В качестве еще одного тестового примера попробуем сделать систему управления роботом для соревнований Кегельринг из статьи «Робот для соревнований».
Нам понадобятся: Вход PinD1 из раздела «Порт D», Выходы PinC1, PinC2, Pin3, PinC4 из раздела «Порт C», два Логических НЕ и Триггер FTRIG из раздела «Логические блоки», два Таймера с задержкой отключения из раздела «Таймеры/Счетчики» и Счетчик милисекунд из того же раздела.
Счетчик милисекунд необходим для нормальной работы таймеров (он использует внутренний Timer0 микроконтроллера). Установим его «Делитель» на 1 MHz в правой панели. Триггер FTRIG отслеживает падающий фронт логического сигнала.
При изменении состояния входа «FTRIG» с 1 на 0, происходит формирование короткого импульса на его выходе. Данный импульс поступает на входы блоков Таймеров с задержкой отключения, данные блоки формируют необходимые по длительности интервалы высокого уровня на своих выходах. Сигнал с вывода первого Таймера с задержкой отключения поступает на выходы PinC1 и PinC2, управляющие первым мотором. Блок «Логическое НЕ» необходим для реализации инверсии мотора. Аналогичным образом работает другая цепь Таймера с задержкой отключения, которая управляет вторым мотором. Интервалы для таймеров можно установить изменив свойство «T» в правой панели свойств. Зададим значение равное 1800 милисекунд для первого таймера и 2400 милисекунд для второго. Таким образом, при изменении состояния входа нашей схемы с 1 на 0, в тот момент, когда робот наткнется на черную ограничительную линию, триггер сформирует короткий импульс, запускающий таймеры. Направление вращения моторов инвертируется и робот начнет двигаться назад. Когда первый таймер перестанет работать, другой таймер еще 600 милисекунд будет обеспечивать вращение второго мотора в обратном направлении и робот повернется на небольшой угол. Когда и второй таймер закончит свою работу, робот снова поедет в прямом направлении.
В завершение краткого рассказа о новой среде разработки, следует добавить, что Horizont-Configurator имеет достаточно хорошую поддержку на youtube в виде целого ряда обучающих роликов. Ссылка на youtube-канал есть на официальном сайте программы.
Пишем задачки на FBD. Пятнашки и Симпсон
В этой статье будет показано, как на языке программирования FBD написать простую программу, которая, тем не менее, будет делать что-то полезное. В нашем примере это будет игра в Пятнашки.
Для начала напомню правила игры: игра в «15», «Пятнашки», «Такен» — популярная головоломка, придуманная в 1878 году Ноем Чепмэном. Представляет собой набор одинаковых квадратных костяшек с нанесёнными числами, заключённых в квадратную коробку. Длина стороны коробки в четыре раза больше длины стороны костяшек для набора из 15 элементов, соответственно в коробке остаётся незаполненным одно квадратное поле. Цель игры — перемещая костяшки по коробке, добиться упорядочивания их по номерам, желательно сделав как можно меньше перемещений.
Вот что получилось в итоге: 
Описание программы, комментарии и картинки под катом.
Основа программы
Как видно из описания игры, у нас есть поле из 16 элементов. Таким образом основой программы будет элемент «Клетка пятнашек».
Сформулируем требования к этому элементу:
— Наша «клетка пятнашек» должна принимать внешние команды, когда игрок говорит, что хочет передвинуть ее на соседнее пустое место.
— Клетка должна знать о соседях по вертикали и горизонтали, чтобы понять, есть ли рядом пустая клетка, с которой можно поменяться местами.
— Клетка должна на старте игры установить начальное значение.
— На выход Клетка должна передать значение, установленное в ней на данный момент.
— Если игрок подал команду Клетке переместиться на соседнее пустое поле, то Клетка должна передать команду соседней пустой Клетке о том, что они сейчас будут меняться местами.
Исходя из этих требований получаем набор входных и выходных сигналов.
- УпрКоманда — команда управления от игрока, соседних клеток или начальной установки значения
- Сверху — значение соседней сверху клетки
- Снизу — значение соседней снизу клетки
- Слева — значение соседней слева клетки
- справа — значение соседней справа клетки
- УстЗначение — значение, которое должно быть установлено в клетку при старте игры
- Значение — значение клетки
- Поехали — команда соседу, что мы хотим «поменяться с ним местами»
В итоге получается вот такой макрос: 
Поставим таких макросов в нашу задачу 16 штук и свяжем их между собой. В итоге получаем:
Все очень просто. Поставили в задачу шестнадцать макросов «Клетка пятнашки» и связали каждую клетку с соседями по вертикали и горизонтали. Если каких-то соседей у клетки нет (например в клетке_1_1 нет соседа сверху и слева), ставим на соответствующем входе «-1».
Набивка основы
На прошлом шаге мы заложили основу программы. Но т.к. сам элемент «Клетка пятнашки» пока у нас пустой, то ничего хорошего наша программа делать не умеет. Пора это исправить, наполнив логикой макрос «Клетка пятнашки».

Это готовая реализация нашего макроса. Кратко принцип работы:
1. Получаем на вход управляющую команду и распаковываем ее. Выделяем отдельно сигнал длительностью один цикл — признак, что поступила команда, и параметр команды. В нашем случае параметр команды это набор целых чисел от 1 до 6, где числа 1 — 4 это команда поменяться значениями с одним из соседей, число 5 означает команду установить начальное значение в ячейку, а число 6 — что игрок кликнул мышкой по данной клетке и хочет «передвинуть» ее на возможно находящееся по соседству пустое поле.
2. Проверяем всех соседей на нулевое значение (числом 0 у нас обозначается пустое поле).
3. Дальше смотрим на текущее значение данной клетки и команду. Если значение клетки равно нулю (клетка пустая) и одновременно пришла команда поменяться значениями с соседом (команды 1-4), то перезаписываем в клетку значение соседней клетки.
4. Если нам пришла команда 5 (установить начальное значение), то просто записываем в память значение со входа «УстЗначение».
5. Если нам пришла команда 6 (игрок кликнул мышкой по данной клетке) и значение одной из соседних клеток равно нулю, то записываем в данную клетку нулевое значение и отправляем команду «Поехали» на соседние клетки с указанием, с каким именно соседом мы хотим поменяться.
Вот собственно и все. Единственный тонкий момент в нашей реализации, это алгоритмы «Задержка». Но догадаться, зачем они нужны, легко просто представив как проходит выполнение программы:
— мы получили команду от игрока «передвинуть» эту клетку на пустое место.
— проверили, действительно рядом есть пустое место.
— записали в клетку «0» и передали соседу, что хотим поменяться с ним значениями.
— сосед (при этом нужно понимать, что отрабатывает все тот же макрос «Клетка пятнашки», просто другой его экземпляр) видит, что ему пришла команда поменяться местами с другой клеткой.
— сосед проверяет что в нем самом записан «0» т.е. он сейчас пустая клетка и записывает себе значение первой клетки, от которой пришла команда поменяться местами.
Но секундочку! Ведь одновременно с тем, как отправить команду соседу мы в нашу первую клетку записали «0», следовательно сосед тоже запишет себе ноль и у нас получатся две пустые клетки, а одно значение (из ряда 1-15) пропадет. И так пока все игровое поле не станет пустым. Чтобы этого не произошло, задерживаем в первой клетке перезапись нулем. Таким образом пустая соседняя клетка успеет себе записать правильное значение.
Победа или поражение
Собственно основа нашей программы готова и уже прекрасно работает. Можно играть. Все дальнейшие действия нужны только для внедрения вспомогательных функций.
Начнем с автоматической проверки победы или проигрыша игрока.
В игре «пятнашки» есть забавный момент заключающийся в том, что из всех возможных начальных раскладов плиток половина раскладов нельзя собрать если плитки с номерами «14» и «15» стоят не в том порядке.
Исходя из этого сформируем два условия:
— условие 1: значение, записанное в каждую клетку соответствует ее порядковому номеру. Т.е. игрок победил.
— условие 2: значения, записанные в клетки соответствуют их порядковому номеру за исключением клеток «14» и «15», которые поменяны местами. Т.е. немного не повезло с раскладом.
Ставим 18 алгоритмов сравнения, и собираем по И два набора по шестнадцать равенств. По ИЛИ формируем признак, что мы пришли к одному из концов расклада.
18 алгоритмов потому, что мы проверяем соответствие 16 ячеек для первого случая и дополнительно перестановку ячеек «14» и «15» для второго случая. Кстати т.к. у нас массив данных заранее определенный, то достаточно пятнадцати проверок для всего поля, т.к. последняя ячейка проверяется автоматом.

Начальная расстановка
Итак, программа у нас готова. Можно играть и при победе выдается сообщение. Все хорошо за исключением того, что в начале игры нужно расставить плитки в поле случайным образом. И оказалось, что в данном примере это самая сложная задача. Но если не упираться в быстродействие, оптимальность алгоритмов и т.п. то можно решить эту задачу «в лоб» малой кровью сравнительно быстро.
Сначала делаем генератор случайных целых чисел 0 — 15. Пойдем старым проверенном способом. Это вполне допустимый вариант т.к. получившаяся последовательность случайных чисел зависит от момента времени, когда была нажата кнопка «Новая игра». А т.к. этот момент времени случает и никогда не повторяется, то в итоге мы получаем довольно простой и хороший генератор случайных чисел.

На выходе алгоритма Действительное-в-Целое (ДвЦ) получаем случайное целое значение 0 — 15.
Теперь стоит задача сформировать ряд неповторяющихся значений, и записать каждое значение в свою ячейку.
Как я уже сказал, конкретно для языка FBD более или менее хорошее решение этой задачи сделать сложно. Или может просто я не вижу очевидное для остальных красивое и простое решение. Конечно всегда есть вариант сделать вставку на ST, но цель сделать задачу именно целиком на языке FBD.
Решение задачи «в лоб» заключается в следующем:
— ставим управляющие элемент.
— при подаче команды забиваем начальный массив «-1» (можно было и любыми числами вне диапазона [0..15]).
— обнуляем счетчик.
— далее генерируем случайное число от 0 до 15 и проверяем, есть ли такое в нашем массиве. Если есть — продолжаем генерацию случайных чисел.
— если такого числа нет — увеличиваем счетчик на единицу и записываем в соответствующую ячейку памяти наше значение.
— повторяем эти действия еще 15 раз.
— проверяем, если мы дошли до шестнадцатого шага, то формируем импульс, по которому отправляем команду на запись этих значений во все ячейки.
Главным недостатком данного алгоритма является многократный перебор выкинутых ранее значений. В теории время генерации такой случайной последовательности может быть бесконечным. В реальности при десятках прогонов программы оно не было больше чем полсекунды. В любом случае на время генерации (раз оно не мгновенное) ставим блокировку на действия игрока.
Вот что в итоге получилось:

Приделываем графику
Теперь, когда задача у нас полностью готова, самое время приделать UI.
Для этого выбираем в интернете любые понравившиеся картинки с пятнашками. Одновременно выбираем картинки для удачного сбора расклада и неудачного. Я решил поместить туда Гомера Симпсона.
Ставим 16 картинок и приделываем к ним анимацию чтобы они показывали число, соответствующее числу в ячейке. А в ячейке с нулем плитка должна быть невидимой. Ставим на каждую плитку формирование команды «6». Снизу располагаем кнопку «Новая игра» по которой будем запускать алгоритм генерации случайной начальной расстановки. Одновременно не забываем поставить блокировку на все плитки во время генерации. Поверх всего этого дела помещаем Гомера. Все. Графика готова, можно играть.
Нарисованный вариант. Можно заметить что изначально все плитки у нас с единицей. 
Начальная позиция. 
Неудачный расклад. 
И победа! 
Что осталось нереализованным
На самом деле не так уж и много:
— можно приделать счетчик ходов. Делается это элементарно. В макросе «Клетка пятнашки» есть команда «6» — команда игрока. Достаточно взять от нее логический сигнал длительностью в один цикл. Вывести этот сигнал наружу. Собрать все шестнадцать сигналов по ИЛИ и завести на алгоритм сложения, охваченный обратной связью. Т.е. всего три дополнительный алгоритма и пара минут работы.
— статистика. Можно сделать счетчик выигранных и неудачных игр. Добавляется два алгоритма сложения и пару алгоритмов обвязки. Тоже пара минут работы.
— отбор раскладов. Как уже говорилось, половина раскладов не сходится изначально из-за невозможности поменять местами плитки «14» и «15». Можно при старте проверять «неудачный расклад» и сразу менять эти две плитки местами.
Сделать все это не сложно, поэтому оставлю такую возможность всем желающим.
Выводы
В этот раз за полчаса на языке FBD была реализована простенькая игрушка «Пятнашки». При этом все используемые алгоритмы простые и понятные. Вся логика обработки сигналов очевидная. Разобраться в такой программе не составит труда любому человеку, знакомому с азами логики и основами языков программирования МЭК 61131.
Для проверки накидал примерно то же самое на С. Вот что получилось:
Т.к. программированием на языке С/С++ я не владею, то скорее всего программа ужасна. Более того я сразу вижу несколько «сомнительных» мест. Однако она работает и работает без ошибок.
Главная идея состоит в том, что одна и та же программа, написанная на языке С и на языке FBD имеет разную сложность понимания человеком «со стороны». И если с языком функциональных блоков разобраться не представляет никакого труда, то с реализацией на С придется повозиться.
И хотя программа на FBD состоит из 153 блоков (и мы помним, что там еще есть 16 макросов, каждый из которых состоит из 32 блоков), но на написание ее ушло гораздо меньше времени, чем на написание 50 строчек кода на С.
Другими словами язык FBD это простой и понятный язык, предназначенный для далеких от программирования людей. Программировать простые задачки на нем легко. И разобраться в принципах работы программы, написанной другим человеком тоже обычно не составляет большой проблемы.