
Расскажу о подходе к оживлению цепей и функциональных блоков в своей среде разработки под ПЛК. Я не использую никаких графических фреймворков и библиотек — это как раз тот случай, когда самому сделать быстрее, чем изучать что-то новое. Все функциональные блоки программы, отображаемые в среде на ПК, рисуются примитивами (прямоугольниками, линиями, точками, текстом и т. д.).
На этапе проектирования платформы я сразу поставил перед собой цель — добиться максимальной гибкости: пользователь может сам нарисовать функциональный блок, обозначить его сигналы и описать поведение в цепи. Для этого я создал шаблон:
<LD_Unit Name="SEND" Group="Message" Width="120" Height="80"> <Variables> -----Входящие сигналы/Переменные----------- </Variables> <types> SEND typedef { Структура } </types> <lists> Линия ------- Квадрат------ Текст-------- </lists> <code> <Line> ------------------------- КОД ----------------------- </Line> </code> </LD_Unit>
Главное - те примитивы которые будут анимированы, привязываются к переменным:
<Item Type="LINE" color="#ffffff" Style="onoff:#96C1FF"> <Value>this</Value> ... После запуска проекта, Item Type="LINE" - конвертируется в обьект.
Style="onoff:#96C1FF" как раз и указывает, что делать: если значение переменной больше нуля, меняются цвет, положение, текст и другие эффекты.
Компилятор.
Компилятор собственной разработки, а не на базе LLVM/Clang или подобных конструкторов. Всю логику расстановки переменных и инструкций в памяти я разрабатывал сам. Вместе с программой для ПЛК создается файл наподобие .elf:
--- DISASSEMBLY & BINARY CODE --- Instruction [Offset] | Binary Representation (MSB..LSB) ------------------------------------------------------------------------ 0] LDRI R3 Address: 818 | 00000110 00110000 00000011 00110010 4] LDPHY R3 R3 R0 | 00001001 00011000 11000000 00000000 8] SDRI R3 Address: 814 | 00001101 00110000 00000011 00101110 12] MOV R0 R28 R0 | 00111100 00000111 00000000 00000000 16] SDRI R0 Address: 818 | 00001101 00000000 00000011 00110010 --- SYMBOLS & VARIABLES MEMORY MAP --- Address | Name | Type | Size -------------------------------------------------------------------------------------- 818 | system.AddrTime | dword | 4 814 | system.Time | dword | 4 778 | system.IN_phy | pointer | 4 798 | system.emodule_.DI_0 | byte | 1 800 | system.emodule_.DI_2 | byte | 1 804 | system.emodule_.DI_6 | byte | 1
--- SYMBOLS & VARIABLES MEMORY MAP --- Address | Name | Type | Size -------------------------------------------------------------------------------------- 818 | system.AddrTime | dword | 4 814 | system.Time | dword | 4 778 | system.IN_phy | pointer | 4 798 | system.emodule_.DI_0 | byte | 1 800 | system.emodule_.DI_2 | byte | 1 804 | system.emodule_.DI_6 | byte | 1
Вместе с человекочитаемым текстом формируется файл для моей среды разработки:
#####XML_SYMBOLS_BEGIN##### <?xml version="1.0" encoding="UTF-8"?> <symbols> <symbol name="system.AddrTime" type="dword" address="818" size="4"/> <symbol name="system.Time" type="dword" address="814" size="4"/> <symbol name="system.IN_phy" type="pointer" address="778" size="4"/> <symbol name="system.emodule_.DI_0" type="byte" address="798" size="1"/> <symbol name="system.emodule_.DI_2" type="byte" address="800" size="1"/>
Здесь содержатся имя переменной, ее тип и адрес. Это позволяет не только отправлять команды на запись значений в переменные, но и читать их:

Теперь, когда компилятор вернул нам регион памяти переменных, мы отправляем команду на МК — просто прислать этот регион (а память у нас виртуальная, обычный массив uint8_t memory[size]).
Получаем в среде этот регион из массива, и среда уже понимает, какая переменная за что отвечает, а движок отрисовки на основе этих значений знает, что делать.
В целом графика выглядит так:
Очень легкая среда, которая даже в режиме отрисовки занимает в ОЗУ всего 120 МБ и может работать даже на первых версиях Raspberry Pi (среда разработана с использованием OpenJDK).
Комментарии (5)

Razumdom
03.11.2026 14:10А на каком микроконтроллере собран ПЛК? Пока я в тексте увидел только Raspberry Pi, а это скорее микропроцессор.

zoshytlogic Автор
03.11.2026 14:10Тут тесте на видео: stm32gf722 в режиме 64MHz. Но требования 5кб ОЗУ и 32КБ флеш. Долгое время тестировал на stm32g030 но Nucleo удобная при отладке, и я использую F7 в режиме 64MHz

Razumdom
03.11.2026 14:10Я тоже делал свой ПЛК на микроконтроллере. Используется GD32F407VE. Все программирование в нем же в WEB интерфейсе, работает на чистом js.
Хотел понять как сделано у Вас. Насколько я понял у Вас на ПК компилятор в 3o|||sheet IDE. А у меня в самом модуле интерпретатор. Интересно было бы подробнее узнать про Ваш компилятор собственной разработки
zoshytlogic Автор
Да, каждый функциональный блок, вместо себя при компиляции подставляет - код, текст, и все блоки превращаются в сплошной текст программы. А там уже компилятор - расставляет каждую переменную в массиве uint8_t memory[] после инструкций (байткода). При отрисовке функциональный блок подставляет - название переменной\обьекта:
<Item Type="TEXT" color="#000000">
<Value>this</Value> при отрисовке, вместо "this" просто подставляется объект . Если надо отобразить переменную (значение) то пишется внутрь <Value>Var3</Value> например, а при отрисовке, Var3 - заменяется на значение переменной которая третья в структуре.