Статья объясняет организацию очереди (FIFO) на ПЛК в Codesys, разбирает теорию и два способа реализации, а также затрагивает динамическое выделение памяти. Приводится пример очереди для одного типа данных с функциональным блоком и перечислением команд.
- Очередь — абстрактный тип данных с доступом по принципу FIFO: первый пришел, первый ушел.
- У очереди два метода: добавить элемент и достать элемент; извлеченные данные удаляются.
- Теоретически очередь реализуется массивом и односвязным списком, но в большинстве ПЛК второй способ невозможен.
- В Codesys 3 можно частично использовать динамическое выделение памяти.
- Пример реализации для одного типа: пользовательский тип udtHole с координатами X, Y и диаметром.
- Функциональный блок fbFifo использует массив на 10 элементов, индексы старта и окончания и перечисление eFifoOperation.
- FIFO
- Codesys 3
- ПЛК
- udtHole
- fbFifo
- eFifoOperation
- динамическое выделение памяти
- функциональный блок
Кому полезно: Инженерам АСУ ТП, программирующим ПЛК на Codesys.
- Статья Функциональный блок + METHOD в Codesys 3.5
- Статья Как один маленький функциональный блок влияет на обслуживание промышленного оборудования
- Статья Модуль SIWAREX WP321 (7MH4 138-6AA00-0BA0), сименсный функциональный блок сиваре
Первоисточник: blog.engcore.ru
Выжимка составлена редакцией сайта с помощью ИИ по тексту статьи.
Здравствуйте, коллеги. Сегодня мы с вами познакомимся с организацией работы при ограниченных ресурсах. Рассмотрим что такое «Очередь» как она работает, реализуем пару вариантов очередей, затронем динамическое выделение памяти в ПЛК.
Я пишу эту статью и программирую в режиме реального времени, т.е. у меня есть задача и я ее решаю. Все свои мысли и попытки(за исключением очень глупых) я записываю в эту статью так что приятного чтения.
ТЕОРИЯ
Очередь — это абстрактный тип данных, в котором доступ организуется по принципу FIFO(first in, first out) — первый пришел, первый ушел. Имеет два метода: добавить в очередь и достать из очереди. Данные которые мы достали из очереди — удаляются.
Согласно теории есть два метода реализации — массивом и односвязным списком. В большинстве ПЛК второй способ невозможен, но в Codesys 3 можно чуть чуть поиграть с динамическим выделением памяти.
РЕАЛИЗАЦИЯ
Реализация для одного типа
Начнем с малого. Реализуем очередь для одного типа данных. Например, мы будем вырезать отверстия.
Создание пользовательского типа данных
Наш пользовательский тип данных будет называться udtHole и будет включать в себя координаты по X и Y и диаметр отверстия.
TYPE udtHole : STRUCT rX:REAL;//Координата по X rY:REAL;//Координата по Y iD:INT;//Размер диаметра END_STRUCT END_TYPE
Функциональный блок
Теперь организуем список необходимых операций, которые нам понадобятся. Всего их три: Ожидание, Добавить элемент, Достать элемент. Для такого типа операций я использую перечисление и CASE в реализации. Мне просто так удобнее. Можно с помощью флагов, триггеров, IF’ов, но я делаю так.
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE eFifoOperation :
(
WAIT := 0, //Ожидание
ENQUEUE :=10, //Добавить в очередь
DEQUEUE :=20 //Достать из очереди
);
END_TYPE
Теперь работаем с FB. Пытаемся уместить все в одном месте.
Нам потребуется на вход:
- Указатель на Глобальный UDT для чтения
- Указатель на UDT куда будем писать.
- Команды управления
На выход у нас получается:
- Сигнал об успешной записи в очередь
- Сигнал об успешном чтении из очереди
- Очередь пустая
- Очередь полная
- В работе
Пункты 3 и 4 можно объединить в ошибку и сделать какой-нибудь код для нее, но я не буду.
Под капотом:
- Массив элементов( для теста 10)
- Индекс старта
- Индекс окончания
FUNCTION_BLOCK fbFifo VAR_INPUT udEnqueuedData: POINTER TO udtHole; //Данные которые будут добавлены в очередь udDequeueData: POINTER TO udtHole; //Данные считанные из очереди enCommand: POINTER TO eFifoOperation; //Команда END_VAR VAR_OUTPUT xEnqueueDone: BOOL; //Добавление в очередь прошло успешно xDequeueDone: BOOL; //Изъятье из очереди прошло успешно xBusy: BOOL; //В Процессе xQueueEmpty: BOOL; //Очередь пуста xQueueFull: BOOL; //Полная очередь xError: BOOL; //Ошибка END_VAR VAR udData: ARRAY[0..9] OF udtHOLE; //Массив iStart: INT:=0; // Индекс чтения iEnd: INT:=0; //Индекс записи xEmpty: BOOL:=TRUE; //Массив пуст xFull: BOOL; //Массив полон END_VAR
Логика работы
Логика легка и проста. читаем из элемента под индексом iStart. Считали прибавили 1. Записываем в индекс iEnd. Записали прибавили единицу.
Дошли до конца, скинули счетчик. Пошли по кругу. Всего может быть 10 элементов. Если индексы пересекаются, то это неопределенность. Либо очередь полная, либо пустая. Так что этот вопрос придется контролировать.
ДОБАВЛЕНИЕ/ИСПРАВЛЕНИЕ
При старте реализации было обнаружено, что значения пустой и полной очереди необходимо обрабатывать внутри кода. Есть два три выхода: переносим в In_Out, переносим указателями в In, создаем в области VAR и во время WAIT копируем значения.
Я выбираю третий вариант с дублированием.
Добавил в Output переменную Error.
В итоге получилось следующее
CASE enCommand^ OF (* Стадия ожидания команды. Производится сбрасывание индексов для кругового обхода И перекидыввание внутренних значений на переменныйе выхода. *) eFifoOperation.WAIT: IF iStart>9 THEN iStart:=0; END_IF IF iEnd>9 THEN iEnd:=0; END_IF xQueueEmpty:=xEmpty; xQueueFull:=xFull; (* Добавление в очередь. Если очередь не полная. Формирование сигнала о том, что очередь заполнена Формирование ошибки при записи в заполненную очередь *) eFifoOperation.ENQUEUE: xBusy:=TRUE; xEnqueueDone:=FALSE; IF NOT xFull THEN udData[iEnd] := udEnqueuedData^; iEnd:=iEnd+1; IF iStart = iEnd OR (iStart=0 AND iEnd=10) THEN xFull:=TRUE; END_IF; IF xEmpty THEN xEmpty:=FALSE; xError:=FALSE; END_IF; xBusy:=FALSE; xEnqueueDone:=TRUE; ELSE xBusy:=FALSE; xError:=TRUE; END_IF; enCommand^:=eFifoOperation.WAIT; (* Изъятие из очереди. Если очередь не пустая. Формирование сигнала о том, что очередь пустая Формирование ошибки при чтении из пустой очереди очередь *) eFifoOperation.DEQUEUE: xBusy:=TRUE; xDequeueDone:=FALSE; IF NOT xEmpty THEN udDequeueData^:=udData[iStart]; iStart:=iStart+1; IF iStart = iEnd OR (iStart =10 AND iEnd=0) THEN xEmpty:=TRUE; END_IF IF xFull THEN xFull:=FALSE; xError:=FALSE; END_IF xBusy:=FALSE; xDequeueDone:=TRUE; ELSE xBusy:=FALSE; xError:=TRUE; END_IF; enCommand^:=eFifoOperation.WAIT; END_CASE
Источник: https://blog.engcore.ru/2021/09/23/очередь-на-плк-codesys-v3-5/