CODESYS V3.5 для enterprise-разработчика - АиП

CODESYS V3.5 для enterprise-разработчика

Первые программируемые логические контроллеры (ПЛК), появившиеся в конце 60-х годов прошлого века, представляли собой замену шкафов релейной автоматики. Их задачей являлось формирование нужного состояния дискретных выходов в зависимости от состояния дискретных входов. Современные контроллеры представляют собой подвид промышленных компьютеров, выполняющих не только простейшую дискретную логику, но и гораздо более сложные операции – обмен данными с другими устройствами, архивирование значений технологических параметров в специфических форматах, запись значений в базы данных и т.д.

Другой тенденцией является всё более тесная интеграция систем автоматизации технологических процессов с системой управления предприятием (MES, ERP). Это приводит к тому, что всё чаще к программированию контроллеров привлекают не инженеров АСУ ТП, а «классических» программистов, привыкших использовать современные enterprise-языки: C++, C#, Java и т. д. Подходы, используемые при программировании контроллеров, могут при первом знакомстве показаться им непривычными. В данной статье мы расскажем, как enterprise-разработчику быстро освоить среду разработки CODESYS V3.5, сделав акцент на известных ему понятиях. Эта среда используется для программирования множества контроллеров – в том числе, для программирования линеек контроллеров ОВЕН ПЛК2хх и СПК1хх.

Среда разработки и система исполнения

CODESYS V3.5 – интегрированная среда разработки (IDE), которая включает редакторы языков программирования, редактор для создания графического интерфейса (GUI), компилятор, отладчик и другие компоненты. 

Созданный пользователем проект перед загрузкой в контроллер преобразуется в набор файлов, загружаемых в директорию PlcLogic, расположенную в памяти ПЛК. Главным среди них является файл с расширением .app, содержащий машинный код для конкретного процессора (для этого в CODESYS входят генераторы кода для разных платформ – Intel, ARM, Power PC и др.). Контроллеры ОВЕН используют архитектуру ARM и операционную систему OpenWrt, основанную на Linux.

Если необходимо использовать CODESYS IDE для программирования контроллера, то он должен включать в себя систему исполнения (runtime). Система исполнения входит в состав прошивки контроллера и «зашивается» в него на этапе изготовления.

Среда разработки является бесплатной для конечного пользователя; некоторые дополнительные компоненты (например, инструмент для интеграции с системой контроля версий Git) требуют лицензирования и приобретаются отдельно. Лицензия на систему исполнения оплачивается производителем контроллера. 

Таргет-файл

Чтобы создать проект в CODESYS, нужно обязательно указать контроллер, для которого он будет создан. Это связано с тем, что CODESYS должен «знать» характеристики контроллера (в частности, требуемый для него генератор кода). Эта информация содержится в таргет-файлах. Таргет-файлы обычно распространяются в виде пакетов (файлов формата .package); таргет-файлы контроллеров ОВЕН можно загрузить с сайта ОВЕН в разделе CODESYS V3/Сервисное ПО. В современных версиях CODESYS установка пакетов выполняется через утилиту CODESYS Installer, которая входит в состав среды разработки.

Механизм выполнения приложения

Контроллеры ориентированы на циклическое выполнение заложенных в них операций. Приложение контроллера выполняется в автоматически создаваемом средой бесконечном цикле, называемом задачей. В проекте может быть несколько задач с разными периодами вызова и приоритетами. Их настройки выполнятся в компоненте Конфигурация задач.
Это может быть непривычно для enterprise-разработчиков, привыкших организовывать подобный цикл в своем коде или использовать планировщики задач для вызова ПО. Поэтому следует учитывать, что весь написанный код будет вызываться циклически. Большинство контроллеров с CODESYS V3.5 (в том числе, контроллеры ОВЕН) используют вытесняющую многозадачность.


Стандарт МЭК 61131-3 и язык ST

Большинство контроллеров программируются на специальных языках, описанных в стандарте МЭК 61131-3. Стандарт определяет 5 языков:

  • LD, FBD, SFC – графические языки программирования;
  • IL – низкоуровневый текстовый язык, синтаксически похожий на язык ассемблера, не является «настоящим» языком ассемблера (то есть его команды не соответствуют командам процессора). В последней редакции стандарта этот язык признан устаревшим;
  • ST – единственный высокоуровневый текстовый язык, описанный в стандарте.

Наличие 5 языков программирования вызвано историческими причинами – первые несколько десятилетий развития контроллеров каждый производитель создавал собственный язык, и перед разработчиками стандарта стояла задача как-то ограничить их количество, учтя при этом интересы крупных компаний, уже вложивших множество средств в разработку своего ПО.
Для enterprise-разработчика естественным выбором будет язык ST (Structured Text). Это высокоуровневый текстовый язык, представляющий собой промышленный диалект языка Pascal. У него лаконичный и строгий синтаксис и типовой набор управляющих операторов – IF, CASE, циклы FOR и WHILE. Для примера сравним код для вычисления факториала, написанный на языках Java и ST:

class FactorialExample{  
 public static void main(String args[]){  
  int i,fact=1;  
  int number=5;// число, для которого вычисляется факториал 
  for(i=1;i<=number;i++){    
      fact=fact*i;    
  }    
  System.out.println("Factorial of "+number+" is: "+fact);    
 }  
}  

 

PROGRAM PLC_PRG
VAR
   i, fact:    INT := 1;
   number:     INT := 5; // число, для которого вычисляется факториал 
   str_fact:   STRING;
   DoCalculate: BOOL := TRUE;
END_VAR

IF DoCalculate THEN
  fact := fact * i;
  i := i + 1;
  IF i > number THEN
     DoCalculate := FALSE;
     i := 1;
     str_fact := CONCAT('Factorial of ', TO_STRING(number) );
     str_fact := CONCAT(str_fact, ' is: ');
     str_fact := CONCAT(str_fact, TO_STRING(fact) );
     fact := 1;
  END_IF
END_IF

Не вдаваясь в мелкие синтаксические нюансы, рассмотрим моменты, отличающиеся концептуально. Во-первых, напомним, что весь код программы контроллера выполняется циклически. Чтобы вычислять факториал только тогда, когда это действительно нужно, следует добавить команду булевского типа с именем DoCalculate. Так как код задачи выполняется циклически, то можно отказаться от цикла FOR, представив его в виде операции инкремент. Таким образом, этот код будет выполняться асинхронно, в течение нескольких циклов задачи; если бы использовали цикл FOR, то код выполнялся бы синхронно (подробнее про синхронное и асинхронное выполнение мы поговорим ниже). Во-вторых, вывод в лог, традиционно используемый в других языках для отладки, при программировании контроллеров используется редко (лог контроллера используется системой исполнения контроллера для вывода сообщений об исключениях и ошибках в его работе); вместо этого значения переменных удобно наблюдать в IDE в режиме отладки – они отображаются во всех фрагментах кода, где эти переменные используются. В примере на ST сформировано текстовое сообщение (как в исходном примере на Java) и записано в текстовую переменную str_fact. Перегрузка оператора «+» для объединения строк в языке ST не поддерживается; вместо этого используется функция стандартной библиотеки CONCAT, которая позволяет объединить две строки (у этой функции фиксированное число аргументов, так что объединить более двух строк не получится). Не поддерживается и неявная конверсия типов, поэтому мы приводим INT к STRING с помощью оператора TO_STRING.
Объявление переменных происходит на отдельной панели текстового редактора; объявлять переменные в области кода нельзя.
ST (как и другие языки стандарта МЭК 61131-3) – компилируемый язык со статической типизацией и статическим выделением памяти. Строго говоря, CODESYS предоставляет средства для динамического выделения памяти, но они используются крайне ограниченно – в основном при разработке библиотек, а не пользовательских приложений. Это связано с тем, что основные требования к приложению контроллера – быстродействие и надежность. За них приходится расплачиваться существенно меньшим уровнем гибкости по сравнению с тем, что предоставляют современные enterprise-языки.
В основном приложения для контроллеров разрабатываются в парадигме структурного программирования. CODESYS – это одна из немногих сред разработки, которая помимо этого предоставляет средства для использования объектно-ориентированного подхода (ООП). Реализация ООП похожа на язык Java и использует те же ключевые слова: INTERFACE, EXTENDS, IMPLEMENTS, SUPER, THIS и т.д. Но широкого распространения ООП в задачах программирования контроллеров не получил. Примеров и документации на эту тему крайне мало. В основном ООП используется самими разработчиками CODESYS, например, он активно применяется в библиотеках GUI и протоколов обмена.

Программирование на ST близко к разработке на ANSI C для embedded-устройств – здесь тоже часто приходится работать с битами, заниматься сериализацией/десериализацией байтовых массивов и т.д.

В CODESYS присутствует стандартный набор типов данных (числа, строки, специальные типы для работы с датой и временем, массивы, структуры, перечисления) и операторов. Для работы с памятью «напрямую» поддерживаются указатели. 

Программные объекты

Приложения CODESYS включает различные программные объекты (POU). Существует три типа объектов POU:

  • программы – высокоуровневые программные объекты, привязываемые к циклически вызываемым задачам контроллера. Самое близкое к ним понятие из enterprise-языков – это точки входа в пользовательский код (функция main() и т. п.). Программы являются верхним уровнем абстракции приложения – они соответствуют крупным фрагментам технологического процесса (например, «управление вентиляцией», «обработка тревог» и т.д.).
  •  функции – аналогичны функциям в других языках программирования;
  •  функциональные блоки – аналоги классов из современных языков программирования. Для использования функциональных блоков нужно объявить их экземпляры (инстансы); для каждого экземпляра выделяется своя область памяти. Ключевое отличие функциональных блоков от функций в том, что переменные блоков сохраняют свои значения между вызовами. Соответственно, функциональные блоки используются для создания счетчиков, таймеров и других объектов, которым требуется сохранение данных. 

Библиотеки

Среда CODESYS включает расширенный набор библиотек с дополнительными функциональными блоками и функциями – генераторами импульсов, регуляторами, блоками чтения/записи файлов и т.д.
Библиотека CAA Memory в CODESYS важна для тех, кто привык программировать на языке C и использовать функции из string.h (memset, memcpy, memcmp и т. д.). 
Библиотеки, разработанные компанией ОВЕН, доступны на сайте ОВЕН в разделе CODESYS V3/Библиотеки и компоненты. Большинство этих библиотек поддерживается только ПЛК ОВЕН.
 

Синхронное и асинхронное выполнение

Приложение контроллера выполняется в циклически вызываемой задаче (одной или нескольких). Ключевые требования к приложению контроллера – быстродействие и надежность. То есть каждая итерация этого бесконечного цикла должна занимать как можно меньше времени, и это время должно быть детерминированным (то есть не меняться от итерации к итерации).
Раньше в программировании ПЛК был широко распространен подход, при котором практически весь код приложения выполнялся синхронно. Характерное исключение составляют таймеры стандартной библиотеки – они выполняются асинхронно, не блокируя работу кода, расположенного после их вызова.

PROGRAM PLC_PRG
VAR
   xDi:        BOOL; // Сигнал от дискретного входа ПЛК
   xAlarm:    BOOL; // Сигнал тревоги
   fbDelay:    TON;  // Экземпляр таймера задержки включения
   i:        INT;
END_VAR

fbDelay
(
   IN := xDi,
   PT := T#5S,
   Q  => xAlarm
);
i := i + 1;

В примере выше приведена реализация задержки формирования сигнала тревоги (переменная xAlarm) при замыкании дискретного входа ПЛК (переменная xDi) с помощью таймера задержки (экземпляр fbDelay блока TON).
Фактически – вызов таймера происходит в каждом цикле задачи, но его код начнет выполняться только в тот момент, когда вход IN примет значение TRUE. Выход Q примет значение TRUE спустя 5 секунд, – и в течение этого времени инкремент переменной i продолжит выполняться, а не будет остановлен.
Многие системные библиотеки имеют синхронные и асинхронные варианты реализации. Рассмотрим, например, производственную линию по маркировке бутылок. Бутылки движутся по конвейеру, и контроллер должен успевать организовать наклейку этикеток без пропусков. При этом код, напечатанный на каждой этикетке, нужно сохранять в файл. Запись в файл может занимать непредсказуемое время (это зависит от времени доступа для конкретной модели flash-памяти, размера файла и т.д.), и поэтому если запись выполняется синхронно, блокируя остальные операции, то время этой итерации цикла может существенно увеличиться, и очередная бутылка будет пропущена. В этом случае разумнее производить запись в асинхронном режиме – тогда она займет несколько итераций цикла задачи, но время выполнения каждого из них будет сравнительно небольшим.

Синхронные библиотеки построены на использовании функций:

SysFile.SysFileWrite(hFile := hFile, ADR(sWriteData), SIZEOF(sWriteData), 
   ADR(dwResult) );
// этот код будет выполнен только после окончания записи в файл...
// ...потому что функция SysFileWrite выполняется синхронно
i := i + 1;

Асинхронные библиотеки построены на использовании функциональных блоков:

fbFileWrite
(
   xExecute := xWriteToFileCmd,
   hFile    := hFile,
   pBuffer  := ADR(sWriteData),
   szSize   := SIZEOF(sWriteData)
);

IF fbFileWrite.xDone THEN
   // здесь разместите код, который будет однократно выполнен после завершения записи
ELSIF fbFileWrite.xError
  // здесь разместите код обработки ошибок
END_IF
// этот код будет выполняться каждый цикл задачи
// в том числе, и во время записи в файл
// потому что экземпляр fbFileWrite выполняется асинхронно
i := i + 1;

В примерах выше опущено объявление переменных, открытие файла и т.д. – продемонстрирована лишь сама разница в использовании синхронных и асинхронных операций.
Также в примерах можно увидеть использование пространств имен (namespaces), характерное и для enterprise-языков. В CODESYS пространства имен присутствуют у библиотек, функциональных блоков, структур, перечислений и некоторых других объектов.


Отладка

CODESYS предоставляет удобные средства для отладки проекта. К ним относятся:

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

Интерфейс IDE

CODESYS предоставляет разработчику средства, без которых сложно представить современную IDE:

  • подсветка синтаксиса (с возможностью настройки цветов);
  • автодополнение и автоформатирование кода;
  • темная тема для редактора ST;
  • переименование переменной, затрагивающее все фрагменты, в которых она используется;
  • перекрестные ссылки для вызываемых объектов (cross references). 

Автоматизированные сборка и тестирование

Современный подход к разработке приложений неразрывно связан с методологией DevOps – непрерывной интеграцией и развертыванием (СI/CD), автоматизированным тестированием, хранением и управлением исходным кодом и т.д.

Программирование ПЛК пока что не пришло к этой методологии. Тем не менее, CODESYS поддерживает ряд отдельных платных плагинов, позволяющих частично решить эти задачи:

  • CODESYS Test Manager – инструмент для автоматического тестирования;
  • CODESYS Git – плагин для подключения к системе управления версиями Git;
  • CODESYS Static Analysis – статический анализатор для проверки соответствия кода заданному набору правил;
  • CODESYS Profiler – профилировщик, позволяющий оценить время выполнения конкретного фрагмента кода для определения «узких мест» в приложении и его последующей оптимизации. 

Кроме того, среда разработки позволяет создавать сценарии на языке Python для автоматизации рутинных операций (фактически, вы получаете API для выполнения команд меню, операций в дереве проекта и т.д.). Эти сценарии могут быть добавлены на панель инструментов в виде дополнительных ярлыков.

Доступ к ОС контроллера

Один из характерных вопросов, который возникает у enterprise-разработчиков при переходе к программированию ПЛК – как переиспользовать существующий код. Многие из современных ПЛК используют операционную систему Linux, поэтому возникает естественное желание установить какие-то пакеты, перекомпилировать и использовать свои C/C++ библиотеки, найти toolchain и т.д.
Большинство контроллеров не являются «открытыми», то есть пользователь ограничен средствами, которые предоставляет IDE, и не может напрямую взаимодействовать с операционной системой ПЛК. Это справедливо и для контроллеров ОВЕН.
Но для наших пользователей мы предоставляем два инструмента для решения специфических задач:

  • вызов утилит операционной системы контроллера с помощью библиотеки CmpSysExec. В состав прошивки входят хорошо известные разработчикам утилиты curl, jq, клиенты для СУБД MySQL/MS, SQL/PostgreSQL, ImageMagick и др. Разработчики ОВЕН готовы добавлять в прошивку новые утилиты по запросам пользователей;
  • написание скриптов на языке Python. Для этого языка существует множество библиотек, которые могут упростить выполнение нехарактерных для ПЛК задач (например, интеграцию с web-сервисами через REST API, работу с XML и т.д.). В состав прошивки контроллеров ОВЕН входит менеджер пакетов pip3. Вызов скриптов осуществляется через библиотеку CmpSysExec. Скрипты являются лишь дополнением к приложению CODESYS, но не могут служить его заменой. 

Документация и гайдлайны

Основными источниками по информации о CODESYS является онлайн-справка и документация ОВЕН.
Для enterprise-языков программирования характерно наличия гайдлайнов, best practices и т.д. Для программирования контроллеров, к сожалению, подобных гайдлайнов довольно мало. Самый известный пример – это гайдлайны от консорциума производителей ПЛК PLCopen.