在电气自动化系统中,PLC(可编程逻辑控制器)程序常需处理大量关联数据:例如一个电机模块需要同时管理启停状态、运行频率、故障代码、累计运行时间、设定转速等;一条输送带可能包含16个光电开关的实时信号、8个变频器参数、4组温度采样值。若用单个变量逐一声明,代码将变得冗长、易错且无法体现数据间的逻辑关系。ST(Structured Text)语言作为IEC 61131-3标准定义的高级文本语言,提供了两种核心机制解决该问题:数组(Array)用于统一管理同类型批量数据,结构体(STRUCT)用于封装异构但语义相关的数据集合。二者可嵌套组合,构建出与物理设备一一对应的“数据镜像”,大幅提升程序可读性、可维护性与复用性。
一、ST数组:高效管理同类型批量数据
数组是一组连续内存中、类型相同、通过下标访问的变量集合。在自动化场景中,它天然适用于传感器阵列、I/O点映射、历史数据缓存等需求。
1. 基本语法与声明方式
ST中数组声明格式为:
变量名 : ARRAY [下界..上界] OF 数据类型;
注意:下界不强制为0,可自定义起始索引,这对工程标识更直观。例如:
// 声明12个通道的温度采样值,通道编号从1开始更符合现场习惯
TempChannels : ARRAY [1..12] OF REAL;
// 声明32个数字量输入点,地址对应PLC硬件槽位0~31
DI_Bank : ARRAY [0..31] OF BOOL;
// 声明5个电机的运行命令(每个命令含启动、停止、急停三个子位)
MotorCmds : ARRAY [1..5] OF STRUCT
Start : BOOL;
Stop : BOOL;
EStop : BOOL;
END_STRUCT;
关键点:
ARRAY [1..12]中的..是英文半角两个句点,不可写作中文全角“…”或单个“.”;OF后必须是已知类型(如REAL,BOOL,INT, 或已定义的结构体/枚举);- 数组大小在编译时固定,运行时不可动态扩容。
2. 初始化与赋值操作
数组支持整体初始化和单元素赋值:
// 声明时全部初始化为0.0
InitTemps : ARRAY [1..4] OF REAL := [0.0, 0.0, 0.0, 0.0];
// 或使用重复语法(仅限常量初始化)
AllZeros : ARRAY [0..7] OF INT := [8(0)]; // 表示8个0
// 运行时单元素赋值
TempChannels[3] := 45.6; // 给第3通道赋值
DI_Bank[15] := TRUE; // 设置第15号DI点为ON
MotorCmds[2].Start := TRUE; // 访问嵌套结构体中的字段
⚠️ 重要限制:ST不支持数组整体赋值(如 A := B),除非B是相同类型的数组变量且处于支持该操作的编译器环境中(多数主流PLC如西门子S7-1500、倍福TwinCAT 3允许,但三菱GX Works3默认禁用)。稳妥做法始终采用循环逐个赋值:
FOR i := 1 TO 12 DO
TempHistory[i] := TempChannels[i];
END_FOR;
3. 实际工程应用:I/O点批量监控与报警
假设某包装机有8组称重传感器,每组含“当前重量”、“超限标志”、“校准状态”三个属性。用数组+结构体组合实现:
TYPE T_WeightSensor : STRUCT
Weight_kg : REAL; // 当前重量
OverLimit : BOOL; // 是否超限
Calibrated : BOOL; // 是否已校准
END_STRUCT;
VAR
Sensors : ARRAY [1..8] OF T_WeightSensor;
AlarmCount : INT; // 当前触发超限的传感器数量
END_VAR
// 扫描逻辑:检查所有传感器,统计超限数
AlarmCount := 0;
FOR i := 1 TO 8 DO
IF Sensors[i].OverLimit THEN
AlarmCount := AlarmCount + 1;
END_IF;
END_FOR;
// 若任一传感器未校准,则禁止启动主流程
MachineReady := TRUE;
FOR i := 1 TO 8 DO
IF NOT Sensors[i].Calibrated THEN
MachineReady := FALSE;
EXIT; // 提前退出循环
END_IF;
END_FOR;
此写法清晰表达了“8个同类传感器”的物理概念,避免了 Sensor1_Weight, Sensor2_Weight, … Sensor8_Weight 等16个独立变量带来的命名混乱与维护困难。
二、ST结构体:封装语义关联的异构数据
结构体(STRUCT)将多个不同类型变量捆绑为一个逻辑单元,其本质是用户自定义的数据类型。它解决的核心问题是:“这些变量总是成组出现、共同描述一个实体”。
1. 结构体定义与实例化
结构体定义语法:
TYPE 类型名 : STRUCT
字段1 : 数据类型1;
字段2 : 数据类型2;
...
END_STRUCT
定义后,即可用该类型声明变量:
TYPE T_Motor : STRUCT
Running : BOOL; // 运行状态
Speed_RPM : INT; // 实际转速
Setpoint_RPM: INT; // 设定转速
FaultCode : WORD; // 故障代码(0=无故障)
RunTime_h : TIME; // 累计运行时间
END_STRUCT;
VAR
PumpA : T_Motor; // 实例化一个泵电机
ConveyorB : T_Motor; // 实例化一条输送带电机
END_VAR
字段访问使用点号 .:PumpA.Running := TRUE;,ConveyorB.Setpoint_RPM := 1200;
2. 结构体嵌套与数组化:构建层级数据模型
真实设备常具层级关系。例如一条多段式加热炉,每段含独立温控器,而每个温控器又包含设定值、测量值、输出功率、PID参数等。此时需结构体嵌套+数组:
TYPE T_PID_Params : STRUCT
Kp : REAL;
Ti : TIME;
Td : TIME;
END_STRUCT;
TYPE T_HeaterZone : STRUCT
SetTemp_C : REAL;
MeasTemp_C : REAL;
Output_Pct : REAL;
PID : T_PID_Params;
HeaterOn : BOOL;
END_STRUCT;
TYPE T_HeatingFurnace : STRUCT
Zones : ARRAY [1..6] OF T_HeaterZone; // 6个加热区
TotalPower_kW: REAL;
IsAutoMode : BOOL;
END_STRUCT;
VAR
Furnace1 : T_HeatingFurnace;
END_VAR
// 使用示例:
Furnace1.Zones[3].SetTemp_C := 850.0; // 设置第3区设定温度
Furnace1.Zones[3].PID.Kp := 2.5; // 设置第3区PID比例增益
Furnace1.TotalPower_kW := Furnace1.TotalPower_kW + 15.2;
该模型完全映射物理设备:Furnace1 是顶层对象 → Zones 是其子数组 → 每个 Zones[i] 是结构体实例 → 其内部字段精确对应硬件参数。修改任意层级,均不影响其他部分,符合高内聚、低耦合设计原则。
3. 结构体作为函数接口参数:提升模块复用性
当编写通用控制功能块(如电机启停FB)时,传入结构体比传入10个独立参数更简洁可靠:
// 定义电机控制指令结构体
TYPE T_MotorCmd : STRUCT
Enable : BOOL;
Direction: BOOL; // TRUE=正转,FALSE=反转
SpeedRef : INT;
ResetFault: BOOL;
END_STRUCT;
// 功能块声明(伪代码,具体语法依PLC平台而定)
FUNCTION_BLOCK FB_MotorCtrl
VAR_INPUT
Cmd : T_MotorCmd; // 单一结构体输入
Feedback : T_Motor; // 反馈结构体输入
END_VAR
VAR_OUTPUT
Status : T_Motor; // 输出更新后的状态
END_VAR
调用时:
// 构造指令
PumpCmd.Enable := Start_PB AND NOT Fault_Hold;
PumpCmd.Direction := TRUE;
PumpCmd.SpeedRef := 1450;
PumpCmd.ResetFault := FaultReset_PB;
// 一行调用,参数清晰无歧义
FB_MotorCtrl(Cmd := PumpCmd, Feedback := PumpA, Status => PumpA);
对比传统方式(FB_MotorCtrl(Enable, Dir, Speed, Rst, FB_Running, ...)),结构体调用杜绝了参数顺序错位风险,且新增字段无需修改所有调用点——只需扩展结构体定义并更新相关逻辑。
三、高级技巧:联合体(UNION)、枚举(ENUM)与结构体协同
1. UNION:同一内存区域的多解释方式
当某寄存器需按不同含义解读时(如一个32位字既可作整数,也可拆分为4个8位状态),使用 UNION:
TYPE T_StatusWord : UNION
RawValue : DWORD;
Bits : STRUCT
Bit0 : BOOL; // 报警
Bit1 : BOOL; // 运行
Bit2 : BOOL; // 就绪
Bit3 : BOOL; // 故障确认
// ... 其余28位
END_STRUCT;
END_UNION;
VAR
MainStatus : T_StatusWord;
END_VAR
// 写入整数值
MainStatus.RawValue := 16#0000_000A; // 二进制 1010
// 直接读取某一位
IF MainStatus.Bits.Bit1 THEN
// 处理运行状态
END_IF;
2. ENUM:为结构体字段赋予语义明确的取值范围
避免用魔法数字(如 State := 2)造成理解障碍:
TYPE T_MotorState : (Stopped := 0, Starting := 1, Running := 2, Stopping := 3, Faulted := 4);
TYPE T_MotorEx : STRUCT
State : T_MotorState; // 类型安全,编译器可检查非法赋值
LastFault : STRING[32];
END_STRUCT;
此时 MotorEx.State := 5; 将被编译器报错,强制开发者使用 MotorEx.State := Faulted;,极大降低误操作风险。
四、实践建议与避坑指南
| 场景 | 推荐做法 | 错误做法 | 后果 |
|---|---|---|---|
| 大型数组声明 | 使用 ARRAY [0..N-1] 而非 [1..N] |
强行用 [1..N] 导致循环易越界 |
FOR循环 i:=1 TO N 正确,但若误写 i:=0 TO N 则访问 Arr[N+1] 致崩溃 |
| 结构体字段命名 | 全小写+下划线(setpoint_rpm) |
混用大小写或驼峰(SetPointRPM) |
不同PLC平台对大小写敏感性不同,统一风格避免移植问题 |
| 跨PLC平台复用 | 将结构体定义放在单独.lib库文件中 |
在各POU中重复定义同名结构体 | 修改字段时需同步所有副本,极易遗漏导致数据错位 |
| 调试查看结构体 | 在在线监控窗口展开结构体节点 | 仅监控单个字段 | 无法快速把握设备整体状态,调试效率低下 |
最后强调:结构体与数组不是炫技工具,而是建模思维的载体。每次定义新结构体前,自问:“这个结构体是否对应一个真实的、可触摸的物理对象?” 若答案为否,则应回归简单变量;若答案为是,则坚定使用——因为自动化程序的本质,就是用代码精确复现物理世界的结构与行为。

暂无评论,快来抢沙发吧!