Ниже будет этакий пятничный "лонгрид", но разбивать его на несколько постов я не буду, чтобы не забивать вам ленту почём зря, поскольку материал специфический, довольно быстро уйдёт с главной в (не побоюсь этого слова) анналы хабра, и кто-нибудь будет время от времени находить его в поиске и таки накопает для себя в этой простыне полезные "зёрна", пусть останется "всё в одном".
Прежде чем продолжить — небольшой дисклеймер — я не имею прямого отношения ни к одной упоминающейся ниже компании, рекламной нагрузки этот чисто технический пост не несёт, просто у меня в руках нарисовалась списанная железка и образовалось некоторое количество свободного времени, всё это совершенно бескорыстно.
Речь пойдет про ПЛК (Программируемом Логическом Контроллере), изготовленным компанией B&R Industrial Automation GmbH.
ПЛК широко применяются на производстве для управления конвейерами, автоматикой, разными исполнительными механизмами.
Вы наверняка видели многочисленные видео, где трудолюбивые роботы неутомимо собирают для нас с вами машинки, на которых мы потом ездим. Для "обычного" зрителя это может походить на магию, но на самом деле нет её там.
Давайте возьмём одно из таких видео, и рассмотрим его чуть поближе. Конкретно это видео с одного баварского китайского завода, на котором собираются моторы для одного очень известного автопроизводителя, конкретно нас интересует небольшой кусочек с отметки 2:55, но это пятнадцатиминутное видео заслуживает того, чтобы посмотреть его на досуге целиком, тут весь процесс от литья жидкого алюминия в форму до готового ДВС, квинтэссенция автоматизации, да и просто автолюбителям наверняка понравится:
Я в курсе про то, что Ютьюб в России нынче работает с перебоями, поэтому скачал его для вас и переложил на Яндекс Диск, с которым проблем быть не должно (тут ещё и навязчивой ютьюбовской рекламы не будет, но вероятно будет яндексовская, тут уж простите): Полное видео (200МБ), кому лень качать двести мегабайт, вот короткая версия чуть меньше полутора минут, там 15 МБ всего. Если по какой-то причине видео будет недоступно ни так ни сяк — напишите в личку или комменты, я найду способ поделиться.
На одном моторостроительном заводе
Обратимся к кусочку с 2:55 в полном видео, здесь конвейер подаёт свежеиспечённые блоки цилиндров, робот хватает их захватом и заносит в кабину радиационной защиты, где им делают рентгеновскую "флюорографию" (по научному это НК — Неразрушающий Контроль). На проверку одного блока в среднем уходит около минуты, причём проверка там выборочная — полностью просвечивается только примерно каждый десятый блок, а остальные частично, только наиболее критичные участи типа собственно цилиндров и постелей коленвала (дефекты обычно не появляются "сразу", процесс сильно инерционный, если оператор литья упустил параметры, то вначале дефекты маленькие, затем всё больше и больше), при появлении дефектов система автоматом переходит в режим полной проверки и ждёт десять годных подряд, после чего снова включает выборочную. Охлаждающие каналы, по которым течёт тосол, тоже проверяются — там могут остаться куски формы.
Ключевые места на этом кадре:
1 — Привод конвейера, мотор как есть, он работает постоянно, там фрикционы в валиках, их просто рукой можно остановить.
2 — Датчики наличия поддона с объектом проверки
3 — Рентгеновский дозиметр, счётчик Гейгера грубо говоря. Рентген там шарашит постоянно, не выключаясь (рентгеновская трубка вообще не любит, когда её постоянно дёргают, поэтому жарит 24 часа в сутки без остановки), поэтому при открывании двери пучок перекрывается специальной заслонкой, и хотя там вся железно безопасно, заказчик установил по требованиям ТБ дополнительную индикацию. Там средненькая трубка стоит, 225 киловольт.
4 — Кнопка экстренного останова. Это на случай, если кто оказался в защищённой зоне, а робот поехал. На самом деле решётчатая дверь там заблокирована электромагнитом, при открывании робот встанет, и двигать его можно будет только в ручном режиме. Но во время пусконаладки оператор ка раз там и находится и производит обучение манипулятора (там, кстати, ничего сложного, просто сохранённые координаты, он тупо перемещается от точки к точке по своей программе). Вторая ступень безопасности при обучении робота — это пульт в руках оператора, там контакт, который надо придавливать, но не до конца, "Переключатель мертвеца" называется.
Правая часть конвейера чуть поближе:
Здесь:
1 — уже знакомые датчики, конвейер вообще обвешан ими как новогодняя ёлка. Вы можете заметить, как они срабатывают, там загорается жёлтый огонёк.
2 — рельсы, которые поднимаются, когда деталь надо переложить на другой конвейер, справа там вторая пара, это чтобы развернуть направление на 180 градусов. Рельсы эти поднимаются пневматикой, на самом начальном кадре видна магистраль сжатого воздуха и регулятор давления, я пометил их номером 1:
Кроме того на этом кадре:
2 — второй регулятор давления воздуха для робота, у него захват тоже на пневматике, там 6 бар рабочее давление.
3 — кондиционеры охлаждения шкафов, в Китае бывает жарковато.
4 — собственно шкаф электрики.
5 — это шкафчик электрики робота. Там внутри компьютер и куча проводов, сбоку обычно висит пульт управления. Если видите на видео робота — ищите невдалеке такой шкафчик, он должен быть.
Почему такие странные перемещения деталей? Завод живёт "своей жизнью", производство постоянно оптимизируется, системы и конвейеры бывает двигают туда-сюда и конвейеры следуют изменениям. Всё как в программировании. В конечном итоге всё это для банальной максимизации прибыли.
Ну и последнее — при движении детали, как вы могли заметить, притормаживаются, для этого там установлены этакие стопперы-сепараторы, тоже пневматические, на первом видео их не видно, но вот на 5:25 он есть, просто выезжает вверх:
Ну а 2 — это датчик положения, этот работает "на просвет", у него напротив стоит зеркальце, которое отражает лучик света, там фотоэлемент, грубо говоря. Дальше там работает примитивнейшая логика "если оператор нажал на кнопку и у нас есть ожидающая проверки деталь, то опусти тормоз, пропусти деталь, подними заслонку после прохождения детали".
Теперь вы знаете почти всё. Дотошный зритель наверняка задаст себе вопрос "куда-то это всё хозяйство должно быть подключено, кто-то ведь должен всем этим управлять?". А подключается всё это к ПЛК, который мы и рассмотрим ниже. Конечно, гору не всегда тянут к Магомету, в случае протяжённого конвейера никто не прокладывает толстенный жгут проводов по всей длине конвейера к контроллеру, вместо этого тянут полевую шину (там четыре провода всего), а на неё вешают модули — "разветвители" и дальше к ним подключают датчики и исполнительные механизмы, примерно как I²C шина, на которую можно повесить сотню устройств. Но с точки зрения программирования контроллера всё будет "прозрачно". Рабочее напряжение там, кстати, 24 вольта, что для датчиков, что для исполнительных механизмов, это стандарт, принятый в промышленности.
Как всё это выглядит в реальном мире? Я, к сожалению не могу показать начинку шкафа (всё таки я под NDA), но у нас есть тестовый стенд, в котором нет ничего секретного, вот так это выглядит в первом приближении:
Кто любит много мигающих лампочек — тому прямая дорога в АСУ ТП.
Почему я взял именно этот завод для примера? А потому, что ваш покорный слуга принимал самое непосредственное участие в разработке этой системы (что на самом первом фото), правда писал я там часть машинного зрения для дефектоскопии, чуть более чем полностью. Системе, что на фото — около двадцати лет от роду, а видео — оно где-то 2017 года. Если есть какие-то специфические вопросы — задавайте в комментах, я постараюсь ответить.
Крутить мы будем ПЛК от B&R, потому что для нас он чуть интереснее, чем Сименсовский, хотя конкретно всё, что я показал выше, работает именно на Сименс, но с точки зрения автоматики разницы по большому счёту нет.
Bernecker + Rainer Industrie-Elektronik GmbH
Маленький экскурс в историю — компания B&R была основана в 1979 году Эрвином Бернекером и Джозефом Райнером, откуда и название, которое иногда пишут как B+R. Довольно молодая в сравнении с "монстрами" типа Сименс и иже с ними. Штаб-квартира и основное производство с учебными центрами располагаются в австрийском Эггельсберге:
Это чуть больше сотни километров от Мюнхена на восток, где-то полтора часа езды. Милое местечко в предгорьях Альп.
Кстати, именно там и находится то самое знаменитое австрийское село:
чутка обсценной лексики под спойлером
Говорят, его название сменили на Fugging, но я проезжал там несколько раз пять лет назад, когда ездил на обучение и застал именно ту вывеску. Вот за тем лесом на горизонте, километров десять и будет Эггельсберг.
В настоящее время компания принадлежит концерну АВВ, который хорошо известен своими роботами да электрикой.
ПЛК B&R X20CP1586
Сегодня у меня на рабочем столе, а значит в обзоре, типичный представитель мира ПЛК от B&R — X20CP1586:
Это ПЛК на процессоре Intel Atom. Конкретно в этой модели установлен Atom E680T, с частотой 1.6 ГГц. Процессор довольно слабенький, 24 кБ кэш данных, 32 кБ кэш команд, 512 кБ кэша второго уровня, третьего и нет вовсе. Выполнен по технологии 45 нм на архитектуре Tunnel Creek. Выпущенный аж в 2010 году, процессор этот был предназначен как раз для использования в ПЛК и всяком эмбеддинге. На борту у конкретно этого ПЛК 512 МБ памяти DDR2 800 SDRAM. Максимально процессор может адресовать 2 ГБ. Память распаяна. Охлаждение пассивное, TDP там всего 4.5 ватта, но диапазон рабочих температур довольно широкий: от -25°C до 60°C (хотя управлять буровой вышкой на крайнем Севере я б ему не доверил). Кстати, современные ПЛК недалеко ушли от этого, в топовых на данный момент X20CP3686X да X20CP3687X установлены Атомы E3930 и E3940, а память добили до одного и двух гигов соответственно, но уже DDR4, спасибо и на этом. Крышку я, с вашего позволения, снимать не буду, там довольно хлипкие "одноразовые" защёлки, радиатор охлаждения процессора хорошо виден через решётку корпуса:
Впрочем нет, я могу "снять" крышку вот так, у меня ж есть томограф в цеху:
Да и радиатор могу "скинуть", вот он, камушек и все потрошки контроллера:
По сути это обычный одноплатный компьютер с парой специальных фишек. Давайте посмотрим на его интерфейсы поближе, они обозначаются IF, снизу видно два обычных RJ45 коннектора.
Слева IF2 — это гигабитный Ethernet. Правее IF3 — POWERLINK, это в общем тот же Ethernet, но "реального времени" расширенный для высокоточной синхронизации узлов сети (мы говорим о временах порядка микросекунд). Затем у нас есть пара USB, это IF4 и IF5 (оба 1.1/2.0, в один из них обычно втыкается донгл с лицензией), над ними разъём CF карты — это суть накопитель, на котором операционная система (или её часть), рантайм и управляющая программа. Справа IF6 (фото сбоку чуть ниже), это X2X Link. Это проприетарная полевая шина для бэкплейна, на которую "нанизываются" модули расширения. Вы можете спросить, где же IF1, а он тут есть — это RS232, выведенный на тот же коннектор (строго говоря это X20TB12 терминал блок), на который подаётся питание, там распиновка на корпусе слева на первой фотке. Батарейка CMOS СR2477N (3 V / 950 mAh) прячется под крышкой, ниже на фото я крышку снял, там батарейка видна.
Пустой слот посередине между терминальным блоком и процессором предназначен для модулей расширения других шин типа, Profibus, ModbusTCP, DeviceNet, PtrofiNet и т.д., например если вам нужно общаться с другой железкой по протоколу CANopen, то вы вставите туда модуль X20IF1041-1, вот так:
Справа "нанизываются" модули расширения, они просто вдвигаются друг в друга с лёгким усилием. Их огромное количество, я в учебных целях возьму пару самых "ходовых", это один модуль входов X20 DI 9371 и один выходов X20 DO 9322, каждый на 12 сигналов, на фото выше они уже вставлены справа.
Модули общаются с контроллером вот так, через интерфейс IF6, там сквозная X2X Link полевая шина да питание:
Последний модуль прикрывается пластиковой крышкой, чтоб контакты не покоцать. Модуль, кстати, выполнен довольно разумно — у него передняя часть отщёлкивается, так что при замене модуля вам не нужно выдёргивать из него лапшу проводов, а просто отщёлкнуть коннектор вместе с подсоединёнными проводами. это исключает ошибки "воткнул провод не туда":
Разбирать я их опять же не буду, но внутрь могу заглянуть, засунув их под рентген, вот что там внутри:
Вот модуль входов DI 9371:
А вот начинка модуля выходов DO 9322:
Рентгеновские картинки залипательны, а так — электроника как электроника. Продвинутые электронщики возможно разглядят тут какие-нибудь мосфеты или опторазвязки, если они есть, но я не специалист, моя схемотехника закончилась на операционниках да самодельных усилителях звука.
Питается эта игрушка от 24х вольт (как я уже писал выше, это стандарт), потребляет где-то пол-ампера, в максимуме полтора. Там два питания — одно для процессора, второе для модулей, но никто не мешает вешать их на один источник. Защита от переполюсовки есть. Диапазон напряжений -20% / +25% — это от 19 до 30 вольт, если калькулятор не врёт.
Для обучения юных падаванов наши практиканты собрали такой вот кубик, тут нет ничего особенного — собственно ПЛК, точно такой же как в обзоре, но с пристёгнутой безопасной частью, пара сервомоторов на SafeMotion, стандартный B&R монитор для SCADA/HMI, кнопки да индикаторы, больше в общем ничего и не надо:
Ниже в разделе "Безопасность" будет фото "безопасных" модулей равно как и фото монитора с обратной стороны в разделе про SCADA.
Программирование ПЛК
Давайте теперь посмотрим, как этот "Лего" конструктор программируется.
Для этого предназначена IDE, которая называется Automation Studio. Актуальная на момент обзора версия 6.1.1.14, выпущенная в середине декабря, но конкретно этот старенький ПЛК в ней уже не поддерживается, так что местами я буду пользоваться четвёртой версией (визуально шестая от четвёртой мало чем отличается). Все скриншоты будут на английском, русского сюда не завезли, тут уж извините. Впрочем других языков кроме немецкого да английского там тоже нет. Скачать можно бесплатно вот отсюда. Вначале надо ставить 6.0.2.177 (придётся скачать около семи гигов), затем накатить обновление до 6.1.1.14 (ещё гигабайт до кучи). Триальную лицензию на 90 дней робот раздаёт бесплатно вот здесь. Там ограничено время работы двумя часами и выпилены онлайн обновления, в остальном "на попробовать и потренироваться" — самое то. По истечении 90 дней можно без проблем просить снова и снова на тот же email. Единственно — я честно не уверен, что на данный момент всё это доступно из России, но для посетителей хабра это не должно составить проблемы, всё-таки технические ресурс. Там в комплект входит очень неплохой симулятор, то есть можно сконфигурировать реальное железо, и запускать код даже при его отсутствии. Ниже по тексту для краткости я буду называть Automation Studio 6 просто "AS6", а Automation Studio 4 — "AS4"; если речь будет идти об обеих версиях, то просто "AS". Последняя версия устанавливается буквально в пару щелчков, под Win11 живёт нормально.
Изначально ПЛК "мёртв", ему нужно залить на флешку образ, который сначала нужно изготовить, для этого "с нуля" создаётся проект и конфигурируется в точном соответствии с железом, которым вы располагаете:
Я не буду превращать пост в простыню скриншотов, коих и так будет неприлично много (у меня в общем не стоит задачи заменить инструкцию), просто обозначу ключевые места, чтобы у вас сложилась "общая картина" того, как с этой штукой работают. У меня, кстати, остались кой какие учебные материалы, в том числе на русском, если кому надо — стукнитесь в личку. Добрый китайский товарищ выложил часть на Гитхаб-brtraining, там правда какая-то часть может попасться на китайском, если кому надо.
Среда разработки выглядит вот таким образом — слева окошко с тремя вкладками — логическое представление, там как раз находятся ваши программы. что крутятся в ПЛК и переменные, затем конфигурационная вкладка, где конфигурируется вся программная часть, и физическое представление, где конфигурируется железо; в центре идёт основная работа; а справа — библиотеки модулей, как физических, так и программных. Нам надо собрать конфигурацию с двумя модулями — входы 9371 и выходы 9322. Они просто перетягивются мышкой из библиотеки, список доступных модулей там очень большой, так что проще воспользоваться поиском, вот так:
Теперь нам надо как-то "достучаться" до выходов и получить значения входов, делается это очень простым способом, давайте поморгаем светодиодиками.
В лучших традициях некошерного программирования я заведу несколько глобальных переменных:
(так то с областями видимости тут всё нормально — глобальные переменные видны всем программам, но можно заводить и локальные, доступные лишь той программе, где они созданы).
К базовым типам надо привыкнуть, скажем USINT — это uint8_t, а не uint16_t, как вы могли бы подумать исходя из названия "Unsigned Short INTeger", но к счастью есть описание:
Это, кстати, не "изобретение" B&R, а стандарт IEC 61131-3. Никто нигде не утверждает, что int обязан быть четырёхбайтовым, здесь он всего два байта.
А теперь я просто сделаю маппинг (отображение) этих булевых переменных на физические выходы:
И так далее. Для входов всё происходит ровно также — читаете переменную, там будет значение на входе. Осталось написать программу, которая будет периодически перебрасывать эти переменные из true в false и обратно.
Вообще говоря среда разработки предоставляет возможности написания программ на десяти языках, пять из которых определены стандартом IEC 61131-3 (европейский EN 61131 или МЭК 61131-3), но для начала давайте воспользуемся классическим Си и вам сразу стает понятен принцип программирования этого контроллера.
Программируем ПЛК на Си
Для того, чтобы добавить Си-программу, надо просто перейти на вкладку логического представления, найти в списке справа "ANSI C Program All in One" да щёлкнуть два раза (или просто перетащить мышкой на проект):
"All in One" — означает "всё в одном", иначе вам будет создано три (один файл — одна функция). Для вас же будет создан файл main.c с тремя функциями, куда нужно будет добавить в нашем случае одну-единственную строчку инвертирования переменной:
В принципе если вы загрузите эту программу в ПЛК, то светодиод сразу начнёт мигать, но тут надо сделать небольшое пояснение. Функции ProgramInit() и ProgramExit() вызываются одноразово при старте ПЛК и останове, а вот ProgramCyclic() вызывается циклически (её имя как бы намекает). Фактически там крутится бесконечный цикл, но он спрятан в недрах ПЛК, вам не нужно о нём заботиться. В этом цикле вначале читаются все входы и переменные, затем выполняется ProgramCyclic(), после чего все выходы устанавливаются в соответствующие значения сообразно логике программы и всё повторяется снова и снова. Как часто вызывается ProgramCyclic()? Это определяется вот здесь в конфигурации Cpu.sw:
Вам доступно восемь "Cyclic" слотов с разным временем цикла. Неиспользуемые слоты можно удалить. Восемь слотов — это максимум. Программы можно добавлять сюда и перетаскивать их между слотами мышкой. В один слот можно поместить любое количество программ (предел мне неизвестен). Есть приоритеты — чем выше программа в слоте, тем выше её приоритет. Предопределённое время цикла каждого слота можно изменить вот здесь:
Минимально возможное время зависит от конкретного ПЛК, обычно 100-400 µs. Duration — это и есть время цикла, а Tolerance — это дополнительное время, которое даётся вам, если вы не уложитесь в основное (это примерно как второй круг при приземлении самолёта). По умолчанию Tolerance установлен в то же значение, что и Duration, но его можно выставить в ноль, тогда останется только один шанс. Увеличивать же его можно шагами Duration, ну то есть если время основного цикла одна секунда, то можно задать одну, две, три и так далее. Если Tolerance отличен от нуля, то цикл всё равно крутится с таймингом, заданным в Duration, а на "второй круг" вы пойдёте только в том случае, если вы в Duration не уложитесь. В общем случае своим кодом вы должны уложиться в суммарное время Duration+Tolerance, иначе ПЛК свалится в ошибку (он ведь суть контроллер реального времени).
Данные операционной системы, что там крутится, B&R не раскрывает, но сильно подозреваю, что Линукс. Как-нибудь на досуге копну поглубже.
Вот собственно и всё, теперь программу можно загрузить в контроллер, это Transfer или Ctrl+F5.
Если у вас нет физического контроллера, то можно активировать симуляцию:
IP адрес контроллера, к которому коннектимся, задаётся в Online->Settings:
Процесс загрузки выглядит вот так:
Как вы могли заметить, ПЛК перезагрузился. Он делает это не всегда, при небольших изменениях программа подменяется "на лету" без останова. На самом деле перезагрузка добавляет определённую головную боль, например, у вас ПЛК может управлять конвейером и для холодного рестарта вам может потребоваться снять все детали. Сименсовский ПЛК значительно более "устойчив", там вы меняете программу, и он как-то умудряется разруливать свои внутренние противоречия, а этот перегружается по каждому чиху, но к этому в общем можно привыкнуть.
А, нам же ещё хочется видеть изменение переменных в реальном времени, для этого есть Монитор, гуда можно перетаскивать переменные, ну и просто наведя мышкой на код можно видеть что там происходит:
Ещё такой момент — если вы работали с ПЛК Сименс, то знаете, что при некоторых условиях вы можете достать логику работы из ПЛК, даже не имея "исходников" проекта. Здесь у вас шансов нет, программа компилируется в машинный код, его возможно можно извлечь с флешки (и то, если он не зашифрован, в чём я не уверен), но дальше — только дизассемблирование, IDA да Ghidra вам в помощь.
Прежде чем продолжать, давайте очень быстро и поверхностно пробежимся по остальным девяти языкам, начиная с тех пяти, что определены в МЭК 61131-3. Устройство у всех программ одинаково — есть Cyclic программа, куда и вписывается (или врисовывается) код.
Другие языки — огласите весь список, пожалуйста
ST — Structured Text. Это текстовый язык и (так уж исторически сложилось) один из основных, на котором происходит разработка программ на B&R ПЛК. Это такой "паскалеподобный" язык.
Мигание светодиодом на нём будет выглядеть просто вот так:
PROGRAM _CYCLIC
LED11 := NOT LED11;
END_PROGRAM
В остальном язык как язык, если вы писали на Паскале или на Дельфи, вам понравится.
IL — Instruction List. Это тоже текстовый язык, весьма низкоуровневый, его ещё иногда называют "ассемблером для ПЛК". Я сто лет на нём ничего не писал, самый простой способ инвертировать выход выглядит вероятно как-то вот так:
PROGRAM _CYCLIC
LDN LED12
ST LED12
END_PROGRAM
Тут мы грузим инвертированный сигнал (LoaDNegative) в аккумулятор и пишем обратно (STore). Там есть и условия и переходы.
LD — Ladder Diagram. Это графический язык, который называют языком релейно-контактной логики. Если вы в прошлой жизни были электриком, то вам сюда.
В последней версии визуально выглядит посимпатичнее чем раньше (а так вообще вся эта среда разработки "привет из девяностых").
FBD — Function Block Diagram. Это также графический язык. Выглядит как-то так:
Вся программа будет выполняться поблочно и последовательно.
SFC — Sequential Function Chart. Тут есть шаги-экшены и переходы между ними.
На самом деле язык действительно не очень простой (в смысле интуитивности), за деталями могу предложить сходить в пост "Светофор на ПЛК – все языки МЭК 61131-3", там всё то же, что и выше, только подробнее.
CFC — Continuous Function Chart. Этот язык не включён в МЭК 61131-3, но представляет собой, скажем так, расширение FBD, куда привнесена идеология потоков данных.
Я оставлю здесь скриншот из файла помощи, здесь рассматривается порядок выполнения блоков:
Не будет вызывать отторжения у тех, кто программировал на LabVIEW, а так всё это уже было в STEP 7 да в CODESYS.
AB — B&R Automation Basic. Это текстовый язык — специальное "изобретение" B&R.
PROGRAM _CYCLIC
IF LED = 1 THEN
LED = 0
ELSE
LED = 1
ENDIF
END_PROGRAM
Что ж, возможно кому-нибудь да придётся по вкусу...
С++. Об этом языке особо говорить не о чем, Си++ он везде плюсплюс, базовый набросок инвертирования выхода от Си отличаться в общем не будет, но ниже будет пример поинтереснее.
Ассемблер. Да, этот ПЛК можно программировать и на ассемблере (добавляя написанные на ассемблере функции как *.s файлы, либо при помощи вставок ассемблерного кода прямо в Си или C++ код), ну а нужно ли — это уже другой вопрос. Я ниже покажу пример, ведь ассемблер — это же всегда увлекательно, тут вы с процессором один-на-один.
Все программы можно комбинировать как угодно, у нас далеко не все АСУ ТП специалисты знакомы с C++ или Си, они пишут на ST, но иметь в одном проекте разные языки вполне норм. Кончено, если вы пишете из двух программ в одну и ту же глобальную переменную, то вы сам себе злобный буратино, но практики "чистого кода" никто не отменял, они работают и в ПЛК.
Измерение времени цикла ПЛК при помощи rdtsc.
Ладно, вернёмся от МЭК 61131-3 к нормальному (или не очень) программированию. Мне на досуге стало любопытно, как можно замерить время выполнения участка кода. Это на самом деле довольно важно, мы как-то делали перемещение манипулятора с хитрым анализом коллизий (чтобы грубо говоря не въехать в детали интерьера кабины), и действительно упёрлись в то, что синусами да косинусами не укладывались во время цикла, а увеличивать его не хотелось, так как это приводило к большим задержкам (приходилось тормозить сильно не доезжая до точки коллизии). То, что "встроено" в B&R нам не подходило по определённым причинам. Ну и просто интересно. И хотя в AS есть инструменты для трассировки и профайлинга, однако мне хотелось "классического" десктопного бенчмарка, и хотя у B&R есть библиотечные функции работы со временем, но я возжелал "достучаться" до самого низа... И тут я вспомнил, что ведь есть ещё rdtsc, ну а интеловский процессор— он и в ПЛК остаётся им.
Вообще в теории, для того, чтобы воспользоваться __rdtsc() надо включить заголовочный файл x86intrin.h либо ia32intrin.h, и хотя оба эти файла вроде в наличии, но нет, в AS4 получаем ошибки:
Где наша не пропадала, ведь есть ещё Ассемблер, засучим рукава, и для начала просто поупражняемся. Пример использования ассемблера, кстати, есть даже в файле помощи B&R, он выглядит вот так:
.globl asmfun
asmfun:
push %ebp
mov %esp, %ebp
sub $0x18, %esp
mov 0x8(%ebp), %eax
mov %ax, 0xfe(%ebp)
movzwl 0xfe(%ebp), %eax
inc %eax
movswl %ax, %edx
mov %edx, %eax
lea 0x0(%esi), %esi
leave
ret
Тут просто инкремент, я не знаю зачем так сложно. Сложим, к примеру просто два целых числа:
.globl add_integers
.type add_integers, @function
add_integers:
push %ebp
mov %esp, %ebp
mov 8(%ebp), %eax # Грузим первый аргумент в eax
add 12(%ebp), %eax # Прибавляем второй туда же, в eax
mov %ebp, %esp
pop %ebp
ret
Этот код надо положить в *.s файл, прибавить до кучи нехитрый заголовочный файл
/* Declaration */
int add_integers(int, int);
И можно использовать:
void _CYCLIC ProgramCyclic(void)
{
res1 = add_integers(a++, b++);
}
И да, оно работает.
Для тех, кому синтаксис GNU Assembler кажется богомерзким, можно и вот так:
.globl add_integers_intel
.type add_integers_intel, @function
.intel_syntax noprefix
add_integers_intel:
push ebp
mov ebp, esp
mov eax, [ebp+8] # Тут справа налево
add eax, [ebp+12] #
mov esp, ebp
pop ebp
ret
Ну а если лень класть код в *.s файл, то можно заинлайнить прямо в Си код:
Хоть так:
int add_integers_inline(int a, int b) {
int result;
__asm__ (
"movl %1, %%eax\n\t"
"addl %2, %%eax"
: "=a" (result)
: "r" (a), "r" (b)
);
return result;
}
void _CYCLIC ProgramCyclic(void)
{
//...
result = add_integers_inline(a++, b++);
}
Либо вообще так:
void _CYCLIC ProgramCyclic(void)
{
//...
__asm__ (
"movl %1, %%eax\n\t"
"addl %2, %%eax"
: "=a" (result)
: "r" (a), "r" (b)
);
}
Кстати, на использование регистров rax и прочих 64-битных компилятор ругается:
что вероятно намекает нам на то, что мы находимся в 32-битном окружении.
Ну вот, теперь. после несложных упражнений можно перейти к делу. Я, кстати, безотносительно ПЛК рекомендую всем написать несколько несложных функций на ассемблере (ещё лучше с SIMD инструкциями), тут вы прокачаете и поймёте соглашения о вызовах, да и процессор перестанет быть магией.
Нам потребуется вот такая нехитрая функция, тут одна команда всего:
uint64_t rdtsc() {
uint32_t low, high;
__asm__ __volatile__ (
"rdtsc"
: "=a" (low), "=d" (high)
);
return ((uint64_t)high << 32) | low;
}
Заметьте, что тут добавлено __volatile__, это нужно так как формально компилятор имеет право переставлять инструкции, не влияя на результат, а нам как раз важно чтобы rdtsc была вызвана именно там, где мы просим.
Делаем несложную программку CycleTime:
#include <bur/plctypes.h>
#include <inttypes.h>
#ifdef _DEFAULT_INCLUDES
#include <AsDefault.h>
#endif
static uint64_t cycles, cycles_saved;
void _INIT ProgramInit(void)
{
cycles_saved = rdtsc();
}
void _CYCLIC ProgramCyclic(void)
{
cycles = rdtsc();
CycleTimeTicks = (UDINT)(cycles - cycles_saved);
cycles_saved = cycles;
}
void _EXIT ProgramExit(void) { }
и кладём её в цикл в одну секунду
Теперь надо как-то вытащить данные наружу (мне хочется иметь график). В принципе в Automation Studio есть встроенный профилировщик, но он выносит мне мозг, я хочу просто получить значения переменной.
Самый простой способ — просто поднять OPC UA сервер и забрать данные подходящим клиентом, он в AS4 вот тут включается:
Вообще класть все настройки в одно здоровенное дерево, чтоб потом лазить туда-сюда — это болезнь всех сред разработки для ПЛК, у Сименса тоже примерно также.
Теперь подключаем нашу переменную в OpcUaMap файле:
Это всё. У любого АСУ ТП инженера в ящике с инструментарием валяется OPC UA клиент, обычно пользуются UA Expert:
Теперь мне проще всего взять LabVIEW с OPC UA тулкитом и набросать вот такую оснастку:
И вот мой график количества циклов:
Среднее значение количества тактов при 1 секундом цикле в 1593439998.97, если округлить до 1593440000, намекает мне, что частота шины там 99.59 МГц (), так как 16 * 99590000 даст мне ровно то самое значение 1593440000 тиков в секунду (процессор этот, напомню, на 1.6 ГГц). Время цикла слегка потрясывет на плюс минус десяток тысяч тактов, но эта тысячная доля процента вообще ни о чём, вы никогда в жизни не получите такого устойчивого значения под Виндовс. Реалтайм есть реалтайм. В принципе эта тенденция будет сохраняться и при уменьшении времени цикла. На минимальном времени в сто микросекунд у нас будет 160000 тактов на цикл, но я не буду утомлять читателя ненужными цифрами. Главное, что мы вытащили счётчик с самого низкого уровня, ниже уже просто некуда.
Измеряем производительность ПЛК вычислением SHA256
Давайте прикинем производительность камушка. В качестве референсного кода я возьму вот эту реализацию SHA256 на чистом Си и просто скопирую файл и заголовок в проект как есть (чуть поправив типы данных в соответетсвии с МЭК 61131-3, чтобы не было предупреждений), собственно и всё. Вы, кстати, пробовали вычислить SHA256 на сименсовском ПЛК? Нет ничего невозможного, но таки придётся повозиться.
Собственно вычисление производится в три строчки, которые я обложу контролем времени, проект выглядит вот так:
Буфер я заведу размером в мегабайт (точнее в мебибайт, если уж быть дотошным) и заполню его буквами "а", чтобы быть уверенным, что дайджест вычисляется верно. На моём десктопе этот код, будучи скомпилированным самым свежим gcc выдаёт на гора около двухсот мегабайт в секунду. Посмотрим, что будет на атомном ПЛК.
Чутка не дотянули до восьми мегабайт в секунду. Ну, как я это называю, камушек с TDP в четыре с половиной ватта и отрабатывает на те самые четыре с половиной ватта. Чудес не бывает, реалтайм это не про скорость, это про детерминированность.
Кстати, загрузку процессора и его температуру можно посмотреть в "Таск менеджере", который суть веб сервер, и мы всегда можем зайти на ПЛК из браузера, вот так:
Стреляем себе в ноги
Тут мой коллега, заметив все эти эксперименты, заявил, что мол как-же так, ведь ПЛК — это типа эталон надёжности, а ты его на Си..., можно же "выстрелить себе в ногу" как это нынче модно говорить. Понятное дело, что ассемблерными вставками я могу отстрелить себе вообще всё что угодно, но давайте попробуем, нет проблем:
void _CYCLIC ProgramCyclic(void)
{
int *p = 0;
*p = 0;
}
этих двух операторов достаточно, чтобы положить ПЛК "на лопатки". При исполнении этого кода он мгновенно перезагрузится, и перейдёт в сервисный режим. Совсем он не умрёт, встроенный веб сервер диагностики выживет, и там можно увидеть статус, он доступен вот здесь:
А в логгере можно увидеть причину останова:
Выброшено исключение, так как мы из программы в TC#4 ткнулись куда не следует, всё честно.
Вернуть всё обратно несложно, надо убрать причину неполадки и загрузить новую программу (флешку в кардридер переставлять не надо, ПЛК в таком режиме вполне вменяем, только перезагрузить его после заливки надо вручную). Важно понимать, что на выходах останутся старые значения и все железки, подключённые к ПЛК, могут перейти в "неопределённое состояние", так можно устроить знатное месиво на конвейере.
Хотите ещё? Нет проблем, счётчик ехал ехал и приехал:
void _INIT ProgramInit(void)
{
cycles_saved = rdtsc();
Counter = 10;
}
void _CYCLIC ProgramCyclic(void)
{
Zhopa = CycleTimeTicks / Counter--;
cycles = rdtsc();
CycleTimeTicks = (UDINT)(cycles - cycles_saved);
cycles_saved = cycles;
}
Если бы я просто константу 0 положил, то получил бы предупреждение компилятора, он не настолько туп.
Через десять секунд ПЛК встанет вот с таким сообщением:
Другой пример, я положу программу для вычисления SHA256 в цикл 10 мс. Поскольку для вычисления нужно раз этак в десять больше времени, то мы гарантированно спровоцируем таймаут.
Результат, как говорится "те же, там же" — мы снова встали в сервис, вот только причина другая:
Максимальное время в TC#3 превышено. Вот что происходит, если мы не укладываемся кодом в отведённый лимит.
И так далее. В принципе "окирпичить" этот ПЛК непросто, так как даже если программа окажется настолько кривой, что всё встанет вообще "колом" (мне удавалось и такое), то всегда можно просто вытащить флешку, закинуть на неё "живую" программу, прошить в кардридере и ПЛК будет как новенький, снова в строю.
Безопасность
Некоторые читатели могут воскликнуть "ну то есть, если всё пошло вразнос, я пытаюсь всё остановить, а ничего не выходит, потому что ПЛК встал, словив исключение — так же нельзя". На самом деле система всегда проектируется с таким расчётом, что ПЛК в любой момент может уйти в отказ. И даже не в смысле программной части — там ведь обычный Атом, память, флешка, куча электроники, отказоустойчивость которой далеко не бесконечна. Но не всё так плохо. Если вы внимательно смотрели на фотку тестового стенда, то видели, что там на рейке висели также и нарядные жёлто-красные модули. Это "безопасные" модули (Safety). Никто и никогда не заводит те же кнопки экстренного останова на обычный ПЛК, их соединяют либо с релейной автоматикой, либо заводят на вот такие специальные модули. Кнопка экстренного останова есть и на нашем тестовом стенде, и если заглянуть на обратную сторону, то вы увидите, что там не два, а четыре провода (два подходят сверху и два снизу), внутри там две пары нормально замкнутых контактов и безопасный ПЛК постоянно их опрашивает коротким импульсами:
сам этот ПЛК тоже сдублированный, да и модули ввода/вывода — тоже. Кроме того, оранжевый PowerLink заведён и на моторы, которые подключены к "безопасному" контроллеру SafeMotion, так что если оператор жамкнет по экстренному останову, то моторы немедленно (безо всяких "мягких тормозных рамп") встанут, даже если основной ПЛК к тому времени будет чуть более чем мёртв, именно для этого там есть "жёлтая" часть:
Ах да, я же обещал безопасные модули, вот они, занимают два слота:
Надёжность не то что бы как в авионике, но, вероятно, близко к тому.
Конечно, хотелось бы иметь и Раст на борту, но вообще говоря не бывает абсолютно безопасных языков программирования, всё чаще встречаются просто кривые руки.
Идём в Плюсплюсы
Компиляция Си и С++ программ осуществляется при помощи тулчейна gcc. Предыдущие версии Automation Studio включали в себя древнейшие версии компиляторов начала века, там просто не поддерживались современные стандарты С++. Мы даже обращались в поддержку, и вот оно, свершилось — в AS6 завезли gcc v.11.3, о чём гордо отчитались в окошке "о программе":
Мир заиграл новыми красками.
Я это к чему. Один из основных паттернов проектирования в АСУ ТП — это конечный автомат. Если бы я проводил собеседование, то непременно спросил бы новичка, у которого ещё свежи теоретические знания основ теории автоматов, чем отличаются автоматы Мили и Мура, начинающего миддла вероятно попросил бы нарисовать и обсудить RS триггер, а самым продвинутым предложил бы изобразить конечный автомат, перемножающий два двоичных числа. Инженер АСУ ТП может быть не в курсе про синглтоны, но вот конечные автоматы знать обязан.
Если вы программируете на ST, то скорее всего вас ждёт развесистый switch, каждый кейс которого — суть состояние. В несложный автоматах это норм, но при усложнении логики будут проблемы.
Наш ведущий инженер сделал универсальную библиотечку для конечных автоматов используя ООП, но во время проектирования он испытывал сильные моральные страдания, будучи вынужден пользоваться устаревшим инструментарием (пытаться подменять тулчейн смысла нет, там gcc допиленный B&R). Выкладывать код с работы я по понятным причинам не имею права (да и с выпуском AS6 он перешёл в состояние "легаси"), но на С++ существуют готовые открытые реализации конечных автоматов разной степени удобства и элегантности. Я взял одно из них — High-Performance Hierarchical Finite State Machine, авторства Andrew Gresyk (я не знаю, как правильно пишется фамилия моего тёзки по-русски). Эта реализация довольно удобная и свежая, в одном заголовочном файле, и полностью совместима с последней версией AS (что удивительно, там в machine.hpp больше семнадцати тысяч строк, но нет, ни одной ошибки или даже предупреждения не вывалилось), кроме того снабжена готовым примером светофора, который мы просто перетащим в проект AS. Ещё одна реализация есть вот здесь — State Machine Design in C++, но её не так удобно перекладывать на ПЛК.
Вот смотрите, как там устроен оригинальный пример Андрея на самом высоком уровне:
int main() {
// shared data storage instance
Context context;
FSM::Instance machine{context};
while (machine.isActive<Off>() == false)
machine.update();
return 0;
}
Он просто идеально ложится на идеологию ПЛК, цикл while — это как раз наша Cyclic программа, которую крутит сам ПЛК, так что выкидываем while, выносим контекст за скобки и вот весь наш код в main.cpp, грубо говоря:
#include "machine.hpp"
// shared data storage instance
Context context;
FSM::Instance machine{context};
void _CYCLIC ProgramCyclic(void)
{
machine.update();
}
Каждый такт циклической программы наш ПЛК будет проворачивать автомат, а тот будет прыгать из одного состояния в другое. Всё.
Состояния я вынесу в отдельный файл, там вначале идёт их список:
#include "machine.hpp"
// data shared between FSM states and outside code
struct Context {
unsigned cycleCount = 0;
unsigned countGreenBlinking = 0;
unsigned countGreen = 0;
unsigned countYellow = 0;
unsigned countRed = 0;
};
// convenience typedef
using M = hfsm2::MachineT<hfsm2::Config::ContextT<Context>>;
// macro magic invoked to simplify FSM structure declaration
#define S(s) struct s
// State machine structure
using FSM = M::PeerRoot<
// sub-machine ..
M::Composite<S(On),
// .. with 5 sub-states
S(Red),
S(YellowDownwards),
S(YellowUpwards),
S(GreenBlinking),
S(Green)
>,
S(Off)
>;
#undef S
//------------------------------------------------------------------------------
static_assert(FSM::regionId<On>() == 1, "");
static_assert(FSM::stateId<On>() == 1, "");
static_assert(FSM::stateId<Red>() == 2, "");
static_assert(FSM::stateId<YellowDownwards>() == 3, "");
static_assert(FSM::stateId<YellowUpwards>() == 4, "");
static_assert(FSM::stateId<GreenBlinking>() == 5, "");
static_assert(FSM::stateId<Green>() == 6, "");
static_assert(FSM::stateId<Off>() == 7, "");
А самое "мясо" выглядит вот так, к примеру, мы находимся в состоянии "мигающий зелёный" и по истечении заданного времени мигания переходим в состояние "жёлтый вверх", это когда он загорается без красного:
struct GreenBlinking
: FSM::State
{
void enter(Control& control) {
TraceMessage("Green Blinking");
CountDownGreenON = true;
control.context().countGreenBlinking = 0;
}
void update(FullControl& control) {
LightGreen = !LightGreen; // Blinking
if ((control.context().countGreenBlinking++ > DurationGreenBlinking) && !LightGreen) {
control.changeTo<YellowUpwards>();
}
CountDown--;
}
};
Мигание зелёного ничем не отличается от мигания светодиодиком, LightGreen заведена прямо на выход ПЛК. Количество "обвязки" минимально и такое решение можно легко масштабировать.
В жёлтом состоянии мы выключим зелёный, зажжём жёлтый и потом перейдём в состояние красного:
struct YellowUpwards
: FSM::State
{
void enter(Control& control) {
TraceMessage("Yellow ^");
LightGreen = false;
LightYellow = true;
CountDownGreenON = false;
control.context().countYellow = 0;
}
void update(FullControl& control) {
if (control.context().countYellow++ > DurationYellow) {
control.changeTo<Red>();
}
}
};
Очень удобно то, что в каждом состоянии есть функция enter(), которая выполняется однократно при входе в это состояние, затем циклически выполняется update() на каждый вызов machine.update(), которую там выше крутит ПЛК.
Трассировка состояний в оригинальном примере шла прямо в консоль std::cout:
struct Green
: FSM::State
{
void enter(Control&) {
std::cout << " Green" << std::endl;
}
void update(FullControl& control) {
control.changeTo<YellowUpwards>();
}
};
Но у нас нет консоли (а жаль), и хотя есть лог, но я добавлю самодельный велосипед:
void ClearTraceLog()
{
for (int i=0; i<MAX_LOG_ENTRIES; i++) strcpy(TraceMessages[i], "");
}
void TraceMessage(const char* Message)
{
for (int i=0; i<MAX_LOG_ENTRIES; i++){
strcpy(TraceMessages[i], TraceMessages[i+1]);
}
strcpy(TraceMessages[MAX_LOG_ENTRIES], Message);
}
И теперь я вижу, как автомат переходит из одного состояния в другое, заодно и то, что он показывает:
Я с воодушевлением продемонстрировал этот проект моему коллеге, дядьке за шестьдесят, начинавшему ещё со STEP 5 (я в общем тоже немолод, мы за четверть века запустили с ним несметное число систем, выпив вместе вёдра пива на пусконаладках), предложил ему на досуге выучить C++ и делать теперь только так и вот он как-то странно на меня посмотрел, нехорошо так посмотрел, не по-доброму... Ну да ладно.
OPC UA в AS6
Тут возникает вопрос — как отобразить на экране всё, что происходит в ПЛК. Понятно, что можно пробросить наружу все необходимые сигналы в OPC UA, и с этого момента можно использовать любой доступный инструментарий, поддерживающий эту технологию, хоть С#, хоть Питон, хоть LabVIEW (что и было выше продемонстрировано).
Но есть маленький нюанс — в AS6 настройки OPC UA сервера переехали в другое место, теперь они вот здесь:
Всё в лучших традициях — в Windows настройки тоже гоняют туда-сюда. Но, кстати, в данном случае стало удобнее.
Тут также кое-что обновили в смысле списка алгоритмов шифрования, кроме того почему-то в анонимном режиме подключение стало невозможно, теперь просто обязательно создание минимум одного пользователя с соответствующим уровнем доступа:
В остальном всё также, переменные маппятся на узлы OPC UA, и вот наша нехитрая программка на LabVIEW
бодро мигает светофором из ПЛК:
Визуализация (SCADA) в mappVIEW
Но на борту у этого ПЛК есть и встроенное решение, которое называется mappVIEW (точнее их два — VC4 и mappVIEW, но VC4 — это отчаянное легаси, там по сути поднимается VNC сервер и вы можете заходить в ПЛК любым VNC клиентом, сравнивать VC4 и mappVIEW — это всё равно что сравнивать WinForms и WPF).
Суть в том, что в логическом представлении вы строите страницы, так как хотите показать их юзеру, просто интерактивно перетаскивая контролы и индикаторы на панели (можно создавать визуализации под разные разрешения, скажем если вы хотите показать что-либо на планшете или смартофоне), а затем связываете их с переменными (это называется Binding, не знаю как лучше по-русски).
В живой разработке это выглядит как-то вот так:
Там много чего можно сделать, например бросить на панель видеоплеер и развлекать оператора видосиками:
Тут же я, не мудрствуя лукаво, просто набросал цветных кружочков да численный индикатор, и буду дёргать свойства видимости. Вся разметка хранится в XML файле, что хорошо с точки зрения контроля версий.
Ну а связи (Bindings) между переменными и значениями или свойствами индикаторов и контролов прописаны в конфигурационной вкладке, там тоже XML:
По сути тут применяется MVVM, всё достаточно разумно. Если вы работали с WPF или Авалонией, то вам всё будет весьма близко и знакомо.
После загрузки этого проекта в ПЛК (или в симулятор) он поднимет вам веб-сервер на 81-м порту (это настраивается само собой) и вот:
Технически там адский коктейль из JS, CSS и HTML5. Кстати, самый "беспроблемный" браузер — это как ни странно Vivaldi, в Файрфоксе и Хроме вёрстка бывало слегка "уезжала", а вот Вивальди — ОК, кроме того Вивальди несложно установить в Stand Alone, там удобный оффлайн инсталлятор, у меня всегда запущено две копии из разных папок, и открыто два окна, потому что если в одной сессии открыть веб сервер диагностики, то сервер mappView в той же сессии открыть не получится, там куки отчаянно дерутся друг с другом.
В случае, если в системе установлен монитор B&R, то оно работает ровно также, только монитор включён в проект, но по сути в мониторе встроен одноплатник на линуксе с веб-браузером (к нему можно и клавиатуру с мышкой подключить), он коннектится к ПЛК точно также как и обычный клиент и просто показывает веб-страничку (управлять тоже можно, он с тач-скрином).
Вот он с обратной стороны, кстати:
IDE
Вообще говоря, Automation Studio — одна из самых ужасных IDE, которую я когда-либо держал в руках, довольно "тормозная", даже на быстром компьютере. В четвёртой версии там был жутко раздражающий баг, проявляющийся в том, что при определённом стечении обстоятельств и интенсивном использовании препроцессора эта среда могла почикать все отступы, превратив код в сплошную простыню операторов, причём это могло произойти при открытии или сохранении проекта. Это так отрабатывала опция "умного отступа". Баг пофиксили, но осадочек остался, и хотя в шестой версии всё стало лучше, но автоматические отступы время от времени "промахиваются" мимо правильных уровней вложенности. Сравнивать редактор AS с MSVS — это всё равно что сравнивать гусеничный трактор с Теслой.
Во время интенсивной работы над кодом я даже время от времени переходил на VS Code. В принципе Си/С++ в проекте лежат как нормальные файлы, их без разницы где редактировать, хоть в Notepad. Само собой в системе контроля версий всё хорошо, хоть в Git, хоть в TFS, чего не скажешь о графических языках из списка МЭК 61131-3.
Вызывать компилятор, кстати, можно и из командной строки, как-то вот так:
<Путь куда установлена AS>\BR.AS.Build.exe" <проект>.apj -all
Опции командной строки
Usage: BR.AS.Build.exe file... [options]
Options:
-h / -? Displays this information
-c <config> Name of the configuration to be built
-t <directory> Temporary directory
-o <directory> Output directory
-all Project rebuild (cleans the binary and parts of the temporary files)
-profile Display profiling information
-X Create cross reference only data
-clean Cleans the Binary and parts of the Temp folder
-clean-temporary Cleans the Temp folder
-clean-binary Cleans the Binary folder
-clean-generated Cleans the Temp\Includes and Temp\Archives\<ConfigName> folder
-clean-diagnosis Cleans the Diagnosis folder
-cleanAll Cleans the Temp, Binaries, Diagnosis and the rest of temporary folders
-buildMode "<mode>" Defines the mode of the build ("Build", "BuildAndTransfer", "BuildAndCreateCompactFlash")
-simulation Flag indicating if a simulated configuration should be built
-buildRUCPackage Flag indicating if a RUC Package should be created during build
С этим уже можно жить, сделав билд-спецификацию в том же VS Code. Но отладка и настройка — тут само собой — только в Automation Studio.
Заключение
Вот собственно и всё, что я хотел рассказать. Надеюсь вы не зря потратили время и узнали для себя что-то новое. Самое главное, наверное, это то, что современные АСУ ТП — это не только про довольно устаревшие релейные схемы, но и достаточно (относительно, скажем так) современные технологии. И да, знание C++ вполне может пригодиться, инженеру АСУ ТП в том числе.
Тема на самом деле очень обширная, к сожалению за кадром осталась отладка — да там, можно ставить точки останова и выполнять программу пошагово, работает через gdb (кто бы сомневался при наличии gcc компилятора), также есть юнит-тестирование, настройка и конфигурация безопасной части (там отдельная IDE — SafeDESIGNER), довольно увлекательно работать с моторами. Возможно даже кого-нибудь этот пост мотивирует начать карьеру АСУ ТП программиста.
Исходники проектов, что были показаны выше, я закинул на Гитхаб, вдруг кому да пригодится.
Всех с наступающим Новым, 2025 Годом и Рождеством!
Комментарии (27)
ednersky
20.12.2024 13:55void _CYCLIC ProgramCyclic(void)
А неколбечное программирование независимых процессов доступно?
Аля асинхронное программирование в вебе, чтобы писать так:
void Led1_PROCESS(void) { for (;;) { LED1 = !LED1; Sleep(0.11); } }
И чтобы оно вот в местах где вызов вроде Sleep или более общего
yield/schedule
переключало бы управление на такого же соседа?AndreyDmitriev Автор
20.12.2024 13:55Не, так, к сожалению не получится. Тут всё завязано на циклическое выполнение.
Вот смотрите, допустим ПЛК крутит 100 мс цикл (ну то есть десять циклов в секунду), а мы в нашем коде хотим что-то делать только один раз в секунду. Sleep(1) тут не покатит (даже если мы насильно попытаемся задержать исполнение, то ПЛК вывалится в ошибку).
Делают примерно вот так:
#include <AsIecCon.h> // Заголовок для МЭК 61131-3 функций TON_typ myTimer; // собственно таймер void _INIT ProgramInit(void) { Counter = 0; myTimer.IN = 1; myTimer.PT = 1000; // 1 секунда задержка } void _CYCLIC ProgramCyclic(void) { // Циклически вызываем таймер TON(&myTimer); myTimer.IN = 1; // Стартуем и работаем пока не прилетит Q myTimer.PT = 1000; // 1 сек, можно менять от цикла к циклу ElapsedTime = myTimer.ET; // тут лежит прошедшее время // Проверяем, что время таймера пришло: if (myTimer.Q) { Counter++; // Тут что-то делаем раз в секунду // Сбрасываем таймер myTimer.IN = 0; } }
Ну и вот:
И так со всем функциями, где требуется таймаут или ожидание.
Допустим, я коннекчусь к какому-нибудь серверу, да хоть к OPC UA, а ПЛК выступает клиентом. У "нормальных" программистов грубо говоря есть функция connect(IP, Port, timeout), и исполнение будет задержано на время таймаута, a здесь добавляется выход Busy, и connect будет всегда завершаться мнговенно после вызова, просто он будет держать выход "занято" активным до тех пор, пока не установит соединение (либо наступит таймаут), и его в цикле надо (именно надо) постоянно опрашивать.
Мне как-то раз кучу таких фунций надо было вызвать (там было много разных OPC UA Methods Call), так я препроцессором в некоторой степени выкрутился, спрятав туда проверку на "занято", код просто компактнее выглядел, но разворачивался примерно в то, что я выше показал.
ednersky
20.12.2024 13:55Не, так, к сожалению не получится. Тут всё завязано на циклическое выполнение.
ну и в том случае, о котором я говорю, всё завязано на циклическое выполнение, просто цикл обрабатывается не в колбеке а переключаясь с fiber на fiber в те моменты когда изнутри них вызывается функция schedule.
Я когда-то писал подобную библиотеку для AVR, правда до ума не довёл (жизнь как-то отвернула от ардуин и вообще этой темы). Была статья на хабре, но теперь она есть только здесь: https://unera.net/all/2017/11/22/arduino-fibers.html
sleep, который в этом примере получается условно бесплатный. пока он "выполняется" на самом деле работает другой "процесс".
смысл в чём, когда мы мигаем светодиодом, то это просто. А когда мы выполняем сложную логику в колбеке, то приходится возиться со стейтмашиной. При программировании же внутри файберов о стейтах можно не думать: знай вызывай себе около мест, где требуется что-то подождать переключатель контекста, чтоб дать другим процессам поработать.
ну вот, я думал, что подобные вещи в профи решениях давно кто-то сделал. Ибо программирование с ними банально удобнее.
AndreyDmitriev Автор
20.12.2024 13:55А, теперь понял. Тут я не буду утвержать, что что-то похожее (или хтя бы внешне похоже выглядещее) сделать невозможно, в библиотеке sys_lib там есть пара фукций, которыми можно попридержать/возобновить исполнение циклической программы:
В общем надо будет попробовать, я этими функциями вообще никогда не пользовался.
ednersky
20.12.2024 13:55в общем, если есть возможность перейти от колбеков к процессам, то это сильно упрощает написание СЛОЖНЫХ программ
tonyk_av
20.12.2024 13:55В ПЛК есть SFC, зачем извращаться с написанием конечных автоматов на С++? Подозреваю, что в вашей реализации нет всего функционала, который даёт SFC для реализации шагового программирования, что делает ещё менее понятным смысл расписывать вашу реализацию КА на С++.
И да, стандарты на функциональную безопасность разрешают использовать для задач управления языки С/С++ с рядом оговорок, о чём я в статье не заметил упоминаний.AndreyDmitriev Автор
20.12.2024 13:55О, это очень хорошее замечание, спасибо. Мне подумалось, что С++ привносит определённую "гибкость", ведь там, пользуясь последними стандартами можно реализовать практически любой мыслимый функционал. Я (как программирующий в том числе на такой "эзотерике" как LabVIEW), вообще считаю, что язык программирования — это всего лишь язык и везде можно реализовать одно и то же, просто вопрос времени. Где-то одно удобнее, где-то другое. В конечном итоге итоге это может снизить общее время разработки при наличии достаточно гибкого и универсального фреймворка (мы ведь можем и свои библиотеки создавать). Что касается стандартов, диктующих применение тех или иных языков для задач управления либо налагающих ограничения, то я, к сожалению, не в той области, где эти стандарты строго применимы, в автомобильной промышленности всё достаточно "свободно". Впрочем мы делали системы и для проверки деталей турбин и лопаток, и даже там ограничений в требованиях не налагалось. Иногда заказчик требует определённый тип или производителя ПЛК, это да, но это лишь для того, чтобы избежать зоопарка железа — если у него всё предприятие на Сименсе, то "подарок" в виде B&R ему совершенно не нужен, так то ему совершенно без разницы, FB там или SFC или что ещё, хоть бейсик. Киньте пожалуйста, если не трудно, в комментарии номер стандарта на функциональную безопасность, я с удовольствием полистаю на досуге.
Fredp
20.12.2024 13:55Почитал. Еще больше уверился, что нужно даже ST запрещать для ПЛК, не говоря про С. Увеличивает требования к персоналу, уменьшает читаемость кода. Практически нет шансов новому электронщику быстро понять программу. А это не просто редкость, это норма. за 2-3 года персонал АСУ на мелких предприятиях обновляется полностью. Иногда и быстрее. А если нужно решать обратную кинематическую задачу, ну так тут PAC и электронщику там все равно понимать нечего...
P.S. У автомобилистов misra С про безопасность...
AndreyDmitriev Автор
20.12.2024 13:55Да не надо ST запрещать, он через какое-то время помрёт сам собой. А представляете, если бы персонал АСУ все как один знали паттерны из "банды четырёх", книги Роберта Мартина, вот это всё? На самом деле проблема существует, да, запустив код на С++ в ПЛК можно легко придти к ситуации "тут больше ничего не трогай, а то сломаешь". Вообще говоря я сам являюсь "адептом" графического и визуального программирования, беда в том, что в настоящий момент просто нет такого языка "общего назначения". Есть LabVIEW, есть то, что используется в ПЛК, но это всё довольно узкоспециализированные "нишевые" продукты. Хотя я бы хотел иметь LabVIEW в ПЛК, я в позапрошлом году программировал на LabVIEW Real-Time cRIO c FPGA на борту, это было круто, но это ни разу не промышленный ПЛК. Моё мнение таково, что в очень далёкой перспективе текстовые языки уйдут в прошлое, и программирование, в том числе и автоматики не будет таким, каим мы его видим сейчас. А так сейчас в промышленном секторе не то чтобы "застой", но такое "плато" — за последнее время мы в основноем переехали с Profibus на Profinet, с OPC на OPC UA, да ещё c PNOZ на Safety ПЛК, в остальном всё осталось как прежде. А тут какая-никакая свежая струя — игрушка с почти свежим gcc на борту.
За misra С спасибо, я читал стандарт NASA (Jet Propusion lab вроде), но только сейчас узнал, что он как раз на этом основан. С нас не требуют, но у нас другие сертификации типа NADCAP и стандарты ASTM, но это совсем другое.
tonyk_av
20.12.2024 13:55В промышленности нужна не свежая струя не пойми чего, а стабильные, проверенные временем решения, к тому же совместимые друг с другом, что не возможно без соблюдения стандартов.
И да, немецкие автоконцерны стали внедрять Codesys для программирования бортовой электроники автомобилей. Надеюсь, понимаете, почему такое происходит.AndreyDmitriev Автор
20.12.2024 13:55Да, я прекрасно понимаю, я кручусь в этой области больше двадцати пяти лет (хотя я чуть из другого лагеря - мы приехали, запустили и уехали). Но тем не менее я б не стал называть С/С++ "не пойми чего", даже Ассемблер с LabVIEW не стал бы.
В промышленности всё довольно консервативно, я это вижу практически, это да. Но если посмотреть на мейнстримные, более-менее активно развивающиеся языки программирования, те же С++/С#, да хоть Питон, то станет ясно, что их развитие диктуется постоянно увеличивающейся сложностью систем, которые на них реализуют. В общем это называется "технический прогресс". Он может временами заруливать "не туда", но если попытаться реализовать на чистейшем Си то, что легко делается сейчас на высокоуровневых языках, то местами получится чудовищная цикломатическая сложность. Все эти ООП с синтаксическими сахарами — это просто борьба с этой сложностью.
Что касается автомобилей — у меня довольно навороченный гибрид как раз с мотором из видео, и вот логика взаимодействия с пользователем порой вызывает у меня желание поговорить по душам с дизайнером HMI — ты сам-то ездил на том, что ты разработал? Codesys тут не помогает. У вас в руках современнейшие средства разработки, ну сделайте удобно, но нет.
А, по поводу "не пойми чего" у меня есть пример. Короче, это был где-то 2002 год, я был молодой и горячий, мой начальник — тоже. Короче, повертев в руках PCI карту Profibus CP5613, он выяснил, что входы/выходы с ET200 можно читать прямо через OPC, безо всякого ПЛК. При этом если взять однопользовательский инлайн сервер OPC.SimaticNET.DP (кажется так), то ещё и лицензия не нужна. Он просил меня найти способ писать/читать Dual Ported RAM карты вообще в обход OPC, но тут я вежливо отклонил его инновации. Короче сказано — сделано. LabVIEW 6.1, DataSocket, благо поддерживает OPC и в путь. На заводе надо было видеть глаза главного инженера завода по АСУ. Подходит он к шкафу — видит кабель Profibus, видит ET200, не видит ПЛК. Спрашивает — а где ПЛК-то? Я ему говорю — а нет его здесь. Он видит комп, чуть морщится и говорит — "а, понял, там сименсовский софт ПЛК". Я ему говорю — не, там вообще нет ПЛК, СКАДА на LabVIEW напрямую управляет всей системой. Он смотрит на бодро движущийся конвейер, а вглазах немой вопрос "Как это вообще работает?". Экономия вылилась боком — почти весь бюджет был съеден постоянными командировками дорогостоящих специалистов, потому что обслуживать и модифицировать этого монстра ни сервис ни тем более АСУ отдел завода не могли. Вот это — "не пойми чего".
Хотя я вот, пока писал этот коммент посмотрел описание программного контроллера SIMATIC S7-1500 - там с первой же страницы Сименс задвигает про С++, так что процесс идёт
А пост — он имеет больше развлекательно-информационный характер, нежели руководство к действию.
jmnemonik
20.12.2024 13:55Не нужна там никакая "свежая струя". Это Вам не айфон, который каждый год новый. В промышленности совсем иные требования и подходы.
AndreyDmitriev Автор
20.12.2024 13:55Я в курсе, выше я коммент написал, но вот тут я просто оставлю скриншот со странички про SIMATIC S7-1500 Software Controller:
Там с места в карьер:
Так что местами плюсплюсы в требования и подходы промышленности вполне себе вписываются. Но всё хорошо в меру, это да.
100h
20.12.2024 13:55Спасибо за статью! Хоть всё и знакомо, но приятно, чёрт возьми, прочитать истины в отличном изложении!
Сам работаю в проекте разарботки Safety PLC уровня SIL3 на базе Codesys.
almaz1c
20.12.2024 13:55Спасибо за статью, но разве B&R и Siemens сейчас не труднодоступны?
Нет ли у кого опыта эксплуатации/внедрения open source сред программирования ПЛК в рамках перехода с B&R, Siemens на аналогичные решения? 4DIAC, например?
AndreyDmitriev Автор
20.12.2024 13:55Спасибо за статью, но разве B&R и Siemens сейчас не труднодоступны?
B&R больше не поставляет оборудование, я видел пресс-релиз. У них, кажется, был офис в Москве, сейчас, вероятно, закрыт. Это, безусловно заметно снижает практическую ценность вышеизложенного материала, но тем не менее, мне подумалось, что кому-нибудь будет просто интересно, это так сказать популяризация автоматизации. Я с удовольствием читаю такие обзоры про любой ПЛК.
Что касается "труднодоступности" B&R и Siemens — я смею надеяться, что это не всегда будет так.
Indemsys
20.12.2024 13:55Так и не понял в чем преимущество программирования ПЛК на C.
На C программируют сложные глубоко встроенные системы. А ПЛК это про программирование только верхнего слоя и про масштабирование.
Если приходится вручную писать расчет SHA256 на ПЛК, значит неправильно выбран ПЛК. SHA считается уже давно аппартатно на STM32, ESP32 и прочей мелочи.
Если на ПЛК в демо-примере обрабатывается пара светодиодов, то тема ПЛК вообще не раскрыта.
ПЛК - это когда вы одним движением руки переходите от десятка сигналов к тысяче. И нужно лишь показать как это движение сделать и как увидеть лимит и не разрушить риалтайм. Нужна демонстрация в IDE масштабного рефакторига - одновременных операций над тычячами переменных таких как: переименования, мапирование на физические сигналы, редактирование типов и т.д.
Как тут поможет C непонятно.
Другое дело графические нотации типа Stateflow. У них хоть и нет нативной масштабируемости, но зато уникальная гибкость в рефакторинге архитектуры. Гибридная графическая нотация типа Stateflow гораздо легче воспринимается чем чисто текстовая.
AndreyDmitriev Автор
20.12.2024 13:55Преимущество Си может проявиться лишь на узком классе задач. Иногда вычисления удобно оформить именно сишной библиотекой. Тема, которую я хотел раскрыть - это поддержка свежих С++ стандартов, что добавляет определённую скажем так, степень свободы. Ну а использовать или нет - это от задачи зависит, мы ж не используем крестовую отвёртку там, где нужен шестигранник. ПЛК это не всегда тысячи сигналов, в нашем случае их несколько десятков, но временами используются хитрые манипуляторы плюс безопасность.
Indemsys
20.12.2024 13:55Просто есть подозрение, что задачи тут не при чем. А речь о компетенциях.
Почему бы просто не признать, что вот при таком уровне компетенций и ресурсах мы выбираем вот такой ПЛК, других выбрать не могли, но нет времени на освоение ST и всех либ с API, поэтому еще и C используем.
Не вижу ничего в этом плохого, нормальная экономика в условиях ограничений.
Но маскировать одну причину некоей абстрактной свободой или некими задачами, это знаете ли так себе. Описали бы тогда вашу задачу. А то ни то, ни сё. Неинформативно.
Как выглядят кривые интерфейсы разных IDE полагаю и так все знают.
ednersky
20.12.2024 13:55Другое дело графические нотации типа Stateflow.
ой ну нет, я посмотрел в код, что там нагенерировался и там ужас-ужас. Си гораздо читабельнее
а оставаться в пределах возни мышкой и без правок руками всяко не выйдет. далеко не всегда будет выходить
Indemsys
20.12.2024 13:55Раньше так про С говорили те, кто писал на ассеблере. А до них про так про ассемблер говорили те, кто набивал перфокарты. А до них про перфокарты так говорили те, кто считал на счетах. Угадайте что говорили о тех кто считал на счетах те, кто ...
Nansch
20.12.2024 13:55Пробовал B&R. Оборудование дешевле, но программистских человекочасов сильно больше. Невозможно там быстро разрабатывать, а дописывать код онлайн и сразу дебажить вообще тормоза. Сименс теперь тоже тормозной в разработке из-за неповоротливого тиапортала. И только Studio5000 от Rockwell оперативней всех. Разве что FTView не дотягивает до WinCC...
AndreyDmitriev Автор
20.12.2024 13:55Полностью согласен. Ценник был основной причиной переезда с Сименс, но среда разработки местами раздражает, да и АСУ отдел стал больше. Единственно - Motion мне чуть удобнее показался - если автоматизировать, скажем сырорезку, где режущий нож синхронно с конвейером работает, то там просто пара щелчков и готово, либо если по хитрым траекториям перемещаться. Но я давно Сименс не смотрел, вот свежий двадцатый тиапортал хочу как раз на рожденственских каникулах погонять.
alex_ak1
20.12.2024 13:55А как происходит общение по сети?
Обычным образом, поднимает сокет, если открылся он (от клиента), то начинаем читать-писать?
Или как-то можно штатно и без особых костылей например получить доступ до переменных?AndreyDmitriev Автор
20.12.2024 13:55В общем да, есть библиотеки, ПЛК может быть как сервером так и клиентом, я попробую на досуге набросать примерчик связи между Питоном и ПЛК. Делал такое на Сименсе, положил кучу времени (там был нужен хитрый протокол), тут должно быть попроще. Для доступа к переменным OPC UA вполне себе штатно. Либо если сокет открыт, свой протокол сделать, либо как вариант сдёрнуть и отреверсить протокол обмена между AS и ПЛК (если он не шифрован), но тут я не копал. У Сименса вот сделали реверсинжиниринг, но в продакшн с этим я б не пошёл.
vilgeforce
Я не настоящий электронщик, но вроде да - опторазвязка стоит в количестве