ST数组与结构体:如何定义和管理复杂数据结构

发布于 2026-03-18 07:31:34 · 浏览 3 次 · 评论 0 条

在电气自动化系统中,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中重复定义同名结构体 修改字段时需同步所有副本,极易遗漏导致数据错位
调试查看结构体 在在线监控窗口展开结构体节点 仅监控单个字段 无法快速把握设备整体状态,调试效率低下

最后强调:结构体与数组不是炫技工具,而是建模思维的载体。每次定义新结构体前,自问:“这个结构体是否对应一个真实的、可触摸的物理对象?” 若答案为否,则应回归简单变量;若答案为是,则坚定使用——因为自动化程序的本质,就是用代码精确复现物理世界的结构与行为。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文