
В прошлой статье https://habr.com/p/1103196/ я рассказал о своем подходе к разработке среды программирования промышленного контроллера — а именно о части, касающейся визуализации данных из цепей лестничных диаграмм и функциональных блоков, которые выполняются на стороне микроконтроллера. В этот раз подробнее остановлюсь на цепочке событий.
Как я уже упоминал, вся модель среды ПЛК (которую я разрабатываю) содержится в XML-файле. Этот файл включает в себя список функциональных блоков:
<LD_Unit Name="GTM" Group="Timer" Width="100" Height="55">

На основе этих данных среда (GUI-приложение на ПК) группирует блоки по вкладкам, используя тег <Group>. Внутри этих модулей (юнитов) также содержатся графические примитивы, которые определяют визуальный облик каждого блока.
<lists> <Item Type="RECT" color="#808080" Style="box" > <Value> </Value> <xy x="1" y="20" /> <dimension width="117" height="57" /> <stroke Stroke="1" /> <fill Fill="1" /> </Item>
Вся цепочка — от XML-строки до отрисовки блока на экране — выглядит следующим образом:

В параметры FBD-блоков я добавил тип Style="box". Теперь при очень длинных названиях в момент отрисовки функциональный блок автоматически расширяется под размер строки. Поначалу я хотел просто обрезать строки (как это сделано в типичных китайских средах разработки), но пришел к выводу, что подход небезопасен. Если элемент ссылается на поле сложной структуры — например, на элемент массива в самом конце строки, — то важную часть данных можно просто выпустить из виду, что неминуемо приведет к ошибкам:

После того как мы получили список примитивов, определяющих визуальный дизайн отрисовки функционального блока, мы применяем указанный в XML стиль - то есть, что этот примитив (линия, круг, текст) - должен делать с числом которое приходит из памяти микроконтроллера(ПЛК). Происходит это непосредственно перед рендерингом примитива (в моем случае код написан на Java):
private void drawLine() { gc.setColor(AnimFabric.parseColor(color)); if(Style!=null && sm!=null) { if(Style.contains("onoff")) { gc.setColor(AnimFabric.onOff(sm.getValue(),Style)); } } Graphics2D g2d = (Graphics2D) gc; g2d.setStroke(new BasicStroke((float) stroke.Stroke)); g2d.drawLine((int) xy.x+X, (int) xy.y+Y, (int) x2y2.x+X, (int) x2y2.y+Y); }
Класс AnimFabric содержит типы анимации, а объект sm — тег и данные, привязанные к этому примитиву.
Первоначально я хотел сделать среду более современной, но затем расставил приоритеты в пользу легковесности. Для этого я использовал стандартные классы OpenJDK — как для отрисовки графики, так и для дизайна GUI в целом. В итоге среда спокойно запускается даже на ранних версиях Raspberry Pi и требует всего 120–200 МБ ОЗУ. Таким образом, из слабого мини-ПК можно сделать полноценный самопрограммируемый промышленный контроллер.
Так же, еще раз процесс работы из прошлого поста:
Нативная часть для железа выполняющая программу в качестве ПЛК, требует на стороне микроконтроллера 5кб ОЗУ, и 32кб флеш. Работает на микроконтроллерах начиная с STM32G030, где после установки интерпретатора байт-кода, есть еще 2-3 Кб на программу чего достаточно чтоб превратить такой микроконтроллер в простое программируемое реле.
Комментарии (8)

Siemargl
04.11.2026 04:32XML осуждаю. Слишком много визуального шума.
Сошлюсь на практику мировых вендоров, у которых программу можно править и в виде LD и в виде парного представления IL (с ограничениями конечно же) одновременно

zoshytlogic Автор
04.11.2026 04:32Спасибо за комментарий! Напишу новую статью о том как реализовано , я взял за пример Allen Bradley , у них логика веток (построение) описывается типа :
"XIC XIC BST XIO XIC NXB XIC BND XIO XIO "
то есть, ветки обозначаются маркерами BST,NXB ,BND . У меня тоже самое - есть скрипт который это описывает подобным образом.
.xml в моих модулях он хранит всего лишь - отрисовку графики блока, внутри локального прямоугольника+ описание поведения ASM подобным синтаксисом.
А сами ветки в глобальном плане- ход программы - он описывается скриптом : какая инструкция за какой идет, и какие ветки где находятся.
Или еще аналогия: .xml хранит классы (библиотеки) как визуального вида так и функций программы. А скрипт хранит логику веток, схему, и комментарии.
Для текстовых языков типа ST - конечно никаких .xml не нужно, там просто текст без графики.

Siemargl
04.11.2026 04:32В простых случаев вызовов блоков - автолайоут.
Сложные - как пример можно посмотреть Siemens cfc. Там автороутинг соединений а блоки можно перемещать по многолистовой схеме

Make_Pic
04.11.2026 04:32Расскажите как реализован интерпретатор байт-кода, как организована компиляция в байт код, как связь с железом (ADC, DAC, DI, DO) осуществляется. Как разделены host = Rasberry и STM32G, Как осуществляется мониторинг и индикация - состояние портов и т.д. Связь с внешним миром - Ethernet, RS485, и т.д. Как делается логирование, сообщения об ошибках?

zoshytlogic Автор
04.11.2026 04:32Я буду и дальше писать периодические посты как это работает. Если коротко:
Компилятор переводит текст программы(собственный язык: си подобные структуры и данные, в инструкции как у ассемблера) в 32 битные единицы и нули.
Массив этих байт (виртуальная прошивка) идет по uart в микроконтроллер в виртуальное ОЗУ ( массив uint8_t memory[size] - в нем все хранится, программа, данные, стек, буферы для связи с внешним миром, единый массив).
Цикл wile() читает эти байты(инструкции) и вызывает обработчики : функция + -прыжок между инструкциями,мвсе как на физическом процессоре, только тут он программный нарисан на си (есть еще и на FPGA).
Но цикл while() не простой, там есть постоянное чтение флага состояния процессора:
Сработало прерывание? Ставим машину в режим : отдать данные на пк, иои сменить задачу (если несколько виртуальных задач работаеют) так же если происходит ошибка (например в лестнице программы плк, деление на ноль или еще что) ставим машину в режим ошибки и вызываем нужную функцию или си, или переводим счетчик команд виртульной машины на виртуальный обработчик (как это в языках ява или питон).
Тут много нюансов есть.
В этом массиве под виртуальное озу: вы можете выделить память под буфер для rs485 /ethernet.
Интеграция с конкретным микроконтроллером необходима тоже если делать плк.
У компилятора есть инструкция extfn, если нужно что то особенное (как например ethernet) компиялтор (то есть среда на ПК) будет вставлять эту инструкцию в ваш нарисованый функциональный блок, а микроконтроллер будет вызывать функцию на си, которую вы внедрили в свой плк.
Для пользователя это будет всего лишь еще один нарисованный функциональный блок, а для производителя плк, нужно будет еще написать функцию что с этими данными из этого блока делать.
Тут главное для понимания не путать нативную прошивку (,который ставит производитель плк на заводе) с виртуальной (программы плк: ST, LD, FBD)

Make_Pic
04.11.2026 04:32Идет у вас почти по моим аналогичным планам во всяком случае то что описали, контроллер то же на FPGA, разница только в компиляторе и среде программирования софте. С интересом буду ждать продолжения цикла статей. Rasberry под линуксом?
Anchomov
"В прошлой статье я рассказал о своем подходе к..."
Здесь ещё можно вставить ссылку на прошлую статью.
И часть, как данные от внешних устройств превращаются в XML файл, можно кратенько где-то осветить для преемственности изложения, - пожалуйста.
Я не критикую ( Боже упаси! ) , просто прошу, идея классная, сам хотел бы таким позаниматься, но не с нуля. Спасибо!
zoshytlogic Автор
Критиковать можно, даже выставляю посты на эту тему на p...bu, а там публика не подготовленная , читал всякое. На публике я хладнокровен :)
Статьи просто пытаюсь не раздувать сильно текстом, со временем все исходники я раскрою полностью , как среды на ПК так и для железа.