Codesys 3.5 Обучение программированию.
270 subscribers
41 photos
13 videos
2 files
66 links
Download Telegram
✅ РАЗБОР ЗАДАЧИ: R_TRIG и F_TRIG

Исходный сигнал:

Цикл:    1      2      3      4      5      6      7
Signal: FALSE FALSE TRUE TRUE FALSE FALSE TRUE


❓ Сколько раз сработают R_TRIG и F_TRIG?

А) R_TRIG — 1, F_TRIG — 1
Б) R_TRIG — 2, F_TRIG — 1
В) R_TRIG — 2, F_TRIG — 2
Г) R_TRIG — 3, F_TRIG — 2

Сначала выберите вариант, затем откройте разбор 👇

🔎 Открыть правильный ответ

✅ Правильный ответ — Б.

R_TRIG сработает 2 раза.
F_TRIG сработает 1 раз.

Разберём по шагам.

⬆️ Между циклами 2 и 3:

FALSE → TRUE

Это передний фронт.

Значит:

R_TRIG.Q = TRUE

только на один цикл.

На следующем цикле сигнал всё ещё TRUE, но нового фронта уже нет.

⬇️ Между циклами 4 и 5:

TRUE → FALSE

Это задний фронт.

Значит:

F_TRIG.Q = TRUE

только на один цикл.

⬆️ Между циклами 6 и 7 снова происходит:

FALSE → TRUE

Поэтому R_TRIG срабатывает второй раз.

Итого:

R_TRIG — 2 срабатывания
F_TRIG — 1 срабатывание


💡 Главная мысль

R_TRIG реагирует только на:

FALSE → TRUE

F_TRIG — только на:

TRUE → FALSE

Само длительное состояние TRUE или FALSE новых импульсов не создаёт.

💬 Получилось ответить правильно до открытия спойлера?

#CODESYS #ПЛК #R_TRIG #F_TRIG #StructuredText #ПрограммированиеПЛК
📘 Новая книга по CODESYS 3.5

Подготовил практическую книгу по программированию ПЛК в CODESYS.

Внутри — понятные объяснения, примеры на Structured Text и разборы типовых задач.

📎 Книгу прикрепляю к этому посту.

Если материал окажется полезным — поделитесь постом с друзьями и коллегами, которым интересны ПЛК, CODESYS и промышленная автоматизация.

🔁 Репосты очень помогают развивать канал и делать больше полезных материалов.

#CODESYS #ПЛК #АСУТП #StructuredText #ПрограммированиеПЛК
👍1🤝1
🔀 CASE в CODESYS: когда алгоритм начинает тонуть в IF

Небольшой проект легко написать на нескольких IF.

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

Для последовательных алгоритмов часто удобнее связка:

ENUM + CASE

Например:

IDLE → STARTING → WORKING → STOPPING → IDLE


Каждое состояние имеет свою логику, а переменная eState всегда показывает, где сейчас находится алгоритм.

👇 В следующем посте — подробный пример на Structured Text.

📤 Если тема актуальна — перешлите пост коллеге, который программирует ПЛК.

#CODESYS #ПЛК #StructuredText #CASE #ПрограммированиеПЛК
🔀 CASE в CODESYS: как строить понятные пошаговые алгоритмы

По мере роста программы ПЛК часто появляется что-то похожее:

IF xStart AND NOT xStep1 THEN
...
END_IF;

IF xStep1 AND xSensor THEN
...
END_IF;

IF xStep2 AND fbTimer.Q THEN
...
END_IF;


Сначала всё выглядит нормально.

Но потом появляются:

• дополнительные условия
• блокировки
• таймеры
• аварийные переходы
• ручной режим
• повторный запуск

И через некоторое время становится трудно ответить на простой вопрос:

«На каком этапе сейчас находится алгоритм?»

Для последовательных процессов часто удобнее использовать CASE и явно хранить текущее состояние.

━━━━━━━━━━━━━━━━━━

🏭 Пример задачи

Пусть после команды «Пуск» механизм должен:

1️⃣ включить двигатель

2️⃣ дождаться срабатывания датчика

3️⃣ работать ещё 5 секунд

4️⃣ остановиться

У процесса можно выделить четыре состояния:

IDLE
STARTING
WORKING
STOPPING


Создадим перечисление:

TYPE E_State :
(
IDLE,
STARTING,
WORKING,
STOPPING
);
END_TYPE


И переменную текущего состояния:

VAR
eState : E_State := IDLE;
END_VAR


Теперь вместо нескольких BOOL-переменных вида:

xStep1
xStep2
xStep3
xStep4


мы имеем одну:

eState

которая однозначно показывает текущий этап.

━━━━━━━━━━━━━━━━━━

⏱ Сначала подготовим таймер

Пусть в состоянии WORKING механизм должен работать 5 секунд.

Объявим:

VAR
eState : E_State := IDLE;
fbWorkTimer : TON;

xStart : BOOL;
xSensor : BOOL;
xMotor : BOOL;
END_VAR


Таймер удобно вызывать циклически:

fbWorkTimer(
IN := eState = WORKING,
PT := T#5s
);


Когда состояние становится WORKING, таймер начинает отсчёт.

При выходе из этого состояния:

IN = FALSE


и TON сбрасывается.

━━━━━━━━━━━━━━━━━━

🔧 Теперь сам CASE

CASE eState OF

IDLE:
xMotor := FALSE;

IF xStart THEN
eState := STARTING;
END_IF;


STARTING:
xMotor := TRUE;

IF xSensor THEN
eState := WORKING;
END_IF;


WORKING:
xMotor := TRUE;

IF fbWorkTimer.Q THEN
eState := STOPPING;
END_IF;


STOPPING:
xMotor := FALSE;
eState := IDLE;

END_CASE


Теперь структура алгоритма читается почти как описание технологического процесса.

━━━━━━━━━━━━━━━━━━

🔎 Разберём по шагам

1. IDLE

Механизм находится в ожидании:

xMotor := FALSE;


При появлении команды:

xStart = TRUE


выполняется:

eState := STARTING;


━━━━━━━━━━━━━━━━━━

2. STARTING

Включаем двигатель:

xMotor := TRUE;


И ждём подтверждающий сигнал от датчика:

IF xSensor THEN
eState := WORKING;
END_IF;


Пока датчик не сработал, алгоритм остаётся в STARTING.

━━━━━━━━━━━━━━━━━━

3. WORKING

Двигатель продолжает работать:

xMotor := TRUE;


А таймер:

fbWorkTimer.Q


станет TRUE после пяти секунд пребывания в состоянии WORKING.

После этого:

eState := STOPPING;


━━━━━━━━━━━━━━━━━━

4. STOPPING

Останавливаем двигатель:

xMotor := FALSE;


и возвращаем автомат в исходное состояние:

eState := IDLE;


Цикл завершён.

━━━━━━━━━━━━━━━━━━

🧠 Что здесь даёт CASE?

Представим:

eState = STARTING


Тогда выполняется только логика ветки:

STARTING:


Код IDLE, WORKING и STOPPING в этом выполнении CASE не выполняется.

Поэтому состояния процесса хорошо отделены друг от друга.

━━━━━━━━━━━━━━━━━━

⚠️ Важная особенность переходов

Допустим, внутри STARTING выполняется:

eState := WORKING;


Это не означает, что ветка WORKING немедленно начнёт выполняться ниже в том же CASE.

Текущая ветка уже была выбрана.
При следующем выполнении этого CASE значение eState будет прочитано снова, и тогда будет выбрана ветка:

WORKING:


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

━━━━━━━━━━━━━━━━━━

✅ Почему ENUM лучше набора чисел?

Можно было бы написать:

iState := 0;
iState := 10;
iState := 20;
iState := 30;


Но что означает:

iState = 20?

Без дополнительного описания — непонятно.

А:

eState = WORKING


читается сразу.

Именно поэтому для состояний удобно использовать ENUM.

━━━━━━━━━━━━━━━━━━

🔧 Диагностика тоже становится проще

Если механизм остановился и оператор сообщает:

«Почему он не продолжает работу?»

в онлайн-режиме можно посмотреть:

eState = STARTING


После этого понятно:

алгоритм ждёт условия перехода из STARTING.

В нашем случае первым делом стоит проверить:

xSensor

Это значительно удобнее, чем искать комбинацию из нескольких:

xStep1, xStep2, xStep3...

━━━━━━━━━━━━━━━━━━

🏗 Где такой подход особенно полезен?

ENUM + CASE хорошо подходит для:

• последовательностей запуска оборудования
• пошаговых технологических процессов
• дозирования
• промывки
• продувки
• перемещения механизмов
• автоматических циклов
• управления режимами
• конечных автоматов
• процедур запуска и останова

━━━━━━━━━━━━━━━━━━

⚠️ Но CASE — не замена каждому IF

Не нужно превращать вообще всю программу в одну гигантскую машину состояний.

Если необходимо просто проверить условие:

IF rTemperature > 90.0 THEN
xHighTemp := TRUE;
END_IF;


обычный IF прекрасно подходит.

CASE особенно полезен тогда, когда есть:

состояния → условия перехода → следующее состояние.

━━━━━━━━━━━━━━━━━━

💡 Главная мысль

IF отвечает на вопрос:

«Выполняется ли условие?»

А связка:

ENUM + CASE


помогает описать:

«В каком состоянии находится алгоритм и куда он должен перейти дальше?»

Для небольшого кода разница может быть незаметна.

Но в большом промышленном проекте понятная структура программы сильно упрощает:

• разработку
• ПНР
• диагностику
• сопровождение
• последующую доработку

Хороший код должен быть понятен не только ПЛК, но и инженеру, который откроет проект через год.

━━━━━━━━━━━━━━━━━━

💬 А как вы строите пошаговые алгоритмы?

Используете CASE, отдельные BOOL для каждого шага или свой шаблон машины состояний?

📤 Если статья оказалась полезной — отправьте её другу или коллеге, который работает с CODESYS и ПЛК.

🔁 Можно также репостнуть материал в профильный чат или канал — так о наших технических разборах узнает больше инженеров.

#CODESYS #ПЛК #АСУТП #StructuredText #ST #CASE #ENUM #ПрограммированиеПЛК
👍5
⚠️ Один экземпляр ФБ — два вызова. Что может пойти не так?

В CODESYS функциональные блоки хранят внутреннее состояние между вызовами.

Но что произойдёт, если один экземпляр вызвать дважды за один цикл ПЛК? 🤔

Например:

fbCounter(xEnable := TRUE);
fbCounter(xEnable := TRUE);


Оказывается, повторный вызов может привести к неожиданному поведению программы.

Особенно это важно для TON, R_TRIG, счётчиков и собственных функциональных блоков.

🔍 Почему так происходит и как избежать ошибок?

👇 Подробный разбор с примерами — в следующем посте.

#CODESYS #ПЛК #StructuredText #ПрограммированиеПЛК
⚙️ FUNCTION_BLOCK в CODESYS: почему повторный вызов может изменить результат?

📘 Часть 1/2. Внутренняя память и повторные вызовы

Функциональные блоки — основа многих программ ПЛК.

Таймеры, счётчики, триггеры, управление оборудованием, обмен по Modbus — всё это часто реализуется через FUNCTION_BLOCK.

Но есть важная особенность:

Функциональный блок сохраняет своё внутреннее состояние между вызовами.

Если один экземпляр вызвать несколько раз за цикл программы, его код выполнится несколько раз.

Разберём, почему это важно.

━━━━━━━━━━━━━━━━━━

🔹 FUNCTION и FUNCTION_BLOCK — в чём разница?

В Structured Text используются разные типы программных единиц.

FUNCTION — функция.

Обычно применяется для вычислений и преобразований данных.

Например:

rResult := ABS(rValue);


FUNCTION_BLOCK — функциональный блок.

В отличие от обычной функции, экземпляр ФБ может сохранять значения внутренних переменных между вызовами.

Например:

fbTimer : TON;
fbTrig : R_TRIG;


Каждый экземпляр имеет собственную память.

Именно поэтому TON отсчитывает время, а R_TRIG способен обнаруживать фронт сигнала.

━━━━━━━━━━━━━━━━━━

🔄 Что происходит при вызове ФБ?

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

FUNCTION_BLOCK FB_Counter
VAR_INPUT
xEnable : BOOL;
END_VAR

VAR_OUTPUT
iCount : INT;
END_VAR

IF xEnable THEN
iCount := iCount + 1;
END_IF;


В программе объявим экземпляр:

VAR
fbCounter : FB_Counter;
END_VAR


И вызовем:

fbCounter(xEnable := TRUE);


При каждом вызове выполняется тело функционального блока.

Значение iCount при этом сохраняется внутри экземпляра.

Если вызывать блок один раз за цикл, счётчик будет увеличиваться на единицу за цикл.

━━━━━━━━━━━━━━━━━━

⚠️ Что произойдёт при двух вызовах?

Теперь напишем:

fbCounter(xEnable := TRUE);

fbCounter(xEnable := TRUE);


Оба вызова обращаются к одному и тому же экземпляру.

Предположим, начальное значение:

iCount = 0


После первого вызова:

iCount = 1


После второго:

iCount = 2


Получается, что за один цикл ПЛК счётчик увеличился сразу на две единицы.

Если продолжить выполнение:

Цикл 1 → 2
Цикл 2 → 4
Цикл 3 → 6
Цикл 4 → 8


Причина проста: каждый вызов повторно выполняет код ФБ.

━━━━━━━━━━━━━━━━━━

🧠 Почему это важно?

Функциональный блок — не просто набор инструкций.

Его экземпляр хранит собственные данные.

При повторном вызове используется та же память, а не создаётся новый независимый объект.

Это особенно важно для блоков, работа которых зависит от предыдущих состояний.

Например:

• R_TRIG запоминает предыдущее состояние сигнала.
• TON хранит состояние таймера.
• CTU хранит значение счётчика.
• Собственные ФБ могут хранить этапы алгоритма.

Повторный вызов может изменить результат работы всей программы.

━━━━━━━━━━━━━━━━━━

📌 Вывод первой части

Два вызова одного экземпляра ФБ — это не два независимых функциональных блока.

Это повторное выполнение одного объекта с общей внутренней памятью.

➡️ В части 2 рассмотрим более интересную ситуацию: как два вызова одного R_TRIG могут создавать ложные импульсы даже при неизменном входном сигнале.

#CODESYS #ПЛК #StructuredText #FUNCTION_BLOCK
⚙️ FUNCTION_BLOCK в CODESYS
📘 Часть 2/3. Почему R_TRIG может выдавать ложные импульсы?

В первой части мы выяснили, что повторные вызовы одного экземпляра ФБ используют общую внутреннюю память.

Теперь рассмотрим ошибку, которую бывает непросто обнаружить при отладке.

━━━━━━━━━━━━━━━━━━

🧠 Один R_TRIG для двух сигналов

Объявим:

VAR
fbTrig : R_TRIG;
xA : BOOL;
xB : BOOL;
END_VAR


И вызовем один экземпляр для двух сигналов:

fbTrig(CLK := xA);
fbTrig(CLK := xB);


На первый взгляд кажется, что мы просто проверяем два сигнала.

Но R_TRIG хранит предыдущее состояние входа CLK.

Поэтому второй вызов использует память, которая уже изменилась после первого.

━━━━━━━━━━━━━━━━━━

🔍 Что произойдёт?

Пусть:

xA = TRUE
xB = FALSE


Перед первым вызовом внутренняя память триггера содержит FALSE.

Первый вызов:

fbTrig(CLK := xA);


Обнаруживается переход:

FALSE → TRUE


Поэтому:

fbTrig.Q = TRUE


Но затем выполняется второй вызов:

fbTrig(CLK := xB);


Теперь вход равен FALSE.

После этого:

fbTrig.Q = FALSE


Если проверить выход после обоих вызовов:

IF fbTrig.Q THEN
// Обработка события
END_IF;


условие не выполнится.

Импульс, сформированный первым вызовом, уже исчез!

━━━━━━━━━━━━━━━━━━

⚠️ А теперь самое интересное

Предположим, сигналы не меняются:

xA = TRUE
xB = FALSE


В каждом цикле выполняются два вызова.

1️⃣ Первый получает TRUE после предыдущего FALSE и формирует импульс.

2️⃣ Второй получает FALSE и обновляет внутреннюю память.

На следующем цикле ситуация повторяется.

В результате R_TRIG формирует импульс при каждом первом вызове, хотя xA всё время остаётся TRUE.

Это серьёзная логическая ошибка!

━━━━━━━━━━━━━━━━━━

📌 Вывод

Один экземпляр R_TRIG нельзя использовать как два независимых триггера.

Каждый вызов изменяет его внутреннее состояние.

➡️ В следующей части: как правильно обрабатывать независимые сигналы и какие правила стоит соблюдать при работе с ФБ.

#CODESYS #ПЛК #StructuredText #R_TRIG
⚙️ FUNCTION_BLOCK в CODESYS
📘 Часть 3/3. Как избежать ошибок повторного вызова ФБ?

В предыдущей части мы рассмотрели, как один экземпляр R_TRIG может создавать ложные импульсы при обработке двух независимых сигналов.

Теперь разберём правильное решение.

━━━━━━━━━━━━━━━━━━

✅ Отдельный экземпляр для каждого сигнала

Если необходимо обнаруживать передний фронт двух независимых сигналов, создаём два экземпляра:

VAR
fbTrigA : R_TRIG;
fbTrigB : R_TRIG;

xA : BOOL;
xB : BOOL;
END_VAR


Вызываем их отдельно:

fbTrigA(CLK := xA);
fbTrigB(CLK := xB);


Теперь каждый экземпляр хранит собственное предыдущее состояние входа.

fbTrigA отслеживает xA.

fbTrigB отслеживает xB.

Сигналы не влияют друг на друга через внутреннюю память триггера.

━━━━━━━━━━━━━━━━━━

🏭 Где это особенно важно?

Повторные вызовы могут создавать проблемы при использовании:

• таймеров TON, TOF, TP
• триггеров R_TRIG, F_TRIG
• счётчиков CTU, CTD
• автоматов состояний
• блоков управления оборудованием
• блоков обмена по Modbus
• собственных ФБ с внутренней памятью

Особенно внимательно нужно относиться к экземплярам, используемым в нескольких частях программы.

━━━━━━━━━━━━━━━━━━

🔧 Можно ли вызывать один ФБ несколько раз?

Да, можно!

Сам по себе повторный вызов не запрещён.

Иногда такое поведение используется намеренно.

Но важно понимать:

Каждый вызов выполняет код того же экземпляра и может изменить его состояние.

Поэтому нельзя считать несколько вызовов одного экземпляра независимыми.

━━━━━━━━━━━━━━━━━━

💡 Практическая рекомендация

Для обычных циклических ФБ полезно придерживаться правила:

Один экземпляр — один вызов за цикл задачи.

Это не обязательное требование языка, а рекомендация по организации программы.

Так проще:

• контролировать последовательность выполнения
• диагностировать ошибки
• анализировать внутреннее состояние
• сопровождать программу
• изменять алгоритм

Если один алгоритм нужен нескольким независимым объектам, создавайте отдельные экземпляры ФБ.

━━━━━━━━━━━━━━━━━━

📌 Главное

FUNCTION_BLOCK — программный объект с собственной памятью.

Повторный вызов экземпляра может изменить его внутреннее состояние.

Два вызова одного ФБ ≠ два независимых ФБ.

Понимание этой особенности помогает избежать ошибок, которые сложно обнаружить при наладке оборудования.

━━━━━━━━━━━━━━━━━━

💬 Вопрос к инженерам

Сталкивались ли вы с ситуациями, когда один экземпляр ФБ вызывался из нескольких мест программы?

Какие ошибки это вызывало?

Поделитесь опытом в комментариях 👇

━━━━━━━━━━━━━━━━━━

📤 Поделитесь статьёй!

Если материал оказался полезным, перешлите все три части друзьям и коллегам, которые программируют ПЛК.

🔁 Репостните материал в профильный чат или канал — возможно, он поможет другим инженерам избежать ошибок.

📚 Больше материалов по CODESYS:
https://t.me/codesys4

#CODESYS #ПЛК #АСУТП #StructuredText #FUNCTION_BLOCK
🧠 РАЗБОР КОДА: R_TRIG

Один экземпляр — два вызова за цикл ПЛК.

Сигналы не изменяются:

xA = TRUE
xB = FALSE

Но один и тот же экземпляр R_TRIG вызывается дважды с разными входными значениями.

❓ Сколько раз увеличится счётчик за 5 циклов ПЛК?

На первый взгляд ответ очевиден. Но есть подвох! 😉

👇 Попробуйте решить задачу самостоятельно.

В следующем посте — варианты ответа и подробный разбор под спойлером.

#CODESYS #ПЛК #R_TRIG #StructuredText
🧠 РАЗБОР КОДА: R_TRIG

⚠️ Найдите подвох в программе!

В CODESYS объявлены переменные:

VAR
fbTrig : R_TRIG;
xA : BOOL := TRUE;
xB : BOOL := FALSE;
iCount : INT := 0;
END_VAR


В каждом цикле ПЛК выполняется:

fbTrig(CLK := xA);

IF fbTrig.Q THEN
iCount := iCount + 1;
END_IF;

fbTrig(CLK := xB);


Условия:

• xA всегда TRUE.
• xB всегда FALSE.
• iCount изначально равен 0.
• Внутренняя память R_TRIG изначально FALSE.
• Код выполняется 5 циклов подряд.

━━━━━━━━━━━━━━━━━━

❓ Чему будет равен iCount после 5 циклов?

А) 0
Б) 1
В) 5
Г) 10

Сначала выберите вариант, затем откройте ответ 👇

🔎 Нажмите, чтобы открыть разбор

✅ Правильный ответ — В) 5.

Разберём первый цикл.

1️⃣ Первый вызов:

fbTrig(CLK := xA);

Поскольку xA = TRUE, а предыдущее состояние FALSE, R_TRIG обнаруживает передний фронт.

fbTrig.Q = TRUE

Счётчик увеличивается:

iCount = 1

2️⃣ Второй вызов:

fbTrig(CLK := xB);

Поскольку xB = FALSE, блок обновляет внутреннюю память значением FALSE.

Теперь fbTrig.Q = FALSE.

На следующем цикле ситуация повторяется!

Первый вызов снова получает TRUE после FALSE и формирует импульс.

Счётчик увеличивается каждый цикл.

Результат:

Цикл 1 → iCount = 1
Цикл 2 → iCount = 2
Цикл 3 → iCount = 3
Цикл 4 → iCount = 4
Цикл 5 → iCount = 5

Итого: iCount = 5.

Хотя xA ни разу не изменялся!

Причина — повторные вызовы одного экземпляра R_TRIG с разными значениями CLK.


━━━━━━━━━━━━━━━━━━

💡 Главная мысль

R_TRIG запоминает предыдущее состояние входа.

Если использовать один экземпляр для разных сигналов, они будут влиять на его внутреннюю память.

Для независимых сигналов используйте отдельные экземпляры R_TRIG.

━━━━━━━━━━━━━━━━━━

💬 Получилось ответить правильно?

Напишите свой вариант в комментариях!

📤 Отправьте эту задачу коллеге-программисту ПЛК!

Проверьте, заметит ли он подвох с первого раза. 😉

🔁 Репостните интерактив в профильный чат — пусть больше инженеров проверят свои знания.

📚 Больше разборов CODESYS:
https://t.me/codesys4

#CODESYS #ПЛК #АСУТП #StructuredText #R_TRIG
🎛 Интерактивный симулятор ПИД-регулятора!

Хотите наглядно увидеть, как работают P, I и D составляющие?

Подготовил небольшой симулятор, который позволяет экспериментировать с настройками ПИД-регулятора и наблюдать результат на графиках. 📈

🔧 Что можно делать:

• изменять коэффициенты Kp, Ki, Kd;
• наблюдать реакцию системы;
• изучать влияние параметров на переходный процесс;
• экспериментировать с настройками без реального оборудования.

💻 Симулятор работает локально, без установки.

📎 Файл симулятора опубликую следующим постом.

━━━━━━━━━━━━━━━━━━

📤 Поделитесь симулятором с друзьями и коллегами!

🔁 Перешлите публикацию в чаты по CODESYS и АСУ ТП — пусть больше инженеров смогут разобраться в ПИД-регулировании на практике.

📚 Больше полезных материалов:
https://t.me/codesys4

#PID #CODESYS #ПЛК #АСУТП #Автоматизация
PID_Simulator_ASUTP_v2.html
30.1 KB
👍3