В основе любой программы для ПЛК, написанной на языке Structured Text, лежит концепция переменной. Переменная — это именованная ячейка памяти контроллера, которая хранит значение определенного типа.
На первый взгляд это понятие кажется простым, но правильное использование переменных, их именование и управление областью видимости — это один из столпов, на котором строится качественный и надежный программный код.
Небрежное отношение к переменным приводит к трудноуловимым ошибкам, путанице при чтении кода и проблемам при его модификации. Давайте разберемся, как правильно работать с переменными в ST.
Что такое переменная и зачем она нужна
Представьте себе склад на заводе. Каждый товар хранится в определенном месте, и это место помечено этикеткой. Переменная в программе работает точно так же: она — это именованное место в памяти контроллера, которое хранит данные. Имя переменной — это как этикетка на полке, а содержимое переменной — это сам товар.
Когда вы объявляете переменную, вы говорите контроллеру: «Выдели мне участок памяти, назови его temperatureSensor, и пусть он может хранить вещественное число (REAL)». С этого момента вы можете использовать это имя в любом месте программы, и контроллер будет знать, что вы имеете в виду конкретную ячейку памяти.
Почему это важно? Потому что это делает программу понятной не только для компьютера, но и для человека. Вместо того чтобы помнить, что значение температуры хранится в адресе памяти 0x1A32, вы просто пишете temperatureSensor, и сразу становится ясно, что вы работаете именно с температурой.
Объявление переменных в ST
В Structured Text переменные объявляются в специальном блоке VAR, который находится в начале программы, функции или функционального блока:
PROGRAM MyProgram
VAR
(* здесь объявляются переменные *)
counterValue: INT;
temperature: REAL;
isActive: BOOL;
statusMessage: STRING;
END_VAR
(* здесь находится основной код программы *)
END_PROGRAM
Каждая переменная объявляется в отдельной строке с указанием имени, двоеточия, типа данных, и опционально — начального значения (инициализации). Если вы не укажете начальное значение, переменная будет иметь значение по умолчанию для своего типа (0 для чисел, FALSE для BOOL, пустую строку для STRING).
Правильное объявление переменной выглядит так:
motorSpeed: INT; (* без инициализации *)
targetPressure: REAL := 10.5; (* с инициализацией *)
systemEnabled: BOOL := TRUE;
errorText: STRING := 'No error';
END_VAR
Соглашения по именованию переменных: язык, которым говорит сообщество
Одна из самых важных, но часто недооцениваемых практик в программировании — это правильное именование переменных. Хорошее имя переменной говорит вам ровно то, что она содержит, плохое имя создает путаницу и приводит к ошибкам.
Рассмотрим два примера одной и той же программы:
(* Плохие имена *)
VAR
x: REAL;
y: INT;
z: BOOL;
a: STRING;
END_VAR
IF y > 100 THEN
z := TRUE;
END_IF;
Когда вы смотрите на такой код, совершенно неясно, что делает программа. Что такое y? Почему мы сравниваем его с 100? Что означает установка z в TRUE?
Теперь посмотрим на тот же код с хорошими именами:
(* Хорошие имена *)
VAR
currentTemperature: REAL;
pumpRunningHours: INT;
isOverheated: BOOL;
lastErrorMessage: STRING;
END_VAR
IF pumpRunningHours > 100 THEN
isOverheated := TRUE;
END_IF;
Теперь код полностью самодокументируется. Просто читая имена переменных, вы понимаете, что происходит в программе.
Основные правила именования переменных:
-
Используйте описательные имена. Имя должно отражать суть того, что хранит переменная. Вместо
tempиспользуйтеwaterTemperatureилиmotorTemperature. ВместоcntиспользуйтеitemCounterилиcycleCount. -
Используйте camelCase для составных имен. В промышленной автоматике принято первое слово писать с маленькой буквы, а каждое следующее слово начинать с заглавной буквы:
motorSpeed,pumpPressure,systemStatus. -
Избегайте аббревиатур. Если вы не уверены, что аббревиатура будет понята всеми, кто будет читать код, лучше напишите полное слово.
CurrentTemperatureлучше, чемCurTemp. -
Избегайте однобуквенных имен. Единственное исключение — переменная счетчика в циклах FOR:
FOR i := 0 TO 9 DO.... Для обычных переменных однобуквенные имена недопустимы. -
Будьте последовательны. Если вы назвали переменную
motorSpeed, не используйтеspeedMotorдля второго двигателя. Выбираете формат и придерживаетесь его:motor1Speed,motor2Speed. -
Используйте префиксы для логического группирования. Если в программе много переменных, относящихся к разным устройствам, можно использовать префиксы:
pump1Pressure,pump1Speed,pump2Pressure,pump2Speed. Это облегчает понимание структуры программы. -
Не используйте технические префиксы типов данных. В некоторых старых языках программирования (например, Visual Basic) было принято использовать префиксы вроде
intCounter,boolEnabled,strErrorMessage. В современном ST этого избегают, так как тип данных можно всегда увидеть в объявлении переменной.
Практический пример соглашений по именованию:
VAR
(* датчики *)
sensorEntrance: BOOL; (* датчик на входе *)
sensorExit: BOOL; (* датчик на выходе *)
beltSpeed: REAL; (* скорость конвейера *)
(* управление *)
motorEnabled: BOOL; (* включен ли двигатель *)
targetSpeed: REAL; (* желаемая скорость *)
(* счетчики и статистика *)
itemCount: INT; (* количество предметов *)
beltRunningHours: DINT; (* часов работы конвейера *)
(* состояние и ошибки *)
systemStatus: STRING; (* текущее состояние *)
lastErrorCode: INT; (* код последней ошибки *)
isInErrorState: BOOL; (* флаг ошибки *)
END_VAR
Даже без комментариев, просто читая имена переменных, можно понять, что делает эта программа.
Локальные и глобальные переменные: масштабы видимости
Когда мы говорим о локальных и глобальных переменных, мы имеем в виду, где и как эти переменные могут быть использованы.
Локальные переменные — это переменные, объявленные внутри программы, функции или функционального блока. Они видны только внутри этого блока кода. Например:
PROGRAM MyProgram
VAR
globalCounter: INT; (* локальная для MyProgram переменная *)
END_VAR
(* основной код *)
END_PROGRAM
FUNCTION_BLOCK MyBlock
VAR
localSpeed: REAL; (* локальная для MyBlock переменная *)
END_VAR
(* код функционального блока *)
END_FUNCTION_BLOCK
В этом примере globalCounter видна только внутри программы MyProgram, а localSpeed видна только внутри функционального блока MyBlock. Они не могут быть использованы друг в друге.
Глобальные переменные — это переменные, объявленные вне программ и функциональных блоков, в специальном блоке VAR_GLOBAL. Они видны везде, во всех программах и функциональных блоках проекта:
VAR_GLOBAL
systemTemperature: REAL;
systemPressure: REAL;
isSystemRunning: BOOL;
END_VAR
PROGRAM Program1
VAR
localCounter: INT;
END_VAR
systemTemperature := 25.5; (* используем глобальную переменную *)
END_PROGRAM
PROGRAM Program2
VAR
localFlag: BOOL;
END_VAR
IF systemPressure > 10 THEN (* используем глобальную переменную *)
(* обработка *)
END_IF;
END_PROGRAM
Здесь переменные systemTemperature, systemPressure и isSystemRunning могут быть использованы в обеих программах.
Когда использовать локальные, а когда глобальные переменные:
Существует золотое правило: используйте локальные переменные везде, где это возможно, и только когда абсолютно необходимо, используйте глобальные переменные.
Почему? Потому что локальные переменные делают код более модульным и менее подверженным ошибкам. Если переменная видна только в одном месте, шанс случайно изменить ее значение в неправильном месте кода значительно снижается.
Глобальные переменные следует использовать для:
-
Данных, которые должны быть доступны нескольким программам или функциональным блокам
-
Конфигурационных параметров системы
-
Данных, которые должны сохраняться между циклами выполнения
Инициализация переменных
Инициализация переменной — это установление начального значения при объявлении. Это критически важно в промышленных приложениях, где непредсказуемое начальное состояние может привести к опасным последствиям.
VAR
(* без инициализации *)
unknownValue: INT;
(* с инициализацией *)
motorSpeed: INT := 0;
isMotorEnabled: BOOL := FALSE;
systemStatus: STRING := 'IDLE';
currentTemperature: REAL := 20.0;
END_VAR
В первом случае unknownValue может иметь произвольное значение в памяти контроллера. Это опасно. Во втором случае все переменные имеют четко определенные начальные значения.
Лучшие практики инициализации:
-
Всегда инициализируйте важные переменные. Если переменная используется в логике управления или безопасности, она должна иметь явно установленное начальное значение.
-
Используйте безопасные начальные значения. Для флагов управления используйте FALSE (отключено) в качестве начального значения. Для счетчиков используйте 0. Для строк — пустую строку или понятное сообщение.
-
Инициализируйте массивы. Если вы создаете массив, убедитесь, что его элементы имеют разумные начальные значения:
temperatures: ARRAY[0..9] OF REAL := [20.0, 20.0, 20.0, 20.0, 20.0,
20.0, 20.0, 20.0, 20.0, 20.0];
flags: ARRAY[0..4] OF BOOL := [FALSE, FALSE, FALSE, FALSE, FALSE];
END_VAR
-
Документируйте причину инициализации. Если начальное значение не очевидно, добавьте комментарий:
(* нужно установить в 1 из-за специфики этого датчика *)
sensorOffset: REAL := 1.0;
(* начинаем с отключенного состояния по соображениям безопасности *)
emergencyStop: BOOL := FALSE;
END_VAR
Область видимости переменных: видит ли программа, что видишь ты?
Область видимости (scope) переменной — это та часть кода, где она может быть использована. Это важный концепт, который помогает предотвратить ошибки и структурировать код.
Глобальная область видимости (VAR_GLOBAL):
Переменные, объявленные в VAR_GLOBAL, видны во всем проекте:
VAR_GLOBAL
factoryTemperature: REAL := 20.0;
factoryPressure: REAL := 1.0;
END_VAR
PROGRAM Building1
BEGIN
factoryTemperature := 25.0; (* может изменять глобальную переменную *)
END_PROGRAM
PROGRAM Building2
BEGIN
IF factoryTemperature > 30 THEN (* может читать глобальную переменную *)
(* обработка *)
END_IF;
END_PROGRAM
Область видимости программы (VAR в PROGRAM):
Переменные, объявленные в блоке VAR внутри PROGRAM, видны только в этой программе:
PROGRAM MyProgram
VAR
localCounter: INT := 0;
END_VAR
(* localCounter видна только здесь *)
END_PROGRAM
PROGRAM AnotherProgram
(* localCounter не видна здесь! *)
END_PROGRAM
Область видимости функции (VAR, VAR_INPUT, VAR_OUTPUT):
Функции и функциональные блоки имеют более сложную структуру видимости:
FUNCTION_BLOCK PumpController
VAR_INPUT
enableSignal: BOOL; (* видна только как входной параметр *)
targetPressure: REAL;
END_VAR
VAR_OUTPUT
pumpRunning: BOOL; (* видна только как выходной параметр *)
currentPressure: REAL;
END_VAR
VAR
internalCounter: INT; (* видна только внутри блока *)
lastError: STRING;
END_VAR
(* внутренний код функционального блока *)
END_FUNCTION_BLOCK
Практический пример структурирования переменных:
Представим систему управления несколькими насосами на заводе:
(* ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ - состояние всей системы *)
VAR_GLOBAL
systemRunning: BOOL := FALSE;
systemErrorCode: INT := 0;
factoryTemperature: REAL := 20.0;
END_VAR
(* ПРОГРАММА 1 - управление первым насосом *)
PROGRAM Pump1Controller
VAR
(* локальные переменные, видны только здесь *)
pump1Speed: INT := 0;
pump1Pressure: REAL := 0.0;
pump1Running: BOOL := FALSE;
runningTime: DINT := 0;
END_VAR
IF systemRunning THEN
pump1Running := TRUE;
pump1Speed := 500;
ELSE
pump1Running := FALSE;
pump1Speed := 0;
END_IF;
END_PROGRAM
(* ПРОГРАММА 2 - управление вторым насосом *)
PROGRAM Pump2Controller
VAR
(* локальные переменные для второго насоса *)
pump2Speed: INT := 0;
pump2Pressure: REAL := 0.0;
pump2Running: BOOL := FALSE;
runningTime: DINT := 0; (* можем использовать такое же имя,
так как это локальная переменная *)
END_VAR
IF systemRunning THEN
pump2Running := TRUE;
pump2Speed := 750;
ELSE
pump2Running := FALSE;
pump2Speed := 0;
END_IF;
END_PROGRAM
(* ФУНКЦИОНАЛЬНЫЙ БЛОК - переиспользуемый компонент *)
FUNCTION_BLOCK PressureSensor
VAR_INPUT
sensorSignal: REAL; (* входные параметры *)
calibrationOffset: REAL;
END_VAR
VAR_OUTPUT
pressureValue: REAL; (* выходные параметры *)
isValid: BOOL;
END_VAR
VAR
(* локальные переменные функционального блока *)
filteredValue: REAL;
sampleCount: INT;
END_VAR
(* обработка сигнала датчика *)
filteredValue := sensorSignal + calibrationOffset;
IF (filteredValue >= 0) AND (filteredValue <= 100) THEN
pressureValue := filteredValue;
isValid := TRUE;
ELSE
isValid := FALSE;
END_IF;
END_FUNCTION_BLOCK
Этот пример показывает правильную организацию переменных:
-
Глобальные переменные содержат состояние, которое нужно везде;
-
Локальные переменные в программах содержат данные, специфичные для каждой программы;
-
Функциональные блоки получают параметры через VAR_INPUT и возвращают результаты через VAR_OUTPUT.
Распространенные ошибки при работе с переменными
-
Использование слишком глобальных переменных. Это создает запутанные зависимости в коде.
-
Неинициализированные переменные. Они могут иметь непредсказуемые начальные значения.
-
Плохие имена переменных. Делают код неразборчивым и подверженным ошибкам.
-
Путаница между локальными и глобальными переменными. Может привести к неожиданному изменению значений.
-
Использование одного имени переменной для разных целей. Например, переиспользование переменной
tempдля временного хранения разных значений в разных частях кода.
Переменные — это не просто способ хранения данных в памяти контроллера. Это способ коммуникации между вами и компьютером, между вами и другими инженерами, которые будут читать ваш код в будущем. Правильное именование, правильная инициализация и правильное управление областью видимости переменных — это признаки профессионального подхода к разработке программного обеспечения для ПЛК.
Хорошо написанный код с правильно названными переменными легче отлаживать, легче модифицировать и легче передать другому инженеру. Это не затраты времени на документирование — это инвестиция в качество и надежность вашего программного обеспечения.
Базовое учебное пособие по языку ST для ПЛК
Для погружения в тему рекомендую познакомиться с практическим туториалом по ST для ПЛК. Учебное пособие написано на русском языке, с кодом. Покрывает 12 глав: типы данных, I/O, функции, таймеры, FSM, отладку.
Что внутри:
- Базис: Переменные (BOOL/INT/DINT/REAL/TIME), массивы, структуры, операции (+/-/MOD);
- Логика: IF/CASE/FOR/WHILE, rising/falling edges с RTRIG;
- Блоки: TON/TOF/TP/CTU/CTD, функции вроде ScaleAnalogInput, FB Timer;
- Проекты: TrafficLight, ConveyorLine, TankControl с PID (Kp/Ki/Kd), safety-таймерами.
Книга делает ST доступным: копируйте код, тестируйте, автоматизируйте реальные машины за часы. Идеально для инженеров — хороший вариант для старта в программировании ПЛК на языке ST.
Базовое учебное пособие по языку ST для ПЛК по символической цене можно купить здесь или здесь.
Путь к мастерству: как глубже изучить ST
Structured Text — это язык, который легко учить, но требует времени для полного овладения. Основы, которые мы рассмотрели в этой статье, дают вам прочную базу для написания простых и средних по сложности программ. Однако настоящее мастерство приходит с практикой и изучением сложных техник.
Для того чтобы углубить свои знания о Structured Text, функциональных блоках, оптимизации кода и лучших практиках в промышленной автоматике, рекомендуем подписаться на канал «ПЛК и автоматика» в Telegram — https://t.me/plcmasters.
Этот канал посвящен углубленному изучению программируемых логических контроллеров и всех аспектов промышленной автоматизации. Здесь вы найдете:
-
Детальные туториалы по различным аспектам ST и других языков IEC 61131-3;
-
Практические примеры из реальных производственных систем;
-
Советы по оптимизации и лучшим практикам проектирования;
-
Новости и обновления в области промышленной автоматики;
-
Ссылки на полезные ресурсы и инструменты для разработчиков.
Будь то вы только начинаете свой путь в мире промышленной автоматики или уже имеете опыт и хотите развиваться дальше, канал предоставит вам необходимый контент и поддержку. Присоединяйтесь к растущему сообществу специалистов и инженеров, которые строят будущее промышленной автоматизации.
Structured Text — это мощный и гибкий язык, и с правильным руководством и практикой вы сможете написать код, который будет эффективно управлять самыми сложными промышленными процессами. Начните свой путь к мастерству прямо сейчас!
