Библиографическое описание:

Поздняков А. Г., Аль-Тибби В. Х. Структура программного кода и практическое использование блока «Функциональный генератор» при программировании в среде CoDeSys // Молодой ученый. — 2014. — №5. — С. 88-93.

Введение, постановка проблемы. Современное производство предъявляет все более высокие требования к инструментам управления, контроля и автоматики. Персональные компьютеры применяются в настоящее время на всех уровнях промышленной автоматизации. Для большинства задач автоматизации применение промышленных ПК не оправдано экономически и технически сложно. Решать задачи автоматизации максимально просто, в кратчайшие сроки и с минимальным привлечением специалистов в области прикладного и системного программного обеспечения позволяют программируемые логические контроллеры (ПЛК). ПЛК — это программно управляемый дискретный автомат, имеющий некоторое множество вхо­дов, подключенных посредством датчиков к объекту управления, и множество выходов, подключенных к исполнительным устройствам. ПЛК контролирует состояния входов и вырабатывает опре­деленные последовательности программно заданных действий, от­ражающихся в изменении выходов. ПЛК предназначен для работы в режиме реального времени в условиях промышленной среды и должен быть доступен для про­граммирования неспециалистом в области информатики [1].

Программирование большинства современных ПЛК осуществляется на языках стандарта международной электротехнической комиссии (МЭК) 61131 [2]. Стандарт содержит описание аппаратных средств, требования к мон­тажу, тестированию, документации и связи по промышленной сети. Часть 3 стандарта описывает требования к языкам программирования, всего таких языков описывается пять: IL – ассемблероподобный текстовый язык; LD – графический язык релейно-контактных схем; ST – паскалеподобный текстовый язык; FBD – графический язык функциональных блоков; SFC – графический язык последовательных функциональных схем.  Для программиро­вания ПЛК не требуется знание всех пяти языков МЭК 61131-3, но выбор языка существенно влияет на способ реализации задачи.

В качестве компонентов организации программ в стандарте указываются функции, функциональные блоки и программы. Любой программный компонент обладает свойством инкапсуляции, т.е. работает как «черный ящик», скрывая детали своей реализации. Для работы с компонентом достаточно знать описание его входов и выходов, а особенности реализации внутреннего алгоритма работы знать необязательно. На базе свойства инкапсуляции программные компоненты позволяют осуществлять структурную декомпозицию проекта. На верхнем уровне функционируют крупные компоненты (программы), излишние подробности на этом уровне мешают комплексному пониманию проблемы. Изучая более мелкие (вложенные) компоненты, можно раскрыть детальное представление программной реализации алгоритма работы системы. Теоретически любой компонент можно изучить и исправить по своему усмотрению. Это, безусловно, относится только к открытым для просмотра и редактирования пользовательским компонентам или библиотекам.

Как правило, описание принципа реализации и алгоритма работы программных компонентов, включаемых даже в открытые библиотеки, производители инструментальных сред программирования ПЛК практикуют очень редко. Поскольку библиотеки открытые и бесплатные, то их использование и подробности функционирования отдаются на откуп пользователю.  Информация по внутреннему принципу функционирования таких компонентов весьма скудная и ограничивается справочными данными. Тем не менее часто возникает необходимость адаптировать функциональные возможности компонента под конкретную задачу с целью минимизации программного кода, оптимизации алгоритма его реализации и упрощения программы в целом. Поэтому актуальной является задача описания и анализа работы таких наиболее распространенных программных блоков, описываемых на языках стандарта МЭК 61131. 

3S CoDeSys [3] это одна из самых полных и удачно реализованных сред программирования МЭК 61131-3. CoDeSys обладает набором открытых функциональных библиотек, предназначенных для решения широкого круга наиболее распространенных задач автоматизации. Одна из таких библиотек - Util.lib содержит дополнительный набор различных функций и функциональных блоков, применяемых регуляторов, генераторов и преобразований аналоговых сигналов. Рассмотрим работу функционального блока (ФБ) GEN, входящего в состав данной библиотеки среды CoDeSys v2.3. Данный блок предназначен для генерации периодически изменяющихся сигналов различных предустановленных форм: треугольный (двуполярный и однополярный), двуполярный пилообразный (нарастающий и ниспадающий), меандр, синусоида и косинусоида.

Описание принципа работы функционального блока. Библиотека Util.lib имеет открытый программный код. Для доступа к коду функциональных блоков, входящих в библиотеку необходимо ее открыть из меню «Открыть» вновь созданного проекта. Листинг программы с авторскими комментариями и описанием используемых переменных представлен ниже.

(*функциональный блок для генерации некоторых периодических функций*)

FUNCTION_BLOCK GEN

(*Раздел описания переменных*)

VAR_INPUT (*входной интерфейс блока*)

MODE: GEN_MODE;

(*определение типа генерируемого сигнала:

TRIANGLE - сигнал треугольной формы от отрицательного значения амплитуды до положительного значения амплитуды сигнала.

TRIANGLE_POS - сигнал треугольной формы от нуля до положительного значения амплитуды сигнала.

            SAWTOOTH_RISE - пилообразный сигнал увеличивающийся от отрицательного значения амплитуды до положительного значения амплитуды сигнала.                                  

           SAWTOOTH_FALL - пилообразный сигнал увеличивающийся от положительного значения амплитуды до отрицательного значения амплитуды.                                             

RECTANGLE - сигнал прямоугольной формы от отрицательного значения амплитуды до положительного значения амплитуды сигнала.

            SINUS - синусоидальный сигнал.

COSINUS - косинусоидальный сигнал. *)

            BASE: BOOL;(* FALSE: период определяется как количество вызовов функционального блока; TRUE: период определяется заданным временем *)

            PERIOD: TIME:=T#1s;  (*время периода сигнала, учитывается только, если BASE=TRUE*)

            CYCLES: INT:=1000; (*число вызовов блока за период, учитывается только, если  BASE=FALSE *)

            AMPLITUDE: INT;            (*амплитуда сигнала*)

            RESET: BOOL; (*обнуление выхода и остановка генерации сигнала*)

END_VAR

VAR_OUTPUT (*выходной интерфейс блока*)

            OUT: INT; (*переменная генерируемого сигнала*)

END_VAR

VAR (*локальные переменные функционального блока*)

            CET: DINT; (*переменная, используемая для отсчета времени генерируемого сигнала (шаг дискретизации по времени)*)

            PER: DINT(*переменная, определяющая длительность периода генерируемого сигнала *)

            COUNTER: INT; (*счетчик вызовов блока*)

            CLOCK: TON; (*экземпляр таймера TON*)

            help: REAL; (*промежуточная переменная для вычисления значения синусоидального и косинусоидального сигналов*)

END_VAR

_____________________________________________________________________________

(* Программный код*)

IF RESET THEN

            COUNTER:=0;

            OUT:=0;

            CLOCK.PT := t#0s;

            CLOCK(IN:=FALSE);

ELSE

            IF BASE=FALSE THEN

                        COUNTER:=(COUNTER+1)MOD CYCLES;

                        CASE MODE OF

                        0:         OUT:=(CYCLES-2*ABS(CYCLES-2*COUNTER))*AMPLITUDE/CYCLES;

                        1: OUT:=(CYCLES-ABS(CYCLES-2*COUNTER))*AMPLITUDE/CYCLES;

                        2: OUT:=(2*COUNTER-CYCLES)*AMPLITUDE/CYCLES;

                        3: OUT:=(CYCLES-2*COUNTER)*AMPLITUDE/CYCLES;

                        4: IF 2*COUNTER>CYCLES THEN

                                               OUT:=-AMPLITUDE;

                            ELSE

                                               OUT:=AMPLITUDE;

                            END_IF

                        5:         OUT:=REAL_TO_INT(SIN(6.283167*COUNTER/CYCLES)*AMPLITUDE);

                        6:         OUT:=REAL_TO_INT(COS(6.283167*COUNTER/CYCLES)*AMPLITUDE);

                        END_CASE;

            ELSE                                                                                   

                        CLOCK;                                                                  

                        IF CLOCK.PT<>PERIOD+PERIOD THEN                      

                                   CLOCK.PT:=PERIOD+PERIOD;              

                                   CLOCK(IN:=TRUE);

                        ELSE

                                   CET:=TIME_TO_DINT(CLOCK.ET);

                                   PER:=TIME_TO_DINT(PERIOD);

                                   IF CET>=PER THEN                                             

                                               CLOCK(IN:=FALSE);

                                               CLOCK(IN:=TRUE);

                                               CET:=CET MOD PER;

                                   END_IF

                                   CASE MODE OF

                                   0:         OUT:=DINT_TO_INT((2*ABS(PER-2*CET)*AMPLITUDE+PER/2)/PER-AMPLITUDE);

                                   1:         OUT:=DINT_TO_INT(((PER-ABS(PER-2*CET))*AMPLITUDE+PER/2)/PER);

                                   2:         OUT:=DINT_TO_INT((CET-PER)*2*AMPLITUDE/PER+AMPLITUDE);

                                   3:         OUT:=DINT_TO_INT((PER-CET)*2*AMPLITUDE/PER+AMPLITUDE);

                                   4:         IF CET*2<PER THEN

                                                           OUT:=-AMPLITUDE;

                                               ELSE

                                                           OUT:=AMPLITUDE;

                                               END_IF

                                   5:         help:=SIN(6.283167*CET/PER)*AMPLITUDE;

                                               OUT:=REAL_TO_INT(help+0.5);

                                   6:         help:=COS(6.283167*CET/PER)*AMPLITUDE;

                                               OUT:=REAL_TO_INT(help+0.5);

                                   END_CASE;

                        END_IF

            END_IF;

END_IF;

Для отсчета отрезков времени функциональным блоком (в частности формирования шага дискретизации) используется таймер задержки на включение TON. Для лучшего понимания программного кода, написанного на языке ST, перейдем к алгоритмическому представлению программы в виде схемы (см. рис.1).

Рис. 1. Схема программы функционального блока GEN

Опишем работу программы. В начале цикла вызова функционального блока проверяется наличие сигнала на входе RESET. При его наличии производится сброс переменных COUNTER, OUT, уставки времени и вызов экземпляра CLOCK функционального блока TON с значением входа IN равным FALSE (т.е. прекращается генерация выходных импульсов).

Далее происходит определение способа отсчета периода импульсов, формируемых блоком. Проверяется значение входа BASE (BASE=FALSE?). Если на входе значение FALSE, формирование периода сигнала идет по количеству циклов (или числу вызовов экземпляра функционального блока), если на входе значение TRUE, формирование периода сигнала происходит в заданных при вызове блока единицах времени. При формировании периода сигнала в единицах времени вызывается экземпляр CLOCK функционального блока TON (CLOCK), затем проверяется неравенство уставки времени двум периодам сигнала (CLOCK <> 2*PERIOD), если уставка еще не была задана, то уставка времени задается равной двум периодам (CLOCK.PT=2*PERIOD) (с запасом, т.к. цикл генерации импульса ведется в течении времени, которое должно быть больше или равно времени периода выдачи импульсов) и запускается экземпляр CLOCK (CLOCK(IN:=TRUE)). При этом внутренней переменной CET присваивается текущее значение времени с выхода ET блока TON (CET:=CLOCK.ET), а переменной PER - значение времени периода импульсов со входа PERIOD (PER:=PERIOD).

Далее проверяется соотношение определяющее достижение текущего отсчета времени заданному времени периода импульсов (CET>=PER?). Если соотношение выполняется, то функциональный блок CLOCK перезапускается подачей переднего фронта сигнала (CLOCK(IN:=FALSE), CLOCK(IN:=TRUE)), производится сброс переменной CET (CET:=CET MOD PER) и происходит переход на вычисление текущей амплитуды формируемого сигнала в зависимости от заранее выбранного режима работы MODE c помощью оператора выбора CASE (MODE=?).Такой же переход для вычисления текущей амплитуды формируемого сигнала происходит сразу же, если соотношение  CET>=PER ? не выполняется.

Рассмотрим один из вариантов работы программы – формирование прямого пилообразного двуполярного сигнала от –А до +А (при MODE=2). Вычисление текущей амплитуды пилообразного сигнала производится по формуле (1):

OUT = ((CET-PER)*2*AMPLITUDE/PER+AMPLITUDE)                                     (1)

Рассмотрим эту формулу подробнее. Найдем значение амплитуды генерируемого на выходе OUT сигнала (для заданного периода времени CET), соответствующее длине отрезка OD (см. рисунок 2). Вначале находим значение длины отрезка DC с отрицательным знаком DC = CET- PER, затем определяем длину отрезка BD через тангенс угла α  BD=DC*tg α. Тангенс угла α равен отношению двойной амплитуды сигнала к его периоду, т.е. 2*AMPLITUDE/PER. Тогда BD = (CET-PER)* 2*AMPLITUDE/PER. Для нахождения отрезка OD, равное значению амплитуды OUT для периода времени CET, необходимо из отрезка BD отнять отрезок OB, соответствующий амплитуде сигнала A или прибавить его к отрезку BD, т.к. произведение (CET-PER)* 2*AMPLITUDE/PER имеет отрицательный знак.

Рис. 2. Определение текущей амплитуды генерируемого сигнала

После вычисления текущей амплитуды формируемого сигнала происходит переход на начало программы (т.е. повторный запуск алгоритма функционального блока в цикле ПЛК).

При формировании периода сигнала по количеству циклов переменная COUNTER сбрасывается в нуль при ее равенстве переменной CYCLES (COUNTER=(COUNTER+1) MOD CYCLES). Затем происходит переход на формирование нового периода сигнала и вычисление текущей амплитуды формируемого сигнала в зависимости от заранее выбранного режима работы MODE c помощью оператора выбора CASE (MODE=?).

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

FUNCTION_BLOCK NEWGEN

VAR_INPUT

            PERIOD: TIME:=t#1s;         (*период сигнала, по умолчанию 1 сек.*)

            AMPLITUDE: INT;              (*амплитуда сигнала*)

            RESET: BOOL;                     (*сброс импульсов*)

END_VAR

VAR_OUTPUT

            OUT: INT;                             (*генерируемый сигнал*)

END_VAR

VAR

            CET: DINT;                           (*отсчет времени генерируемого периода сигнала*)

            PER:DINT;                            (*длительность периода генерируемого сигнала*)

            CLOCK:TON;                       (*экземпляр таймера TON*)

END_VAR

_____________________________________________________________________________

IF RESET THEN

            OUT:=0;                                 (*обнуление выхода*)

            CLOCK.PT := t#0s;               (*сброс уставки таймера TON*)

            CLOCK(IN:=FALSE);         (*отключениеблока TON*)

ELSE

            CLOCK;                                (*вызов блока таймера*)

            IF CLOCK.PT<>PERIOD+PERIOD THEN                                                                     CLOCK.PT:=PERIOD+PERIOD; (*задание временной уставки с запасом*)   

                        CLOCK(IN:=TRUE);                     (*таймер активен*)

            ELSE

            CET:=TIME_TO_DINT(CLOCK.ET); (* присваивание переменной CET текущего значения времени *)

            PER:=TIME_TO_DINT(PERIOD); (*присваивание переменной PER значения периода сигнала *)

                        IF CET>=PER THEN (*определение окончания времени формирования                                                                      одного периода*)

                                        CLOCK(IN:=FALSE); (* перезапуск таймера TON*)

                                        CLOCK(IN:=TRUE);

                                        CET:=CET MOD PER; (*сброс текущего значения времени для формирования нового периода сигнала*)

                        END_IF

                        OUT:=DINT_TO_INT((CET-PER)*2*AMPLITUDE/PER+AMPLITUDE);

                        (*вычисление текущей амплитуды сигнала*)

                        END_IF

END_IF

Выводы. В работе проведен анализ принципа работы функционального блока GEN библиотеки Util.lib. Приведенная схема программы и описание ее работы позволяет провести быструю модификацию блока для решения конкретной задачи.  Приведены рекомендации по практическому использованию блока при программировании на языках стандарта МЭК 61131 на примере генератора пилообразного сигнала. Модификация программы под конкретную задачу позволяет минимизировать программный код и уменьшить минимально допустимое время цикла генерируемых импульсов.

Литература:

1.      И.В. Петров. Программируемые контроллеры: стандартные языки и приемы прикладного проектирования. Солон-Пресс, 2008 г. -256 с.

2.      МЭК 61131-3—2012. Программируемые контроллеры. Часть 3. Языки программирования.

3.      Руководство пользователя по программированию ПЛК в CoDeSys 2.3, 2006г. – 453с.  Режим доступа: http://www.kipshop.ru/CoDeSys/steps/codesys_v23_ru.pdf (доступ свободный) – Яз. Рус.

Обсуждение

Социальные комментарии Cackle