В прошлой статье 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)


  1. Anchomov
    04.11.2026 04:32

    "В прошлой статье я рассказал о своем подходе к..."
    Здесь ещё можно вставить ссылку на прошлую статью.
    И часть, как данные от внешних устройств превращаются в XML файл, можно кратенько где-то осветить для преемственности изложения, - пожалуйста.
    Я не критикую ( Боже упаси! ) , просто прошу, идея классная, сам хотел бы таким позаниматься, но не с нуля. Спасибо!


    1. zoshytlogic Автор
      04.11.2026 04:32

      Критиковать можно, даже выставляю посты на эту тему на p...bu, а там публика не подготовленная , читал всякое. На публике я хладнокровен :)

      Статьи просто пытаюсь не раздувать сильно текстом, со временем все исходники я раскрою полностью , как среды на ПК так и для железа.


  1. Siemargl
    04.11.2026 04:32

    XML осуждаю. Слишком много визуального шума.

    Сошлюсь на практику мировых вендоров, у которых программу можно править и в виде LD и в виде парного представления IL (с ограничениями конечно же) одновременно


    1. 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 не нужно, там просто текст без графики.


      1. Siemargl
        04.11.2026 04:32

        В простых случаев вызовов блоков - автолайоут.

        Сложные - как пример можно посмотреть Siemens cfc. Там автороутинг соединений а блоки можно перемещать по многолистовой схеме


  1. Make_Pic
    04.11.2026 04:32

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


    1. zoshytlogic Автор
      04.11.2026 04:32

      Я буду и дальше писать периодические посты как это работает. Если коротко:

      Компилятор переводит текст программы(собственный язык: си подобные структуры и данные, в инструкции как у ассемблера) в 32 битные единицы и нули.

      Массив этих байт (виртуальная прошивка) идет по uart в микроконтроллер в виртуальное ОЗУ ( массив uint8_t memory[size] - в нем все хранится, программа, данные, стек, буферы для связи с внешним миром, единый массив).

      Цикл wile() читает эти байты(инструкции) и вызывает обработчики : функция + -прыжок между инструкциями,мвсе как на физическом процессоре, только тут он программный нарисан на си (есть еще и на FPGA).

      Но цикл while() не простой, там есть постоянное чтение флага состояния процессора:

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

      Тут много нюансов есть.

      В этом массиве под виртуальное озу: вы можете выделить память под буфер для rs485 /ethernet.

      Интеграция с конкретным микроконтроллером необходима тоже если делать плк.

      У компилятора есть инструкция extfn, если нужно что то особенное (как например ethernet) компиялтор (то есть среда на ПК) будет вставлять эту инструкцию в ваш нарисованый функциональный блок, а микроконтроллер будет вызывать функцию на си, которую вы внедрили в свой плк.

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

      Тут главное для понимания не путать нативную прошивку (,который ставит производитель плк на заводе) с виртуальной (программы плк: ST, LD, FBD)


      1. Make_Pic
        04.11.2026 04:32

        Идет у вас почти по моим аналогичным планам во всяком случае то что описали, контроллер то же на FPGA, разница только в компиляторе и среде программирования софте. С интересом буду ждать продолжения цикла статей. Rasberry под линуксом?